mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 17:33:47 +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 len(ctx.Args()) == 1 {
|
||||||
if err := utils.ImportChain(chain, ctx.Args().First()); err != nil {
|
if err := utils.ImportChain(chain, ctx.Args().First()); err != nil {
|
||||||
utils.Fatalf("Import error: %v", err)
|
log.Error("Import error", "err", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, arg := range ctx.Args() {
|
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))
|
fmt.Printf("Import done in %v.\n\n", time.Since(start))
|
||||||
|
|
||||||
// Output pre-compaction stats mostly to see the import trashing
|
// Output pre-compaction stats mostly to see the import trashing
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,6 @@ func ImportChain(chain *core.BlockChain, fn string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stream := rlp.NewStream(reader, 0)
|
stream := rlp.NewStream(reader, 0)
|
||||||
|
|
||||||
// Run actual the import.
|
// Run actual the import.
|
||||||
|
|
@ -150,25 +149,34 @@ func ImportChain(chain *core.BlockChain, fn string) error {
|
||||||
if checkInterrupt() {
|
if checkInterrupt() {
|
||||||
return fmt.Errorf("interrupted")
|
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())
|
log.Info("Skipping batch as all blocks present", "batch", batch, "first", blocks[0].Hash(), "last", blocks[i-1].Hash())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if _, err := chain.InsertChain(missing); err != nil {
|
||||||
if _, err := chain.InsertChain(blocks[:i]); err != nil {
|
|
||||||
return fmt.Errorf("invalid block %d: %v", n, err)
|
return fmt.Errorf("invalid block %d: %v", n, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool {
|
func missingBlocks(chain *core.BlockChain, blocks []*types.Block) []*types.Block {
|
||||||
for _, b := range bs {
|
head := chain.CurrentBlock()
|
||||||
if !chain.HasBlock(b.Hash(), b.NumberU64()) {
|
for i, block := range blocks {
|
||||||
return false
|
// 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 {
|
func ExportChain(blockchain *core.BlockChain, fn string) error {
|
||||||
|
|
|
||||||
|
|
@ -50,10 +50,10 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin
|
||||||
// validated at this point.
|
// validated at this point.
|
||||||
func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
||||||
// Check whether the block's known, and if not, that it's linkable
|
// 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
|
return ErrKnownBlock
|
||||||
}
|
}
|
||||||
if !v.bc.HasBlockAndState(block.ParentHash()) {
|
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
|
||||||
return consensus.ErrUnknownAncestor
|
return consensus.ErrUnknownAncestor
|
||||||
}
|
}
|
||||||
// Header validity is known at this point, check the uncles and transactions
|
// 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
|
// Make sure the state associated with the block is available
|
||||||
if _, err := state.New(currentBlock.Root(), bc.stateCache); err != nil {
|
if _, err := state.New(currentBlock.Root(), bc.stateCache); err != nil {
|
||||||
// Dangling block without a state associated, init from scratch
|
// Dangling block without a state associated, init from scratch
|
||||||
log.Warn("Head state missing, resetting chain", "number", currentBlock.Number(), "hash", currentBlock.Hash())
|
log.Warn("Head state missing, repairing chain", "number", currentBlock.Number(), "hash", currentBlock.Hash())
|
||||||
return bc.Reset()
|
if err := bc.repair(¤tBlock); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Everything seems to be fine, set as the head block
|
// Everything seems to be fine, set as the head block
|
||||||
bc.currentBlock = currentBlock
|
bc.currentBlock = currentBlock
|
||||||
|
|
@ -423,6 +425,24 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
|
||||||
return nil
|
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.
|
// Export writes the active chain to the given writer.
|
||||||
func (bc *BlockChain) Export(w io.Writer) error {
|
func (bc *BlockChain) Export(w io.Writer) error {
|
||||||
return bc.ExportN(w, uint64(0), bc.currentBlock.NumberU64())
|
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
|
// HasBlockAndState checks if a block and associated state trie is fully present
|
||||||
// in the database or not, caching it if 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
|
// Check first that the block itself is known
|
||||||
block := bc.GetBlockByHash(hash)
|
block := bc.GetBlock(hash, number)
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -624,11 +644,20 @@ func (bc *BlockChain) Stop() {
|
||||||
|
|
||||||
bc.wg.Wait()
|
bc.wg.Wait()
|
||||||
|
|
||||||
// Ensure the state of the latest block is also stored to disk
|
// Ensure the state of a recent block is also stored to disk before exiting.
|
||||||
root := bc.CurrentHeader().Root
|
// 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
|
||||||
if err := bc.triedb.Commit(root); err != nil {
|
// the last N blocks as sync assistance to remote nodes; 2) a restart during
|
||||||
log.Error("Failed to commit latest state trie", "err", err)
|
// 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")
|
log.Info("Blockchain manager stopped")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ type Downloader struct {
|
||||||
// LightChain encapsulates functions required to synchronise a light chain.
|
// LightChain encapsulates functions required to synchronise a light chain.
|
||||||
type LightChain interface {
|
type LightChain interface {
|
||||||
// HasHeader verifies a header's presence in the local chain.
|
// 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 retrieves a header from the local chain.
|
||||||
GetHeaderByHash(common.Hash) *types.Header
|
GetHeaderByHash(common.Hash) *types.Header
|
||||||
|
|
@ -179,7 +179,7 @@ type BlockChain interface {
|
||||||
LightChain
|
LightChain
|
||||||
|
|
||||||
// HasBlockAndState verifies block and associated states' presence in the local chain.
|
// 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 retrieves a block from the local chain.
|
||||||
GetBlockByHash(common.Hash) *types.Block
|
GetBlockByHash(common.Hash) *types.Block
|
||||||
|
|
@ -671,7 +671,7 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Otherwise check if we already know the header or not
|
// 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()
|
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
|
// 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
|
arrived = true
|
||||||
|
|
||||||
// Modify the search interval based on the response
|
// 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
|
end = check
|
||||||
break
|
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.
|
// 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)
|
block := dl.GetBlockByHash(hash)
|
||||||
if block == nil {
|
if block == nil {
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue