mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
core: materialize accepted block state on the accept path (equivocation-commit-gap backstop)
Under equivocation on a fresh chain, the consensus engine can finalize (via the α-of-K cert / catch-up path) a block that this node verified only as a transient non-canonical sibling — whose state trie was then dereferenced when that sibling was rejected — WITHOUT re-verifying it on the finalize path. The node's head then points at a block whose state root is absent from the trie database, so the next BuildBlock fails 'failed to create new current environment: missing trie node' and the chain wedges. This is the fresh re-genesised testnet/devnet block-2 halt (node v1.32.4 / evm v1.99.52); mainnet is unaffected (its genesis lacks the fresh precompile suite and its mature operation does not equivocate at the tip). Fix: in BlockChain.Accept, if the accepted block's state root is not present, re- execute it on the parent (== last accepted, whose state is present by construction) before it becomes the head. reprocessBlock's ValidateState re-checks the computed root against the header, so a non-deterministic transition surfaces loudly rather than silently. On the common (non-equivocation) path this is a cheap HasState no-op. The WARN log fires ONLY on the commit-gap, so it doubles as confirming instrumentation. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -1342,6 +1342,42 @@ func (bc *BlockChain) Accept(block *types.Block) error {
|
||||
}
|
||||
}
|
||||
|
||||
// [ACCEPT-BACKSTOP] Guarantee the accepted block's state is materialized before
|
||||
// it becomes the head. Under equivocation the consensus engine can finalize (via
|
||||
// the α-of-K cert / catch-up path) a block that this node verified only as a
|
||||
// transient non-canonical sibling — whose state trie was then dereferenced when
|
||||
// that sibling was rejected — and the finalize path does NOT re-verify it. The
|
||||
// result is a head whose state root is absent from the trie database, so the next
|
||||
// BuildBlock fails with "missing trie node" and the chain wedges (the fresh
|
||||
// re-genesised testnet/devnet block-2 halt). Re-executing here on the parent
|
||||
// (== last accepted, whose state is present by construction) rematerializes the
|
||||
// exact accepted root; commitWithSnap's ValidateState re-checks the computed root
|
||||
// against the header, so a mismatch surfaces loudly instead of silently. When the
|
||||
// state is already present (the common, non-equivocation path) this is a cheap
|
||||
// HasState no-op. The WARN fires ONLY on the equivocation-commit-gap, so it doubles
|
||||
// as the confirming instrumentation for that root cause.
|
||||
if !bc.HasState(block.Root()) {
|
||||
parent := bc.lastAccepted // parent == block.ParentHash(), asserted above
|
||||
log.Warn("[ACCEPT-BACKSTOP] accepted block state not materialized; re-executing on parent",
|
||||
"height", block.NumberU64(),
|
||||
"acceptedHash", block.Hash(),
|
||||
"acceptedRoot", block.Root(),
|
||||
"parentHash", parent.Hash(),
|
||||
"parentRoot", parent.Root(),
|
||||
"canonicalAtHeight", canonical,
|
||||
)
|
||||
reRoot, err := bc.reprocessBlock(parent, block)
|
||||
if err != nil {
|
||||
return fmt.Errorf("accept: failed to materialize state for accepted block %s:%d: %w", block.Hash(), block.NumberU64(), err)
|
||||
}
|
||||
if reRoot != block.Root() {
|
||||
return fmt.Errorf("accept: re-executed root %s != accepted header root %s for block %s:%d (non-deterministic state transition)",
|
||||
reRoot.Hex(), block.Root().Hex(), block.Hash(), block.NumberU64())
|
||||
}
|
||||
log.Warn("[ACCEPT-BACKSTOP] re-materialized accepted block state",
|
||||
"height", block.NumberU64(), "root", block.Root())
|
||||
}
|
||||
|
||||
// Enqueue block in the acceptor
|
||||
bc.lastAccepted = block
|
||||
bc.addAcceptorQueue(block)
|
||||
|
||||
Reference in New Issue
Block a user