Port leanSpec fork-choice hardening fixes - #1030
Conversation
GossipAttestationValidationError gained HeadNotDescendantOfFinalized as part of the leanSpec rejection hardening (PR #1029), but AttestationValidationError in chain.zig was not updated to include it. Since validateAttestationData returns AttestationValidationError!void and propagates the result of validateAttestationDataForGossip, the coercion failed at compile time: error: 'error.HeadNotDescendantOfFinalized' not a member of destination error set Fix: add HeadNotDescendantOfFinalized to AttestationValidationError and update the inline comment to note that no BlocksByRoot fetch is enqueued for this error (the block is pruned and unservable below finalized).
… check The HeadNotDescendantOfFinalized gossip guard used a unidirectional ancestry check: checkpointIsAncestorUnlocked(finalized, head). This works when head.slot >= finalized.slot, but incorrectly rejects valid attestations whose head references a canonical block at a slot below the current finalized slot. When head.slot < finalized.slot, checkpointIsAncestorUnlocked returns false immediately (ancestor.slot > descendant.slot), causing a spurious rejection. The fix checks the reverse direction in that case: the head must be an ancestor of finalized, confirming it was on the canonical chain that led to finalization. Fixes spectest: test_finalization_prunes_stale_attestation_signatures
|
🔧 CI fix pushed (commit 0f590d2) Root cause: The test fixture Fix: Corrected all 9 proposal pubkeys to exactly 104 hex chars (52 bytes), each being the original key with the last byte replaced by |
… fix The previous commit corrected truncated proposal pubkeys in test fixtures (validators 2-8). This changes the SSZ hash of the genesis state, so update the hardcoded expected root from 0x228ecb2f88891fab88a05a104ccac95f1513e138d53469340b9ce04f70fa1019 to 0x9a77892fc5afa43bbfa1462bd9c96d239ee8b0fd863b1c3a08e69b6f7efbb8d2
|
🔧 CI fix pushed (commit Failure: Root cause: The previous commit ( Fix: Updated the expected state root:
CI should pass on the next run. |
CI Analysis:
|
| Category | Count | Root Cause |
|---|---|---|
InvalidFixture |
155 | Networking codec fixture format changed: {codecName, input, output} → {codec: {kind, ...}, output}. Runner still expects old codecName field. |
FixtureMismatch |
23 | State transition hardening tests expose implementation gaps (InvalidValidatorId, InvalidPostState, InvalidSlotIndex, InvalidPreState, InvalidProposer, InvalidJustificationRoot, InvalidJustificationVotesLength) + fork_choice field mismatches (latestNewAggregatedTargetSlots, attestationSignatureTargetSlots). |
UnsupportedFixture |
1 | Unknown SSZ type Validators in decode rejection test. |
What Needs To Be Done
-
networking_codec_runner.zig: RewriterunCase()to parse the new discriminated-union format — extractcodec.kindinstead ofcodecName, and read codec-specific fields from thecodecobject instead of a separateinputobject. Each codec handler (runVarint,runGossipTopic,runSnappyBlock, etc.) needs its field paths updated. -
State transition logic (
pkgs/types/src/state.zig): The new hardening tests expect certain blocks to be rejected (no post-state), but the implementation errors out with unexpected error types or produces a valid post-state when it shouldn't. -
Fork choice runner: New test fields (
latestNewAggregatedTargetSlots,attestationSignatureTargetSlots) need to be tracked.
leanSpec Submodule Changes (48 new, 61 modified, 41 deleted test files)
Key PRs: #1177-#1186 (rejection hardening), #1156 (networking decode-failure refactor), #1170 (smart block deconstruction).
|
🔧 CI fix pushed (commit Updated the spectest runners for the latest leanSpec fixture artifact:
Local validation with Zig 0.16.0 and the failing CI run’s downloaded
Monitoring the new CI run now. |
…ounds head slot process_attestations now skips attestations whose head/source/target slot exceeds historical_block_hashes.len() instead of returning InvalidSlotIndex. This aligns the test with the behavior introduced in the leanSpec fork-choice hardening port (PR #1030, leanSpec issues 1181-1186).
|
🔴→✅ CI fix pushed (b143273) Failure: Root cause: The leanSpec hardening port changed Fix: Updated the test to expect a clean return and verify state is unaffected (no justification recorded) — matching the |
ch4r10t33r
left a comment
There was a problem hiding this comment.
LGTM. Clean port of the leanSpec fork-choice/state hardening:
- equal-slot equivocation now resolved deterministically by canonical att-data root, unifying all three tracker-update sites so arrival order can't steer fork-choice weight;
- STF out-of-bounds source/target/head attestations now skip instead of halting the STF, aligning with leanSpec attestation_data_matches_chain (fixes the earlier InvalidSlotIndex divergence);
- far-slot block gap bounded before transition work (underflow-safe via the preceding slot>parent check);
- genesis rejects reused attestation/proposal signing keys; config + genesis-root test updated consistently.
Verified: the block-gap subtraction can't underflow, BlockTooFarInFuture is used (runner), and equivocation hashing only fires on equal-slot ties. Well-tested (equivocation, skip, key-reuse), and CI is fully green including both test jobs, gen-fixtures (spectest runners vs the bumped leanSpec pin), and build-all-provers (STF compiles for the zkVM) — the validation the author couldn't run locally.
|
Added zig-libp2p v0.2.89 dependency bump in |
Summary
Validation
zig fmt --check pkgs/node/src/forkchoice.zig pkgs/node/src/chain.zig pkgs/spectest/src/runner/fork_choice_runner.zig pkgs/configs/src/lib.zigzig ast-checkon touched Zig filesgit diff --checkBlocked locally:
zig build test --summary allfails before zeam code is compiled because current workspace Zig is0.15.2while cached deps expectBuild.Graph.io/ newer Build API.cargo fmt/cargo clippyunavailable becausecargois not installed in this environment.