Rootstock full node · Rust · MIT
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.
$ 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
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.
What it does
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.
Skeleton-based bulk header sync with parallel chunk downloads across every connected peer,
then body downloads and real-time tip following via NewBlockHashes.
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.
A REVM-based block pipeline reproducing RSK semantics: transaction validation, gas accounting, state transitions, receipts and per-block REMASC reward distribution.
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.
secp256k1 recovery, block-header introspection, HD wallet utilities, REMASC, and the BTC–RSK Bridge: two-way peg, federation lifecycle, SPV header chain and governance.
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.
Competing forks are tracked, total difficulty compared, and canonical chain pointers rewritten when a heavier branch wins.
Full RLPx encryption inbound and outbound, Kademlia UDP discovery, peer exchange, persistent node tables — including rskj's multi-frame message chunking.
Pending transactions validated against live state (nonce, balance, intrinsic gas, chain ID), tracked per sender, and rebroadcast to every other peer.
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.
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.
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_*.
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.
Not a port
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.
Per block and per transaction, a check that execution did not create rBTC from nothing. RSK's supply is bounded by the BTC locked in the peg, so a bug that mints rBTC is the worst failure this node could have — and otherwise an invisible one, because state roots would agree with an rskj carrying the same bug and nothing else looks at the total.
No rskj equivalent →Re-executes all of mainnet block by block, comparing every state root against its header. It adds no feature; it turns "we believe this matches rskj" into a measurement — and it found Bridge divergences that agreed for millions of blocks, then parted at a single transaction.
No rskj equivalent →Eight stated relations over the node's three position markers, checked after every commit, four of them now repaired automatically rather than merely reported — plus a progress measure for the node that believes it is syncing and is not.
No rskj equivalent →The interesting part
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.
Consensus behaviour that is invisible in rskj's own source because it lives inside the JDK or inside bitcoinj and Guava. The most dangerous category: read every line of rskj and still fork.
9 findings →Unconditional writes, missed resets, exception paths that still mutate state. Visible in the source, clearly unintended, and load-bearing because mainnet history depends on them.
11 findings →Where RSK intentionally departs from canonical Ethereum: per-RSKIP fork policy, fork-independent gas constants, frontier-forever account semantics, REMASC fee routing.
18 findings →The rskj-specific RLPx framing and sync messages a client must speak to be talked to at all — including 32 KB multi-frame chunking that geth and reth never emit.
7 findings →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/