mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
A follower that merely MISSED a cert stayed behind forever, and with 2 of 5 in
that state a 4-of-5 chain cannot reach α and halts. That is lux-testnet, stuck
at height 16821 with two validators holding 16818-16820 unfinalized.
rePollAllPending abandons a non-own block at maxRePollAttempts and logs
"gossiped block abandoned after attempt cap — not re-soliciting further
(recoverable via cert/catch-up)"
but nothing requested that recovery. Cert-gossip is send-once at assembly, so a
node that missed the broadcast never sees it again; and the BLOCK gate,
claimCatchupLocked, deliberately suppresses a fetch for a block already tracked
— which this block is. Its height-domain twin claimCertCatchupLocked exists for
exactly this case, says so in its own comment ("fetches the CERT for a block we
already hold"), and had ZERO callers. stuckRoundThreshold was defined and never
read. Runtime.requestCertCatchup, named in three comments, was never written.
Both ENDS were already built and live: the responder pairs each block with its
cert (encodeCatchupEntry), and the requester cert-accepts through
AcceptCatchupBlock, finalizing on the verified cert with no re-vote. Only the
ask was missing.
So ask. On abandon, claim the height-keyed gate under the lock and issue one
RequestAncestors after it — the same claim-under-lock / fire-after-unlock shape
the missing-bytes path already uses, because the transport takes its own locks.
chainID/networkID are fixed per-handler (every Catchup implementation ignores
them) and an empty `from` is the documented "no peer preference, node layer
samples" form.
Why it shipped: the unwired half was the untested half. catchup_selfheal_test.go
covered the missing-ANCESTOR arm — which is wired — and no test in engine/chain
referenced claimCertCatchupLocked, certCatchup or stuckRound at all. The new
test drives a TRACKED non-own block past the cap and asserts a fetch; it fails
on the parent commit with "got 0" and passes here.
Full ./engine/chain suite green (212s).
⚠ NOT verified under -race: this toolchain cannot build cgo (missing C headers),
so `go test -race` does not run here. The added lock discipline copies the
existing pattern verbatim — read t.catchup under t.mu alongside t.proposer, fire
outside — and certFetches is a function-local slice.
Hanzo Dev <[email protected]>
Co-authored-by: Hanzo Dev <[email protected]>