go-ethereum/core/vm/ewasm.go
Guillaume Ballet b2482f5a4a core/vm: fix metering contract support
Still some issues for when the creation tx fails
2018-10-20 17:35:06 +02:00

188 lines
5.1 KiB
Go

// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// This file lists the EEI functions, so that they can be bound to any
// ewasm-compatible module, as well as the types of these functions
package vm
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/go-interpreter/wagon/wasm"
"github.com/go-interpreter/wagon/exec"
)
type terminationType int
// List of termination reasons
const (
TerminateFinish = iota
TerminateRevert
TerminateSuicide
TerminateInvalid
)
// InterpreterEWASM implements the Interpreter interface for ewasm.
type InterpreterEWASM struct {
vm *exec.VM
evm *EVM
StateDB StateDB
gasTable params.GasTable
contract *Contract
returnData []byte
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 {
metering := cfg.EWASMInterpreter["metering"] == "true"
var meteringVM *exec.VM
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,
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
// the return byte-slice and an error if one occurred.
func (in *InterpreterEWASM) Run(contract *Contract, input []byte, ro bool) ([]byte, error) {
// Increment the call depth which is restricted to 1024
in.evm.depth++
defer func() { in.evm.depth-- }()
in.contract = contract
in.contract.Input = input
initialGas := contract.Gas
module, err := wasm.ReadModule(bytes.NewReader(contract.Code), WrappedModuleResolver(in))
if err != nil {
return nil, fmt.Errorf("Error decoding module at address %s: %v", contract.Address().Hex(), err)
}
// The module should not have any start function
if module.Start != nil {
return nil, fmt.Errorf("A contract should not have a start function: found #%d", module.Start.Index)
}
vm, err := exec.NewVM(module)
if err != nil {
return nil, fmt.Errorf("could not create the vm: %v", err)
}
vm.RecoverPanic = true
in.vm = vm
// Look for the "main" function and execute it after checking it
// has the right kind of signature.
for name, entry := range module.Export.Entries {
if name == "main" && entry.Kind == wasm.ExternalFunction {
// Check input and output types
sig := module.FunctionIndexSpace[entry.Index].Sig
if len(sig.ParamTypes) == 0 && len(sig.ReturnTypes) == 0 {
_, err = vm.ExecCode(int64(entry.Index))
if err != nil {
in.terminationType = TerminateInvalid
}
if in.StateDB.HasSuicided(contract.Address()) {
if initialGas-contract.Gas-params.TxGas < 2*params.SuicideRefundGas {
in.StateDB.AddRefund((initialGas - contract.Gas - params.TxGas) / 2)
} else {
in.StateDB.AddRefund(params.SuicideRefundGas)
}
err = nil
}
return in.returnData, err
}
// Found a main but it doesn't have the right signature - fail
break
}
}
return nil, errors.New("Could not find a suitable 'main' function in that contract")
}
// CanRun checks the binary for a WASM header and accepts the binary blob
// if it matches.
func (in *InterpreterEWASM) CanRun(file []byte) bool {
// Check the header
if len(file) <= 8 || string(file[:4]) != "\000asm" {
return false
}
// Check the version
ver := binary.LittleEndian.Uint32(file[4:])
if ver != 1 {
return false
}
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
}