Skip to content

Commit c53b0fe

Browse files
authored
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
1 parent d8a351b commit c53b0fe

File tree

2 files changed

+63
-43
lines changed

2 files changed

+63
-43
lines changed

core/blockchain.go

+55-42
Original file line numberDiff line numberDiff line change
@@ -340,28 +340,38 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
340340
// Make sure the state associated with the block is available
341341
head := bc.CurrentBlock()
342342
if !bc.HasState(head.Root) {
343-
// Head state is missing, before the state recovery, find out the
344-
// disk layer point of snapshot(if it's enabled). Make sure the
345-
// rewound point is lower than disk layer.
346-
var diskRoot common.Hash
347-
if bc.cacheConfig.SnapshotLimit > 0 {
348-
diskRoot = rawdb.ReadSnapshotRoot(bc.db)
349-
}
350-
if diskRoot != (common.Hash{}) {
351-
log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash(), "snaproot", diskRoot)
352-
353-
snapDisk, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, diskRoot, true)
354-
if err != nil {
355-
return nil, err
356-
}
357-
// Chain rewound, persist old snapshot number to indicate recovery procedure
358-
if snapDisk != 0 {
359-
rawdb.WriteSnapshotRecoveryNumber(bc.db, snapDisk)
360-
}
343+
if head.Number.Uint64() == 0 {
344+
// The genesis state is missing, which is only possible in the path-based
345+
// scheme. This situation occurs when the state syncer overwrites it.
346+
//
347+
// The solution is to reset the state to the genesis state. Although it may not
348+
// match the sync target, the state healer will later address and correct any
349+
// inconsistencies.
350+
bc.resetState()
361351
} else {
362-
log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash())
363-
if _, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, common.Hash{}, true); err != nil {
364-
return nil, err
352+
// Head state is missing, before the state recovery, find out the
353+
// disk layer point of snapshot(if it's enabled). Make sure the
354+
// rewound point is lower than disk layer.
355+
var diskRoot common.Hash
356+
if bc.cacheConfig.SnapshotLimit > 0 {
357+
diskRoot = rawdb.ReadSnapshotRoot(bc.db)
358+
}
359+
if diskRoot != (common.Hash{}) {
360+
log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash(), "snaproot", diskRoot)
361+
362+
snapDisk, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, diskRoot, true)
363+
if err != nil {
364+
return nil, err
365+
}
366+
// Chain rewound, persist old snapshot number to indicate recovery procedure
367+
if snapDisk != 0 {
368+
rawdb.WriteSnapshotRecoveryNumber(bc.db, snapDisk)
369+
}
370+
} else {
371+
log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash())
372+
if _, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, common.Hash{}, true); err != nil {
373+
return nil, err
374+
}
365375
}
366376
}
367377
}
@@ -620,6 +630,28 @@ func (bc *BlockChain) SetSafe(header *types.Header) {
620630
}
621631
}
622632

633+
// resetState resets the persistent state to genesis state if it's not present.
634+
func (bc *BlockChain) resetState() {
635+
// Short circuit if the genesis state is already present.
636+
root := bc.genesisBlock.Root()
637+
if bc.HasState(root) {
638+
return
639+
}
640+
// Reset the state database to empty for committing genesis state.
641+
// Note, it should only happen in path-based scheme and Reset function
642+
// is also only call-able in this mode.
643+
if bc.triedb.Scheme() == rawdb.PathScheme {
644+
if err := bc.triedb.Reset(types.EmptyRootHash); err != nil {
645+
log.Crit("Failed to clean state", "err", err) // Shouldn't happen
646+
}
647+
}
648+
// Write genesis state into database.
649+
if err := CommitGenesisState(bc.db, bc.triedb, bc.genesisBlock.Hash()); err != nil {
650+
log.Crit("Failed to commit genesis state", "err", err)
651+
}
652+
log.Info("Reset state to genesis", "root", root)
653+
}
654+
623655
// setHeadBeyondRoot rewinds the local chain to a new head with the extra condition
624656
// that the rewind must pass the specified state root. This method is meant to be
625657
// 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
646678
pivot := rawdb.ReadLastPivotNumber(bc.db)
647679
frozen, _ := bc.db.Ancients()
648680

649-
// resetState resets the persistent state to genesis if it's not available.
650-
resetState := func() {
651-
// Short circuit if the genesis state is already present.
652-
if bc.HasState(bc.genesisBlock.Root()) {
653-
return
654-
}
655-
// Reset the state database to empty for committing genesis state.
656-
// Note, it should only happen in path-based scheme and Reset function
657-
// is also only call-able in this mode.
658-
if bc.triedb.Scheme() == rawdb.PathScheme {
659-
if err := bc.triedb.Reset(types.EmptyRootHash); err != nil {
660-
log.Crit("Failed to clean state", "err", err) // Shouldn't happen
661-
}
662-
}
663-
// Write genesis state into database.
664-
if err := CommitGenesisState(bc.db, bc.triedb, bc.genesisBlock.Hash()); err != nil {
665-
log.Crit("Failed to commit genesis state", "err", err)
666-
}
667-
}
668681
updateFn := func(db ethdb.KeyValueWriter, header *types.Header) (*types.Header, bool) {
669682
// Rewind the blockchain, ensuring we don't end up with a stateless head
670683
// 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
674687
if newHeadBlock == nil {
675688
log.Error("Gap in the chain, rewinding to genesis", "number", header.Number, "hash", header.Hash())
676689
newHeadBlock = bc.genesisBlock
677-
resetState()
690+
bc.resetState()
678691
} else {
679692
// Block exists, keep rewinding until we find one with state,
680693
// keeping rewinding until we exceed the optional threshold
@@ -703,7 +716,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha
703716
}
704717
if beyondRoot || newHeadBlock.NumberU64() == 0 {
705718
if newHeadBlock.NumberU64() == 0 {
706-
resetState()
719+
bc.resetState()
707720
} else if !bc.HasState(newHeadBlock.Root()) {
708721
// Rewind to a block with recoverable state. If the state is
709722
// missing, run the state recovery here.

eth/downloader/downloader.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,14 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int,
398398
log.Info("Block synchronisation started")
399399
}
400400
if mode == SnapSync {
401-
// Snap sync uses the snapshot namespace to store potentially flakey data until
401+
// Snap sync will directly modify the persistent state, making the entire
402+
// trie database unusable until the state is fully synced. To prevent any
403+
// subsequent state reads, explicitly disable the trie database and state
404+
// syncer is responsible to address and correct any state missing.
405+
if d.blockchain.TrieDB().Scheme() == rawdb.PathScheme {
406+
d.blockchain.TrieDB().Reset(types.EmptyRootHash)
407+
}
408+
// Snap sync uses the snapshot namespace to store potentially flaky data until
402409
// sync completely heals and finishes. Pause snapshot maintenance in the mean-
403410
// time to prevent access.
404411
if snapshots := d.blockchain.Snapshots(); snapshots != nil { // Only nil in tests

0 commit comments

Comments
 (0)