mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
core/vm: meter contracts
This is achieved by passing `--vm.ewasm="metering:true"` to geth
This commit is contained in:
parent
78d789cd28
commit
be16aabe6f
5 changed files with 99 additions and 9 deletions
|
|
@ -406,6 +406,9 @@ 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 */
|
||||
ret = evm.interpreter.PostContractCreation(ret)
|
||||
|
||||
// check whether the max code size has been exceeded
|
||||
maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
|
||||
// if the contract creation ran successfully and no errors were returned
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@ import (
|
|||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/go-interpreter/wagon/wasm"
|
||||
|
||||
|
|
@ -58,13 +61,44 @@ type InterpreterEWASM struct {
|
|||
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 {
|
||||
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
|
||||
|
|
@ -145,3 +179,14 @@ func (in *InterpreterEWASM) CanRun(file []byte) bool {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -690,10 +690,27 @@ 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) {
|
||||
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 {
|
||||
in.gasAccounting(GasCostCreate)
|
||||
savedVM := in.vm
|
||||
savedContract := in.contract
|
||||
defer func() {
|
||||
in.vm = savedVM
|
||||
in.contract = savedContract
|
||||
}()
|
||||
in.terminationType = TerminateInvalid
|
||||
|
||||
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
|
||||
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
|
||||
in.contract = savedContract
|
||||
_, addr, gasLeft, _ := in.evm.Create(in.contract, input, gas, big.NewInt(0).SetBytes(value))
|
||||
|
||||
switch in.terminationType {
|
||||
case TerminateFinish:
|
||||
in.contract.Gas += gasLeft
|
||||
savedContract.Gas += gasLeft
|
||||
p.WriteAt(addr.Bytes(), int64(resultOffset))
|
||||
return EEICallSuccess
|
||||
case TerminateRevert:
|
||||
in.contract.Gas += gas
|
||||
savedContract.Gas += gas
|
||||
return ErrEEICallRevert
|
||||
default:
|
||||
in.contract.Gas += gasLeft
|
||||
savedContract.Gas += gasLeft
|
||||
return ErrEEICallFailure
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ type Config struct {
|
|||
JumpTable [256]operation
|
||||
|
||||
// Type of the EWASM interpreter
|
||||
EWASMInterpreter string
|
||||
EWASMInterpreter map[string]string
|
||||
// Type of the EVM interpreter
|
||||
EVMInterpreter string
|
||||
}
|
||||
|
|
@ -66,6 +66,10 @@ type Interpreter interface {
|
|||
// }
|
||||
// ```
|
||||
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
|
||||
|
|
@ -271,3 +275,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
func (in *EVMInterpreter) CanRun(code []byte) bool {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
|
|
@ -148,10 +149,20 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
|||
}
|
||||
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 (
|
||||
vmConfig = vm.Config{
|
||||
EnablePreimageRecording: config.EnablePreimageRecording,
|
||||
EWASMInterpreter: config.EWASMInterpreter,
|
||||
EWASMInterpreter: ewasmOptions,
|
||||
EVMInterpreter: config.EVMInterpreter,
|
||||
}
|
||||
cacheConfig = &core.CacheConfig{Disabled: config.NoPruning, TrieNodeLimit: config.TrieCache, TrieTimeLimit: config.TrieTimeout}
|
||||
|
|
|
|||
Loading…
Reference in a new issue