fix: tighten attestation future-slot bound (leanSpec #682)#317
fix: tighten attestation future-slot bound (leanSpec #682)#317MegaRedHand wants to merge 2 commits intomainfrom
Conversation
The previous time check accepted votes up to a full slot ahead of the local clock. With 4s slots that is ~3.2s of free pre-positioning room, enough for an adversary to publish next-slot aggregates before any honest validator could produce them, and the next proposer would happily include them. The bound is now expressed in interval units against the store's local time and gated by a new GOSSIP_DISPARITY_INTERVALS = 1 constant, the lean analogue of mainnet's MAXIMUM_GOSSIP_CLOCK_DISPARITY (~800 ms).
🤖 Kimi Code ReviewSecurity: Integer overflow in slot calculation
let attestation_start_interval = data.slot * INTERVALS_PER_SLOT;The multiplication can overflow if let attestation_start_interval = data
.slot
.checked_mul(INTERVALS_PER_SLOT)
.ok_or_else(|| StoreError::InvalidAttestationData {
reason: "slot value too large".into(),
})?;Consensus correctness: Good The change from slot-based ( Minor: Error message clarity
The error message mixes units (slots vs. intervals). Consider including the calculated start interval for debugging: #[error("Attestation slot {attestation_slot} is too far in future (attestation starts at interval {attestation_start_interval}, store time: {store_time})")]Style: OK Import reformatting in Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
Greptile SummaryThis PR tightens the attestation future-slot bound in
Confidence Score: 3/5Do not merge until the overflow in A single P1 logic bug: an adversary-supplied
|
| Filename | Overview |
|---|---|
| crates/blockchain/src/lib.rs | Adds GOSSIP_DISPARITY_INTERVALS = 1 constant with clear doc comment; no issues found. |
| crates/blockchain/src/store.rs | Rewrites attestation time-check to interval units and renames error field to store_time; introduces a potential integer overflow in data.slot * INTERVALS_PER_SLOT when slot value is attacker-controlled. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Receive gossip attestation] --> B[validate_attestation_data]
B --> C[Availability Check\nblocks exist in store?]
C -->|no| E1[Err: UnknownBlock]
C -->|yes| D[Topology Check\nsource ≤ target ≤ head?]
D -->|no| E2[Err: SourceExceedsTarget / HeadOlderThanTarget]
D -->|yes| F[Consistency Check\ncheckpoint slots match block slots?]
F -->|no| E3[Err: SlotMismatch]
F -->|yes| G["Time Check (NEW)\nattestation_start_interval = data.slot * INTERVALS_PER_SLOT\nattestation_start_interval > store.time() + GOSSIP_DISPARITY_INTERVALS?"]
G -->|yes – too far future| E4[Err: AttestationTooFarInFuture\nreports store_time in intervals]
G -->|no| H[Ok – attestation accepted]
style G fill:#fffde7,stroke:#f9a825
style E4 fill:#ffebee,stroke:#c62828
Prompt To Fix All With AI
This is a comment left during a code review.
Path: crates/blockchain/src/store.rs
Line: 183-184
Comment:
**Integer overflow on adversary-supplied slot**
`data.slot` arrives from an untrusted peer. If an attacker sends an attestation with `data.slot ≥ u64::MAX / INTERVALS_PER_SLOT + 1` (≈ 3.69 × 10¹⁸), the multiplication `data.slot * INTERVALS_PER_SLOT` overflows. In a debug build this panics (remote DoS); in a release build the value wraps to a small number, causing the time-check to silently pass and admitting an astronomically far-future attestation.
The old code divided `store.time()` instead of multiplying `data.slot`, so it was immune to this. A checked or saturating multiply sidesteps the issue:
```suggestion
let attestation_start_interval = data.slot.saturating_mul(INTERVALS_PER_SLOT);
if attestation_start_interval > store.time() + GOSSIP_DISPARITY_INTERVALS {
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix: tighten attestation future-slot bou..." | Re-trigger Greptile
🤖 Claude Code ReviewNow I have enough context for a thorough review. PR #317 —
|
🤖 Codex Code ReviewFindings
Aside from that, the interval-based tightening itself is consistent with the store’s interval clock model. This was a static review; I did not run the test suite. Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Tested against the leanSpec PR headPinned That covers the 3 new boundary regressions added by leanSpec#682 (in both gossip- and aggregated-attestation suites) and all the restructured existing tests. I'm leaving the |
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Summary
Ports leanSpec PR #682 —
forkchoice: tighten validate_attestation future-slot bound.The previous time check accepted votes up to a full slot ahead of the local clock. With 4 s slots that leaves ~3.2 s of free pre-positioning room: an adversary can publish next-slot aggregates before any honest validator could produce them, and the next proposer happily includes them.
The bound is now expressed in interval units against the store's local time and gated by a new
GOSSIP_DISPARITY_INTERVALS = 1constant — the lean analogue of mainnet'sMAXIMUM_GOSSIP_CLOCK_DISPARITY(one ~800 ms interval).Before / after
data.slot > store.time() / INTERVALS_PER_SLOT + 1rejectsdata.slot * INTERVALS_PER_SLOT > store.time() + GOSSIP_DISPARITY_INTERVALSrejectsChanges
crates/blockchain/src/lib.rs: addGOSSIP_DISPARITY_INTERVALS = 1constant.crates/blockchain/src/store.rs: rewrite the time-check arm ofvalidate_attestation_datain interval units; rename thecurrent_slotfield ofStoreError::AttestationTooFarInFuturetostore_timeso the variant accurately reflects what was compared.Notes
leanSpecis pinned atbc17f7a(2026-04-20). PR #682 was authored 2026-04-25 and is still open upstream, so this port is ahead of the pinned spec; the new boundary regression tests added in #682 will land here when the pin is bumped.data.slot <= current_slotrule was strictly correct for producer/receiver alignment but left no room for clock skew. The new rule allows exactly one interval, so a Zeam node and an ethlambda node tolerate each other under normal NTP drift.Test plan
cargo build -p ethlambda-blockchaincargo clippy --workspace --all-targets -- -D warningscargo fmt --all -- --checkcargo test --workspace --lib(69 passed, 6 ignored)