feat: read-state bootstrap for co-located ephemeral/Regtest followers#10787
feat: read-state bootstrap for co-located ephemeral/Regtest followers#10787nuttycom wants to merge 9 commits into
Conversation
A read-only secondary RocksDB instance has nothing to flush, and RocksDB rejects `flush()`/`flush_wal()` on secondaries with "Not supported operation in secondary mode". `DiskDb::shutdown` logged those benign failures at INFO as "unexpected error flushing database ... during shutdown". Model the database open mode as a `DbMode` enum (`Persistent`, `Ephemeral`, `ReadOnlySecondary`) instead of separate `ephemeral`/`read_only` flags, making the nonsensical "ephemeral read-only secondary" state -- which would delete the primary's files on drop -- unrepresentable internally. `DiskDb::new` rejects that flag combination with a `StateInitError::ReadOnlyEphemeralConflict` (a real error, enforced in release builds, not a debug assertion), and shutdown skips flushing a secondary. The existing file-deletion behaviour is preserved for every valid mode. While here, match the expected RocksDB shutdown error by `ErrorKind::ShutdownInProgress` rather than by substring, consistent with the existing `e.kind()` check at the database open site. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Locates the most recent block in a locator that is on the best chain, reusing find_chain_intersection. Best-chain-only; the locator carries side-chain info. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Exposes find_fork_point as a ReadRequest/ReadResponse pair, dispatched against the best chain, mirroring the FindBlockHashes family. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
oxarbitrage
left a comment
There was a problem hiding this comment.
Looks good overall — I'm fine with the new RPC method for this. I initially considered a file-based approach since this is co-located, but since Zallet already maintains a JSON-RPC connection for steady-state operations (sendrawtransaction, mempool polling), the RPC endpoint is a natural fit for bootstrap discovery.
A few suggestions to make it mergeable:
-
Avoid the
Nonechurn inRpcImpl::new— instead of addingindexer_grpc_addras a constructor parameter (which touches 30+ test call sites), initialize it asNoneinsidenew()and add a setter that onlystart.rscalls. That eliminates ~10 files from the diff. -
Rebase onto current main —
FindForkPointalready landed via #10764, so those changes should drop out of the diff.
Would you mind cleaning it up? Happy to take another look then, looking to merge.
On the testing side, we should also prepare an integration test for the new RPC method and potentially update how we bootstrap in the integration test suite. Zallet's bootstrap path will likely need changes on its side as well to consume getreadstateinfo.
Builds atop #10764
Motivation
A co-located read-state follower (e.g. Zallet's
zebra-statebackend) opens a node'sstate DB as a read-only RocksDB secondary and follows its non-finalized best chain over the
indexer gRPC stream. It cannot follow an ephemeral or Regtest node today: an
ephemeral DB lives at a random temp path the follower can't discover, and a follower has no
way to learn a Regtest node's configured activation heights.
Solution
getreadstateinfoJSON-RPC method (zebra-only): reports the node's live state DBpath, indexer gRPC address, DB format version/kind, and network identity (kind + genesis
hash +, for Regtest, activation heights).
ReadRequest::StateDbInfo→ReadResponse::StateDbInfo: surfaces the livefinalized-state DB path/version/kind from the read-state service (correct for ephemeral
DBs, whose path can't be recomputed from config).
Config::read_only_db_path): open a read-only secondaryat an explicit on-disk path so a follower can tail an ephemeral primary's randomly-located
DB.
FindForkPointread request/helper for reorgtracking; don't flush read-only secondary DBs on shutdown.
Testing
ReadRequest::StateDbInforeports the live DB; read-only open honors anexplicit path;
getreadstateinforeports state + Regtest network info.cargo fmt --all -- --check,cargo clippy -p zebra-state -p zebra-rpc -p zebrad --all-targets -- -D warnings, andcargo test -p zebra-state -p zebra-rpcpass locally(141 + 2 tests).
Notes for reviewers / known limitations
default Testnet, and Regtest report enough to reconstruct the network; a custom Testnet
would be mis-reconstructed as the default public Testnet. Intentional for this scope.
getreadstateinforeturns a local filesystem path and the indexer address.It is gated by the same JSON-RPC cookie auth as every method, is read-only, and only
reflects config the operator already set. The reported
indexer_grpc_addrmay be a publicbind address.
AI disclosure
Implemented with Claude Code assistance (design, code, and tests). The contributor is the
sole responsible author.
Closes #10786.