core/vm: improve EVM instance reusability (#26341)

This change improves reusability of the EVM struct. Two methods are added:

- SetBlockContext(...)
- SetTracer(...)

Other attributes like the TransactionContext and the StateDB can already be updated.
BlockContext and Tracer are partially not updateable right now. This change fixes it and
opens the potential to reuse an EVM struct in more ways.

Co-authored-by: Felix Lange <fjl@twurst.com>
pull/26612/head
lmittmann 2 years ago committed by GitHub
parent 3a79a99f80
commit 877d2174fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      core/vm/evm.go
  2. 6
      core/vm/instructions.go
  3. 12
      core/vm/instructions_test.go
  4. 110
      core/vm/interpreter.go

@ -133,7 +133,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
chainConfig: chainConfig, chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
} }
evm.interpreter = NewEVMInterpreter(evm, config) evm.interpreter = NewEVMInterpreter(evm)
return evm return evm
} }
@ -160,6 +160,14 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
return evm.interpreter return evm.interpreter
} }
// SetBlockContext updates the block context of the EVM.
func (evm *EVM) SetBlockContext(blockCtx BlockContext) {
evm.Context = blockCtx
num := blockCtx.BlockNumber
timestamp := blockCtx.Time
evm.chainRules = evm.chainConfig.Rules(num, blockCtx.Random != nil, timestamp)
}
// Call executes the contract associated with the addr with the given input as // Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes // parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an // the necessary steps to create accounts and reverses the state in case of an

@ -824,9 +824,9 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address())
interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance)
interpreter.evm.StateDB.Suicide(scope.Contract.Address()) interpreter.evm.StateDB.Suicide(scope.Contract.Address())
if interpreter.cfg.Debug { if interpreter.evm.Config.Debug {
interpreter.cfg.Tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) interpreter.evm.Config.Tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance)
interpreter.cfg.Tracer.CaptureExit([]byte{}, 0, nil) interpreter.evm.Config.Tracer.CaptureExit([]byte{}, 0, nil)
} }
return nil, errStopToken return nil, errStopToken
} }

@ -203,7 +203,7 @@ func TestAddMod(t *testing.T) {
var ( var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
evmInterpreter = NewEVMInterpreter(env, env.Config) evmInterpreter = NewEVMInterpreter(env)
pc = uint64(0) pc = uint64(0)
) )
tests := []struct { tests := []struct {
@ -293,7 +293,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
scope = &ScopeContext{nil, stack, nil} scope = &ScopeContext{nil, stack, nil}
evmInterpreter = NewEVMInterpreter(env, env.Config) evmInterpreter = NewEVMInterpreter(env)
) )
env.interpreter = evmInterpreter env.interpreter = evmInterpreter
@ -534,7 +534,7 @@ func TestOpMstore(t *testing.T) {
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.Config) evmInterpreter = NewEVMInterpreter(env)
) )
env.interpreter = evmInterpreter env.interpreter = evmInterpreter
@ -560,7 +560,7 @@ func BenchmarkOpMstore(bench *testing.B) {
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.Config) evmInterpreter = NewEVMInterpreter(env)
) )
env.interpreter = evmInterpreter env.interpreter = evmInterpreter
@ -583,7 +583,7 @@ func TestOpTstore(t *testing.T) {
env = NewEVM(BlockContext{}, TxContext{}, statedb, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, statedb, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.Config) evmInterpreter = NewEVMInterpreter(env)
caller = common.Address{} caller = common.Address{}
to = common.Address{1} to = common.Address{1}
contractRef = contractRef{caller} contractRef = contractRef{caller}
@ -625,7 +625,7 @@ func BenchmarkOpKeccak256(bench *testing.B) {
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.Config) evmInterpreter = NewEVMInterpreter(env)
) )
env.interpreter = evmInterpreter env.interpreter = evmInterpreter
mem.Resize(32) mem.Resize(32)

@ -29,10 +29,7 @@ type Config struct {
Tracer EVMLogger // Opcode logger Tracer EVMLogger // Opcode logger
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
ExtraEips []int // Additional EIPS that are to be enabled
JumpTable *JumpTable // EVM instruction table, automatically populated if unset
ExtraEips []int // Additional EIPS that are to be enabled
} }
// ScopeContext contains the things that are per-call, such as stack and memory, // ScopeContext contains the things that are per-call, such as stack and memory,
@ -45,8 +42,8 @@ type ScopeContext struct {
// EVMInterpreter represents an EVM interpreter // EVMInterpreter represents an EVM interpreter
type EVMInterpreter struct { type EVMInterpreter struct {
evm *EVM evm *EVM
cfg Config table *JumpTable
hasher crypto.KeccakState // Keccak256 hasher instance shared across opcodes hasher crypto.KeccakState // Keccak256 hasher instance shared across opcodes
hasherBuf common.Hash // Keccak256 hasher result array shared aross opcodes hasherBuf common.Hash // Keccak256 hasher result array shared aross opcodes
@ -56,53 +53,48 @@ type EVMInterpreter struct {
} }
// NewEVMInterpreter returns a new instance of the Interpreter. // NewEVMInterpreter returns a new instance of the Interpreter.
func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
// If jump table was not initialised we set the default one. // If jump table was not initialised we set the default one.
if cfg.JumpTable == nil { var table *JumpTable
switch { switch {
case evm.chainRules.IsShanghai: case evm.chainRules.IsShanghai:
cfg.JumpTable = &shanghaiInstructionSet table = &shanghaiInstructionSet
case evm.chainRules.IsMerge: case evm.chainRules.IsMerge:
cfg.JumpTable = &mergeInstructionSet table = &mergeInstructionSet
case evm.chainRules.IsLondon: case evm.chainRules.IsLondon:
cfg.JumpTable = &londonInstructionSet table = &londonInstructionSet
case evm.chainRules.IsBerlin: case evm.chainRules.IsBerlin:
cfg.JumpTable = &berlinInstructionSet table = &berlinInstructionSet
case evm.chainRules.IsIstanbul: case evm.chainRules.IsIstanbul:
cfg.JumpTable = &istanbulInstructionSet table = &istanbulInstructionSet
case evm.chainRules.IsConstantinople: case evm.chainRules.IsConstantinople:
cfg.JumpTable = &constantinopleInstructionSet table = &constantinopleInstructionSet
case evm.chainRules.IsByzantium: case evm.chainRules.IsByzantium:
cfg.JumpTable = &byzantiumInstructionSet table = &byzantiumInstructionSet
case evm.chainRules.IsEIP158: case evm.chainRules.IsEIP158:
cfg.JumpTable = &spuriousDragonInstructionSet table = &spuriousDragonInstructionSet
case evm.chainRules.IsEIP150: case evm.chainRules.IsEIP150:
cfg.JumpTable = &tangerineWhistleInstructionSet table = &tangerineWhistleInstructionSet
case evm.chainRules.IsHomestead: case evm.chainRules.IsHomestead:
cfg.JumpTable = &homesteadInstructionSet table = &homesteadInstructionSet
default: default:
cfg.JumpTable = &frontierInstructionSet table = &frontierInstructionSet
}
var extraEips []int
if len(cfg.ExtraEips) > 0 {
// Deep-copy jumptable to prevent modification of opcodes in other tables
cfg.JumpTable = copyJumpTable(cfg.JumpTable)
}
for _, eip := range cfg.ExtraEips {
if err := EnableEIP(eip, cfg.JumpTable); err != nil {
// Disable it, so caller can check if it's activated or not
log.Error("EIP activation failed", "eip", eip, "error", err)
} else {
extraEips = append(extraEips, eip)
}
}
cfg.ExtraEips = extraEips
} }
var extraEips []int
return &EVMInterpreter{ if len(evm.Config.ExtraEips) > 0 {
evm: evm, // Deep-copy jumptable to prevent modification of opcodes in other tables
cfg: cfg, table = copyJumpTable(table)
}
for _, eip := range evm.Config.ExtraEips {
if err := EnableEIP(eip, table); err != nil {
// Disable it, so caller can check if it's activated or not
log.Error("EIP activation failed", "eip", eip, "error", err)
} else {
extraEips = append(extraEips, eip)
}
} }
evm.Config.ExtraEips = extraEips
return &EVMInterpreter{evm: evm, table: table}
} }
// Run loops and evaluates the contract's code with the given input data and returns // Run loops and evaluates the contract's code with the given input data and returns
@ -160,13 +152,13 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
}() }()
contract.Input = input contract.Input = input
if in.cfg.Debug { if in.evm.Config.Debug {
defer func() { defer func() {
if err != nil { if err != nil {
if !logged { if !logged {
in.cfg.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) in.evm.Config.Tracer.CaptureState(pcCopy, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
} else { } else {
in.cfg.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err) in.evm.Config.Tracer.CaptureFault(pcCopy, op, gasCopy, cost, callContext, in.evm.depth, err)
} }
} }
}() }()
@ -176,14 +168,14 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// the execution of one of the operations or until the done flag is set by the // the execution of one of the operations or until the done flag is set by the
// parent context. // parent context.
for { for {
if in.cfg.Debug { if in.evm.Config.Debug {
// Capture pre-execution values for tracing. // Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas logged, pcCopy, gasCopy = false, pc, contract.Gas
} }
// Get the operation from the jump table and validate the stack to ensure there are // Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation. // enough stack items available to perform the operation.
op = contract.GetOp(pc) op = contract.GetOp(pc)
operation := in.cfg.JumpTable[op] operation := in.table[op]
cost = operation.constantGas // For tracing cost = operation.constantGas // For tracing
// Validate stack // Validate stack
if sLen := stack.len(); sLen < operation.minStack { if sLen := stack.len(); sLen < operation.minStack {
@ -221,15 +213,15 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
// Do tracing before memory expansion // Do tracing before memory expansion
if in.cfg.Debug { if in.evm.Config.Debug {
in.cfg.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
logged = true logged = true
} }
if memorySize > 0 { if memorySize > 0 {
mem.Resize(memorySize) mem.Resize(memorySize)
} }
} else if in.cfg.Debug { } else if in.evm.Config.Debug {
in.cfg.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err) in.evm.Config.Tracer.CaptureState(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
logged = true logged = true
} }
// execute the operation // execute the operation

Loading…
Cancel
Save