core/vm: meter contracts

This is achieved by passing `--vm.ewasm="metering:true"` to geth
This commit is contained in:
Guillaume Ballet 2018-10-08 14:02:23 +02:00
parent 78d789cd28
commit be16aabe6f
5 changed files with 99 additions and 9 deletions

View file

@ -406,6 +406,9 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
ret, err := run(evm, contract, nil, false) 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 // check whether the max code size has been exceeded
maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
// if the contract creation ran successfully and no errors were returned // if the contract creation ran successfully and no errors were returned

View file

@ -24,7 +24,10 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/go-interpreter/wagon/wasm" "github.com/go-interpreter/wagon/wasm"
@ -58,13 +61,44 @@ type InterpreterEWASM struct {
terminationType terminationType terminationType terminationType
staticMode bool staticMode bool
metering bool
meteringContract *Contract
meteringVM *exec.VM
} }
// NewEWASMInterpreter creates a new wagon-based ewasm interpreter. It // NewEWASMInterpreter creates a new wagon-based ewasm interpreter. It
// currently takes a *vm.EVM pointer as a proxy to the client's internal // currently takes a *vm.EVM pointer as a proxy to the client's internal
// state; this will be fixed in subsequent updates. // state; this will be fixed in subsequent updates.
func NewEWASMInterpreter(evm *EVM, cfg Config) Interpreter { 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 // 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 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
}

View file

@ -690,10 +690,27 @@ func getBlockCoinbase(p *exec.Process, in *InterpreterEWASM, resultOffset int32)
p.WriteAt(in.evm.Coinbase.Bytes(), int64(resultOffset)) 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 { func create(p *exec.Process, in *InterpreterEWASM, valueOffset uint32, codeOffset uint32, length uint32, resultOffset uint32) int32 {
in.gasAccounting(GasCostCreate) in.gasAccounting(GasCostCreate)
savedVM := in.vm savedVM := in.vm
savedContract := in.contract savedContract := in.contract
defer func() {
in.vm = savedVM
in.contract = savedContract
}()
in.terminationType = TerminateInvalid in.terminationType = TerminateInvalid
if int(codeOffset)+int(length) > len(in.vm.Memory()) { 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 gas := in.contract.Gas - in.contract.Gas/64
in.gasAccounting(gas) 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 _, addr, gasLeft, _ := in.evm.Create(in.contract, input, gas, big.NewInt(0).SetBytes(value))
in.contract = savedContract
switch in.terminationType { switch in.terminationType {
case TerminateFinish: case TerminateFinish:
in.contract.Gas += gasLeft savedContract.Gas += gasLeft
p.WriteAt(addr.Bytes(), int64(resultOffset)) p.WriteAt(addr.Bytes(), int64(resultOffset))
return EEICallSuccess return EEICallSuccess
case TerminateRevert: case TerminateRevert:
in.contract.Gas += gas savedContract.Gas += gas
return ErrEEICallRevert return ErrEEICallRevert
default: default:
in.contract.Gas += gasLeft savedContract.Gas += gasLeft
return ErrEEICallFailure return ErrEEICallFailure
} }
} }

View file

@ -41,7 +41,7 @@ type Config struct {
JumpTable [256]operation JumpTable [256]operation
// Type of the EWASM interpreter // Type of the EWASM interpreter
EWASMInterpreter string EWASMInterpreter map[string]string
// Type of the EVM interpreter // Type of the EVM interpreter
EVMInterpreter string EVMInterpreter string
} }
@ -66,6 +66,10 @@ type Interpreter interface {
// } // }
// ``` // ```
CanRun([]byte) bool 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 // 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 { func (in *EVMInterpreter) CanRun(code []byte) bool {
return true 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
}

View file

@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"math/big" "math/big"
"runtime" "runtime"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -148,10 +149,20 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
} }
rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) 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 ( var (
vmConfig = vm.Config{ vmConfig = vm.Config{
EnablePreimageRecording: config.EnablePreimageRecording, EnablePreimageRecording: config.EnablePreimageRecording,
EWASMInterpreter: config.EWASMInterpreter, EWASMInterpreter: ewasmOptions,
EVMInterpreter: config.EVMInterpreter, EVMInterpreter: config.EVMInterpreter,
} }
cacheConfig = &core.CacheConfig{Disabled: config.NoPruning, TrieNodeLimit: config.TrieCache, TrieTimeLimit: config.TrieTimeout} cacheConfig = &core.CacheConfig{Disabled: config.NoPruning, TrieNodeLimit: config.TrieCache, TrieTimeLimit: config.TrieTimeout}