Files
consensus/engine
zeekayandHanzo Dev b519af197a engine/chain: behind-node self-heal — fetch missing intermediate ancestor on cert defer
Fixes the live mainnet killer: a slipped validator (e.g. 192 behind) receives verified
α-of-K certs for blocks it TRACKS but whose intermediate ancestors it LACKS. The finalize
fold returns ErrAncestorNotTracked; HandleIncomingCert logged "REFUSED ... (behind; fetch
and retry)" and returned WITHOUT calling requestCatchup — so the node never self-healed,
stayed effectively n−1 at zero margin, and any flap dropped the fleet below α and stalled
finality. Clean restart did not fix it (boots, re-reads its head, re-stalls). The only fetch
trigger in HandleIncomingCert was gated behind "cert's OWN block untracked" (topology.go
!exists arm), never the missing-INTERMEDIATE-ancestor case.

Fix (surgical, two hunks):
  - ledger.go: pathFromTip now returns a TYPED *AncestorNotTracked carrying the specific
    untracked ancestor id (Missing) and the certified target (Target). errors.Is(err,
    ErrAncestorNotTracked) still holds via Unwrap, so every existing classifier is byte-for-
    byte unchanged; the plain sentinel only named the id inside a format string, which is why
    the receiver could log the defer but never target a fetch.
  - topology.go: HandleIncomingCert's error arm errors.As the typed error and issues exactly
    one requestCatchup(Missing, EmptyNodeID). Gated on the cert having VERIFIED (a forged cert
    can never make us fetch arbitrary ids); claimCatchupLocked rate-limits; peer selection is
    the node layer's job. The node-layer fetch machinery (networkCatchup.RequestAncestors →
    requestContext → GetAncestors → AcceptCatchupBlock) is fully wired and serves the finalized
    gap oldest-first, re-applying the deferred cert — it was simply never invoked on this path.

Result: a behind node self-heals to the tip (requestCatchup → gap serve → finalize), restoring
5/5 and margin, so a flap no longer drops below α. This is the launch-gate invariant on the
current engine and is prerequisite plumbing for any future engine (same Finalize fold).

Tests (fail-on-old/pass-on-new, -race clean):
  - TestFinalize_MissingAncestor_TypedError: the fold yields *AncestorNotTracked with the
    correct Missing id; errors.Is(ErrAncestorNotTracked) preserved.
  - TestSelfHeal_CertMissingIntermediateAncestor_TriggersFetch: a verified cert for a tracked
    block with a missing intermediate ancestor fires exactly ONE ancestor fetch targeting the
    missing id (pre-fix: 0 — logged REFUSED, never self-healed).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-09 21:22:41 -07:00
..
2025-12-19 08:49:11 -08:00
…
…