Sign inSign up

caltru1sm/xro-node

By caltru1sm

•Updated about 2 months ago

XRO (RaiblocksOne) node, built from source. amd64 + arm64. Syncs from scratch in ~45 min.

Image
0

1.2K

caltru1sm/xro-node repository overview

⁠XRO (RaiblocksOne) node

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.

⁠2.0.3 — arm64, and a smaller image

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:

  • RPC on a published port now works. Nodes created before 2.0.1 generated a 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.
  • Wrong-network config is caught at startup. The entrypoint now refuses to boot if the genesis values or peering host are Nano mainnet's instead of XRO's. Previously a node with one wrong value started cleanly onto a different network and just reported zero peers, which is much harder to diagnose.

A docker-compose.yml now ships in the repo at docker/xro/ if you'd rather use that than the docker run below.

⁠2.0.0-R2 — new nodes can sync from scratch

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:

blocksaccountstime
before156,1441,459stops forever
after626,2208,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.

⁠2.0.0-R1 — the bandwidth release

If your node has been eating a terabyte a month, that was a bug and this fixes it.

beforeafter
download0.46 Mbps0.07 Mbps
upload3.32 Mbps0.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:

  • You get the full benefit immediately, on your own. Download drops because nodes answering you honour the size you ask for, even old ones. Upload drops because you stop handing out 1000-entry lists. Nothing here waits on anyone else upgrading.
  • You also help operators who haven't upgraded. Asking for 128 instead of 1000 means every old node serving you sends less, so their bandwidth improves for free.

Syncing isn't slowed. A node bootstrapping through an upgraded server hit ~154,000 blocks in four minutes, same as before.

⁠Running it

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".

⁠Starting from scratch

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.

⁠Ports

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.

⁠Optional tuning

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

⁠What 2.0.0 fixed before this

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.

⁠Tags

TagWhat it is
latest, 2.0.3Current. Use this one. amd64 + arm64.
2.0.2, 2.0.1amd64 + arm64. Superseded by 2.0.3.
2.0.0-R2amd64 only. Fresh sync works, but the RPC bind issue is present.
2.0.0-R1Bandwidth fix, but new nodes cannot sync from scratch.
2.0.0Older still — the bandwidth bug is in this one too.
1.0.0-yxse-nan-20251022yxse/nan:latest as the network was actually running it
0.9.0-bootstrap-fix-20260511An earlier attempt at the bootstrap fix
0.2.2-legacy-R1The 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.

Tag summary

Content type

Image

Digest

sha256:81a1e9c63…

Size

271.7 MB

Last updated

about 2 months ago

docker pull caltru1sm/xro-node