mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
A validator keeps two records of the chain: the consensus ledger (what a quorum decided) and the VM (what this node executed). Finalization folds the ledger across blocks the VM never applied, so the ledger legitimately runs ahead. Both accept lanes — runtime catch-up and bootstrap — gated fetched blocks on the ledger height, so a node whose ledger led its VM discarded every block in the gap it had just asked for and counted the batch accepted. It re-asked forever. Found wedged on five live fleets. Position is now the LOWER of the two heads, in both lanes; a VM naming no block, or one whose head is unreadable, does not lower it (absence is not a reading of zero). Blocks between the applied head and the ledger are executed straight onto the VM — the network settled those heights long ago, so what is missing is execution, not a decision. Two authorities gate a replay, in order. The ledger's own per-height record first: it names which block belongs at a height (canonical or envelope), and a block it finalized differently is refused without consulting anything else. Where the ledger cannot speak — below its boot seed or pruning window — a verified quorum cert may vouch. The cert path binds on what the cert SIGNS, never on transport. The outer BlockID is not in the signed message, so a held cert can be repointed at any block without breaking a signature; binding on it let any peer apply an arbitrary block to a catching-up validator's VM (red PoC, refused now). It binds on the signed canonical tuple instead, verifies at the block's own P-chain epoch (not height 0, which resolves the genesis set — rejecting every honest cert and admitting a long-range one), cross-checks the set-root, and requires the irreversible Quasar tier. Replay also refuses a non-contiguous block before Accept, since proposervm commits its outer envelope before the EVM refuses. Nothing here decides: no vote, no cert consumed, no ledger movement. A tracked above-band block returns ErrCatchupDeferred, distinct from a rejection, so a converging node stops logging its own progress as failure. Co-authored-by: Hanzo Dev <[email protected]>