core, eth/downloader: fix genesis state missing due to state sync (#28124)
* core: fix chain repair corner case in path-based scheme * eth/downloader: disable trie database whenever state sync is launched
This commit is contained in:
parent
d8a351b58f
commit
c53b0fef2a
@ -340,28 +340,38 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
|
|||||||
// Make sure the state associated with the block is available
|
// Make sure the state associated with the block is available
|
||||||
head := bc.CurrentBlock()
|
head := bc.CurrentBlock()
|
||||||
if !bc.HasState(head.Root) {
|
if !bc.HasState(head.Root) {
|
||||||
// Head state is missing, before the state recovery, find out the
|
if head.Number.Uint64() == 0 {
|
||||||
// disk layer point of snapshot(if it's enabled). Make sure the
|
// The genesis state is missing, which is only possible in the path-based
|
||||||
// rewound point is lower than disk layer.
|
// scheme. This situation occurs when the state syncer overwrites it.
|
||||||
var diskRoot common.Hash
|
//
|
||||||
if bc.cacheConfig.SnapshotLimit > 0 {
|
// The solution is to reset the state to the genesis state. Although it may not
|
||||||
diskRoot = rawdb.ReadSnapshotRoot(bc.db)
|
// match the sync target, the state healer will later address and correct any
|
||||||
}
|
// inconsistencies.
|
||||||
if diskRoot != (common.Hash{}) {
|
bc.resetState()
|
||||||
log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash(), "snaproot", diskRoot)
|
|
||||||
|
|
||||||
snapDisk, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, diskRoot, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Chain rewound, persist old snapshot number to indicate recovery procedure
|
|
||||||
if snapDisk != 0 {
|
|
||||||
rawdb.WriteSnapshotRecoveryNumber(bc.db, snapDisk)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash())
|
// Head state is missing, before the state recovery, find out the
|
||||||
if _, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, common.Hash{}, true); err != nil {
|
// disk layer point of snapshot(if it's enabled). Make sure the
|
||||||
return nil, err
|
// rewound point is lower than disk layer.
|
||||||
|
var diskRoot common.Hash
|
||||||
|
if bc.cacheConfig.SnapshotLimit > 0 {
|
||||||
|
diskRoot = rawdb.ReadSnapshotRoot(bc.db)
|
||||||
|
}
|
||||||
|
if diskRoot != (common.Hash{}) {
|
||||||
|
log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash(), "snaproot", diskRoot)
|
||||||
|
|
||||||
|
snapDisk, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, diskRoot, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Chain rewound, persist old snapshot number to indicate recovery procedure
|
||||||
|
if snapDisk != 0 {
|
||||||
|
rawdb.WriteSnapshotRecoveryNumber(bc.db, snapDisk)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash())
|
||||||
|
if _, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, common.Hash{}, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -620,6 +630,28 @@ func (bc *BlockChain) SetSafe(header *types.Header) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resetState resets the persistent state to genesis state if it's not present.
|
||||||
|
func (bc *BlockChain) resetState() {
|
||||||
|
// Short circuit if the genesis state is already present.
|
||||||
|
root := bc.genesisBlock.Root()
|
||||||
|
if bc.HasState(root) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Reset the state database to empty for committing genesis state.
|
||||||
|
// Note, it should only happen in path-based scheme and Reset function
|
||||||
|
// is also only call-able in this mode.
|
||||||
|
if bc.triedb.Scheme() == rawdb.PathScheme {
|
||||||
|
if err := bc.triedb.Reset(types.EmptyRootHash); err != nil {
|
||||||
|
log.Crit("Failed to clean state", "err", err) // Shouldn't happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Write genesis state into database.
|
||||||
|
if err := CommitGenesisState(bc.db, bc.triedb, bc.genesisBlock.Hash()); err != nil {
|
||||||
|
log.Crit("Failed to commit genesis state", "err", err)
|
||||||
|
}
|
||||||
|
log.Info("Reset state to genesis", "root", root)
|
||||||
|
}
|
||||||
|
|
||||||
// setHeadBeyondRoot rewinds the local chain to a new head with the extra condition
|
// setHeadBeyondRoot rewinds the local chain to a new head with the extra condition
|
||||||
// that the rewind must pass the specified state root. This method is meant to be
|
// that the rewind must pass the specified state root. This method is meant to be
|
||||||
// used when rewinding with snapshots enabled to ensure that we go back further than
|
// used when rewinding with snapshots enabled to ensure that we go back further than
|
||||||
@ -646,25 +678,6 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
|
|||||||
pivot := rawdb.ReadLastPivotNumber(bc.db)
|
pivot := rawdb.ReadLastPivotNumber(bc.db)
|
||||||
frozen, _ := bc.db.Ancients()
|
frozen, _ := bc.db.Ancients()
|
||||||
|
|
||||||
// resetState resets the persistent state to genesis if it's not available.
|
|
||||||
resetState := func() {
|
|
||||||
// Short circuit if the genesis state is already present.
|
|
||||||
if bc.HasState(bc.genesisBlock.Root()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Reset the state database to empty for committing genesis state.
|
|
||||||
// Note, it should only happen in path-based scheme and Reset function
|
|
||||||
// is also only call-able in this mode.
|
|
||||||
if bc.triedb.Scheme() == rawdb.PathScheme {
|
|
||||||
if err := bc.triedb.Reset(types.EmptyRootHash); err != nil {
|
|
||||||
log.Crit("Failed to clean state", "err", err) // Shouldn't happen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Write genesis state into database.
|
|
||||||
if err := CommitGenesisState(bc.db, bc.triedb, bc.genesisBlock.Hash()); err != nil {
|
|
||||||
log.Crit("Failed to commit genesis state", "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (*types.Header, bool) {
|
updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (*types.Header, bool) {
|
||||||
// Rewind the blockchain, ensuring we don't end up with a stateless head
|
// Rewind the blockchain, ensuring we don't end up with a stateless head
|
||||||
// block. Note, depth equality is permitted to allow using SetHead as a
|
// block. Note, depth equality is permitted to allow using SetHead as a
|
||||||
@ -674,7 +687,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
|
|||||||
if newHeadBlock == nil {
|
if newHeadBlock == nil {
|
||||||
log.Error("Gap in the chain, rewinding to genesis", "number", header.Number, "hash", header.Hash())
|
log.Error("Gap in the chain, rewinding to genesis", "number", header.Number, "hash", header.Hash())
|
||||||
newHeadBlock = bc.genesisBlock
|
newHeadBlock = bc.genesisBlock
|
||||||
resetState()
|
bc.resetState()
|
||||||
} else {
|
} else {
|
||||||
// Block exists, keep rewinding until we find one with state,
|
// Block exists, keep rewinding until we find one with state,
|
||||||
// keeping rewinding until we exceed the optional threshold
|
// keeping rewinding until we exceed the optional threshold
|
||||||
@ -703,7 +716,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
|
|||||||
}
|
}
|
||||||
if beyondRoot || newHeadBlock.NumberU64() == 0 {
|
if beyondRoot || newHeadBlock.NumberU64() == 0 {
|
||||||
if newHeadBlock.NumberU64() == 0 {
|
if newHeadBlock.NumberU64() == 0 {
|
||||||
resetState()
|
bc.resetState()
|
||||||
} else if !bc.HasState(newHeadBlock.Root()) {
|
} else if !bc.HasState(newHeadBlock.Root()) {
|
||||||
// Rewind to a block with recoverable state. If the state is
|
// Rewind to a block with recoverable state. If the state is
|
||||||
// missing, run the state recovery here.
|
// missing, run the state recovery here.
|
||||||
|
@ -398,7 +398,14 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int,
|
|||||||
log.Info("Block synchronisation started")
|
log.Info("Block synchronisation started")
|
||||||
}
|
}
|
||||||
if mode == SnapSync {
|
if mode == SnapSync {
|
||||||
// Snap sync uses the snapshot namespace to store potentially flakey data until
|
// Snap sync will directly modify the persistent state, making the entire
|
||||||
|
// trie database unusable until the state is fully synced. To prevent any
|
||||||
|
// subsequent state reads, explicitly disable the trie database and state
|
||||||
|
// syncer is responsible to address and correct any state missing.
|
||||||
|
if d.blockchain.TrieDB().Scheme() == rawdb.PathScheme {
|
||||||
|
d.blockchain.TrieDB().Reset(types.EmptyRootHash)
|
||||||
|
}
|
||||||
|
// Snap sync uses the snapshot namespace to store potentially flaky data until
|
||||||
// sync completely heals and finishes. Pause snapshot maintenance in the mean-
|
// sync completely heals and finishes. Pause snapshot maintenance in the mean-
|
||||||
// time to prevent access.
|
// time to prevent access.
|
||||||
if snapshots := d.blockchain.Snapshots(); snapshots != nil { // Only nil in tests
|
if snapshots := d.blockchain.Snapshots(); snapshots != nil { // Only nil in tests
|
||||||
|
Loading…
Reference in New Issue
Block a user