mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
A node that fell behind cannot rejoin by voting — the network will not re-vote a decided height — so AcceptCatchupBlock finalizes each gap block exclusively by running a SERVED cert through HandleIncomingCert. The set of heights a straggler can recover is therefore the set its peers can still produce a cert for. certBytesByBlock is a plain map built empty at New(), so that set was bounded by process lifetime rather than by the window maxServedCerts names: recoverability depended on how recently the peers happened to start, and a peer whose gap predated their start could ask for finalized+1 indefinitely while the contiguity guard refused the tip certs it was offered instead. CertStore gives the cert the same durability as the block it proves. One file per cert named "<height>-<blockID>", so the name is the whole index — lexical order is height order, eviction follows the chain, and there is no separate index to fall out of step with the directory. The node opens it per chain from the chain data dir (chains/manager.go), beside the vote guard. It fails OPEN where the vote guard fails closed, and the asymmetry is the point: an unreadable cert costs a peer one fetch elsewhere and a corrupt one is refused by HandleIncomingCert exactly as a forged one is, while unreadable equivocation memory risks a fork. Nothing downstream trusts these bytes because they came from disk. The durable write runs OFF the engine lock, in persistServedCert — it is an atomic replace with an fsync (~17ms measured) and finality must not wait on a disk. The in-memory window has already answered for the height by then, so the only exposure in the gap is losing the ability to serve one height. writeFileAtomic is now the single atomic writer in the package, shared with the vote guard so the two durable stores cannot drift on crash-safety. Tests: cert_store_test.go — survives a restart, retains the newest window by height at both entry points (recovery at open and the hot Put path), recovers the window from names alone, leaves files it does not own untouched, and round-trips the name across the uint64 edges. Negative control executed: removing the durable read from CertForBlock fails TestCertOutlivesTheProcess and nothing else. Co-authored-by: Hanzo Dev <[email protected]>