fix(state-transition): bound distinct attestation data in the transition - #555
fix(state-transition): bound distinct attestation data in the transition#555MegaRedHand wants to merge 1 commit into
Conversation
🤖 Kimi Code ReviewThis PR implements a DoS protection mechanism (leanSpec #536) to limit distinct Security & Correctness
Code Quality
Minor Suggestions
Consensus Safety Approved with minor style nits. Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Claude Code ReviewThis is a clean, well-scoped fix. Review complete. Review: PR 555 — bound distinct attestation data in the transitionOverall: Correct, minimal, and well-tested fix that closes a real gap: Correctness
Security / DoS
Tests
Minor/nit
No changes requested. Automated review by Claude (Anthropic) · sonnet · custom prompt |
Greptile SummaryAdds transition-level enforcement of the distinct attestation-data cap.
Confidence Score: 5/5The PR appears safe to merge, with no actionable correctness or security issues identified. The transition now enforces the same distinct-attestation-data limit and inclusive boundary as block import, while existing callers isolate failed mutations in disposable cloned state.
|
| Filename | Overview |
|---|---|
| crates/blockchain/state_transition/src/lib.rs | Correctly mirrors the existing import-boundary cap inside process_attestations, using identical distinctness semantics and boundary behavior, with focused regression tests. |
Reviews (1): Last reviewed commit: "fix(state-transition): bound distinct at..." | Re-trigger Greptile
🤖 Codex Code Review
No other correctness or security problems stood out in this diff. I could not run the targeted tests here because the toolchain wrapper tries to write under Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
The per-block cap on distinct AttestationData is a transition rule in leanSpec (`process_attestations`), and `fork_choice.on_block` says so explicitly: "The transition itself bounds the distinct-data count. Only the wire-level duplicate prohibition lives here." We enforced it only at the import boundary in `on_block`, so `state_transition()` accepted an over-cap block and then failed on the state root instead. Both block production (`build_block` -> `process_block`) and spec-fixture replay call the transition without going through `on_block`, so neither was bounded. Check it at the top of `process_attestations`, ahead of the justification-bookkeeping guards as the spec does. The `on_block` check stays for now: it runs before signature verification, so an over-cap block is still rejected without paying for proof verification. (leanSpec #536)
3a8ead6 to
29cc787
Compare
What
Enforce the per-block cap on distinct
AttestationDatainsideprocess_attestations, where leanSpec has it, in addition to the existing check at the import boundary inon_block.Why
leanSpec puts the bound in the transition (
state_transition.process_attestations) andfork_choice.on_blockdefers to it explicitly:We only had it in
on_block(store.rs), sostate_transition()accepted an over-cap block and then failed on the state root instead. Two callers reach the transition without passing throughon_block:build_block->process_blockstate_transition/runSTATE_ROOT_MISMATCHThe check goes first in
process_attestations, ahead of the justification-bookkeeping guards, matching the spec's order when a block violates two rules at once.The duplicate check in
on_blockstaysDeliberately, for now: it runs before
verify_block_signatures, so an over-cap block is still rejected without paying for proof verification. Whether we collapse the two sites into one is a follow-up decision.Testing
cargo test --workspace --profile release-fast: green, no new tests added here.test_block_exceeding_distinct_attestation_data_cap_rejects_block, which asserts failure but not yet the reason. Cross-checked against test(spec): assert the rejection reason on expected-failure fixtures #547, which adds the reason assertion to the fixture runners: with both branches applied, that fixture passes for the right reason (TOO_MANY_ATTESTATION_DATA, previouslySTATE_ROOT_MISMATCH).Whichever of the two lands first, the other's exhaustive
From<&Error> for RejectionReasonmatch makes the missing mapping arm a compile error, so the reason cannot be silently dropped.(leanSpec #536)