You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(fork-choice): make the equal-slot equivocation tie deterministic (leanSpec #1181) (#503)
## What
Ports leanSpec
[#1181](leanEthereum/leanSpec#1181): make the
equal-slot equivocation tie in fork choice deterministic.
## Why
An equivocating validator can sign two distinct votes `A` and `B` for
the same slot. Nothing rejects the second (the pool is keyed by
attestation data, and the two data differ), so both are admitted.
`extract_latest_attestations` then resolved the equal-slot tie with a
strict `existing.slot < entry.data.slot` while iterating in **insertion
(arrival) order**, so the winner was simply whichever was seen first.
Two honest nodes with the same blocks and votes but different arrival
order could land the equivocator's weight on different branches and pick
**different heads permanently** — a determinism/safety bug that unit
tests miss because it only appears across nodes.
## Changes
`crates/storage/src/store.rs` — both `extract_latest_attestations`
implementations (the aggregated `PayloadBuffer` and the raw
`GossipSignatureBuffer`):
- Process votes newest-first, breaking the equal-slot tie toward the
**larger canonical attestation-data root** — the same rule the
block-level fork-choice tiebreak applies to block roots. The extracted
head becomes a pure function of pool contents, independent of
arrival/insertion order. An equivocator is counted once, on one branch
every node agrees on.
- The pool key is already `hash_tree_root(data)`, so the tie needs no
extra hashing.
Block production is **not** changed: ethlambda's block builder already
uses `data_root` as its deterministic final tiebreak
(`EntryScore::ordering_key`, from leanSpec #1149), so it is already
order-independent.
The `drain` doc comment is updated (vote-extraction determinism no
longer depends on drain order), and the unit test that asserted
first-seen-wins is rewritten to assert order-independence (larger
canonical root wins in both arrival orders).
## Tests
- `extract_latest_attestations_canonical_root_wins_on_slot_tie`
(rewritten from `..._first_inserted_wins_on_slot_tie`)
`cargo fmt`, `clippy -D warnings`, and the storage lib tests (44) pass.
> Since #1181 is merged in leanSpec, the released fork-choice fixtures
encode the new expected head for the equivocation vectors; this change
aligns ethlambda with them. Recommend a `forkchoice_spectests` run
against fresh fixtures to confirm.
0 commit comments