mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 09:23:48 +00:00
cmd, core, eth: support restarting with missing states
This commit is contained in:
parent
bb40af204d
commit
be674fa6bf
6 changed files with 64 additions and 27 deletions
|
|
@ -202,7 +202,7 @@ func importChain(ctx *cli.Context) error {
|
|||
|
||||
if len(ctx.Args()) == 1 {
|
||||
if err := utils.ImportChain(chain, ctx.Args().First()); err != nil {
|
||||
utils.Fatalf("Import error: %v", err)
|
||||
log.Error("Import error", "err", err)
|
||||
}
|
||||
} else {
|
||||
for _, arg := range ctx.Args() {
|
||||
|
|
@ -211,7 +211,7 @@ func importChain(ctx *cli.Context) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
chain.Stop()
|
||||
fmt.Printf("Import done in %v.\n\n", time.Since(start))
|
||||
|
||||
// Output pre-compaction stats mostly to see the import trashing
|
||||
|
|
|
|||
|
|
@ -116,7 +116,6 @@ func ImportChain(chain *core.BlockChain, fn string) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
stream := rlp.NewStream(reader, 0)
|
||||
|
||||
// Run actual the import.
|
||||
|
|
@ -150,25 +149,34 @@ func ImportChain(chain *core.BlockChain, fn string) error {
|
|||
if checkInterrupt() {
|
||||
return fmt.Errorf("interrupted")
|
||||
}
|
||||
if hasAllBlocks(chain, blocks[:i]) {
|
||||
missing := missingBlocks(chain, blocks[:i])
|
||||
if len(missing) == 0 {
|
||||
log.Info("Skipping batch as all blocks present", "batch", batch, "first", blocks[0].Hash(), "last", blocks[i-1].Hash())
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := chain.InsertChain(blocks[:i]); err != nil {
|
||||
if _, err := chain.InsertChain(missing); err != nil {
|
||||
return fmt.Errorf("invalid block %d: %v", n, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool {
|
||||
for _, b := range bs {
|
||||
if !chain.HasBlock(b.Hash(), b.NumberU64()) {
|
||||
return false
|
||||
func missingBlocks(chain *core.BlockChain, blocks []*types.Block) []*types.Block {
|
||||
head := chain.CurrentBlock()
|
||||
for i, block := range blocks {
|
||||
// If we're behind the chain head, only check block, state is available at head
|
||||
if head.NumberU64() > block.NumberU64() {
|
||||
if !chain.HasBlock(block.Hash(), block.NumberU64()) {
|
||||
return blocks[i:]
|
||||
}
|
||||
continue
|
||||
}
|
||||
// If we're above the chain head, state availability is a must
|
||||
if !chain.HasBlockAndState(block.Hash(), block.NumberU64()) {
|
||||
return blocks[i:]
|
||||
}
|
||||
}
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
func ExportChain(blockchain *core.BlockChain, fn string) error {
|
||||
|
|
|
|||
|
|
@ -50,10 +50,10 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin
|
|||
// validated at this point.
|
||||
func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
||||
// Check whether the block's known, and if not, that it's linkable
|
||||
if v.bc.HasBlockAndState(block.Hash()) {
|
||||
if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) {
|
||||
return ErrKnownBlock
|
||||
}
|
||||
if !v.bc.HasBlockAndState(block.ParentHash()) {
|
||||
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
|
||||
return consensus.ErrUnknownAncestor
|
||||
}
|
||||
// Header validity is known at this point, check the uncles and transactions
|
||||
|
|
|
|||
|
|
@ -219,8 +219,10 @@ func (bc *BlockChain) loadLastState() error {
|
|||
// Make sure the state associated with the block is available
|
||||
if _, err := state.New(currentBlock.Root(), bc.stateCache); err != nil {
|
||||
// Dangling block without a state associated, init from scratch
|
||||
log.Warn("Head state missing, resetting chain", "number", currentBlock.Number(), "hash", currentBlock.Hash())
|
||||
return bc.Reset()
|
||||
log.Warn("Head state missing, repairing chain", "number", currentBlock.Number(), "hash", currentBlock.Hash())
|
||||
if err := bc.repair(¤tBlock); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Everything seems to be fine, set as the head block
|
||||
bc.currentBlock = currentBlock
|
||||
|
|
@ -423,6 +425,24 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// repair tries to repair the current blockchain by rolling back the current block
|
||||
// until one with associated state is found. This is needed to fix incomplete db
|
||||
// writes caused either by crashes/power outages, or simply non-committed tries.
|
||||
//
|
||||
// This method only rolls back the current block. The current header and current
|
||||
// fast block are left intact.
|
||||
func (bc *BlockChain) repair(head **types.Block) error {
|
||||
for {
|
||||
// Abort if we've rewound to a head block that does have associated state
|
||||
if _, err := state.New((*head).Root(), bc.stateCache); err == nil {
|
||||
log.Info("Rewound blockchain to past state", "number", (*head).Number(), "hash", (*head).Hash())
|
||||
return nil
|
||||
}
|
||||
// Otherwise rewind one block and recheck state availability there
|
||||
(*head) = bc.GetBlock((*head).ParentHash(), (*head).NumberU64()-1)
|
||||
}
|
||||
}
|
||||
|
||||
// Export writes the active chain to the given writer.
|
||||
func (bc *BlockChain) Export(w io.Writer) error {
|
||||
return bc.ExportN(w, uint64(0), bc.currentBlock.NumberU64())
|
||||
|
|
@ -531,9 +551,9 @@ func (bc *BlockChain) HasBlock(hash common.Hash, number uint64) bool {
|
|||
|
||||
// HasBlockAndState checks if a block and associated state trie is fully present
|
||||
// in the database or not, caching it if present.
|
||||
func (bc *BlockChain) HasBlockAndState(hash common.Hash) bool {
|
||||
func (bc *BlockChain) HasBlockAndState(hash common.Hash, number uint64) bool {
|
||||
// Check first that the block itself is known
|
||||
block := bc.GetBlockByHash(hash)
|
||||
block := bc.GetBlock(hash, number)
|
||||
if block == nil {
|
||||
return false
|
||||
}
|
||||
|
|
@ -624,11 +644,20 @@ func (bc *BlockChain) Stop() {
|
|||
|
||||
bc.wg.Wait()
|
||||
|
||||
// Ensure the state of the latest block is also stored to disk
|
||||
root := bc.CurrentHeader().Root
|
||||
|
||||
if err := bc.triedb.Commit(root); err != nil {
|
||||
log.Error("Failed to commit latest state trie", "err", err)
|
||||
// Ensure the state of a recent block is also stored to disk before exiting.
|
||||
// It is fine if this state does not exist (fast start/stop cycle), but it is
|
||||
// advisable to leave an N block gap from the head so 1) a restart loads up
|
||||
// the last N blocks as sync assistance to remote nodes; 2) a restart during
|
||||
// a (small) reorg doesn't require deep reprocesses; 3) chain "repair" from
|
||||
// missing states are constantly tested.
|
||||
//
|
||||
// This may be tuned a bit on mainnet if its too annoying to reprocess the last
|
||||
// N blocks.
|
||||
if number := bc.CurrentBlock().NumberU64(); number >= triesInMemory {
|
||||
recent := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - triesInMemory + 1)
|
||||
if err := bc.triedb.Commit(recent.Root()); err != nil {
|
||||
log.Error("Failed to commit recent state trie", "err", err)
|
||||
}
|
||||
}
|
||||
log.Info("Blockchain manager stopped")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ type Downloader struct {
|
|||
// LightChain encapsulates functions required to synchronise a light chain.
|
||||
type LightChain interface {
|
||||
// HasHeader verifies a header's presence in the local chain.
|
||||
HasHeader(h common.Hash, number uint64) bool
|
||||
HasHeader(common.Hash, uint64) bool
|
||||
|
||||
// GetHeaderByHash retrieves a header from the local chain.
|
||||
GetHeaderByHash(common.Hash) *types.Header
|
||||
|
|
@ -179,7 +179,7 @@ type BlockChain interface {
|
|||
LightChain
|
||||
|
||||
// HasBlockAndState verifies block and associated states' presence in the local chain.
|
||||
HasBlockAndState(common.Hash) bool
|
||||
HasBlockAndState(common.Hash, uint64) bool
|
||||
|
||||
// GetBlockByHash retrieves a block from the local chain.
|
||||
GetBlockByHash(common.Hash) *types.Block
|
||||
|
|
@ -671,7 +671,7 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
|
|||
continue
|
||||
}
|
||||
// Otherwise check if we already know the header or not
|
||||
if (d.mode == FullSync && d.blockchain.HasBlockAndState(headers[i].Hash())) || (d.mode != FullSync && d.lightchain.HasHeader(headers[i].Hash(), headers[i].Number.Uint64())) {
|
||||
if (d.mode == FullSync && d.blockchain.HasBlockAndState(headers[i].Hash(), headers[i].Number.Uint64())) || (d.mode != FullSync && d.lightchain.HasHeader(headers[i].Hash(), headers[i].Number.Uint64())) {
|
||||
number, hash = headers[i].Number.Uint64(), headers[i].Hash()
|
||||
|
||||
// If every header is known, even future ones, the peer straight out lied about its head
|
||||
|
|
@ -736,7 +736,7 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
|
|||
arrived = true
|
||||
|
||||
// Modify the search interval based on the response
|
||||
if (d.mode == FullSync && !d.blockchain.HasBlockAndState(headers[0].Hash())) || (d.mode != FullSync && !d.lightchain.HasHeader(headers[0].Hash(), headers[0].Number.Uint64())) {
|
||||
if (d.mode == FullSync && !d.blockchain.HasBlockAndState(headers[0].Hash(), headers[0].Number.Uint64())) || (d.mode != FullSync && !d.lightchain.HasHeader(headers[0].Hash(), headers[0].Number.Uint64())) {
|
||||
end = check
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ func (dl *downloadTester) HasHeader(hash common.Hash, number uint64) bool {
|
|||
}
|
||||
|
||||
// HasBlockAndState checks if a block and associated state is present in the testers canonical chain.
|
||||
func (dl *downloadTester) HasBlockAndState(hash common.Hash) bool {
|
||||
func (dl *downloadTester) HasBlockAndState(hash common.Hash, number uint64) bool {
|
||||
block := dl.GetBlockByHash(hash)
|
||||
if block == nil {
|
||||
return false
|
||||
|
|
|
|||
Loading…
Reference in a new issue