Rootstock full node · Rust · MIT

A Rootstock node,
rewritten in Rust.

Rustock syncs RSK mainnet from genesis — or from a proved state snapshot — verifies Bitcoin merged-mining proofs, replays every transaction against an rskj-byte-compatible Unitrie, follows the tip in real time, and serves an rskj-compatible JSON-RPC.

rustock — mainnet
$ cargo run -p rustock-cli --release -- \
    --data-dir ./data --log-to-stdout

INFO rustock: Rustock/0.1.0 · network-id 30
INFO networking: discovery bootstrapped, 16 peers
INFO sync: skeleton 9,246,700 → tip, 4 chunks in flight
INFO execution: block #9,246,724 · 41 txs · 3.2M gas
INFO execution: state root ok (matches header)
INFO rpc: listening on 127.0.0.1:4444

Live

A Rustock node, right now

These numbers come from a Rustock instance following RSK mainnet, polled once a minute through a read-only JSON-RPC proxy. It is the same binary the quickstart builds.

  • Synced from genesis — this node executed every block. Snapshot sync is now an option too, and verifies the header chain under the same proof-of-work rules.
  • Merged mining verified — each header's Bitcoin AuxPow proof checked in full.
  • State root matched — the Unitrie root is recomputed and compared per block.
Node status JSON-RPC · eth_syncing
Best block
—
Peers
—
Chain state
—
Distance to tip
—
—connecting…

What it does

A complete node, not a sync client

Rustock is not a light client or an indexer. It performs the whole job of a Rootstock full node: it downloads the chain, proves the work behind it, executes it, stores it, serves it to peers and answers queries about it.

Full block sync

Skeleton-based bulk header sync with parallel chunk downloads across every connected peer, then body downloads and real-time tip following via NewBlockHashes.

Snapshot sync, both sides

Join by downloading proved state instead of executing nine years of it — and serve snapshots to other peers, including rskj clients in rskj's own format. Every chunk is proved complete, not merely included.

EVM execution

A REVM-based block pipeline reproducing RSK semantics: transaction validation, gas accounting, state transitions, receipts and per-block REMASC reward distribution.

Unitrie world state

RSK's Unitrie from scratch — a radix-2 binary trie with shared paths and embedded values, byte-identical node encoding, and RocksDB persistence behind a write cache.

Every RSK precompile

secp256k1 recovery, block-header introspection, HD wallet utilities, REMASC, and the BTC–RSK Bridge: two-way peg, federation lifecycle, SPV header chain and governance.

Merged mining, both ways

Full AuxPow verification, difficulty adjustment, gas limit and timestamp rules, every activation-height-gated RSKIP — and block production: templates, uncle selection, the Bitcoin coinbase and the mnr_* RPC that mining software speaks.

Reorg handling

Competing forks are tracked, total difficulty compared, and canonical chain pointers rewritten when a heavier branch wins.

P2P networking

Full RLPx encryption inbound and outbound, Kademlia UDP discovery, peer exchange, persistent node tables — including rskj's multi-frame message chunking.

Transaction pool & relay

Pending transactions validated against live state (nonce, balance, intrinsic gas, chain ID), tracked per sender, and rebroadcast to every other peer.

rskj-compatible RPC

An axum HTTP server speaking eth, net, web3, rpc, rsk, txpool, debug, trace, sco and mnr — shaped to rskj's output, not go-ethereum's.

Tracing & subscriptions

debug_traceTransaction and the trace_* call tree from one shared inspector, plus eth_subscribe over WebSocket with correct removed: true log retraction across a reorg.

Peer scoring & bans

Fifteen event counters per peer by node id and by address, punishment that grows on repeat offences, and CIDR bans that persist across restarts — inspectable through sco_*.

Pruning & epoch GC

A mark-and-sweep collector over rotating epochs for trie state, and block pruning below a retention floor — one batch per block, so the store is never half-pruned.

166klines of Rust across 10 crates
2,475tests, from RLP to the Bridge
121catalogued rskj compatibility findings
61RSKIPs implemented or gated

Not a port

The parts that exist only here

Most of Rustock answers to something in rskj. A handful of components do not — they exist because this is a second implementation and has to prove it agrees with the first, or because rskj never needed them.

The interesting part

Bug-compatible on purpose

A second implementation of a live chain does not get to be correct — it has to be identical. Nine years of RSK mainnet history depend on details that no specification records: JDK HashMap iteration order, BigInteger sign bytes, bitcoinj's byte-order conventions, and a handful of rskj code paths that were never intended but are now consensus. Rustock found them the only way they can be found — by replaying the chain and diverging.

Run it in three commands

Rust stable, a C++ toolchain and libclang. Everything else is vendored.

# build
git clone https://github.com/SergioDemianLerner/rustock && cd rustock
cargo build --workspace --release

# sync RSK mainnet from genesis
cargo run -p rustock-cli --release -- --data-dir ./data --log-to-stdout

# ask it something
curl -s -X POST -H 'content-type: application/json' \
  --data '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}' \
  http://127.0.0.1:4444/