Allow process in state processor to be interrupted by caller

This commit is contained in:
Jerry 2023-04-11 15:30:08 -07:00
parent 362db7ad10
commit 650e1413f3
No known key found for this signature in database
GPG key ID: 5B33FA23CB103211
7 changed files with 71 additions and 18 deletions

View file

@ -1803,7 +1803,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals, setHead bool)
// Process block using the parent state as reference point
substart := time.Now()
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig, nil)
if err != nil {
bc.reportBlock(block, receipts, err)
atomic.StoreUint32(&followupInterrupt, 1)

View file

@ -17,6 +17,7 @@
package core
import (
"context"
"errors"
"fmt"
"io/ioutil"
@ -123,9 +124,11 @@ func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, compara
if full {
cur := blockchain.CurrentBlock()
tdPre = blockchain.GetTd(cur.Hash(), cur.NumberU64())
if err := testBlockChainImport(blockChainB, blockchain); err != nil {
if err := testBlockChainImport(blockChainB, blockchain, nil); err != nil {
t.Fatalf("failed to import forked block chain: %v", err)
}
last := blockChainB[len(blockChainB)-1]
tdPost = blockchain.GetTd(last.Hash(), last.NumberU64())
} else {
@ -143,7 +146,7 @@ func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, compara
// testBlockChainImport tries to process a chain of blocks, writing them into
// the database if successful.
func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
func testBlockChainImport(chain types.Blocks, blockchain *BlockChain, ctx context.Context) error {
for _, block := range chain {
// Try and process the block
err := blockchain.engine.VerifyHeader(blockchain, block.Header(), true)
@ -156,11 +159,14 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
}
return err
}
statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.stateCache, nil)
if err != nil {
return err
}
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{})
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}, ctx)
if err != nil {
blockchain.reportBlock(block, receipts, err)
return err
@ -180,6 +186,26 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
return nil
}
func TestBlockChainImportInterrupt(t *testing.T) {
t.Parallel()
db, blockchain, err := newCanonical(ethash.NewFaker(), 10, true)
if err != nil {
t.Fatalf("failed to make new canonical chain: %v", err)
}
defer blockchain.Stop()
blockChainB := makeFakeNonEmptyBlockChain(blockchain.CurrentBlock(), 5, ethash.NewFaker(), db, forkSeed, 5)
ctx, cancel := context.WithCancel(context.Background())
cancel()
if err := testBlockChainImport(blockChainB, blockchain, ctx); err != context.Canceled {
t.Errorf("block chain import is not cancelled correctly, got %v, want %v", err, context.Canceled)
}
}
// testHeaderChainImport tries to process a chain of header, writing them into
// the database if successful.
func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error {
@ -476,7 +502,7 @@ func testBrokenChain(t *testing.T, full bool) {
// Create a forked chain, and try to insert with a missing link
if full {
chain := makeBlockChain(blockchain.CurrentBlock(), 5, ethash.NewFaker(), db, forkSeed)[1:]
if err := testBlockChainImport(chain, blockchain); err == nil {
if err := testBlockChainImport(chain, blockchain, nil); err == nil {
t.Errorf("broken block chain not reported")
}
} else {

View file

@ -335,6 +335,19 @@ func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethd
return blocks
}
// makeBlockChain creates a deterministic chain of blocks rooted at parent with fake invalid transactions.
func makeFakeNonEmptyBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int, numTx int) []*types.Block {
blocks, _ := GenerateChain(params.TestChainConfig, parent, engine, db, n, func(i int, b *BlockGen) {
addr := common.Address{0: byte(seed), 19: byte(i)}
b.SetCoinbase(addr)
for j := 0; j < numTx; j++ {
b.txs = append(b.txs, types.NewTransaction(0, addr, big.NewInt(1000), params.TxGas, nil, nil))
}
})
return blocks
}
type fakeChainReader struct {
config *params.ChainConfig
stateSyncData []*types.StateSyncData

View file

@ -17,6 +17,7 @@
package core
import (
"context"
"fmt"
"math/big"
"time"
@ -273,7 +274,7 @@ var parallelizabilityTimer = metrics.NewRegisteredTimer("block/parallelizability
// returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error.
// nolint:gocognit
func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config, interruptCtx context.Context) (types.Receipts, []*types.Log, uint64, error) {
blockstm.SetProcs(cfg.ParallelSpeculativeProcesses)
var (

View file

@ -17,6 +17,7 @@
package core
import (
"context"
"fmt"
"math/big"
@ -56,7 +57,7 @@ func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consen
// Process returns the receipts and logs accumulated during the process and
// returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error.
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) {
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config, interruptCtx context.Context) (types.Receipts, []*types.Log, uint64, error) {
var (
receipts types.Receipts
usedGas = new(uint64)
@ -74,6 +75,14 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {
if interruptCtx != nil {
select {
case <-interruptCtx.Done():
return nil, nil, 0, interruptCtx.Err()
default:
}
}
msg, err := tx.AsMessage(types.MakeSigner(p.config, header.Number), header.BaseFee)
if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)

View file

@ -17,6 +17,8 @@
package core
import (
"context"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
@ -47,5 +49,5 @@ type Processor interface {
// Process processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles.
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error)
Process(block *types.Block, statedb *state.StateDB, cfg vm.Config, interruptCtx context.Context) (types.Receipts, []*types.Log, uint64, error)
}

View file

@ -131,7 +131,9 @@ func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
return nil, fmt.Errorf("block #%d not found", next)
}
_, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{})
_, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}, nil)
if err != nil {
return nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
}