From be16aabe6f8f7b51aec12be272a8cabca327aff9 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Mon, 8 Oct 2018 14:02:23 +0200 Subject: [PATCH] core/vm: meter contracts This is achieved by passing `--vm.ewasm="metering:true"` to geth --- core/vm/evm.go | 3 +++ core/vm/ewasm.go | 47 +++++++++++++++++++++++++++++++++++++++++- core/vm/ewasm_eei.go | 32 ++++++++++++++++++++++------ core/vm/interpreter.go | 13 +++++++++++- eth/backend.go | 13 +++++++++++- 5 files changed, 99 insertions(+), 9 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index b52f2455be..f7132ec444 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -406,6 +406,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, ret, err := run(evm, contract, nil, false) + /* The new contract needs to be metered after it has executed the constructor */ + ret = evm.interpreter.PostContractCreation(ret) + // check whether the max code size has been exceeded maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize // if the contract creation ran successfully and no errors were returned diff --git a/core/vm/ewasm.go b/core/vm/ewasm.go index f58835ce7b..9f4b5c7626 100644 --- a/core/vm/ewasm.go +++ b/core/vm/ewasm.go @@ -24,7 +24,10 @@ import ( "encoding/binary" "errors" "fmt" + "math/big" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/go-interpreter/wagon/wasm" @@ -58,13 +61,44 @@ type InterpreterEWASM struct { terminationType terminationType staticMode bool + + metering bool + + meteringContract *Contract + meteringVM *exec.VM } // NewEWASMInterpreter creates a new wagon-based ewasm interpreter. It // currently takes a *vm.EVM pointer as a proxy to the client's internal // state; this will be fixed in subsequent updates. func NewEWASMInterpreter(evm *EVM, cfg Config) Interpreter { - return &InterpreterEWASM{StateDB: evm.StateDB, evm: evm, gasTable: evm.chainConfig.GasTable(evm.BlockNumber)} + metering := cfg.EWASMInterpreter["metering"] == "true" + + var meteringVM *exec.VM + var meteringContract *Contract + if metering { + meteringContractAddress := common.HexToAddress("0x000000000000000000000000000000000000000a") + meteringContract = NewContract(nil, AccountRef(meteringContractAddress), &big.Int{}, 0) + meteringCode := evm.StateDB.GetCode(meteringContractAddress) + meteringContract.SetCallCode(&meteringContractAddress, crypto.Keccak256Hash(meteringCode), meteringCode) + + module, err := wasm.ReadModule(bytes.NewReader(evm.StateDB.GetCode(meteringContractAddress)), nil) + if err != nil { + panic(fmt.Sprintf("Error loading the metering contract: %v", err)) + } + meteringVM, err = exec.NewVM(module) + if err != nil { + panic(fmt.Sprintf("Error allocating metering contract: %v", err)) + } + } + return &InterpreterEWASM{ + StateDB: evm.StateDB, + evm: evm, + gasTable: evm.chainConfig.GasTable(evm.BlockNumber), + meteringVM: meteringVM, + meteringContract: meteringContract, + metering: metering, + } } // Run loops and evaluates the contract's code with the given input data and returns @@ -145,3 +179,14 @@ func (in *InterpreterEWASM) CanRun(file []byte) bool { return true } + +// PostContractCreation meters the contract once its init code has +// been run. +func (in *InterpreterEWASM) PostContractCreation(code []byte) []byte { + if in.metering { + // hera currently seems to ignore the error code + metered, _ := sentinel(in, code) + return metered + } + return code +} diff --git a/core/vm/ewasm_eei.go b/core/vm/ewasm_eei.go index d4fa379836..c0672cc28e 100644 --- a/core/vm/ewasm_eei.go +++ b/core/vm/ewasm_eei.go @@ -690,10 +690,27 @@ func getBlockCoinbase(p *exec.Process, in *InterpreterEWASM, resultOffset int32) p.WriteAt(in.evm.Coinbase.Bytes(), int64(resultOffset)) } +func sentinel(in *InterpreterEWASM, input []byte) ([]byte, error) { + savedContract := in.contract + savedVM := in.vm + defer func() { + in.contract = savedContract + in.vm = savedVM + }() + in.contract = in.meteringContract + in.vm = in.meteringVM + meteredCode, err := in.meteringVM.ExecCode(0) + return meteredCode.([]byte), err +} + func create(p *exec.Process, in *InterpreterEWASM, valueOffset uint32, codeOffset uint32, length uint32, resultOffset uint32) int32 { in.gasAccounting(GasCostCreate) savedVM := in.vm savedContract := in.contract + defer func() { + in.vm = savedVM + in.contract = savedContract + }() in.terminationType = TerminateInvalid if int(codeOffset)+int(length) > len(in.vm.Memory()) { @@ -713,21 +730,24 @@ func create(p *exec.Process, in *InterpreterEWASM, valueOffset uint32, codeOffse gas := in.contract.Gas - in.contract.Gas/64 in.gasAccounting(gas) - _, addr, gasLeft, _ := in.evm.Create(in.contract, input, gas, big.NewInt(0).SetBytes(value)) + /* Meter the contract code if metering is enabled */ + if in.metering { + /* It seems that hera doesn't handle errors */ + input, _ = sentinel(in, input) + } - in.vm = savedVM - in.contract = savedContract + _, addr, gasLeft, _ := in.evm.Create(in.contract, input, gas, big.NewInt(0).SetBytes(value)) switch in.terminationType { case TerminateFinish: - in.contract.Gas += gasLeft + savedContract.Gas += gasLeft p.WriteAt(addr.Bytes(), int64(resultOffset)) return EEICallSuccess case TerminateRevert: - in.contract.Gas += gas + savedContract.Gas += gas return ErrEEICallRevert default: - in.contract.Gas += gasLeft + savedContract.Gas += gasLeft return ErrEEICallFailure } } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 8e934f60e8..66dc33d2bc 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -41,7 +41,7 @@ type Config struct { JumpTable [256]operation // Type of the EWASM interpreter - EWASMInterpreter string + EWASMInterpreter map[string]string // Type of the EVM interpreter EVMInterpreter string } @@ -66,6 +66,10 @@ type Interpreter interface { // } // ``` CanRun([]byte) bool + // Hook to be called once a newly created contract's init code + // has been called and is going to be stored. This let the + // interpreter post-process the bytecode before it is persisted. + PostContractCreation([]byte) []byte } // EVMInterpreter represents an EVM interpreter @@ -271,3 +275,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( func (in *EVMInterpreter) CanRun(code []byte) bool { return true } + +// PostContractCreation doesn't need to do anything in the case +// of EVM1 bytecode, but it could turn out useful when extending +// the tracer. +func (in *EVMInterpreter) PostContractCreation(code []byte) []byte { + return code +} diff --git a/eth/backend.go b/eth/backend.go index e172db00b5..4bcf805e0f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -22,6 +22,7 @@ import ( "fmt" "math/big" "runtime" + "strings" "sync" "sync/atomic" @@ -148,10 +149,20 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { } rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) } + + ewasmOptions := make(map[string]string) + for _, option := range strings.Split(config.EWASMInterpreter, ",") { + opt := strings.Split(option, ":") + if len(opt) != 2 || len(opt[0]) == 0 { + panic(fmt.Sprintf("Invalid ewasm option: expected a format of name:value, got: %s", option)) + } + ewasmOptions[opt[0]] = opt[1] + } + var ( vmConfig = vm.Config{ EnablePreimageRecording: config.EnablePreimageRecording, - EWASMInterpreter: config.EWASMInterpreter, + EWASMInterpreter: ewasmOptions, EVMInterpreter: config.EVMInterpreter, } cacheConfig = &core.CacheConfig{Disabled: config.NoPruning, TrieNodeLimit: config.TrieCache, TrieTimeLimit: config.TrieTimeout}