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 := coreVM.NewEVM(permissiveContext, statedb, &params.ChainConfig{}, coreVM.Config{})
evm.StateDB.SetCode(contractAddr, code) evm.StateDB.SetCode(contractAddr, code)
output, leftOver, err := evm.Call(coreVM.AccountRef(callerAddr), contractAddr, input, gas, big.NewInt(0)) output, leftOver, err := evm.Call(coreVM.AccountRef(callerAddr), contractAddr, input, gas, big.NewInt(0))
if err != nil { if err != nil {

View file

@ -31,3 +31,6 @@ const u128Len = 16
// Max recursion depth for contracts // Max recursion depth for contracts
const maxCallDepth = 1024 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 // EVM. The contract is a scoped environment for this execution context
// only. // only.
contract := NewContract(caller, AccountRef(address), value, gas) 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) contract.SetCodeOptionalHash(&address, codeAndHash)
if evm.vmConfig.NoRecursion && evm.depth > 0 { 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) ret, err := run(evm, contract, nil, false)
/* The new contract needs to be metered after it has executed the constructor */ /* The new contract needs to be metered after it has executed the constructor */
if err == nil {
for _, interpreter := range evm.interpreters { for _, interpreter := range evm.interpreters {
if interpreter.CanRun(contract.Code) { if interpreter.CanRun(contract.Code) {
ret = interpreter.PostContractCreation(ret) ret, err = interpreter.PostContractCreation(ret)
}
} }
} }

View file

@ -63,7 +63,8 @@ type InterpreterEWASM struct {
metering bool metering bool
meteringContract *Contract meteringContract *Contract
meteringVM *exec.VM meteringModule *wasm.Module
meteringStartIndex int64
} }
// NewEWASMInterpreter creates a new wagon-based ewasm interpreter. It // NewEWASMInterpreter creates a new wagon-based ewasm interpreter. It
@ -72,29 +73,32 @@ type InterpreterEWASM struct {
func NewEWASMInterpreter(evm *EVM, cfg Config) Interpreter { func NewEWASMInterpreter(evm *EVM, cfg Config) Interpreter {
metering := cfg.EWASMInterpreter["metering"] == "true" metering := cfg.EWASMInterpreter["metering"] == "true"
var meteringVM *exec.VM inter := &InterpreterEWASM{
var meteringContract *Contract
if metering {
meteringContractAddress := common.HexToAddress("0x000000000000000000000000000000000000000a")
meteringCode := evm.StateDB.GetCode(meteringContractAddress)
module, err := wasm.ReadModule(bytes.NewReader(meteringCode), 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, StateDB: evm.StateDB,
evm: evm, evm: evm,
gasTable: evm.chainConfig.GasTable(evm.BlockNumber), gasTable: evm.chainConfig.GasTable(evm.BlockNumber),
meteringVM: meteringVM,
meteringContract: meteringContract,
metering: metering, metering: metering,
} }
if metering {
meteringContractAddress := common.HexToAddress(sentinelContractAddress)
meteringCode := evm.StateDB.GetCode(meteringContractAddress)
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))
}
// 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 inter
} }
// 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
@ -176,13 +180,35 @@ func (in *InterpreterEWASM) CanRun(file []byte) bool {
return true 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 // PostContractCreation meters the contract once its init code has
// been run. // been run.
func (in *InterpreterEWASM) PostContractCreation(code []byte) []byte { func (in *InterpreterEWASM) PostContractCreation(code []byte) ([]byte, error) {
if in.metering { if in.metering {
// hera currently seems to ignore the error code metered, _, err := sentinel(in, code)
metered, _ := sentinel(in, code) if len(metered) < 5 || err != nil {
return metered return nil, fmt.Errorf("Error metering the generated contract code, err=%v", err)
} }
return code return metered, nil
}
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)) 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 savedContract := in.contract
savedVM := in.vm savedVM := in.vm
defer func() { defer func() {
in.contract = savedContract in.contract = savedContract
in.vm = savedVM in.vm = savedVM
}() }()
meteringContractAddress := common.HexToAddress("0x000000000000000000000000000000000000000a") meteringContractAddress := common.HexToAddress(sentinelContractAddress)
meteringCode := in.StateDB.GetCode(meteringContractAddress) meteringCode := in.StateDB.GetCode(meteringContractAddress)
in.contract = NewContract(in.contract, AccountRef(meteringContractAddress), &big.Int{}, 0) in.contract = NewContract(in.contract, AccountRef(meteringContractAddress), &big.Int{}, in.contract.Gas)
in.contract = in.meteringContract
in.contract.SetCallCode(&meteringContractAddress, crypto.Keccak256Hash(meteringCode), meteringCode) 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 var asBytes []byte
if err == nil { if err == nil {
asBytes = meteredCode.([]byte) 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 { 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 */ /* Meter the contract code if metering is enabled */
if in.metering { 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)) _, 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 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 // Hook to be called once a newly created contract's init code
// has been called and is going to be stored. This let the // has been called and is going to be stored. This let the
// interpreter post-process the bytecode before it is persisted. // interpreter post-process the bytecode before it is persisted.
PostContractCreation([]byte) []byte PostContractCreation([]byte) ([]byte, error)
} }
// EVMInterpreter represents an EVM interpreter // EVMInterpreter represents an EVM interpreter
@ -276,9 +280,15 @@ func (in *EVMInterpreter) CanRun(code []byte) bool {
return true 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 // PostContractCreation doesn't need to do anything in the case
// of EVM1 bytecode, but it could turn out useful when extending // of EVM1 bytecode, but it could turn out useful when extending
// the tracer. // the tracer.
func (in *EVMInterpreter) PostContractCreation(code []byte) []byte { func (in *EVMInterpreter) PostContractCreation(code []byte) ([]byte, error) {
return code return code, nil
} }