Skip to content

starknet_committer,starknet_patricia,starknet_patricia_storage: hoist per-node db key prefix in index-layout serialization#14852

Open
gkaempfer wants to merge 2 commits into
mainfrom
claude/perf/starknet-committer-hoist-db-key-prefix-40448
Open

starknet_committer,starknet_patricia,starknet_patricia_storage: hoist per-node db key prefix in index-layout serialization#14852
gkaempfer wants to merge 2 commits into
mainfrom
claude/perf/starknet-committer-hoist-db-key-prefix-40448

Conversation

@gkaempfer

Copy link
Copy Markdown
Contributor

Summary

Follow-up performance review of #14850 (which removed a redundant .to_vec() copy in create_db_key), scoped to the same hot path: Patricia trie DB key construction, which runs once per node per block during block commitment.

In the index-layout storage backend, IndexLayoutStarknetStorageValue::get_static_prefix (crates/starknet_committer/src/db/index_db/leaves.rs) rebuilds a fresh 32-byte Vec<u8> from a contract's address for every node in that contract's storage trie (inner nodes and leaves alike) — even though every node serialized in one FilledTree::serialize call, or deleted in one serialize_deleted_nodes iteration, shares the same contract address. For a contract with thousands of modified storage slots in a block, this recomputes and heap-allocates an identical prefix thousands of times instead of once.

Facts layout doesn't have this problem — its prefix depends on node type (inner vs. leaf), not just on the key context — so it's left untouched.

Change

  • starknet_patricia_storage::db_object: added HasDynamicPrefix::static_prefix_hint(key_context) -> Option<DbKeyPrefix>, a hook that returns the prefix without an instance when it depends only on key_context (Some, via the blanket HasStaticPrefix impl — covers index layout) or None when it's node-data-dependent (facts layout's manual HasDynamicPrefix impl doesn't get the blanket override, so it stays None, and its get_db_object ignores the hint — unchanged behavior).
  • starknet_patricia::db_layout + filled_tree::tree: NodeLayout::get_db_object gained a prefix_hint: Option<&DbKeyPrefix> parameter; FilledTreeImpl::serialize computes the hint once before its per-node loop and reuses it for every node.
  • starknet_committer::db::index_db::db: IndexNodeLayout::get_db_object uses the hint when present; serialize_deleted_nodes (a separate per-contract loop) now computes the prefix once per contract instead of once per node index.
  • create_db_key now borrows its prefix (&DbKeyPrefix) instead of taking it by value, since callers with a precomputed prefix no longer need to move it per node; all call sites updated.
  • Added unit tests (starknet_patricia_storage::db_object_test) directly pinning the invariant the optimization relies on: the hoisted hint equals the per-instance prefix, and two objects sharing a key context but differing in data produce identical db keys via both paths.

Test plan

  • cargo build -p starknet_patricia_storage -p starknet_patricia -p starknet_committer -p starknet_transaction_prover
  • SEED=0 cargo test -p starknet_patricia_storage -p starknet_patricia -p starknet_committer -p starknet_transaction_prover — all green, with two known-unrelated exceptions confirmed pre-existing on a clean origin/main checkout:
    • starknet_transaction_prover's test_compiled_class_v1_to_casm_round_trip fails in this sandbox because it needs to download the Cairo 1 compiler over a network the sandbox doesn't have; reproduces identically on origin/main.
    • starknet_patricia_storage's test_storage_concurrent_access::case_2_mdbx_storage uses a hardcoded /tmp/test_mdbx_storage path with no cleanup between runs; it only fails when the suite is re-run repeatedly against leftover on-disk state from a prior run in the same environment. Passes cleanly otherwise.
  • unset CI && scripts/rust_fmt.sh — no diff
  • Reviewed by an independent Opus pass, which added the db_object_test.rs unit tests above and confirmed no blocking issues; a second independent Opus pass reached the same verdict.

🤖 Generated with Claude Code


Generated by Claude Code

claude added 2 commits July 21, 2026 11:33
… per-node db key prefix out of index-layout serialization loop

Follow-up performance review of #14850, scoped to the same hot path (Patricia
trie node key construction).

In index-layout storage tries, `IndexLayoutStarknetStorageValue::get_static_prefix`
rebuilds a fresh `Vec<u8>` from the contract address for every single node
(inner and leaf alike), even though all nodes serialized in one
`FilledTree::serialize` call, or deleted in one `serialize_deleted_nodes`
iteration, share the same contract address. For a contract with thousands of
modified storage slots in a block, this recomputes and heap-allocates an
identical 32-byte prefix thousands of times instead of once.

Add `HasDynamicPrefix::static_prefix_hint`, a per-type hook that returns the
prefix without needing an instance when it doesn't depend on node data
(`Some`, via the blanket `HasStaticPrefix` impl — this covers index layout)
or `None` when it does (facts layout's inner-vs-leaf branching, left
untouched). `FilledTreeImpl::serialize` computes the hint once per call and
threads it through `NodeLayout::get_db_object`; `serialize_deleted_nodes`
computes it once per contract in its existing loop. `create_db_key` now
borrows its prefix instead of taking it by value, since callers with a
precomputed prefix no longer need to move it per node.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQNuoKFwFs933ZC5oRvcAC
Covers the correctness contract of HasDynamicPrefix::static_prefix_hint:
that the hint matches the per-instance get_prefix, and that two objects
sharing a key_context but differing in unrelated data produce identical
db keys through both the hoisted and per-instance paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQNuoKFwFs933ZC5oRvcAC
@gkaempfer gkaempfer self-assigned this Jul 21, 2026
@cursor

cursor Bot commented Jul 21, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes how storage keys are assembled on the block-commitment serialization path; keys must stay byte-identical, which the new tests are meant to guarantee.

Overview
Performance follow-up on Patricia trie DB key construction during block commitment: index-layout storage was rebuilding the same contract-address-derived prefix for every node in a contract storage trie (and again when serializing deleted nodes).

Adds HasDynamicPrefix::static_prefix_hint (and DBObject::db_key_from_prefix) so callers can compute a prefix once when it depends only on key_context. FilledTree::serialize hoists that hint before its per-node loop; NodeLayout::get_db_object takes an optional prefix_hint. Index layout uses the hint; facts layout still derives prefix per node (inner vs leaf) and ignores the hint.

create_db_key now takes &DbKeyPrefix; get_node_index_db_key_with_prefix supports deleted-node key batching with one prefix per contract. New db_object_test asserts hoisted keys match the old per-instance path.

Reviewed by Cursor Bugbot for commit 911cc8f. Bugbot is set up for automated code reviews on this repo. Configure here.

@reviewable-StarkWare

Copy link
Copy Markdown

This change is Reviewable

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.

3 participants