Open
Conversation
kkovaacs
reviewed
Apr 15, 2026
Contributor
kkovaacs
left a comment
There was a problem hiding this comment.
I've added some comments, but I think the overall concept is solid.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lock-free Store state
Problem
On
next, the store'sapply_blockuses a two-phase commit withoneshotchannels to synchronize DB and in-memory updates. It acquires aMutexto prevent concurrent writes, then coordinates between a DB update task and the main apply path using channels (acquired_allowed,inform_acquire_done). During the commit phase, it acquiresRwLock::write()oninner(which holds the nullifier tree, account tree, and blockchain) and a separateRwLock::write()onforest, blocking all readers for the duration of the DB commit and in-memory mutations.This means:
RwLockread guards before it can acquire the write lock. On Testnet we have observedstore.Rpc/GetAccounthaving P99 ~4.4s over 5.6M calls in a 3 day period. This can cause the write path to slow down significantly.acquired_allowed/inform_acquire_done) adds error-prone complexity and extraConcurrentWriteerror paths for root staleness checks.Solution
Replace the
RwLock/Mutex/channel coordination with a singleArcSwap<InMemoryState>and a dedicated writer task serialized by anmpscchannel.Readers call
snapshot()which returnsArc<InMemoryState>viaArcSwap::load_full()— a wait-free atomic refcount increment with no data cloning and no locks. The returnedArcis a frozen view: readers are completely unaffected if the writer publishes new state while they hold it.The writer is a dedicated
tokio::spawned task that owns the writable trees directly as local variables — noArc, no locks, no interior mutability. It receives blocks via anmpscchannel (one at a time), validates against the current snapshot, commits to DB with no locks held, applies mutations to its owned trees, then builds a newInMemoryStatewith snapshot-backed read-only copies of the trees and atomically publishes viaArcSwap::store().Both the nullifier tree and account tree are RocksDB-backed. The writer holds writable
LargeSmt<RocksDbStorage>instances. After applying mutations (which write to RocksDB), the writer createsLargeSmt<RocksDbSnapshotStorage>instances for the newInMemoryState— these use native RocksDB snapshots for point-in-time consistent reads with zero data copying.Changes
InMemoryState: New struct holdingblock_num, nullifier tree, account tree, blockchain MMR, and forest. Lives behindArcSwaponState.Scoped<T>: New wrapper pairing query results with theBlockNumberof the snapshot that produced them, so callers always know which block the data corresponds to. Applied toget_transaction_inputs,sync_nullifiers,sync_notes,sync_account_vault,sync_account_storage_maps,sync_transactions.apply_block.rs) with a dedicatedmpsc-based writer loop (writer.rs). Owns writable trees as local variables — no locks held during DB commit.RocksDbSnapshotStorage: New read-only,Cloneable storage backend using native RocksDB snapshots. Used for the in-memory snapshot trees so readers don't block the writer.SmtStoragesplit intoSmtStorageReader(read) andSmtStorage(read+write). Read-only snapshot trees only needSmtStorageReader.AccountTreeWithHistoryand loader functions relaxed accordingly.Clonederives added toAccountTreeWithHistory,AccountStateForest, and upstreamLargeSmt/LargeSmtForest(via patches) to support snapshot construction.RwLock<InnerState>,RwLock<AccountStateForest>,Mutex<()>write lock,apply_block.rs, all.awaiton snapshot calls.Further changes required
We will want to do the following either in this PR or followups:
LargeSmtForest<ForestInMemoryBackend>with a RocksDb backend.LargeSmtso that we can reduce memory footprint ofNullifierTree,AccountTree, andAccountStateForestinInMemoryState.Arc<InMemoryState>instances at any one time.Performance characteristics
nextRwLockread guard (may block on writer)RwLockwrite lock oninnermpscInMemoryState— trees use RocksDB snapshots, no tree data copied)