core: improve chain reset head

This commit is contained in:
Gary Rong 2026-06-29 15:03:11 +08:00
parent 07dd8e052f
commit 135927b5c9

View file

@ -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()