Skip to content

perf: avoid duplicate SMT work in State::apply_block by precomputing account state forest mutations #2111

@kkovaacs

Description

@kkovaacs

Summary

miden_node_store::state::State::apply_block currently performs expensive account-state SMT work twice when applying a block with public account updates.

The expensive path is:

  1. State::apply_block() computes account and nullifier tree mutations.
  2. The DB apply path calls models::queries::apply_block(), which calls upsert_accounts().
  3. upsert_accounts() updates SQLite account state and recomputes account-level commitments:
    • vault root
    • storage map roots
    • storage header commitment
    • final account commitment validation
  4. After the DB transaction commits, State::apply_block() updates the in-memory account state forest via AccountStateForest::apply_block_updates().
  5. The account state forest applies the same vault and storage map deltas again, recomputing the same SMT roots.

This duplicates the costly SMT work, especially for storage map updates where upsert_accounts() may need to load complete map contents from SQLite and reconstruct the Merkle tree.

Problem

upsert_accounts() currently needs to derive the updated account row values itself. For partial public account updates, that requires:

  • reading latest vault assets from SQLite,
  • applying the vault delta,
  • recomputing the vault root,
  • reading storage map entries for changed slots,
  • applying storage deltas,
  • recomputing storage map roots,
  • producing the updated AccountStorageHeader,
  • deriving the updated account commitment.

Later, AccountStateForest::apply_block_updates() applies the same account deltas to the in-memory forest and recomputes the same vault/storage roots again.

This makes block application slower than necessary and makes the DB layer responsible for commitment computation that should already be available from the in-memory state transition machinery.

Proposed solution

Refactor block application so account state forest mutations are computed before the SQLite transaction is applied.

The desired flow would be:

  1. Validate the block as today.
  2. Compute account tree and nullifier tree mutations as today.
  3. Compute account state forest mutations for public account deltas before applying SQLite changes.
    • This should also produce the updated vault roots and storage map root commitments needed by SQLite.
  4. Pass the precomputed account state data into the DB apply path.
  5. upsert_accounts() should stop reconstructing vaults/storage maps just to compute commitments.
    • It should insert/update the account row using the precomputed vault_root, storage_header, and/or storage map root values.
    • It should still persist the changed vault assets and storage map values.
    • It should still validate that the derived/precomputed final account commitment matches BlockAccountUpdate::final_state_commitment().
  6. Once the SQLite transaction commits, apply:
    • account tree mutations,
    • nullifier tree mutations,
    • precomputed account state forest mutations.

Dependency / prerequisite

This assumes miden-crypto exposes a LargeSmtForest mutation API similar to the account/nullifier tree APIs, for example:

  • compute_mutations(...)
  • apply_mutations(...)

That API does not exist today, so this issue depends on adding or exposing the necessary LargeSmtForest mutation support first. 0xMiden/crypto#1009 is tracking this new API in miden-crypto.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels
    No fields configured for Enhancement.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions