Repository navigation
perf(core): drop the per-state-read heap allocations in the witness path - #198
Conversation
Deletes the second sources of truth and the dead parameters/accessors the /simplify review found, with no observable change on canonical-block paths. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CHVpMMX9N69sUNbuKgpBVY
Deletes the `writer` parameter threaded through `validate_block`, `validate_block_deriving_updates`, `replay_block` and `verify_and_replay`, and with it EIP-3155 trace output. A feature deletion, not a cleanup. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CHVpMMX9N69sUNbuKgpBVY
…s_used from the executor Both are definitional rewrites of security-critical derivations, argued in the PR body: `trie_hash()` is `keccak256(encoded_2718())`, and mega-evm's `gas_used` is the last receipt's cumulative gas. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CHVpMMX9N69sUNbuKgpBVY
Block integrity verification becomes the RPC retry loop's finalize step and runs on the blocking pool; the advancer's pre-advance hooks and store commit run there too. Panic semantics are preserved and pinned by tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CHVpMMX9N69sUNbuKgpBVY
`WitnessDatabase` reads go through salt's `find()` with in-place decoding and stack-allocated key encodings; `LightWitness::metadata` stops cloning the `SaltValue` it only reads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CHVpMMX9N69sUNbuKgpBVY
Claude review status
✅ Review clean Last reviewed: New this round: 0 finding(s), 0 question(s) · Resolved this round: 0 · Open questions: 0 |
Codecov Report❌ Patch coverage is
☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 50741dc320
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…tate-read-allocations # Conflicts: # bin/stateless-validator/src/app.rs # bin/stateless-validator/src/chain_sync.rs # bin/stateless-validator/src/metrics.rs # bin/stateless-validator/src/runner.rs # bin/stateless-validator/src/validator_db.rs # bin/stateless-validator/tests/integration.rs # crates/stateless-common/Cargo.toml # crates/stateless-common/src/metrics.rs # crates/stateless-common/src/rpc_client.rs # crates/stateless-core/src/chain_spec.rs # crates/stateless-core/src/db.rs # crates/stateless-core/src/executor.rs # crates/stateless-core/src/pipeline/advancer.rs # crates/stateless-core/src/pipeline/fetcher.rs # crates/stateless-core/src/pipeline/tests.rs # crates/stateless-r2/src/fetch.rs
… test bite The encoder docs justified themselves as "for per-state-read hot paths", which asks the next contributor an unmeasurable question; the decidable one is whether the caller needs to own the key. encode() is now documented as the owned form derived from the borrowing primaries. stack_key_encodings_match_encode compared each encoder against encode(), which delegates to it — true by construction, so it killed no mutation. It now asserts the literal byte layout, which catches a swapped concatenation (verified: the mutation fails the new test and passes the old one). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Summary
PR 5/6 of the #170 split. #197 has merged and
mainis merged in as of960a3bd, so this now targetsmaindirectly. Removes the heap allocations on the per-state-read path of block replay. Listed in vincent's review of #170 as "A hot-path optimization" worth its own line.This lookup runs once per unique account and storage slot touched by a block, so it is the hottest thing in
WitnessDatabase.WitnessDatabasereads go through salt'sfind(), which hands back theSaltValue(an inline array) instead ofplain_value()'s freshly heap-allocated copy of the value bytes. Callers decode from.value()in place.PlainKey::account_key_bytes(a borrow of the address) andPlainKey::storage_key_bytes(aFixedBytes<52>) replacePlainKey::{Account,Storage}(..).encode()'sVec.encode()itself delegates to them, so there is one definition of each encoding.bucket_id_for_account/bucket_id_for_slotuse them too — that is one allocation per call removed on another per-read path.LightWitness::metadatastops cloning theSaltValueit only reads.Net: two heap allocations per witnessed state read, plus one per
bucket_id_for_*call.Correctness
The interesting claim is that
find()returns the same bytesplain_value()did. That is pinned by the mainnet fixture replays (validate_block_mainnet_fixtures,validate_block_deriving_updates_mainnet_fixtures): a read that returned different bytes changes execution and fails the post-state-root check on every fixture.stack_key_encodings_match_encodepins the narrower claim: each encoder produces one specific byte layout. It asserts the literal bytes rather than one encoder againstencode()— whileencode()delegates, comparing the two passes by construction and pins nothing, so a drift in the shared primary would go unnoticed. Mutation-checked: swapping the concatenation order instorage_key_bytesfails the test.Follow-ups (deliberately not in this PR)
Two siblings of the same root cause are left alone, both because they reach outside this diff:
FIXMEatstate.rs:232names two copy sources on the read path. This PR removes the second (salt_val.value().to_vec()). The first isStateReader::valuereturningSaltValueby value, which lands here as a 94-byte clone per read; removing it means changing that trait to return a borrow and bumping the tag.LightWitnessExecutor::direct_lookup_tblallocates oneVecper witness entry at build time (light_witness.rs:253) — the same "plain key on the heap" shape, at the construction site. Unifying it with the stack encoders needs an enum key or a raw-entry lookup, since a padded 52-byte inline key cannotBorrow<[u8]>for a 20-byte account key.Testing
cargo fmt --all --check,cargo clippy --workspace --all-targets --all-features(0 warnings),cargo sort --check, full workspace suite 490 passed / 0 failed,cargo test -p stateless-core --no-default-features --lib --no-runclean.A quality pass landed in
cccd964: the encoder docs now state the rule by ownership (own the key →encode(); borrow it for a lookup → the byte encoders) instead of by "hot path", which is the question a contributor can actually answer; and the encoder test asserts literal bytes instead of comparing an encoder against the caller that forwards to it.