1 Commits
Author SHA1 Message Date
zeekayandHanzo Dev 075596355c engine/chain: vote-guard clone-artifact re-sign fallback (v4 Strategy-A)
THE clone artifact: restoring all validators from ONE snapshot taken at a voted-but-
UNFINALIZED height boots each node with committedSlot[H] durably set (the binding) but
certVotes EMPTY — the vote-guard persisted the BINDING, not the SIGNATURE. The old
emitConvergedVote saw hasSignedHeight(H)==true and returned early, and
snapshotVotableSlotsLocked skipped the bound height, so no node re-contributed its vote →
the height never reached alpha-of-K → wedged forever (froze testnet at 287; would freeze
ANY build, incl finalizing mainnet — not a build regression).

FIX (cryptographer abf6b7d signed-off, HARD RULE preserved): a node whose self-vote is
MISSING at an already-bound height re-signs the DURABLE committedSlot[H] canonical ONLY —
never a freshly-computed winner. reserveSlotForSign already returns idempotent-true for the
bound canonical and REFUSES any conflicting sibling (VC-off) / decided height, so this can
never double-finalize (equivocation-safe). Two seams:
  - snapshotVotableSlotsLocked: re-offer a bound height iff our self-vote is absent there.
  - emitConvergedVote: on an already-bound height, target committedSlot[H] (committedCanonical
    helper) and re-sign only when certVotes[self] is absent.

PROOF: TestMultiNode_CloneArtifact_ResignFallback_Finalizes — 5 nodes bind committedSlot[1]
mid-vote (votes partitioned, H1 unfinalized), KILL ALL + RESTART ALL from the fsync'd
guards (committedSlot re-seeded, certVotes empty), heal → H1 finalizes 5/5 in ~2.7s. FAILS
without the re-offer (wedged); PASSES with it. Full equivocation/no-double-finalize/storm/
sibling-fork suite stays green (288s).

Re-sign fallback covers the actual testnet artifact (v3/no-sig snapshots). The v4 codec
(persist round+sig for VC-on aggregation robustness) is the follow-on durability layer.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-05 12:17:43 -07:00