About Rustock

An independent Rootstock full node, written in Rust, built in the open, and honest about what it is not.

Project status

Experimental prototype. Do not use in production.

Rustock is a prototype implemented in an AI-assisted manner. The code has not been reviewed in depth and has not been security audited. It may — and will — break and contain bugs. Use at your own risk.

That warning is the first thing in the repository's README and it belongs here too, unsoftened. What Rustock does do is real: it syncs RSK mainnet from genesis, verifies every merged-mining proof, executes every transaction, matches the state root at every block, follows the tip and answers RPC queries. What it has not had is the years of adversarial attention that make a client safe to put money behind.

Use it to study the protocol, to cross-check rskj, to index or analyse the chain, to test a second implementation's answers against the first. Do not use it to validate anything you cannot afford to be wrong about.

Why a second client

Rootstock has had one full node implementation for its entire existence. A network with one client has no way to distinguish "the protocol" from "what that program does" — every implementation detail is potentially consensus, and nobody finds out which until someone writes a second one.

That is most of what this project has produced so far, and the most useful part of it: a catalogue of 112 behaviours that a Rootstock client must reproduce, each one found by replaying mainnet and diverging, each pinned to the block where it first mattered. Some are in the RSKIPs. Some are in rskj's source. Some are in the JDK. None of them were written down in one place before.

A consensus-frozen bug in rskj's receipt reporting came out of the same process, and was reported upstream. Client diversity pays dividends before the second client is ever trusted with anything.

Maintainers

Javier Álvarez Cid-Fuentes

Creator of Rustock. Javier built Rustock — architecture, the from-scratch Unitrie, the execution pipeline, the Bridge, and the whole-chain replay methodology that produced the compatibility catalogue. The project exists because he decided to find out whether a second Rootstock client could be written, and then wrote it.

Sergio Demian Lerner

Maintainer. Designer of the Rootstock protocol and of the merged-mining and two-way-peg schemes Rustock has to reproduce exactly. Reviews, direction, and the protocol archaeology that turns a state-root mismatch into an explanation.

With particular thanks to Javier Álvarez Cid-Fuentes, who created Rustock. Everything on this site describes his work.

On AI assistance

Rustock was written with heavy AI assistance, and the project says so everywhere rather than quietly. Two reasons, and they pull in opposite directions.

The first is a warning. AI-assisted code has not been through the thinking that review substitutes for, and it is unusually good at being plausible. That is exactly the wrong property in consensus code, where plausible-and-wrong forks the chain. Hence the banner on every page.

The second is that it is the honest description of how a 120,000-line client covering nine years of undocumented consensus behaviour got written by a very small number of people. The methodology is what made it tractable: the chain itself is the oracle. Every block is a test vector, a state-root mismatch names the exact point of disagreement, and rskj can be asked directly what it did. Neither a human nor a model has to be right on the first try, because mainnet grades every answer.

Roadmap

Rustock executes blocks and maintains full state, but it is not feature-complete relative to rskj. The known gaps, in the project's own order:

AreaStatusNotes
Block sync, execution, stateworkingGenesis to tip on mainnet, state root matched per block
JSON-RPC (eth / net / web3 / rsk / txpool)workingState, calls, receipts, logs, persistent filters
P2P, discovery, relay, serving peersworkingRustock answers requests as well as making them
The BridgeworkingAll 70 methods in rskj's dispatch table have an arm; no silent empty-bytes fallthrough
Union Bridge (RSKIP-502)workingSeven methods, enabled at reed810. The storage cells are keyed by fromLongString and every value is RLP — read raw, the contract address falls back to zero and every authorized call is rejected. RSKIP-529's base and super event methods are not here yet
RSKIP-144 sublists — consensusworkingSublist edges validated against rskj's two-thread bound, receipt gas accumulated per sublist, REMASC and Bridge calls barred from a parallel sublist, and a block whose sublists collide is rejected — the independence of the partition is a condition of validity, not something a validator may assume the miner established
Parallel transaction executionworkingSublists run on their own threads, each committing in sublist order, so the trie is the merge. --execution-threads, default 1, clamped at 2 by rskj's constant. Verified by 3,812 block verifications on testnet across six ranges, replayed at one thread and at two and compared against rskj's own state — zero divergences
RSKIP-144 miner sideworkingrskj's ParallelizeTransactionHandler ported, so blocks Rustock mines are partitioned the way the network's other miner would partition them. The block that gets published is the one that was executed
RSKIP-351 versioned headersworkingV1 and V2 extension bodies decoded, hashed and re-encoded byte-for-byte, including the little-endian edge packing and the commitment to keccak256(logs_bloom) rather than the bloom
Metrics endpointworkingPrometheus over --metrics-port, twelve counters and gauges covering execution, sync, bodies and peers. A separate listener from the JSON-RPC server, because a scrape is not an RPC call
Snapshot syncworkingBoth sides — joining from a proved state snapshot, and serving them, including to rskj clients in rskj's format
Merged mining and block productionworkingTemplates, uncle selection, coinbase, fork-detection data, and the mnr_* RPC
eth_getProofworkingEIP-1186's envelope over unitrie node messages, since RSK has no Merkle-Patricia trie to prove against. A stock EIP-1186 verifier will not verify these — it fails to parse them, which is the safe failure, and the module says so at the top. rskj does not implement the method at all, so offering it is a deliberate divergence. The shape, and why
Single-pass gas estimationworkingeth_estimateGas answered in one execution rather than the ~24 a binary search needs, by recording the peak of RSK's non-monotonic gas counter. The binary search stays behind a flag, where it also serves as the test oracle
Signature cacheworkingRecovered senders remembered across pool admission, block execution and RPC rendering — two lock acquisitions per block rather than one per transaction, so the parallel region is never serialised. Two tiers, so a peer's block traffic cannot evict the pool's entries
debug_* / trace_*workingOpcode stream and call tree from one shared inspector
WebSocket eth_subscribeworkingnewHeads, logs with reorg retraction, newPendingTransactions
Peer scoring, punishment, bansworkingPort of co.rsk.scoring, with the sco_* namespace
Pruning and epoch GCworkingBoth opt-in, and the collector takes pinned roots so a served checkpoint is never swept
Testnet replaypartialPast every divergence found so far — testnet genesis, blocks #1 and #60, the pegout UTXO cell, the V2 base-event window, the union bridge, and the first block to actually split its transactions. 3,812 blocks across six ranges now replay clean against rskj's state at one thread and at two, but a continuous genesis-to-tip testnet replay has still not been completed
FlatDB trie storepartial--trie-engine flatdb, default rocksdb. An append-only store whose index holds no key bytes, so every read verifies itself; 1.76× serially on real mainnet state. Experimental for one precise reason — it has not run on a real chain — and the rollout is staged so the engine change can be measured on its own. See the research note
Wallet / account managementdeliberately minimal--wallet-file hands the node a file of hex keys and it will sign for those and nothing else; without the flag eth_sign and friends refuse exactly as before. No encryption, no passphrase, no personal_*, nothing written back — it exists so a load harness can drive eth_sendTransaction. Encrypted key management is a real thing to get right and is not approximated here
Grouped log bloom indexpartialPer-block header blooms give the same answers; rskj's grouped index would make a wide eth_getLogs cheaper
Explorer API (ots_*)not startedThe Otterscan set — the largest coherent block of RPC Rustock lacks, and the one that would let a block explorer run with no external indexer
Security auditnot startedThe gating item for any production conversation

Contributing

The most valuable contribution is a block number. If Rustock diverges from the chain at a specific height, that report is worth more than a patch — it is a new entry in the catalogue and a new regression test, and it is the one thing that cannot be found by reading code.

  • Report a divergence — the block number, the mismatch, and --log-level debug output around it.
  • Close a named gap — the grouped log bloom index, the explorer (ots_*) namespace, eth_getBlockReceipts and debug_traceCall are all tracked as issues, each well-bounded with a clear oracle in rskj.
  • Add tests where the coverage comparison says they are thin — it is a ranked to-do list that the project wrote against itself.
  • Correct the catalogue. It is incomplete by construction and at least one entry has already been superseded by a better reading of rskj.

Everything happens on GitHub. Tests run with cargo test --workspace, formatting with cargo fmt, and dependency policy with cargo deny check — all three run in CI.

License

MIT. Rustock is an independent project: it is not affiliated with, endorsed by, or an official client of the Rootstock network, IOV Labs, or Rootstock Labs. The name is a portmanteau of Rust and Rootstock and nothing more.

Contact