Recognize bad transactions and break loop in blockstm executor

This commit is contained in:
Jerry 2022-09-27 22:52:45 -07:00
parent 471afc8da2
commit e63e390ec7
5 changed files with 104 additions and 67 deletions

View file

@ -223,6 +223,7 @@ type BlockChain struct {
// NewBlockChain returns a fully initialised block chain using information // NewBlockChain returns a fully initialised block chain using information
// available in the database. It initialises the default Ethereum Validator // available in the database. It initialises the default Ethereum Validator
// and Processor. // and Processor.
//
//nolint:gocognit //nolint:gocognit
func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64, checker ethereum.ChainValidator) (*BlockChain, error) { func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64, checker ethereum.ChainValidator) (*BlockChain, error) {
if cacheConfig == nil { if cacheConfig == nil {
@ -420,6 +421,19 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
return bc, nil return bc, nil
} }
// Similar to NewBlockChain, this function creates a new blockchain object, but with a parallel state processor
func NewParallelBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(header *types.Header) bool, txLookupLimit *uint64, checker ethereum.ChainValidator) (*BlockChain, error) {
bc, err := NewBlockChain(db, cacheConfig, chainConfig, engine, vmConfig, shouldPreserve, txLookupLimit, checker)
if err != nil {
return nil, err
}
bc.processor = NewParallelStateProcessor(chainConfig, bc, engine)
return bc, nil
}
// empty returns an indicator whether the blockchain is empty. // empty returns an indicator whether the blockchain is empty.
// Note, it's a special case that we connect a non-empty ancient // Note, it's a special case that we connect a non-empty ancient
// database with an empty node, so that we can plugin the ancient // database with an empty node, so that we can plugin the ancient

View file

@ -23,6 +23,7 @@ type ExecTask interface {
MVReadList() []ReadDescriptor MVReadList() []ReadDescriptor
MVWriteList() []WriteDescriptor MVWriteList() []WriteDescriptor
MVFullWriteList() []WriteDescriptor MVFullWriteList() []WriteDescriptor
Hash() common.Hash
Sender() common.Address Sender() common.Address
Settle() Settle()
} }
@ -48,7 +49,8 @@ func (ev *ExecVersionView) Execute() (er ExecResult) {
} }
type ErrExecAbortError struct { type ErrExecAbortError struct {
Dependency int Dependency int
OriginError error
} }
func (e ErrExecAbortError) Error() string { func (e ErrExecAbortError) Error() string {
@ -308,12 +310,33 @@ func (pe *ParallelExecutor) Prepare() {
} }
} }
func (pe *ParallelExecutor) Close(wait bool) {
close(pe.chTasks)
close(pe.chSpeculativeTasks)
if wait {
pe.workerWg.Wait()
}
close(pe.chResults)
if wait {
pe.settleWg.Wait()
}
close(pe.chSettle)
}
// nolint: gocognit // nolint: gocognit
func (pe *ParallelExecutor) Step(res ExecResult) (result ParallelExecutionResult, err error) { func (pe *ParallelExecutor) Step(res *ExecResult) (result ParallelExecutionResult, err error) {
tx := res.ver.TxnIndex tx := res.ver.TxnIndex
if _, ok := res.err.(ErrExecAbortError); res.err != nil && !ok { if abortErr, ok := res.err.(ErrExecAbortError); ok && abortErr.OriginError != nil && pe.skipCheck[tx] {
err = res.err // If the transaction failed when we know it should not fail, this means the transaction itself is
// bad (e.g. wrong nonce), and we should exit the execution immediately
err = fmt.Errorf("could not apply tx %d [%v]: %w", tx, pe.tasks[tx].Hash(), abortErr.OriginError)
pe.Close(false)
return return
} }
@ -440,12 +463,7 @@ func (pe *ParallelExecutor) Step(res ExecResult) (result ParallelExecutionResult
if pe.validateTasks.countComplete() == len(pe.tasks) && pe.execTasks.countComplete() == len(pe.tasks) { if pe.validateTasks.countComplete() == len(pe.tasks) && pe.execTasks.countComplete() == len(pe.tasks) {
log.Debug("blockstm exec summary", "execs", pe.cntExec, "success", pe.cntSuccess, "aborts", pe.cntAbort, "validations", pe.cntTotalValidations, "failures", pe.cntValidationFail, "#tasks/#execs", fmt.Sprintf("%.2f%%", float64(len(pe.tasks))/float64(pe.cntExec)*100)) log.Debug("blockstm exec summary", "execs", pe.cntExec, "success", pe.cntSuccess, "aborts", pe.cntAbort, "validations", pe.cntTotalValidations, "failures", pe.cntValidationFail, "#tasks/#execs", fmt.Sprintf("%.2f%%", float64(len(pe.tasks))/float64(pe.cntExec)*100))
close(pe.chTasks) pe.Close(true)
close(pe.chSpeculativeTasks)
pe.workerWg.Wait()
close(pe.chResults)
pe.settleWg.Wait()
close(pe.chSettle)
var dag DAG var dag DAG
@ -469,13 +487,9 @@ func (pe *ParallelExecutor) Step(res ExecResult) (result ParallelExecutionResult
} }
// Send speculative tasks // Send speculative tasks
for pe.execTasks.minPending() != -1 || len(pe.execTasks.inProgress) == 0 { for pe.execTasks.minPending() != -1 {
nextTx := pe.execTasks.takeNextPending() nextTx := pe.execTasks.takeNextPending()
if nextTx == -1 {
nextTx = pe.execTasks.takeNextPending()
}
if nextTx != -1 { if nextTx != -1 {
pe.cntExec++ pe.cntExec++
@ -502,7 +516,7 @@ func executeParallelWithCheck(tasks []ExecTask, profile bool, check PropertyChec
for range pe.chResults { for range pe.chResults {
res := pe.resultQueue.Pop().(ExecResult) res := pe.resultQueue.Pop().(ExecResult)
result, err = pe.Step(res) result, err = pe.Step(&res)
if err != nil { if err != nil {
return result, err return result, err
@ -521,7 +535,5 @@ func executeParallelWithCheck(tasks []ExecTask, profile bool, check PropertyChec
} }
func ExecuteParallel(tasks []ExecTask, profile bool) (result ParallelExecutionResult, err error) { func ExecuteParallel(tasks []ExecTask, profile bool) (result ParallelExecutionResult, err error) {
return executeParallelWithCheck(tasks, profile, func(pe *ParallelExecutor) error { return executeParallelWithCheck(tasks, profile, nil)
return nil
})
} }

View file

@ -117,7 +117,7 @@ func (t *testExecTask) Execute(mvh *MVHashMap, incarnation int) error {
} }
if deps != -1 { if deps != -1 {
return ErrExecAbortError{deps} return ErrExecAbortError{deps, fmt.Errorf("Dependency error")}
} }
return nil return nil
@ -153,6 +153,10 @@ func (t *testExecTask) Sender() common.Address {
return t.sender return t.sender
} }
func (t *testExecTask) Hash() common.Hash {
return common.BytesToHash([]byte(fmt.Sprintf("%d", t.txIdx)))
}
func randTimeGenerator(min time.Duration, max time.Duration) func(txIdx int, opIdx int) time.Duration { func randTimeGenerator(min time.Duration, max time.Duration) func(txIdx int, opIdx int) time.Duration {
return func(txIdx int, opIdx int) time.Duration { return func(txIdx int, opIdx int) time.Duration {
return time.Duration(rand.Int63n(int64(max-min))) + min return time.Duration(rand.Int63n(int64(max-min))) + min

View file

@ -19,7 +19,6 @@ package core
import ( import (
"fmt" "fmt"
"math/big" "math/big"
"time"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
@ -106,7 +105,7 @@ func (task *ExecutionTask) Execute(mvh *blockstm.MVHashMap, incarnation int) (er
task.result, err = ApplyMessageNoFeeBurnOrTip(evm, task.msg, new(GasPool).AddGas(task.gasLimit)) task.result, err = ApplyMessageNoFeeBurnOrTip(evm, task.msg, new(GasPool).AddGas(task.gasLimit))
if task.result == nil || err != nil { if task.result == nil || err != nil {
return blockstm.ErrExecAbortError{Dependency: task.statedb.DepTxIndex()} return blockstm.ErrExecAbortError{Dependency: task.statedb.DepTxIndex(), OriginError: err}
} }
reads := task.statedb.MVReadMap() reads := task.statedb.MVReadMap()
@ -127,7 +126,7 @@ func (task *ExecutionTask) Execute(mvh *blockstm.MVHashMap, incarnation int) (er
} }
if task.statedb.HadInvalidRead() || err != nil { if task.statedb.HadInvalidRead() || err != nil {
err = blockstm.ErrExecAbortError{Dependency: task.statedb.DepTxIndex()} err = blockstm.ErrExecAbortError{Dependency: task.statedb.DepTxIndex(), OriginError: err}
return return
} }
@ -152,6 +151,10 @@ func (task *ExecutionTask) Sender() common.Address {
return task.sender return task.sender
} }
func (task *ExecutionTask) Hash() common.Hash {
return task.tx.Hash()
}
func (task *ExecutionTask) Settle() { func (task *ExecutionTask) Settle() {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -342,14 +345,8 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat
return nil, nil, 0, err return nil, nil, 0, err
} }
statedb.Finalise(p.config.IsEIP158(blockNumber))
start := time.Now()
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards) // Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles()) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles())
fmt.Println("Finalize time of parallel execution:", time.Since(start))
return receipts, allLogs, *usedGas, nil return receipts, allLogs, *usedGas, nil
} }

View file

@ -234,28 +234,33 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
}, },
} }
genesis = gspec.MustCommit(db) genesis = gspec.MustCommit(db)
blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil)
parallelBlockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil)
) )
defer blockchain.Stop() defer blockchain.Stop()
for i, tt := range []struct { defer parallelBlockchain.Stop()
txs []*types.Transaction
want string for _, bc := range []*BlockChain{blockchain, parallelBlockchain} {
}{ for i, tt := range []struct {
{ // ErrTxTypeNotSupported txs []*types.Transaction
txs: []*types.Transaction{ want string
mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)), }{
{ // ErrTxTypeNotSupported
txs: []*types.Transaction{
mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)),
},
want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: transaction type not supported",
}, },
want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: transaction type not supported", } {
}, block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
} { _, err := bc.InsertChain(types.Blocks{block})
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config) if err == nil {
_, err := blockchain.InsertChain(types.Blocks{block}) t.Fatal("block imported without errors")
if err == nil { }
t.Fatal("block imported without errors") if have, want := err.Error(), tt.want; have != want {
} t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
if have, want := err.Error(), tt.want; have != want { }
t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
} }
} }
} }
@ -274,28 +279,33 @@ func TestStateProcessorErrors(t *testing.T) {
}, },
}, },
} }
genesis = gspec.MustCommit(db) genesis = gspec.MustCommit(db)
blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil) blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil)
parallelBlockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil, nil)
) )
defer blockchain.Stop() defer blockchain.Stop()
for i, tt := range []struct { defer parallelBlockchain.Stop()
txs []*types.Transaction
want string for _, bc := range []*BlockChain{blockchain, parallelBlockchain} {
}{ for i, tt := range []struct {
{ // ErrSenderNoEOA txs []*types.Transaction
txs: []*types.Transaction{ want string
mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)), }{
{ // ErrSenderNoEOA
txs: []*types.Transaction{
mkDynamicTx(0, common.Address{}, params.TxGas-1000, big.NewInt(0), big.NewInt(0)),
},
want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x9280914443471259d4570a8661015ae4a5b80186dbc619658fb494bebc3da3d1",
}, },
want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x9280914443471259d4570a8661015ae4a5b80186dbc619658fb494bebc3da3d1", } {
}, block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config)
} { _, err := bc.InsertChain(types.Blocks{block})
block := GenerateBadBlock(genesis, ethash.NewFaker(), tt.txs, gspec.Config) if err == nil {
_, err := blockchain.InsertChain(types.Blocks{block}) t.Fatal("block imported without errors")
if err == nil { }
t.Fatal("block imported without errors") if have, want := err.Error(), tt.want; have != want {
} t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
if have, want := err.Error(), tt.want; have != want { }
t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want)
} }
} }
} }