Archive post
Exchange node in four lines (Hive 1.28.8)
• gtg
Run a Hive 1.28.8 exchange node with Docker Compose, snapshots and reusable block logs, with API examples for tracking deposits.
How many commands does it take to run a Hive node for an exchange?
Four. One of them is `cd`, and the last one is for watching the logs while you drink your coffee ;-)
Remember [Hive Node Setup for the Smart, the Dumb, and the Lazy](/@gtg/hive-node-setup-for-the-smart-the-dumb-and-the-lazy)? This one is for the lazy.
## No scripts, one compose file
We were asked for a "cloud-native" way to run a node: no helper scripts, just one declarative file. Hive 1.28.8 has exactly that: [`docker/exchange`](https://github.com/openhive-network/hive/tree/1.28.8/docker/exchange), a Docker Compose setup for an exchange node.
I serve a copy of it at https://gtg.openhive.network/get/exchange/ - slightly modified from upstream: it tracks the accounts I chose to support, so it matches my exchange snapshot, and it keeps the block log and the snapshot in plain folders next to the compose file.
Cyberpunk Hive chan, generated by AI during HiveFest 2026
## What's in the box
```
hive-exchange-1.28.8
├── .env <- your settings: ports, BLOCK_LOG_DIR, SNAPSHOT_URL
├── compose.yml <- the node itself, nothing to edit
├── config.ini <- hived options and the tracked accounts
├── README.md
├── blockchain <- your block_log, or block_log_part.* files
└── snapshot <- the unpacked snapshot (downloaded here if empty)
```
**You have a block log?** If you had a Hive node running recently, you probably do. It's preferred and recommended to re-use it. Blocks never change. That's the whole point. Put it into `blockchain`: one big `block_log` file or a set of `block_log_part.*` files, both work. Or keep it where it is and point `BLOCK_LOG_DIR` in `.env` to it.
**No block log?** Leave `blockchain` empty. The node then runs pruned (see below).
The snapshot takes care of itself: if `snapshot` is empty, the node downloads my exchange snapshot (6.3 GB) into it. Already have it unpacked? Put it there.
## The four lines
```
wget -qO- https://gtg.openhive.network/get/exchange/hive-exchange-1.28.8.tgz | tar xz
cd hive-exchange-1.28.8
docker compose up -d
docker compose logs -f hived
```
Unpack it on the disk where the block log is (or will be). If your block log stays where it is, add one line before `docker compose up -d`:
```
echo BLOCK_LOG_DIR=/path/to/your/blockchain >> .env
```
~~Four lines~~ Five lines. The coffee is still the last one.
The node loads the snapshot, replays your block log from the snapshot's block on, and syncs the rest from the network. Without a block log, it does the same minus the replay. Minutes, not even hours.
When the log says `entering live mode`, press Ctrl+C. That stops the log, not the node. `docker compose ps` shows the node as `healthy` once it has caught up.
The real run with a block log, sped up
The log stopped at the same block for minutes?
> _Have you tried turning it off and on again?_
> - Roy, The IT Crowd
```
docker compose down && docker compose up -d
```
## Your block log, three rules
- Stop your old node first.
- hived in the container runs as UID 1000, so it has to be able to write there: `sudo chown -R 1000 blockchain` (or your `BLOCK_LOG_DIR`).
- Your block log has to be at least as new as the snapshot: it must contain the block the snapshot was made at. A node that was in sync until you stopped it is fine. An older one makes hived stop with "Chain state ... does not match block log", over and over.
## All blocks, or pruned?
With a block log, the node has all blocks: either in one big file (`block-log-split = -1`) or in parts of a million blocks each (`block-log-split = 9999`, the hived default). `config.ini` says `-1`, and when the bundle finds `block_log_part.*` files it switches to `9999` for you. Want parts instead of one big file? Set `block-log-split = 9999` in `config.ini` and hived splits it on the next start. It keeps the original file, so you need the space twice until you delete it.
Without a block log, the node is pruned: it runs with `block-log-split = 0` and keeps only the newest block. State is complete, and so is the account history of the accounts on the list. That is enough to follow your deposits. But `get_block` and `get_transaction` need all blocks.
No block log of your own, but you want all blocks? Set this in `.env` before the first start:
```
BLOCK_LOG_URL=https://gtg.openhive.network/get/blockchain/block_log
```
The node then downloads the block log into `blockchain`, loads the snapshot, and replays only the blocks made after the snapshot.
## Is your account on the list?
The snapshot has account history only for the accounts listed in `config.ini` (the `account-history-rocksdb-track-account-range` lines).
On the list? You are done. Keep the list as it is.
Not on the list? Then the snapshot cannot give you its history. Before the first start, add your account to `config.ini` and clear `SNAPSHOT_URL` in `.env`. With a block log the node replays all of it, with history for your account from the first block. That takes hours, not minutes. Without one, it syncs the whole chain from the P2P network, which takes even longer.
Running an exchange that people actually use, and your account isn't on the list? Let me know, with a good reason, and it can be in the next snapshot.
`SNAPSHOT_URL` points to `latest-1.28.8.tgz`, which is always my newest snapshot made with 1.28.8.
## The block log rule
**Never download the block log twice.** Monolithic or split, keep it and reuse it for every replay and every upgrade. It is 587 GB today, and it does not get any smaller. That is what the `blockchain` folder is for.
`docker compose down -v` removes the volumes, that is the state and the account history. The `blockchain` and `snapshot` folders stay. Plain `docker compose down` keeps everything.
## Bonus: asking the node
The API listens on `127.0.0.1:8091` only. Keep it off the public internet.
Where is the chain?
```
curl -s --data '{"jsonrpc":"2.0","method":"database_api.get_dynamic_global_properties","id":1}' http://127.0.0.1:8091/
```
Look for `head_block_number` and `last_irreversible_block_num`.
The last 10 operations of an account. Let's take `binance-hot2`, one of Binance's accounts on the list:
```
curl -s --data '{"jsonrpc":"2.0","method":"account_history_api.get_account_history","params":{"account":"binance-hot2","start":-1,"limit":10},"id":1}' http://127.0.0.1:8091/
```
Each entry has the operation, its `block` and its `trx_id`. By default you get only operations from irreversible blocks, so everything you see there is final. This one works on a pruned node too.
Did a transaction make it? Ask with its `trx_id`:
```
curl -s --data '{"jsonrpc":"2.0","method":"account_history_api.get_transaction","params":{"id":"TRX_ID"},"id":1}' http://127.0.0.1:8091/
```
If you get the transaction back, it is in an irreversible block. Until then the answer is "Unknown Transaction", and thanks to OBI that usually lasts only seconds. It needs all blocks, and it knows the transactions of the accounts on the list.
And a block:
```
curl -s --data '{"jsonrpc":"2.0","method":"block_api.get_block","params":{"block_num":BLOCK_NUM},"id":1}' http://127.0.0.1:8091/
```
That needs all blocks too. A pruned node answers "has been pruned" for everything but the newest one.
For deposits, account history is the better tool anyway: it also shows virtual operations, like recurrent transfers or transfers from savings, which you will not find in any block.
It's an exchange node, but other nodes run almost the same way: a seed node, a witness node, a personal wallet node, a broadcaster, or a basic API node for your simple bots. Same compose file, a different `config.ini`, and most likely the same snapshot. hived only warns that the snapshot has more plugins than it needs. Seed and API nodes need all the blocks, though, so bring your block log.