diff --git a/core/blockchain.go b/core/blockchain.go index d8b101f616..a3fdb8b1cb 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1956,6 +1956,11 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s ) defer interrupt.Store(true) // terminate the prefetch at the end + // Short circuit if shutting down + if bc.insertStopped() { + return nil, fmt.Errorf("block processing interrupted during shutdown") + } + if bc.cfg.NoPrefetch { statedb, err = state.New(parentRoot, bc.statedb) if err != nil { @@ -2006,6 +2011,10 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s }(time.Now(), throwaway, block) } + if bc.insertStopped() { + return nil, fmt.Errorf("block processing interrupted during shutdown") + } + // If we are past Byzantium, enable prefetching to pull in trie node paths // while processing transactions. Before Byzantium the prefetcher is mostly // useless due to the intermediate root hashing after each transaction. @@ -2046,6 +2055,10 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s } ptime := time.Since(pstart) + if bc.insertStopped() { + return nil, fmt.Errorf("block processing interrupted during shutdown") + } + vstart := time.Now() if err := bc.validator.ValidateState(block, statedb, res, false); err != nil { bc.reportBlock(block, res, err) @@ -2053,6 +2066,10 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s } vtime := time.Since(vstart) + if bc.insertStopped() { + return nil, fmt.Errorf("block processing interrupted during shutdown") + } + // If witnesses was generated and stateless self-validation requested, do // that now. Self validation should *never* run in production, it's more of // a tight integration to enable running *all* consensus tests through the diff --git a/core/blockchain_test.go b/core/blockchain_test.go index f47e922e18..740861ac59 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -25,6 +25,7 @@ import ( "math/rand" "os" "path" + "strings" "sync" "testing" "time" @@ -4407,3 +4408,45 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64, } } } + +// TestBlockProcessingInterrupt tests graceful shutdown during block processing +func TestBlockProcessingInterrupt(t *testing.T) { + testBlockProcessingInterrupt(t, rawdb.HashScheme) + testBlockProcessingInterrupt(t, rawdb.PathScheme) +} + +func testBlockProcessingInterrupt(t *testing.T, scheme string) { + var ( + genesis = &Genesis{ + BaseFee: big.NewInt(params.InitialBaseFee), + Config: params.AllEthashProtocolChanges, + } + engine = ethash.NewFaker() + ) + + options := DefaultConfig().WithStateScheme(scheme) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, options) + defer blockchain.Stop() + + genDb, blocks := makeBlockChainWithGenesis(genesis, 1, engine, canonicalSeed) + defer genDb.Close() + + // Test interrupt during block processing + blockchain.InterruptInsert(true) + parent := blockchain.genesisBlock.Header() + _, err := blockchain.processBlock(parent.Root, blocks[0], true, false) + + if err == nil { + t.Error("Expected block processing to be interrupted, but it succeeded") + } + if !strings.Contains(fmt.Sprintf("%v", err), "block processing interrupted during shutdown") { + t.Errorf("Expected 'block processing interrupted during shutdown' error, got: %v", err) + } + + // Test processing works after reset + blockchain.InterruptInsert(false) + _, err = blockchain.processBlock(parent.Root, blocks[0], true, false) + if err != nil { + t.Errorf("Failed to process block after reset: %v", err) + } +}