Files
consensus/engine
zeekayandHanzo Dev 8793eb2d71 fix(engine/chain): QCv2 canonical-commitment finality + seed-hint semantics (incident 1082814)
Durable fix for the finality-equivocation bug class. Root cause: finality was
defined over the OUTER proposervm envelope id, and the in-memory finality ledger
was SEEDED from vm.LastAccepted on boot. Two outer envelopes wrapping the SAME
inner execution block looked like a fork -> fatal EQUIVOCATION (reportCertEquivocation
-> Logger.Crit -> Fatal -> os.Exit), halting mainnet 43h. They were duplicates.

Two layers, shipped together (atomic roll, all validators upgrade at once):

PART A - seed semantics. ChainConsensus.SyncState (the vm.LastAccepted reconcile)
now writes a NON-AUTHORITATIVE recovery HINT, never certified finality.
FinalityLedger separates a certified frontier (advanced ONLY by a verified QC or
bootstrap frontier-trust) from a hint anchor. GetFinalizedHeight is certified-only;
GetFinalizedTip/BuildAnchor return the higher of {certified tip, hint} for the VM
to build on. A locally-seeded sibling can never manufacture a finalized entry, so
it can never equivocate. The equal-height "different block => equivocation" check
in SyncState was the bug and is deleted.

PART B - QCv2 canonical cert. Finality is keyed on the inner EXECUTION commitment,
not the envelope. VotePosition carries both outer transport ids {BlockID, ParentID}
and canonical {CanonicalID, ParentCanonicalID, ExecutionStateRoot, PayloadRoot}.
byHeight indexes the CANONICAL id. Two certs at one height conflict IFF canonical
differs; same canonical / different envelope = duplicate alias (idempotent no-op).
CanonicalVoteMessage (tag LUX/chain/vote/v2, QuorumCertVersion=3) binds the
canonical identity and EXCLUDES the envelope, so validators that executed the same
inner block sign byte-identical messages even under different envelopes -> their
votes interoperate. The non-wrapped degrade (Empty canonical => bind BlockID) is
resolved in ONE place (canonicalVoteMessageFor). Canonical id is plumbed from
proposervm via the optional canonicalCommitter block iface + setCanonicalFromVM /
canonicalIDOf (falls back to b.ID for a bare block).

HARD GATE: the only writers of certified finalizedTip/byHeight are the cert fold
(Finalize, via ApplyCert/FinalizeBranch) and bootstrap frontier-trust
(AcceptBootstrapBlock). SyncState writes only the hint; preferredTip/build anchor
are build-only, never finality.

Proof suite (all green): 6-row regression matrix + invariant (incident_1082814_test.go),
canonical inversions (cert_canonical_inversion_test.go), two fuzz targets
(cert_fuzz_test.go: codec round-trip/malformed + finalize-decision invariant),
-race clean, go vet clean, staticcheck adds zero findings. 335 engine/chain tests
pass. Pre-fix repro captured (seed A + cert B at one height returned
ErrHeightAlreadyFinalized; now finalizes the canonical block, no equivocation).

No deploy/push/merge/tag — review-pending.

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-30 13:45:42 -07:00
..
2025-12-19 08:49:11 -08:00