mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
v1.35.4's decided-height floor was durable via the vote-guard file, but a node upgrading IN PLACE from the old consensus carries a LEGACY v1 guard file (no finalizedThrough -> floor 0) whose below-tip decided-height slots were already pruned, and its certified ledger is a (0,false) hint until the first post-upgrade finalize (PART-A). In that window the sign gate's floor was 0, so a re-gossiped sibling at a decided-below-tip height could be signed — the fork, now reachable on a mainnet-first in-place roll (RED fix-priority #1). Fix: seed engine.decidedFloor DIRECTLY from vm.LastAccepted at Start, BEFORE the signing goroutines launch (seedDecidedFloorFromVMLocked), and again in SyncState from the reconciled height. vm.LastAccepted is durable and a sound lower bound on the decided height (every accepted block was finalized), so the floor is real from the first instant of boot — no reliance on the transient SyncState hint, the vote-guard file, or a post-upgrade finalize. SIGN-GATE ONLY: advances only t.decidedFloor (under slotMu); never touches byHeight / ledger.Height() (PART-A intact); can only ever REFUSE MORE signing. Test: TestRed_V1ToV2Upgrade_BootSeedFromVM — legacy v1 guard (floor 0) + vm.LastAccepted at the tip + NO SyncState -> reserveSlotForSign refuses a decided-below-tip sibling immediately on boot. Mutation-verified: disabling the boot seed reproduces the fork (RED). Full engine/chain suite green non-race + -race; build clean. Declined the optional fsync-skip for a floor-only prune: it would let the authoritative never-lags vote-guard floor go stale and fall back to a possibly-lagging vm.LastAccepted — a durability regression for negligible I/O savings (a finalize normally prunes the prior tip, so it fsyncs anyway). Keeps height-only slot, strictly-below prune, durable floor, Crit->exit fatal. Co-authored-by: Hanzo Dev <[email protected]>