diff --git a/core/blockchain.go b/core/blockchain.go index 590e05e0a8..8c945a8573 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -329,18 +329,14 @@ func (bc *BlockChain) loadLastState() error { if head == (common.Hash{}) { // Corrupt or empty database, init from scratch log.Warn("Empty database, resetting chain") - bc.chainmu.Unlock() - defer bc.chainmu.Lock() - return bc.Reset() + return bc.resetWithGenesisBlockNoLock(bc.genesisBlock) } // Make sure the entire head block is available currentBlock := bc.GetBlockByHash(head) if currentBlock == nil { // Corrupt or empty database, init from scratch log.Warn("Head block missing, resetting chain", "hash", head) - bc.chainmu.Unlock() - defer bc.chainmu.Lock() - return bc.Reset() + return bc.resetWithGenesisBlockNoLock(bc.genesisBlock) } // Make sure the state associated with the block is available if _, err := state.New(currentBlock.Root(), bc.stateCache); err != nil { @@ -388,6 +384,85 @@ func (bc *BlockChain) loadLastState() error { return nil } +// setHeadNoLock rewinds the local chain to a new head. The callers should hold bc.chainmu. +func (bc *BlockChain) setHeadNoLock(head uint64) error { + log.Warn("Rewinding blockchain", "target", head) + + updateFn := func(db ethdb.KeyValueWriter, header *types.Header) { + // Rewind the block chain, ensuring we don't end up with a stateless head block + if currentBlock := bc.CurrentBlock(); currentBlock != nil && header.Number.Uint64() < currentBlock.NumberU64() { + newHeadBlock := bc.GetBlock(header.Hash(), header.Number.Uint64()) + if newHeadBlock == nil { + newHeadBlock = bc.genesisBlock + } else { + if _, err := state.New(newHeadBlock.Root(), bc.stateCache); err != nil { + // Rewound state missing, rolled back to before pivot, reset to genesis + newHeadBlock = bc.genesisBlock + } + } + rawdb.WriteHeadBlockHash(db, newHeadBlock.Hash()) + + // Degrade the chain markers if they are explicitly reverted. + // In theory we should update all in-memory markers in the + // last step, however the direction of SetHead is from high + // to low, so it's safe the update in-memory markers directly. + bc.currentBlock.Store(newHeadBlock) + headBlockGauge.Update(int64(newHeadBlock.NumberU64())) + } + + // Rewind the fast block in a simpleton way to the target head + if currentFastBlock := bc.CurrentFastBlock(); currentFastBlock != nil && header.Number.Uint64() < currentFastBlock.NumberU64() { + newHeadFastBlock := bc.GetBlock(header.Hash(), header.Number.Uint64()) + // If either blocks reached nil, reset to the genesis state + if newHeadFastBlock == nil { + newHeadFastBlock = bc.genesisBlock + } + rawdb.WriteHeadFastBlockHash(db, newHeadFastBlock.Hash()) + + // Degrade the chain markers if they are explicitly reverted. + // In theory we should update all in-memory markers in the + // last step, however the direction of SetHead is from high + // to low, so it's safe the update in-memory markers directly. + bc.currentFastBlock.Store(newHeadFastBlock) + headFastBlockGauge.Update(int64(newHeadFastBlock.NumberU64())) + } + } + + // Rewind the header chain, deleting all block bodies until then + delFn := func(db ethdb.KeyValueWriter, hash common.Hash, num uint64) { + // Ignore the error here since light client won't hit this path + frozen, _ := bc.db.Ancients() + if num+1 <= frozen { + // Truncate all relative data(header, total difficulty, body, receipt + // and canonical hash) from ancient store. + if err := bc.db.TruncateAncients(num + 1); err != nil { + log.Crit("Failed to truncate ancient data", "number", num, "err", err) + } + + // Remove the hash <-> number mapping from the active store. + rawdb.DeleteHeaderNumber(db, hash) + } else { + // Remove relative body and receipts from the active store. + // The header, total difficulty and canonical hash will be + // removed in the hc.SetHead function. + rawdb.DeleteBody(db, hash, num) + rawdb.DeleteReceipts(db, hash, num) + } + // Todo(rjl493456442) txlookup, bloombits, etc + } + bc.hc.SetHead(head, updateFn, delFn) + + // Clear out any stale content from the caches + bc.bodyCache.Purge() + bc.bodyRLPCache.Purge() + bc.receiptsCache.Purge() + bc.blockCache.Purge() + bc.txLookupCache.Purge() + bc.futureBlocks.Purge() + + return bc.loadLastState() +} + // SetHead rewinds the local chain to a new head. In the case of headers, everything // above the new head will be deleted and the new one set. In the case of blocks // though, the head may be further rewound if block bodies are missing (non-archive @@ -541,6 +616,34 @@ func (bc *BlockChain) Reset() error { return bc.ResetWithGenesisBlock(bc.genesisBlock) } +// resetWithGenesisBlockNoLock purges the entire blockchain, restoring it to the +// specified genesis state. The callers should hold bc.chainmu. +func (bc *BlockChain) resetWithGenesisBlockNoLock(genesis *types.Block) error { + // Dump the entire block chain and purge the caches + if err := bc.setHeadNoLock(0); err != nil { + return err + } + + // Prepare the genesis block and reinitialise the chain + batch := bc.db.NewBatch() + rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()) + rawdb.WriteBlock(batch, genesis) + if err := batch.Write(); err != nil { + log.Crit("Failed to write genesis block", "err", err) + } + bc.writeHeadBlock(genesis) + + // Last update all in-memory chain markers + bc.genesisBlock = genesis + bc.currentBlock.Store(bc.genesisBlock) + headBlockGauge.Update(int64(bc.genesisBlock.NumberU64())) + bc.hc.SetGenesis(bc.genesisBlock.Header()) + bc.hc.SetCurrentHeader(bc.genesisBlock.Header()) + bc.currentFastBlock.Store(bc.genesisBlock) + headFastBlockGauge.Update(int64(bc.genesisBlock.NumberU64())) + return nil +} + // ResetWithGenesisBlock purges the entire blockchain, restoring it to the // specified genesis state. func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error { diff --git a/light/lightchain.go b/light/lightchain.go index 88bea4fead..6ef72511a9 100644 --- a/light/lightchain.go +++ b/light/lightchain.go @@ -153,9 +153,7 @@ func (lc *LightChain) HeaderChain() *core.HeaderChain { func (lc *LightChain) loadLastState() error { if head := rawdb.ReadHeadHeaderHash(lc.chainDb); head == (common.Hash{}) { // Corrupt or empty database, init from scratch - lc.chainmu.Unlock() - lc.Reset() - lc.chainmu.Lock() + lc.resetWithGenesisBlockNoLock(lc.genesisBlock) } else { if header := lc.GetHeaderByHash(head); header != nil { lc.hc.SetCurrentHeader(header) @@ -169,6 +167,12 @@ func (lc *LightChain) loadLastState() error { return nil } +// setHeadNoLock rewinds the local chain to a new head. Callers should hold lc.chainmu. +func (lc *LightChain) setHeadNoLock(head uint64) error { + lc.hc.SetHead(head, nil, nil) + return lc.loadLastState() +} + // SetHead rewinds the local chain to a new head. Everything above the new // head will be deleted and the new one set. func (lc *LightChain) SetHead(head uint64) error { @@ -189,6 +193,25 @@ func (lc *LightChain) Reset() { lc.ResetWithGenesisBlock(lc.genesisBlock) } +// resetWithGenesisBlockNoLock purges the entire blockchain, restoring it to the +// specified genesis state. Callers should hold lc.chainmu. +func (lc *LightChain) resetWithGenesisBlockNoLock(genesis *types.Block) { + // Dump the entire block chain and purge the caches + lc.setHeadNoLock(0) + + // Prepare the genesis block and reinitialise the chain + batch := lc.chainDb.NewBatch() + rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()) + rawdb.WriteBlock(batch, genesis) + rawdb.WriteHeadHeaderHash(batch, genesis.Hash()) + if err := batch.Write(); err != nil { + log.Crit("Failed to reset genesis block", "err", err) + } + lc.genesisBlock = genesis + lc.hc.SetGenesis(lc.genesisBlock.Header()) + lc.hc.SetCurrentHeader(lc.genesisBlock.Header()) +} + // ResetWithGenesisBlock purges the entire blockchain, restoring it to the // specified genesis state. func (lc *LightChain) ResetWithGenesisBlock(genesis *types.Block) {