Skip to content

feat: Lock-free apply_block refactor#2345

Open
sergerad wants to merge 25 commits into
nextfrom
sergerad-lockfree-store-state
Open

feat: Lock-free apply_block refactor#2345
sergerad wants to merge 25 commits into
nextfrom
sergerad-lockfree-store-state

Conversation

@sergerad

@sergerad sergerad commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Makes the store's block-write path lock-free for readers. Reads previously contended on RwLocks over the in-memory trees (and were blocked during the DB-commit window of apply_block); they now load an immutable snapshot via ArcSwap and are never blocked by writes.

Why:

  • Read endpoints (sync, account/nullifier proofs, chain tip) no longer stall while a block is being applied.
  • Removes the cross-task lock choreography in apply_block (oneshot handshakes between the DB task and the in-memory update), which was fragile and hard to reason about.

How:

  • Single-writer task: a new BlockWriter task (crates/store/src/state/writer.rs) owns the mutable nullifier tree, account tree, blockchain MMR, and account-state forest. State::apply_block forwards blocks to it over an mpsc channel; requests are processed serially, so no locks are needed anywhere.
  • Snapshot publication: after each DB commit the writer builds a new immutable InMemoryState (trees backed by read-only RocksDB snapshot views) and publishes it atomically via ArcSwap. Readers load_full() a snapshot wait-free and keep a consistent frozen view even while the next block commits.
  • Read consistency scoped by snapshot tip: queries that combine DB and in-memory data (get_block_header, get_transaction_inputs note lookups) are now scoped to the snapshot's block number, since mid-apply the DB may be ahead of the published snapshot. select_existing_note_commitments gains an up_to_block bound.
  • Simplified DB apply: Db::apply_block is now a plain transaction — the oneshot allow_acquire/acquire_done synchronization is removed.
  • Deterministic shutdown: State::shutdown drains and joins the writer task so tree storage is released before the data directory is re-opened or deleted (used by stress-test seeding).
  • Observability: SnapshotGuard tracks live snapshot generations and lifetimes; warns when a snapshot outlives 10s or more than 4 generations are pinned (a leaked/slow reader pins a RocksDB snapshot).
  • Supporting changes: read-only reader() views for AccountStateForest / AccountTreeWithHistory (relaxed to BackendReader/SmtStorageReader bounds), chain_tip is now sync, new tracing field names allowlisted.

Followup:

  • Type-enforced read consistency: a new state view type could be added to provide stronger enforcement of state consistency (where DB queries need to be scoped by block number). For example:
/// A consistent read view of the store, pinned at the snapshot's block height.
pub(crate) struct StateView {
    snapshot: Arc<InMemoryState>,
    db: Arc<Db>,
}

impl State {
    fn view(&self) -> StateView {
        StateView { snapshot: self.snapshot(), db: Arc::clone(&self.db) }
    }
}

impl StateView {
    fn tip(&self) -> BlockNumber { self.snapshot.latest_block_num() }

    // Tree/MMR access delegates to the snapshot.
    fn blockchain(&self) -> &Blockchain { &self.snapshot.blockchain }

    // DB access is scoped automatically — callers can't pass their own tip.
    async fn select_existing_note_commitments(
        &self,
        commitments: Vec<Word>,
    ) -> Result<HashSet<Word>> {
        self.db.select_existing_note_commitments(commitments, self.tip()).await
    }
}

Changelog

[[entry]]
scope       = "node"
impact      = "changed"
description = "Store reads are lock-free: readers use atomically published in-memory snapshots and are no longer blocked while blocks are applied."

`

@sergerad
sergerad marked this pull request as ready for review July 23, 2026 01:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant