Fix oversized blocks during stall recovery (#259)#260
Conversation
build_block() greedily includes every matching attestation from the payload pool with no size cap. During stall recovery the pool can grow large enough that the SSZ-encoded SignedBlockWithAttestation exceeds MAX_PAYLOAD_SIZE (10 MiB), causing gossip publish failures. The test simulates a stall by populating the pool with 50 entries carrying ~253 KB proofs each, producing a 12.4 MiB block. It asserts the block must stay under the limit, so it fails until a size cap is enforced in build_block.
With XMSS proofs averaging ~253 KB each, an uncapped block builder can produce blocks exceeding the 10 MiB MAX_PAYLOAD_SIZE gossip limit during stall recovery. Cap build_block to MAX_ATTESTATIONS_PER_BLOCK = 24, which yields ~6 MiB and leaves headroom for header and proposer signature. The budget is threaded into extend_proofs_greedily so it stops selecting proofs once the cap is reached.
🤖 Kimi Code ReviewOverall Assessment: Good fix for a critical DoS vulnerability (issue #259). The implementation correctly caps block size to stay within the 10 MiB gossip limit while maximizing validator coverage via greedy selection. Minor Issues:
Security & Consensus:
Code Quality:
Dependencies:
Consensus Correctness: Action Required:
Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Codex Code ReviewFindings
No other material correctness issues stood out in the touched logic. I could not run the targeted test in this environment because Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Greptile SummaryThis PR fixes a block size overflow during stall recovery by capping attestations at Confidence Score: 5/5Safe to merge — the cap logic is correct, the budget is properly threaded, and a regression test validates the fix end-to-end. No P0 or P1 issues found. The remaining budget is recomputed correctly for each entry in the for-loop, extend_proofs_greedily respects it, the outer loop terminates correctly once the cap is hit, and the new dev-only dependencies are scoped appropriately. All findings are P2 or below. No files require special attention.
|
| Filename | Overview |
|---|---|
| crates/blockchain/src/store.rs | Adds MAX_ATTESTATIONS_PER_BLOCK=24 cap in build_block and threads a remaining-budget parameter into extend_proofs_greedily; includes regression test for the stall scenario. Logic is correct: remaining is recomputed per entry in the for-loop, extend_proofs_greedily respects the budget, and the outer loop correctly terminates after the cap is hit. |
| crates/blockchain/Cargo.toml | Adds libssz and libssz-types under [dev-dependencies] only, scoped appropriately to the new regression test. |
| Cargo.lock | Lock file updated to record libssz and libssz-types as new dev dependencies for ethlambda-blockchain. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[build_block called] --> B{aggregated_payloads empty?}
B -- Yes --> Z[Build final block with 0 attestations]
B -- No --> C[Sort entries by target.slot, data_root]
C --> D[Outer loop: found_new = false]
D --> E[For each sorted entry]
E --> F{remaining = MAX 24 - len\nremaining == 0?}
F -- Yes --> G[break for-loop]
F -- No --> H{entry already processed?}
H -- Yes --> E
H -- No --> I{source == current_justified\nand head in known_block_roots?}
I -- No --> E
I -- Yes --> J[Mark processed, found_new = true]
J --> K[extend_proofs_greedily\nwith budget = remaining]
K --> L[Add up to budget attestations\nfor this att_data]
L --> E
G --> M{found_new?}
M -- No --> Z
M -- Yes --> N[process_slots + process_block\ncheck justification]
N --> O{justification advanced?}
O -- No --> Z
O -- Yes --> D
Z --> P[Compute final state_root\nReturn Block + proofs]
Reviews (1): Last reviewed commit: "Cap attestations per block to 24 to prev..." | Re-trigger Greptile
🤖 Claude Code ReviewHere is my review of PR #260: Review: Fix oversized blocks during stall recoveryOverall: Clean, focused fix that correctly addresses the root cause. Logic is sound, test coverage is meaningful. Correctness
Outer loop extra iteration — when the cap is hit mid-loop and // after the inner for loop, before the justification check:
if aggregated_attestations.len() >= MAX_ATTESTATIONS_PER_BLOCK {
break;
}This is a minor performance concern, not a correctness issue.
Count-Based vs Byte-Based CapThe cap is count-based (24) rather than byte-based. This is pragmatic and avoids expensive SSZ encoding during block construction, but it implicitly assumes proof sizes stay bounded. The constant is well-documented with the ~253 KB average and ~6 MiB projected block size. If the leanSpec proof format changes (e.g., larger aggregation trees), this constant would need revisiting. Consider a comment noting this dependency. Test
One observation: the test only covers the case where every entry has exactly 1 proof with 1 validator (no greedy merge needed). A case where one data_root contributes multiple proofs (testing budget consumption within a single Dependencies
Summary
Automated review by Claude (Anthropic) · sonnet · custom prompt |
Replace the fixed count cap (MAX_ATTESTATIONS_PER_BLOCK = 24) with a byte budget (MAX_ATTESTATION_PROOF_BYTES = 9 MiB) that tracks cumulative proof_data.len() as proofs are added. This adapts automatically to varying proof sizes instead of assuming ~253 KB averages. The 9 MiB budget leaves 1 MiB margin within the 10 MiB MAX_PAYLOAD_SIZE gossip limit for block header, proposer signature, attestation metadata, and SSZ encoding overhead.
Summary
MAX_ATTESTATIONS_PER_BLOCK = 24inbuild_block, preventing blocks from exceeding the 10 MiBMAX_PAYLOAD_SIZEgossip limit during stall recoveryextend_proofs_greedilyso proof selection stops once the cap is reachedCloses #259
Context
With XMSS aggregated proofs averaging ~253 KB each, just ~39 attestations exceed the 10 MiB limit. During stall recovery the payload pool can grow well past that, causing
MessageTooLargeon gossip publish andsnappy decoded len exceeds maxon receiving peers.24 attestations produces ~6 MiB, leaving headroom for the block header, proposer signature, and proof size variance.