Files
consensus/engine
zeekayandHanzo Dev 4397552c91 fix(engine): stop steering the VM backwards, and stop calling a certified descendant a fork
All five devnet validators os.Exit(1)'d on v1.36.33, once each, at three distinct
height pairs (1258/1259, 1363/1364, 1364/1365) — always certified = head - 1, and
with every surviving node holding byte-identical blocks at every one of those
heights. There was no fork anywhere; in luxd-1's case the block it refused to
prefer was the block it had just built and the fleet had just accepted.

Two defects, one crash.

PRODUCER. acceptWithCertCore releases t.mu across every VM call-out, then steers
the VM with its STALE local blockID. A finalize that completed in that window has
already advanced the ledger AND the EVM to blockID+1, so the steer is BACKWARDS
and the EVM's accepted-irreversibility guard (evm/core/blockchain.go:1987,
commonBlock < lastAccepted) correctly refuses it. That guard is right and is left
alone. Fixed by steering at the LIVE build anchor — PreferredBuildTip, descended
from ledger.BuildAnchor, which the accept ordering (ApplyCert BEFORE VM.Accept)
keeps at or above the VM's own accepted head. It is the same value the build path
already steers with: one build target, computed one way, read in both places.

CLASSIFIER. reconcileVMToCertified asked only "is the head the ledger's certified
canonical at ITS OWN height?" — trivially true for every healthy node whose head
is certified — and called that a two-blocks-at-one-height double-finalization. It
never established that `certified` was at that same height. The ledger holds one
canonical per height along one contiguous chain (Finalize refuses equivocation
AND non-descendant branches), so when both blocks are certified at their own
heights they lie on that one chain and the head merely DESCENDS from the target:
nothing is orphaned. The fail-closed halt is NOT weakened — it now fires on the
state that is actually unsafe, steering off a certified head onto a block our own
ledger does not certify at its height, and TestReconcile_CertifiedHead_HaltsFail-
Closed proves it with both blocks readable so the LEDGER decides, not a missing id.

Direction chosen: NEITHER roll the head back nor certify forward. The VM head is
legitimately ahead and already CONTAINS the certified block. Rolling back is what
evm/core/blockchain.go:1987 exists to refuse, and FinalityLedger.BuildAnchor
already documents head > certified as the designed state. The correct action is
no action — plus not issuing the backwards steer in the first place.

Regression, failing before / passing after:
  TestReconcile_CertifiedDescendantHead_NoHalt   the incident, reduced
  TestReconcile_CertifiedAncestorHead_NoHalt     the mirror
  TestAcceptWithCert_StaleSteerIsNotIssued       the race window, injected
                                                 deterministically via Accept

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 03:10:10 -07:00
..
2025-12-19 08:49:11 -08:00