XRO (RaiblocksOne) node, built from source. amd64 + arm64. Syncs from scratch in ~45 min.
1.2K
This is the node image I run on my own node. Built from source out of Caltru1sm/xro-node, which is a fork of yxse/nan-node.
Use latest (2.0.3). Older tags are archives — see the table at the bottom.
latest is now multi-architecture. The same tag works on x86, a Raspberry Pi 4/5 or
CM5, an Oracle Ampere A1, or Graviton — Docker picks the right one on pull. There's no
separate arm64 tag to hunt for.
The image also dropped from 1.36 GB to 729 MB by leaving out GUI and test-harness dependencies the node never uses.
Two fixes worth knowing about if you've run into them:
config-rpc.toml bound to the container's own loopback, so -p 127.0.0.1:8076:8076
couldn't reach it and the RPC looked dead while the node was fine. That file is written
once and never rewritten, so upgrading alone won't repair an existing node — see the
troubleshooting section in DEPLOY.md for the one-line fix.A docker-compose.yml now ships in the repo at docker/xro/ if you'd rather use that
than the docker run below.
This was broken. A node starting from an empty folder climbed to about 156,000 blocks and stopped there permanently. Every fresh node hit it.
Point a new node at an empty folder and leave it alone — it's fully synced in about 45 minutes. Tested three times end to end, no restarts, no snapshot:
| blocks | accounts | time | |
|---|---|---|---|
| before | 156,144 | 1,459 | stops forever |
| after | 626,220 | 8,459 | ~45 min |
What was wrong: when a node gets a block whose source it doesn't have, it parks that account as blocked and notes which account it's waiting on. A background pass goes and fetches those. That pass skipped any account already sitting in the request queue — sensible, except the queue had filled with ~94,000 accounts that were sent funds and never opened. Being somewhere in a queue that size is not the same as being handled. The blocked accounts were never fetched and the node stopped growing. They now go to the front of the queue instead of being skipped.
Existing nodes were never affected — this only ever hit nodes syncing from nothing.
If your node has been eating a terabyte a month, that was a bug and this fixes it.
| before | after | |
|---|---|---|
| download | 0.46 Mbps | 0.07 Mbps |
| upload | 3.32 Mbps | 0.37 Mbps |
| per month | ~1,240 GB | ~145 GB |
Nodes spend nearly all their traffic on frontier scanning — swapping lists of every account and its latest block, so they can spot anything they're missing. The lists were the problem. A node asked for the protocol maximum of 1000 accounts per reply and got all 1000, over and over, on a network with about 8,400 accounts total. One node was measured serving 2.2 million account entries in five minutes — 99% of its upload.
Both ends now cap at 128 per reply. Nodes still see the whole network, they just stop re-sending each other the same list a thousand at a time.
Two things worth knowing:
Syncing isn't slowed. A node bootstrapping through an upgraded server hit ~154,000 blocks in four minutes, same as before.
docker run -d --name xro --restart unless-stopped --network host \
-e prefix="xro_" \
-e name="RaiblocksOne" \
-e account="xro_3oh6tp9b8y65w1fzp3aaxgebptkdhnrziiqaairqfhzkcntiwn1r97cgzikp" \
-e source="D5E4D58E937883E01BFB0508EB989B6A4B7D31F842E8443176BFF255350E5018" \
-e work="9da7e03b2a2ec54b" \
-e signature="FBB7588466EAA76196181C3CDF249178BAC70929E28AABBD5DE284362A34899AB4C972A232824067F439E89F2672B792105D3A55763B852FD21967EC7957260D" \
-e peering="peering.raione.cc" -e peering_port=8075 \
-e RPC_PORT=8076 -e WS_PORT=8078 \
-v /data/nodes/xro:/root \
caltru1sm/xro-node:latest
Change -v /data/nodes/xro:/root to wherever your data lives. That's the only line you
have to get right — the rest is the same for everyone. All the -e values are required;
leave one out and the container exits.
Check it:
curl -d '{"action":"version"}' http://127.0.0.1:8076
Should say "node_vendor": "Nano 2.0.3".
Just start it against an empty folder. Fully synced in about 45 minutes, unattended.
If you'd rather not wait, seed it with a ledger snapshot instead — a 4 minute download rather than a 45 minute sync. Optional now, not required:
wget https://github.com/Caltru1sm/xro-node/releases/download/xro-node-2.0.0/xro-ledger-625082-2026-08-04.data.ldb.gz
wget https://github.com/Caltru1sm/xro-node/releases/download/xro-node-2.0.0/xro-ledger-625082-2026-08-04.sha256
sha256sum -c xro-ledger-625082-2026-08-04.sha256
mkdir -p /data/nodes/xro/RaiblocksOne
gunzip -c xro-ledger-625082-2026-08-04.data.ldb.gz > /data/nodes/xro/RaiblocksOne/data.ldb
The snapshot is data.ldb only — no wallet, no node identity. Your node makes its own
node_id_private.key on first start. Never copy someone else's or you'll both be running
the same identity on the network.
Only forward 8075 (peering). RPC (8076) and websocket (8078) should stay off the internet — put them behind a reverse proxy with TLS if you need them reachable, and never expose a control-enabled RPC.
If your node is already synced, this cuts the remaining download traffic further:
[node.bootstrap]
frontier_rate_limit = 1
Leave it alone while catching up — it will slow that down. The two cap settings default to 128 and can be changed if you want:
[node.bootstrap]
frontier_request_count = 128 # how many you ask for per reply
[node.bootstrap_server]
max_frontiers_served = 128 # how many you hand out per reply
XRO's chain was built with proof-of-work below what Nano's thresholds expect. Out of the 592,298 blocks I scanned, 403,985 (68%) were under epoch_1 and 454,888 (77%) under epoch_2.
If your node already had the blocks you'd never notice — Nano doesn't re-check blocks it
has already stored. But any node re-fetching history threw them out with
insufficient_work, so new nodes couldn't sync and a node that had fallen behind couldn't
catch up. Lowering the thresholds to match the chain fixed both. It only ever accepts
blocks the network already has, so it can't split the chain.
The Nano base version stays at 28.0.0 — that part is honest, it really is a V28 fork.
Also in 2.0.0: bootstrap weights actually filled in (upstream shipped an empty list with
max_blocks_live set to Nano mainnet's height, which XRO will never reach), built from
source instead of wget-ing a binary off a release page with no checksum, docker stop
no longer SIGKILLs it, and a missing config no longer regenerates itself wide open with
control RPC on.
2.0.0-R1 also fixes the peering setting, which the node never actually read — a brand
new node had no way to find the network at all. Existing nodes were unaffected.
| Tag | What it is |
|---|---|
latest, 2.0.3 | Current. Use this one. amd64 + arm64. |
2.0.2, 2.0.1 | amd64 + arm64. Superseded by 2.0.3. |
2.0.0-R2 | amd64 only. Fresh sync works, but the RPC bind issue is present. |
2.0.0-R1 | Bandwidth fix, but new nodes cannot sync from scratch. |
2.0.0 | Older still — the bandwidth bug is in this one too. |
1.0.0-yxse-nan-20251022 | yxse/nan:latest as the network was actually running it |
0.9.0-bootstrap-fix-20260511 | An earlier attempt at the bootstrap fix |
0.2.2-legacy-R1 | The original 2024 node, raiblocksone/raione:R1_V.02.2 |
The three oldest are archives, not releases. They have the threshold bug. I'm keeping
them up because the raiblocksone Docker Hub account is gone and those images were about
to exist nowhere — 0.9.0 and 0.2.2 only survived because they were still sitting on my
node's disk. They're not my builds, they're other people's work I copied so it wouldn't
disappear.
Content type
Image
Digest
sha256:81a1e9c63…
Size
271.7 MB
Last updated
about 2 months ago
docker pull caltru1sm/xro-node