Files
consensus/engine
zeekayandHanzo Dev a14d50d4cf fix(engine/chain): durable decided-height floor — close CROSS-RESTART prune-then-resign (v1.35.4)
v1.35.3's decided-height gate read GetFinalizedHeight() = the certified ledger height,
which on boot is a NON-authoritative (0,false) hint until the first post-restart cert
(incident-1082814 PART-A). Meanwhile the strictly-below prune PERSISTED the removal of
below-tip slots. So after a correlated rolling restart of a storming fresh net, a
finalized-BELOW-tip height H had: no slot{H} (pruned from the durable file) AND a dead
gate (fh=(0,false)) -> a re-gossiped, differently-enveloped sibling B at decided height H
collected this node's SECOND signature -> 2nd alpha-of-K cert -> os.Exit(1) fleet-wide.
(RED PoC: confirmed failing on v1.35.3; the exact rolling-deploy-on-fresh-net trigger.)

Fix — make the decided-through floor DURABLE and reconstructed on boot, WITHOUT
regressing PART-A:
  - Vote-guard file v2: fsync the decided-through floor (finalizedThrough) in the SAME
    atomic write as the pruned binding set, so the floor and the slot removal are atomic
    on disk. decodeVoteGuard reads legacy v1 files (floor=0) too — no brick on upgrade.
  - engine.decidedFloor: seeded on boot from the vote-guard file's floor (WithVoteGuard),
    advanced at each finalize (pruneCommittedSlotsBelow). This is authoritative: the
    vote-guard is written by the consensus engine at finalize time, so it NEVER lags the
    decision (unlike vm.LastAccepted, which a fire-and-forget Accept can leave frozen).
  - consensus.GetDecidedFloor() = max(certified height, vm.LastAccepted hint height): a
    complementary lower bound, and the sole floor for a signer with a fresh vote-guard.
    HARD CONSTRAINT (PART-A): SIGN-GATE-ONLY — reads the hint HEIGHT only, never its
    identity; never writes to / enters byHeight / the equivocation index / the
    authoritative ledger.Height() (which stays (0,false)-until-cert). It can only ever
    REFUSE MORE signing (fail-safe direction).
  - reserveSlotForSign AND snapshotVotableSlots now refuse/skip height <=
    max(decidedFloor, GetDecidedFloor()).

Test: TestRed_CrossRestart_DecidedHeightUnsignable reproduces the PoC — finalize H then
H+1 (prune deletes slot{H} from memory AND the fsync'd file), reopen the store into a
fresh engine, and prove a sibling at decided height H is refused via the durable floor
alone (no SyncState hint), and via the vm.LastAccepted hint alone (fresh vote-guard).
Mutation-verified: disabling the floor seeding reproduces the fork (RED). Full
engine/chain suite green non-race + -race; vote-guard round-trip asserts the floor.

Corrected the false 'refusal spans a crash' comment (v1.35.3) to describe the durable
sources. Keeps height-only slot, strictly-below prune, atomic reserve, Crit->exit fatal.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-01 21:17:04 -07:00
..
2025-12-19 08:49:11 -08:00