core/vm: fix metering

Node is now able to synchronize with the
testnet when passing geth the option:
`--vm.ewasm="metering:true"`.
This commit is contained in:
Guillaume Ballet 2018-10-21 18:54:52 +02:00
parent 8fb0c4b53d
commit 972931c10b
6 changed files with 98 additions and 38 deletions

View file

@ -111,10 +111,8 @@ func runCmd(ctx *cli.Context) error {
}
evm := coreVM.NewEVM(permissiveContext, statedb, &params.ChainConfig{}, coreVM.Config{})
evm.StateDB.SetCode(contractAddr, code)
output, leftOver, err := evm.Call(coreVM.AccountRef(callerAddr), contractAddr, input, gas, big.NewInt(0))
if err != nil {

View file

@ -31,3 +31,6 @@ const u128Len = 16
// Max recursion depth for contracts
const maxCallDepth = 1024
// Address of the sentinel (metering) contract
const sentinelContractAddress = "0x000000000000000000000000000000000000000a"

View file

@ -393,6 +393,15 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
// EVM. The contract is a scoped environment for this execution context
// only.
contract := NewContract(caller, AccountRef(address), value, gas)
for _, interpreter := range evm.interpreters {
if interpreter.CanRun(codeAndHash.code) {
var err error
codeAndHash.code, err = interpreter.PreContractCreation(codeAndHash.code, contract)
if err != nil {
return nil, address, gas, nil
}
}
}
contract.SetCodeOptionalHash(&address, codeAndHash)
if evm.vmConfig.NoRecursion && evm.depth > 0 {
@ -407,9 +416,11 @@ 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 */
for _, interpreter := range evm.interpreters {
if interpreter.CanRun(contract.Code) {
ret = interpreter.PostContractCreation(ret)
if err == nil {
for _, interpreter := range evm.interpreters {
if interpreter.CanRun(contract.Code) {
ret, err = interpreter.PostContractCreation(ret)
}
}
}

View file

@ -62,8 +62,9 @@ type InterpreterEWASM struct {
metering bool
meteringContract *Contract
meteringVM *exec.VM
meteringContract *Contract
meteringModule *wasm.Module
meteringStartIndex int64
}
// NewEWASMInterpreter creates a new wagon-based ewasm interpreter. It
@ -72,29 +73,32 @@ type InterpreterEWASM struct {
func NewEWASMInterpreter(evm *EVM, cfg Config) Interpreter {
metering := cfg.EWASMInterpreter["metering"] == "true"
var meteringVM *exec.VM
var meteringContract *Contract
inter := &InterpreterEWASM{
StateDB: evm.StateDB,
evm: evm,
gasTable: evm.chainConfig.GasTable(evm.BlockNumber),
metering: metering,
}
if metering {
meteringContractAddress := common.HexToAddress("0x000000000000000000000000000000000000000a")
meteringContractAddress := common.HexToAddress(sentinelContractAddress)
meteringCode := evm.StateDB.GetCode(meteringContractAddress)
module, err := wasm.ReadModule(bytes.NewReader(meteringCode), nil)
var err error
inter.meteringModule, err = wasm.ReadModule(bytes.NewReader(meteringCode), WrappedModuleResolver(inter))
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))
// TODO when the metering contract abides by that rule, check that it
// only exports "main" and "memory".
inter.meteringStartIndex = int64(inter.meteringModule.Export.Entries["main"].Index)
mainSig := inter.meteringModule.FunctionIndexSpace[inter.meteringStartIndex].Sig
if len(mainSig.ParamTypes) != 0 || len(mainSig.ReturnTypes) != 0 {
panic(fmt.Sprintf("Invalid main function for the metering contract: index=%d sig=%v", inter.meteringStartIndex, mainSig))
}
}
return &InterpreterEWASM{
StateDB: evm.StateDB,
evm: evm,
gasTable: evm.chainConfig.GasTable(evm.BlockNumber),
meteringVM: meteringVM,
meteringContract: meteringContract,
metering: metering,
}
return inter
}
// Run loops and evaluates the contract's code with the given input data and returns
@ -176,13 +180,35 @@ func (in *InterpreterEWASM) CanRun(file []byte) bool {
return true
}
// PreContractCreation meters the contract's its init code before it
// is run.
func (in *InterpreterEWASM) PreContractCreation(code []byte, contract *Contract) ([]byte, error) {
savedContract := in.contract
in.contract = contract
defer func() {
in.contract = savedContract
}()
if in.metering {
metered, _, err := sentinel(in, code)
if len(metered) < 5 || err != nil {
return nil, fmt.Errorf("Error metering the init contract code, err=%v", err)
}
return metered, nil
}
return code, nil
}
// PostContractCreation meters the contract once its init code has
// been run.
func (in *InterpreterEWASM) PostContractCreation(code []byte) []byte {
func (in *InterpreterEWASM) PostContractCreation(code []byte) ([]byte, error) {
if in.metering {
// hera currently seems to ignore the error code
metered, _ := sentinel(in, code)
return metered
metered, _, err := sentinel(in, code)
if len(metered) < 5 || err != nil {
return nil, fmt.Errorf("Error metering the generated contract code, err=%v", err)
}
return metered, nil
}
return code
return code, nil
}

View file

@ -691,24 +691,34 @@ 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) {
func sentinel(in *InterpreterEWASM, input []byte) ([]byte, uint64, error) {
savedContract := in.contract
savedVM := in.vm
defer func() {
in.contract = savedContract
in.vm = savedVM
}()
meteringContractAddress := common.HexToAddress("0x000000000000000000000000000000000000000a")
meteringContractAddress := common.HexToAddress(sentinelContractAddress)
meteringCode := in.StateDB.GetCode(meteringContractAddress)
in.contract = NewContract(in.contract, AccountRef(meteringContractAddress), &big.Int{}, 0)
in.contract = in.meteringContract
in.contract = NewContract(in.contract, AccountRef(meteringContractAddress), &big.Int{}, in.contract.Gas)
in.contract.SetCallCode(&meteringContractAddress, crypto.Keccak256Hash(meteringCode), meteringCode)
in.vm = in.meteringVM
vm, err := exec.NewVM(in.meteringModule)
vm.RecoverPanic = true
in.vm = vm
if err != nil {
panic(fmt.Sprintf("Error allocating metering VM: %v", err))
}
in.contract.Input = input
meteredCode, err := in.vm.ExecCode(in.meteringStartIndex)
if meteredCode == nil {
meteredCode = in.returnData
}
var asBytes []byte
if err == nil {
asBytes = meteredCode.([]byte)
}
return asBytes, err
return asBytes, savedContract.Gas - in.contract.Gas, err
}
func create(p *exec.Process, in *InterpreterEWASM, valueOffset uint32, codeOffset uint32, length uint32, resultOffset uint32) int32 {
@ -740,8 +750,10 @@ func create(p *exec.Process, in *InterpreterEWASM, valueOffset uint32, codeOffse
/* Meter the contract code if metering is enabled */
if in.metering {
/* It seems that hera doesn't handle errors */
input, _ = sentinel(in, input)
input, _, _ = sentinel(in, input)
if len(input) < 5 {
return ErrEEICallFailure
}
}
_, addr, gasLeft, _ := in.evm.Create(in.contract, input, gas, big.NewInt(0).SetBytes(value))

View file

@ -66,10 +66,14 @@ type Interpreter interface {
// }
// ```
CanRun([]byte) bool
// Hook to be called on the init code of a contract to be created.
// This let the interpreter pre-process the init-code before it is
// executed.
PreContractCreation([]byte, *Contract) ([]byte, error)
// 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
PostContractCreation([]byte) ([]byte, error)
}
// EVMInterpreter represents an EVM interpreter
@ -276,9 +280,15 @@ func (in *EVMInterpreter) CanRun(code []byte) bool {
return true
}
// PreContractCreation doesn't need to do anything in the case
// of EVM1 bytecode.
func (in *EVMInterpreter) PreContractCreation(code []byte, contract *Contract) ([]byte, error) {
return code, nil
}
// 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
func (in *EVMInterpreter) PostContractCreation(code []byte) ([]byte, error) {
return code, nil
}