From 135927b5c979395645c49703bed1ba18bcd77402 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Mon, 29 Jun 2026 15:03:11 +0800 Subject: [PATCH] core: improve chain reset head --- core/blockchain.go | 54 ++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index d6edd90133..717b08e0b8 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -966,14 +966,11 @@ func (bc *BlockChain) rewindPathHead(head *types.Header, root common.Hash) (*typ return head, rootNumber } } - // Recover if the target state if it's not available yet. - if !bc.HasState(head.Root) { - if err := bc.triedb.Recover(head.Root); err != nil { - log.Error("Failed to rollback state, resetting to genesis", "err", err) - return bc.genesisBlock.Header(), rootNumber - } - } - log.Info("Rewound to block with state", "number", head.Number, "hash", head.Hash()) + // Note, the state of the located head may not be physically present yet if + // it's only recoverable. The actual recovery is intentionally deferred once + // the new head is finalized, so that a deep rewind rolls the state back in + // one shot. + log.Info("Rewound to block with available state", "number", head.Number, "hash", head.Hash()) return head, rootNumber } @@ -1034,17 +1031,11 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha bc.currentBlock.Store(newHeadBlock) headBlockGauge.Update(int64(newHeadBlock.Number.Uint64())) - // The head state is missing, which is only possible in the path-based - // scheme. This situation occurs when the chain head is rewound below - // the pivot point. In this scenario, there is no possible recovery - // approach except for rerunning a snap sync. Do nothing here until the - // state syncer picks it up. - if !bc.HasState(newHeadBlock.Root) { - if newHeadBlock.Number.Uint64() != 0 { - log.Crit("Chain is stateless at a non-genesis block") - } - log.Info("Chain is stateless, wait state sync", "number", newHeadBlock.Number, "hash", newHeadBlock.Hash()) - } + // Note, the located head state might not be physically present yet; in + // the path-based scheme a recoverable state is materialized in a single + // shot once the rewind is finalized (see below). The stateless-head + // handling is therefore deferred until then, also avoiding a per-block + // recoverability check whose cost would grow as the rewind goes deeper. } // Rewind the snap block in a simpleton way to the target head if currentSnapBlock := bc.CurrentSnapBlock(); currentSnapBlock != nil && header.Number.Uint64() < currentSnapBlock.Number.Uint64() { @@ -1113,6 +1104,31 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha bc.hc.SetHead(head, updateFn, delFn) } } + // In the path-based scheme, the rewind loop above only locates the new head + // without materializing its state, so the potentially deep rollback is done + // here in a single shot. This rolls back the whole rewound range at once, + // performing a single fsync rather than one per block, which is critical when + // rewinding a large number of blocks. + if newHeadBlock := bc.CurrentBlock(); !bc.HasState(newHeadBlock.Root) { + switch { + case bc.stateRecoverable(newHeadBlock.Root): + if err := bc.triedb.Recover(newHeadBlock.Root); err != nil { + // The state was confirmed recoverable just above, so a failure here + // can only stem from an unexpected I/O error. There is no safe way to + // continue with a half-rolled-back state, hence crash hard. + log.Crit("Failed to recover state", "number", newHeadBlock.Number, "hash", newHeadBlock.Hash(), "err", err) + } + case newHeadBlock.Number.Uint64() != 0: + // rewindHead only returns a non-genesis head when its state is present + // or recoverable, so this branch should be unreachable. + log.Crit("Chain is stateless at a non-genesis block", "number", newHeadBlock.Number, "hash", newHeadBlock.Hash()) + default: + // The chain head was rewound below the snap-sync pivot to a stateless + // genesis. There is no recovery approach except rerunning a snap sync; + // do nothing here until the state syncer picks it up. + log.Info("Chain is stateless, wait state sync", "number", newHeadBlock.Number, "hash", newHeadBlock.Hash()) + } + } // Clear out any stale content from the caches bc.bodyCache.Purge() bc.bodyRLPCache.Purge()