mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
The finalized tip is durable and the block tree above it is not, so the two disagree after every restart: the engine comes back knowing it finalized through height H and remembering nothing above H, while the VM still holds every block it ever accepted. A cert for a block above H then walks down looking for ancestors the node HAS, finds them missing from the live tree, and returns ErrAncestorNotTracked. The advice that error carries — fetch and retry — cannot help, because the catch-up path finalizes contiguously from a per-block cert and no cert was ever assembled for the interior of the gap. Finality therefore cannot re-cross the seam, and the gap widens with each restart. blocksAncestry now resolves a miss through the VM. That is the STRONGER source, not a weaker one: the VM's chain is what this node actually executed and committed, where the live tree is populated from gossip. It supplies parent links only — the cert has already cleared the ⅔-by-stake predicate on its own — and a block the VM does not hold still misses, so the behind-node defer is preserved. The live tree keeps priority, since its entry carries this process's own view. Wired in the constructor once both the consensus core and the VM are set; an engine with no VM is unchanged. Tests: finality_seam_test.go — crosses the seam for a run the VM holds and the tree does not, still fails closed on a block the VM lacks, resolves from the live tree when there is no VM, and prefers the live entry over a read-back. Negative control executed: removing the read-back fails the seam test alone. Co-authored-by: Hanzo Dev <[email protected]>