feat(storage): prune stale aggregated payloads on finalization#389
feat(storage): prune stale aggregated payloads on finalization#389MegaRedHand wants to merge 5 commits into
Conversation
The new and known aggregated payload buffers were only bounded by FIFO eviction at 64/512 entries: post-finalization entries lingered until naturally pushed out, polluting `existing_proofs_for_data` lookups and occasionally forcing recursive XMSS aggregation when plain aggregation would suffice. Mirrors leanSpec's `prune_stale_attestation_data`: drop entries whose `target.slot <= finalized.slot` from both buffers when finalization advances, alongside the existing gossip-signature prune.
🤖 Kimi Code ReviewThe PR implements pruning for aggregated attestation payloads on finalization, correctly mirroring the leanSpec's Code Quality & Safety
Correctness
Testing The unit tests cover:
Recommendation: Approve after considering the Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Claude Code ReviewPR #389 —
|
🤖 Codex Code Review
I couldn’t run the Rust tests here because Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Greptile SummaryThis PR introduces
Confidence Score: 5/5Safe to merge — the changes are additive pruning logic with no risk of incorrectly dropping live data, and all accounting invariants (total_proofs, order) are correctly maintained. The new PayloadBuffer::prune correctly tracks the before/after count, decrements total_proofs for each evicted entry, and scrubs stale roots from order in a single retain pass. The GossipSignatureBuffer refactor is a straightforward drop of an unnecessary HashSet with no behavioural change. The store-level method acquires locks sequentially and sums results safely. All three new tests exercise the prune paths including the no-op and combined-buffer cases. No files require special attention.
|
| Filename | Overview |
|---|---|
| crates/storage/src/store.rs | Adds PayloadBuffer::prune, Store::prune_stale_aggregated_payloads, and wires them into update_checkpoints; also cleans up GossipSignatureBuffer::prune to remove an unnecessary HashSet allocation. Logic, accounting (total_proofs), and order cleanup all look correct. Three tests validate the new paths. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[update_checkpoints] -->|finalized.slot > old_finalized_slot| B[prune_live_chain]
A --> C[prune_gossip_signatures]
A --> D[prune_stale_aggregated_payloads]
D --> E[new_payloads.lock.prune]
D --> F[known_payloads.lock.prune]
E --> G[PayloadBuffer::prune]
F --> G
G -->|data.retain| H{target.slot > finalized_slot?}
H -->|yes| I[keep entry]
H -->|no| J[total_proofs -= proofs.len
remove entry]
G --> K[order.retain: keep if data.contains_key]
C --> L[GossipSignatureBuffer::prune]
L -->|data.retain| M{slot > finalized_slot?}
M -->|yes| N[keep entry]
M -->|no| O[total_signatures -= sigs.len
remove entry]
L --> P[order.retain: keep if data.contains_key]
Reviews (2): Last reviewed commit: "Merge branch 'main' into feat/prune-stal..." | Re-trigger Greptile
Replace the pruned_roots HashSet with a size-delta counter; filter self.order via self.data.contains_key after retain. Same semantics, one fewer allocation per prune, and matches the disjoint-field-borrow pattern used elsewhere in the file.
…::prune Mirror the same size-delta + data.contains_key pattern applied to PayloadBuffer::prune in the prior commit, so both buffers share a single prune idiom.
Summary
PayloadBuffer::prune(finalized_slot)andStore::prune_stale_aggregated_payloads(finalized_slot), mirroring leanSpec'sprune_stale_attestation_datafor the two aggregated payload pools.Store::update_checkpointsnext to the existingprune_live_chain/prune_gossip_signaturescalls, and includespruned_payloadsin the "Pruned finalized data" log line.new_payloadsandknown_payloads.Motivation
ethlambda was missing the equivalent of leanSpec's
prune_stale_attestation_data. The new and known aggregated payload buffers were only bounded by FIFO eviction at 64/512 entries, so post-finalization entries (target.slot ≤ finalized.slot) could linger for ~33 min at 1 attestation/slot. Lingering known proofs polluteexisting_proofs_for_datalookups: when a fresh gossip-signature batch happens to collide on the exact staledata_root,build_jobselects the stale entry as a child and routes throughaggregate_mixedrecursive aggregation instead of plain XMSS aggregation. Criterion now matches leanSpec exactly: keep ifftarget.slot > finalized.slot.Test plan
cargo test -p ethlambda-storage— 38 tests pass (including the 3 new ones)cargo clippy -p ethlambda-storage --all-targets -- -D warningscleanpruned_payloadsat finalization boundaries