mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
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:
parent
8fb0c4b53d
commit
972931c10b
6 changed files with 98 additions and 38 deletions
|
|
@ -111,10 +111,8 @@ func runCmd(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
evm := coreVM.NewEVM(permissiveContext, statedb, ¶ms.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 {
|
||||
|
|
|
|||
|
|
@ -31,3 +31,6 @@ const u128Len = 16
|
|||
|
||||
// Max recursion depth for contracts
|
||||
const maxCallDepth = 1024
|
||||
|
||||
// Address of the sentinel (metering) contract
|
||||
const sentinelContractAddress = "0x000000000000000000000000000000000000000a"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue