refactor: deduplicate storage helpers, eliminate redundant hash computations, and consolidate test utilities#172
Conversation
…, and consolidate test utilities - Extract iter_aggregated_payloads() and insert_aggregated_payload() private helpers parameterized by Table, replacing 4 copy-pasted methods in storage/store.rs - Remove duplicate extract_attestations() test helper in forkchoice_spectests.rs, use public Store::extract_latest_attestations() directly - Consolidate create_test_state() into a shared test_utils module in the rpc crate - Reuse pre-computed tree_hash_root values in on_gossip_aggregated_attestation, on_block_core, aggregate_committee_signatures, and insert_gossip_signature - Cache data_root lookups in extract_latest_attestations to avoid N+1 DB reads - Single-pass over iter_known_aggregated_payloads in produce_block_with_signatures instead of scanning the table twice - Fix stale comment: intervals 0/3 -> 0/4 to match the 5-interval model
🤖 Kimi Code ReviewReview SummaryThis PR introduces several improvements to attestation aggregation and storage efficiency. The changes are generally correct and improve code maintainability, but there are a few areas that need attention. Issues Found
Positive Changes
Suggested Fixes
The PR is nearly ready but requires fixing the compilation error before merging. Automated review by Kimi (Moonshot AI) · custom prompt |
🤖 Codex Code Review
No correctness or security regressions stood out in the signature-root changes ( I could not run tests locally due sandboxed rustup temp-file permission errors ( Automated review by OpenAI Codex · custom prompt |
🤖 Claude Code ReviewNow I have enough context to write the review. Let me compile my findings. Code Review: PR #172 — Storage deduplication, hash elimination, test utilities consolidationOverall: Clean, well-scoped cleanup. The deduplication and single-pass optimizations are correct. A few minor points below.
|
Greptile SummaryCode quality cleanup that eliminates redundant computations and duplicate code across the codebase with no behavior changes. Key improvements:
All changes are well-justified mechanical refactorings. The PR description provides excellent traceability with a detailed explanation for each optimization. Confidence Score: 5/5
|
| Filename | Overview |
|---|---|
| crates/blockchain/src/store.rs | Eliminated 4 redundant tree_hash_root() calls on hot paths and optimized block production to scan known payloads once instead of twice |
| crates/blockchain/tests/forkchoice_spectests.rs | Removed 20-line duplicate extract_attestations() helper in favor of public Store::extract_latest_attestations() API |
| crates/storage/src/store.rs | Deduplicated aggregated payload methods with parameterized helpers and added data_root cache to avoid N+1 lookups |
Last reviewed commit: 3b569d6
Extracts Checkpoint struct and its deser_dec_str helper from state.rs into a new checkpoint.rs file in crates/common/types/src/. Re-exports from state.rs so all existing imports via state::Checkpoint continue to work unchanged.
…checkpoint module directly Replace all `state::Checkpoint` imports with `checkpoint::Checkpoint` across the codebase (14 files). This removes the backwards-compatibility re-export from state.rs, making checkpoint.rs the single source of truth for the Checkpoint type.
…rrow extract_latest_attestations API - Batch insert_aggregated_payload: new insert_aggregated_payloads_batch performs a single read-write-commit cycle instead of N individual round-trips per validator. Updated all loop callers in aggregate_committee_signatures, on_gossip_aggregated_attestation, and on_block_core. - Narrow extract_latest_attestations to accept Iterator<Item = SignatureKey> instead of full (SignatureKey, Vec<StoredAggregatedPayload>) tuples. The function only uses the keys, so this eliminates unnecessary cloning of large proof data in produce_block_with_signatures. - Deduplicate prune_gossip_signatures and prune_attestation_data_by_root into a shared prune_by_slot helper parameterized by table and a slot-extraction closure. - Make deser_dec_str private in checkpoint.rs (only used by serde internally).
CheckpointInfo was a redundant struct with the same fields as Checkpoint. Use Checkpoint directly in ForkChoiceResponse.
blockchain/tests/common.rs and state_transition/tests/types.rs contained ~250 lines of identical serde Deserialize structs and From impls for converting JSON test fixtures into domain types. Since these are integration tests in different crates, they couldn't share code via mod. New crate crates/common/test-fixtures/ holds the 12 shared types (Container, Config, Checkpoint, BlockHeader, Validator, TestState, Block, BlockBody, AggregatedAttestation, AggregationBits, AttestationData, TestInfo) plus the deser_pubkey_hex helper. Both consumer files now re-export from the crate and keep only their test-specific types.
Summary
Code quality cleanup after the devnet 3 merge (#107). Ran systematic reviews (code reuse, quality, efficiency) across all changed files and fixed the actionable findings. Net result: no behavior changes, reduced I/O round-trips on hot paths.
tree_hash_root()computations on hot pathsdata_rootcache to avoid N+1 DB lookups in attestation extractionCheckpointto its own module with direct imports (no re-exports)prune_by_slothelperextract_latest_attestationsAPI to accept only keys (avoids cloning large proof data)ethlambda-test-fixturescrateChanges in detail
1. Storage method deduplication (
crates/storage/src/store.rs)iter_known_aggregated_payloads/iter_new_aggregated_payloadsandinsert_known_aggregated_payload/insert_new_aggregated_payloadwere character-for-character identical except for theTableenum variant.Fix: Extracted two private helpers parameterized by
Table:iter_aggregated_payloads(table)— shared iterator logicinsert_aggregated_payload(table, key, payload)— shared read-modify-write logic2. Redundant
tree_hash_root()elimination (crates/blockchain/src/store.rs)Four call sites were computing
tree_hash_root()on data that had already been hashed:on_gossip_aggregated_attestationaggregated.data.tree_hash_root()computed twiceon_block_coreblock.tree_hash_root()computed before verification, then recomputed on the clone afteraggregate_committee_signaturesdata.tree_hash_root()recomputed on data already fetched by itsdata_rootkeyon_gossip_attestation→insert_gossip_signatureattestation.data.tree_hash_root()computed in caller, then recomputed insideFix: Reuse the pre-computed value in each case. For
insert_gossip_signature, changed the signature to accept(data_root, slot)instead of&AttestationData.3. Test helper deduplication
forkchoice_spectests.rs: Removed 20-line duplicateextract_attestations()— calls the publicStore::extract_latest_attestations()directly.rpc/fork_choice.rs+rpc/lib.rs: Extracted sharedtest_utils::create_test_state().4.
data_rootcache inextract_latest_attestations(crates/storage/src/store.rs)Many validators attest to the same data (often just 1-3 distinct roots). Added a
HashMap<H256, Option<AttestationData>>cache to deduplicateget_attestation_data_by_rootlookups.5. Single-pass in
produce_block_with_signatures(crates/blockchain/src/store.rs)Was calling
store.iter_known_aggregated_payloads()twice — once for attestation data, then again for proofs. Now collects once and derives both from it.6. Move
Checkpointto dedicated module (crates/common/types/src/checkpoint.rs)Extracted
Checkpointstruct anddeser_dec_strhelper fromstate.rs. All import sites updated to usecheckpoint::Checkpointdirectly — nopub usere-exports.7. Batch aggregated payload inserts (
crates/storage/src/store.rs)Each
insert_aggregated_payloadcall performed its own read-modify-write-commit cycle. When storing an aggregated attestation covering N validators, this meant N separate storage round-trips.Fix: New
insert_aggregated_payloads_batchperforms a single read-write-commit cycle for all entries. Groups by key to handle duplicate keys correctly. Updated all loop callers inaggregate_committee_signatures,on_gossip_aggregated_attestation, andon_block_core.8. Deduplicate prune functions (
crates/storage/src/store.rs)prune_gossip_signaturesandprune_attestation_data_by_rootwere structurally identical (iterate table, decode, check slot, collect keys, batch delete). Extracted sharedprune_by_slot(table, finalized_slot, get_slot)helper parameterized by a slot-extraction closure.9. Narrow
extract_latest_attestationsAPIChanged from
impl Iterator<Item = (SignatureKey, Vec<StoredAggregatedPayload>)>toimpl Iterator<Item = SignatureKey>. The function only uses the keys — passing full payloads forcedproduce_block_with_signaturesto clone allVec<StoredAggregatedPayload>(containing multi-KB XMSS proof data) unnecessarily.10. Extract shared test fixture types into
ethlambda-test-fixturescrateblockchain/tests/common.rsandstate_transition/tests/types.rscontained ~250 lines of identical serdeDeserializestructs andFromimpls for converting JSON test fixtures into domain types. Since these are integration tests in different crates, they couldn't share code viamod.Fix: New
crates/common/test-fixtures/crate holds the 12 shared types (Container,Config,Checkpoint,BlockHeader,Validator,TestState,Block,BlockBody,AggregatedAttestation,AggregationBits,AttestationData,TestInfo) plus thedeser_pubkey_hexhelper. Both consumer files now re-export from the crate and keep only their test-specific types (ProposerAttestationin forkchoice,StateTransitionTestVector/PostStatein STF).11. Stale comment fix (
crates/blockchain/src/lib.rs)Comment said "intervals 0/3" but the 5-interval model promotes attestations at intervals 0 and 4.
How to test
All existing tests cover these changes — no new behavior was introduced.
cargo test --workspace --release cargo clippy --workspace -- -D warnings cargo fmt --all -- --checkAll 102 tests pass.