Unverified Commit 7abedf9b authored by Guillaume Ballet's avatar Guillaume Ballet Committed by GitHub

core/vm: support for multiple interpreters (#17093)

- Define an Interpreter interface
- One contract can call contracts from other interpreter types.
- Pass the interpreter to the operands instead of the evm.
  This is meant to prevent type assertions in operands.
parent 27a278e6
...@@ -26,4 +26,5 @@ var ( ...@@ -26,4 +26,5 @@ var (
ErrTraceLimitReached = errors.New("the number of logs reached the specified limit") ErrTraceLimitReached = errors.New("the number of logs reached the specified limit")
ErrInsufficientBalance = errors.New("insufficient balance for transfer") ErrInsufficientBalance = errors.New("insufficient balance for transfer")
ErrContractAddressCollision = errors.New("contract address collision") ErrContractAddressCollision = errors.New("contract address collision")
ErrNoCompatibleInterpreter = errors.New("no compatible interpreter")
) )
...@@ -51,7 +51,20 @@ func run(evm *EVM, contract *Contract, input []byte) ([]byte, error) { ...@@ -51,7 +51,20 @@ func run(evm *EVM, contract *Contract, input []byte) ([]byte, error) {
return RunPrecompiledContract(p, input, contract) return RunPrecompiledContract(p, input, contract)
} }
} }
return evm.interpreter.Run(contract, input) for _, interpreter := range evm.interpreters {
if interpreter.CanRun(contract.Code) {
if evm.interpreter != interpreter {
// Ensure that the interpreter pointer is set back
// to its current value upon return.
defer func(i Interpreter) {
evm.interpreter = i
}(evm.interpreter)
evm.interpreter = interpreter
}
return interpreter.Run(contract, input)
}
}
return nil, ErrNoCompatibleInterpreter
} }
// Context provides the EVM with auxiliary information. Once provided // Context provides the EVM with auxiliary information. Once provided
...@@ -103,7 +116,8 @@ type EVM struct { ...@@ -103,7 +116,8 @@ type EVM struct {
vmConfig Config vmConfig Config
// global (to this context) ethereum virtual machine // global (to this context) ethereum virtual machine
// used throughout the execution of the tx. // used throughout the execution of the tx.
interpreter *Interpreter interpreters []Interpreter
interpreter Interpreter
// abort is used to abort the EVM calling operations // abort is used to abort the EVM calling operations
// NOTE: must be set atomically // NOTE: must be set atomically
abort int32 abort int32
...@@ -122,9 +136,12 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon ...@@ -122,9 +136,12 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon
vmConfig: vmConfig, vmConfig: vmConfig,
chainConfig: chainConfig, chainConfig: chainConfig,
chainRules: chainConfig.Rules(ctx.BlockNumber), chainRules: chainConfig.Rules(ctx.BlockNumber),
interpreters: make([]Interpreter, 1),
} }
evm.interpreter = NewInterpreter(evm, vmConfig) evm.interpreters[0] = NewEVMInterpreter(evm, vmConfig)
evm.interpreter = evm.interpreters[0]
return evm return evm
} }
...@@ -134,6 +151,11 @@ func (evm *EVM) Cancel() { ...@@ -134,6 +151,11 @@ func (evm *EVM) Cancel() {
atomic.StoreInt32(&evm.abort, 1) atomic.StoreInt32(&evm.abort, 1)
} }
// Interpreter returns the current interpreter
func (evm *EVM) Interpreter() Interpreter {
return evm.interpreter
}
// 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
...@@ -291,9 +313,9 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte ...@@ -291,9 +313,9 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
// Make sure the readonly is only set if we aren't in readonly yet // Make sure the readonly is only set if we aren't in readonly yet
// this makes also sure that the readonly flag isn't removed for // this makes also sure that the readonly flag isn't removed for
// child calls. // child calls.
if !evm.interpreter.readOnly { if !evm.interpreter.IsReadOnly() {
evm.interpreter.readOnly = true evm.interpreter.SetReadOnly(true)
defer func() { evm.interpreter.readOnly = false }() defer func() { evm.interpreter.SetReadOnly(false) }()
} }
var ( var (
...@@ -414,6 +436,3 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment * ...@@ -414,6 +436,3 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *
// ChainConfig returns the environment's chain configuration // ChainConfig returns the environment's chain configuration
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
// Interpreter returns the EVM interpreter
func (evm *EVM) Interpreter() *Interpreter { return evm.interpreter }
This diff is collapsed.
...@@ -30,20 +30,23 @@ type twoOperandTest struct { ...@@ -30,20 +30,23 @@ type twoOperandTest struct {
expected string expected string
} }
func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)) { func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)) {
var ( var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
pc = uint64(0) pc = uint64(0)
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
) )
env.interpreter.intPool = poolOfIntPools.get()
env.interpreter = evmInterpreter
evmInterpreter.intPool = poolOfIntPools.get()
for i, test := range tests { for i, test := range tests {
x := new(big.Int).SetBytes(common.Hex2Bytes(test.x)) x := new(big.Int).SetBytes(common.Hex2Bytes(test.x))
shift := new(big.Int).SetBytes(common.Hex2Bytes(test.y)) shift := new(big.Int).SetBytes(common.Hex2Bytes(test.y))
expected := new(big.Int).SetBytes(common.Hex2Bytes(test.expected)) expected := new(big.Int).SetBytes(common.Hex2Bytes(test.expected))
stack.push(x) stack.push(x)
stack.push(shift) stack.push(shift)
opFn(&pc, env, nil, nil, stack) opFn(&pc, evmInterpreter, nil, nil, stack)
actual := stack.pop() actual := stack.pop()
if actual.Cmp(expected) != 0 { if actual.Cmp(expected) != 0 {
t.Errorf("Testcase %d, expected %v, got %v", i, expected, actual) t.Errorf("Testcase %d, expected %v, got %v", i, expected, actual)
...@@ -51,13 +54,13 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64 ...@@ -51,13 +54,13 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
// Check pool usage // Check pool usage
// 1.pool is not allowed to contain anything on the stack // 1.pool is not allowed to contain anything on the stack
// 2.pool is not allowed to contain the same pointers twice // 2.pool is not allowed to contain the same pointers twice
if env.interpreter.intPool.pool.len() > 0 { if evmInterpreter.intPool.pool.len() > 0 {
poolvals := make(map[*big.Int]struct{}) poolvals := make(map[*big.Int]struct{})
poolvals[actual] = struct{}{} poolvals[actual] = struct{}{}
for env.interpreter.intPool.pool.len() > 0 { for evmInterpreter.intPool.pool.len() > 0 {
key := env.interpreter.intPool.get() key := evmInterpreter.intPool.get()
if _, exist := poolvals[key]; exist { if _, exist := poolvals[key]; exist {
t.Errorf("Testcase %d, pool contains double-entry", i) t.Errorf("Testcase %d, pool contains double-entry", i)
} }
...@@ -65,15 +68,18 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64 ...@@ -65,15 +68,18 @@ func testTwoOperandOp(t *testing.T, tests []twoOperandTest, opFn func(pc *uint64
} }
} }
} }
poolOfIntPools.put(env.interpreter.intPool) poolOfIntPools.put(evmInterpreter.intPool)
} }
func TestByteOp(t *testing.T) { func TestByteOp(t *testing.T) {
var ( var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
) )
env.interpreter.intPool = poolOfIntPools.get()
env.interpreter = evmInterpreter
evmInterpreter.intPool = poolOfIntPools.get()
tests := []struct { tests := []struct {
v string v string
th uint64 th uint64
...@@ -94,13 +100,13 @@ func TestByteOp(t *testing.T) { ...@@ -94,13 +100,13 @@ func TestByteOp(t *testing.T) {
th := new(big.Int).SetUint64(test.th) th := new(big.Int).SetUint64(test.th)
stack.push(val) stack.push(val)
stack.push(th) stack.push(th)
opByte(&pc, env, nil, nil, stack) opByte(&pc, evmInterpreter, nil, nil, stack)
actual := stack.pop() actual := stack.pop()
if actual.Cmp(test.expected) != 0 { if actual.Cmp(test.expected) != 0 {
t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.v, test.th, test.expected, actual) t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.v, test.th, test.expected, actual)
} }
} }
poolOfIntPools.put(env.interpreter.intPool) poolOfIntPools.put(evmInterpreter.intPool)
} }
func TestSHL(t *testing.T) { func TestSHL(t *testing.T) {
...@@ -200,11 +206,14 @@ func TestSLT(t *testing.T) { ...@@ -200,11 +206,14 @@ func TestSLT(t *testing.T) {
testTwoOperandOp(t, tests, opSlt) testTwoOperandOp(t, tests, opSlt)
} }
func opBenchmark(bench *testing.B, op func(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) { func opBenchmark(bench *testing.B, op func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) {
var ( var (
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
) )
env.interpreter = evmInterpreter
// convert args // convert args
byteArgs := make([][]byte, len(args)) byteArgs := make([][]byte, len(args))
for i, arg := range args { for i, arg := range args {
...@@ -217,7 +226,7 @@ func opBenchmark(bench *testing.B, op func(pc *uint64, evm *EVM, contract *Contr ...@@ -217,7 +226,7 @@ func opBenchmark(bench *testing.B, op func(pc *uint64, evm *EVM, contract *Contr
a := new(big.Int).SetBytes(arg) a := new(big.Int).SetBytes(arg)
stack.push(a) stack.push(a)
} }
op(&pc, env, nil, nil, stack) op(&pc, evmInterpreter, nil, nil, stack)
stack.pop() stack.pop()
} }
} }
...@@ -435,22 +444,25 @@ func TestOpMstore(t *testing.T) { ...@@ -435,22 +444,25 @@ func TestOpMstore(t *testing.T) {
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
) )
env.interpreter.intPool = poolOfIntPools.get()
env.interpreter = evmInterpreter
evmInterpreter.intPool = poolOfIntPools.get()
mem.Resize(64) mem.Resize(64)
pc := uint64(0) pc := uint64(0)
v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700"
stack.pushN(new(big.Int).SetBytes(common.Hex2Bytes(v)), big.NewInt(0)) stack.pushN(new(big.Int).SetBytes(common.Hex2Bytes(v)), big.NewInt(0))
opMstore(&pc, env, nil, mem, stack) opMstore(&pc, evmInterpreter, nil, mem, stack)
if got := common.Bytes2Hex(mem.Get(0, 32)); got != v { if got := common.Bytes2Hex(mem.Get(0, 32)); got != v {
t.Fatalf("Mstore fail, got %v, expected %v", got, v) t.Fatalf("Mstore fail, got %v, expected %v", got, v)
} }
stack.pushN(big.NewInt(0x1), big.NewInt(0)) stack.pushN(big.NewInt(0x1), big.NewInt(0))
opMstore(&pc, env, nil, mem, stack) opMstore(&pc, evmInterpreter, nil, mem, stack)
if common.Bytes2Hex(mem.Get(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { if common.Bytes2Hex(mem.Get(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" {
t.Fatalf("Mstore failed to overwrite previous value") t.Fatalf("Mstore failed to overwrite previous value")
} }
poolOfIntPools.put(env.interpreter.intPool) poolOfIntPools.put(evmInterpreter.intPool)
} }
func BenchmarkOpMstore(bench *testing.B) { func BenchmarkOpMstore(bench *testing.B) {
...@@ -458,7 +470,10 @@ func BenchmarkOpMstore(bench *testing.B) { ...@@ -458,7 +470,10 @@ func BenchmarkOpMstore(bench *testing.B) {
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
) )
env.interpreter = evmInterpreter
mem.Resize(64) mem.Resize(64)
pc := uint64(0) pc := uint64(0)
memStart := big.NewInt(0) memStart := big.NewInt(0)
...@@ -467,6 +482,6 @@ func BenchmarkOpMstore(bench *testing.B) { ...@@ -467,6 +482,6 @@ func BenchmarkOpMstore(bench *testing.B) {
bench.ResetTimer() bench.ResetTimer()
for i := 0; i < bench.N; i++ { for i := 0; i < bench.N; i++ {
stack.pushN(value, memStart) stack.pushN(value, memStart)
opMstore(&pc, env, nil, mem, stack) opMstore(&pc, evmInterpreter, nil, mem, stack)
} }
} }
...@@ -45,7 +45,30 @@ type Config struct { ...@@ -45,7 +45,30 @@ type Config struct {
// passed environment to query external sources for state information. // passed environment to query external sources for state information.
// The Interpreter will run the byte code VM based on the passed // The Interpreter will run the byte code VM based on the passed
// configuration. // configuration.
type Interpreter struct { type Interpreter interface {
// Run loops and evaluates the contract's code with the given input data and returns
// the return byte-slice and an error if one occurred.
Run(contract *Contract, input []byte) ([]byte, error)
// CanRun tells if the contract, passed as an argument, can be
// run by the current interpreter. This is meant so that the
// caller can do something like:
//
// ```golang
// for _, interpreter := range interpreters {
// if interpreter.CanRun(contract.code) {
// interpreter.Run(contract.code, input)
// }
// }
// ```
CanRun([]byte) bool
// IsReadOnly reports if the interpreter is in read only mode.
IsReadOnly() bool
// SetReadOnly sets (or unsets) read only mode in the interpreter.
SetReadOnly(bool)
}
// EVMInterpreter represents an EVM interpreter
type EVMInterpreter struct {
evm *EVM evm *EVM
cfg Config cfg Config
gasTable params.GasTable gasTable params.GasTable
...@@ -55,8 +78,8 @@ type Interpreter struct { ...@@ -55,8 +78,8 @@ type Interpreter struct {
returnData []byte // Last CALL's return data for subsequent reuse returnData []byte // Last CALL's return data for subsequent reuse
} }
// NewInterpreter returns a new instance of the Interpreter. // NewEVMInterpreter returns a new instance of the Interpreter.
func NewInterpreter(evm *EVM, cfg Config) *Interpreter { func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
// We use the STOP instruction whether to see // We use the STOP instruction whether to see
// the jump table was initialised. If it was not // the jump table was initialised. If it was not
// we'll set the default jump table. // we'll set the default jump table.
...@@ -73,14 +96,14 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter { ...@@ -73,14 +96,14 @@ func NewInterpreter(evm *EVM, cfg Config) *Interpreter {
} }
} }
return &Interpreter{ return &EVMInterpreter{
evm: evm, evm: evm,
cfg: cfg, cfg: cfg,
gasTable: evm.ChainConfig().GasTable(evm.BlockNumber), gasTable: evm.ChainConfig().GasTable(evm.BlockNumber),
} }
} }
func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack *Stack) error { func (in *EVMInterpreter) enforceRestrictions(op OpCode, operation operation, stack *Stack) error {
if in.evm.chainRules.IsByzantium { if in.evm.chainRules.IsByzantium {
if in.readOnly { if in.readOnly {
// If the interpreter is operating in readonly mode, make sure no // If the interpreter is operating in readonly mode, make sure no
...@@ -102,7 +125,7 @@ func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack ...@@ -102,7 +125,7 @@ func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack
// It's important to note that any errors returned by the interpreter should be // It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for // considered a revert-and-consume-all-gas operation except for
// errExecutionReverted which means revert-and-keep-gas-left. // errExecutionReverted which means revert-and-keep-gas-left.
func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) { func (in *EVMInterpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
if in.intPool == nil { if in.intPool == nil {
in.intPool = poolOfIntPools.get() in.intPool = poolOfIntPools.get()
defer func() { defer func() {
...@@ -209,7 +232,7 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er ...@@ -209,7 +232,7 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
} }
// execute the operation // execute the operation
res, err := operation.execute(&pc, in.evm, contract, mem, stack) res, err := operation.execute(&pc, in, contract, mem, stack)
// verifyPool is a build flag. Pool verification makes sure the integrity // verifyPool is a build flag. Pool verification makes sure the integrity
// of the integer pool by comparing values to a default value. // of the integer pool by comparing values to a default value.
if verifyPool { if verifyPool {
...@@ -234,3 +257,19 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er ...@@ -234,3 +257,19 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er
} }
return nil, nil return nil, nil
} }
// CanRun tells if the contract, passed as an argument, can be
// run by the current interpreter.
func (in *EVMInterpreter) CanRun(code []byte) bool {
return true
}
// IsReadOnly reports if the interpreter is in read only mode.
func (in *EVMInterpreter) IsReadOnly() bool {
return in.readOnly
}
// SetReadOnly sets (or unsets) read only mode in the interpreter.
func (in *EVMInterpreter) SetReadOnly(ro bool) {
in.readOnly = ro
}
...@@ -24,7 +24,7 @@ import ( ...@@ -24,7 +24,7 @@ import (
) )
type ( type (
executionFunc func(pc *uint64, env *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) executionFunc func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error)
gasFunc func(params.GasTable, *EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 gasFunc func(params.GasTable, *EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64
stackValidationFunc func(*Stack) error stackValidationFunc func(*Stack) error
memorySizeFunc func(*Stack) *big.Int memorySizeFunc func(*Stack) *big.Int
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment