mirror of https://github.com/ethereum/go-ethereum
core, core/vm: implemented a generic environment (#3348)
Environment is now a struct (not an interface). This reduces a lot of tech-debt throughout the codebase where a virtual machine environment had to be implemented in order to test or run it. The new environment is suitable to be used en the json tests, core consensus and light client.pull/3412/head
parent
7f79d249a6
commit
3fc7c97827
@ -0,0 +1,73 @@ |
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/core/vm" |
||||
) |
||||
|
||||
// BlockFetcher retrieves headers by their hash
|
||||
type HeaderFetcher interface { |
||||
// GetHeader returns the hash corresponding to their hash
|
||||
GetHeader(common.Hash, uint64) *types.Header |
||||
} |
||||
|
||||
// NewEVMContext creates a new context for use in the EVM.
|
||||
func NewEVMContext(msg Message, header *types.Header, chain HeaderFetcher) vm.Context { |
||||
return vm.Context{ |
||||
CanTransfer: CanTransfer, |
||||
Transfer: Transfer, |
||||
GetHash: GetHashFn(header, chain), |
||||
|
||||
Origin: msg.From(), |
||||
Coinbase: header.Coinbase, |
||||
BlockNumber: new(big.Int).Set(header.Number), |
||||
Time: new(big.Int).Set(header.Time), |
||||
Difficulty: new(big.Int).Set(header.Difficulty), |
||||
GasLimit: new(big.Int).Set(header.GasLimit), |
||||
GasPrice: new(big.Int).Set(msg.GasPrice()), |
||||
} |
||||
} |
||||
|
||||
// GetHashFn returns a GetHashFunc which retrieves header hashes by number
|
||||
func GetHashFn(ref *types.Header, chain HeaderFetcher) func(n uint64) common.Hash { |
||||
return func(n uint64) common.Hash { |
||||
for header := chain.GetHeader(ref.ParentHash, ref.Number.Uint64()-1); header != nil; header = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) { |
||||
if header.Number.Uint64() == n { |
||||
return header.Hash() |
||||
} |
||||
} |
||||
|
||||
return common.Hash{} |
||||
} |
||||
} |
||||
|
||||
// CanTransfer checks wether there are enough funds in the address' account to make a transfer.
|
||||
// This does not take the necessary gas in to account to make the transfer valid.
|
||||
func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool { |
||||
return db.GetBalance(addr).Cmp(amount) >= 0 |
||||
} |
||||
|
||||
// Transfer subtracts amount from sender and adds amount to recipient using the given Db
|
||||
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { |
||||
db.SubBalance(sender, amount) |
||||
db.AddBalance(recipient, amount) |
||||
} |
@ -1,217 +0,0 @@ |
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core/vm" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
) |
||||
|
||||
// Call executes within the given contract
|
||||
func Call(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) { |
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
if env.Depth() > int(params.CallCreateDepth.Int64()) { |
||||
caller.ReturnGas(gas, gasPrice) |
||||
|
||||
return nil, vm.DepthError |
||||
} |
||||
if !env.CanTransfer(caller.Address(), value) { |
||||
caller.ReturnGas(gas, gasPrice) |
||||
|
||||
return nil, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", value, env.Db().GetBalance(caller.Address())) |
||||
} |
||||
|
||||
snapshotPreTransfer := env.SnapshotDatabase() |
||||
var ( |
||||
from = env.Db().GetAccount(caller.Address()) |
||||
to vm.Account |
||||
) |
||||
if !env.Db().Exist(addr) { |
||||
if vm.Precompiled[addr.Str()] == nil && env.ChainConfig().IsEIP158(env.BlockNumber()) && value.BitLen() == 0 { |
||||
caller.ReturnGas(gas, gasPrice) |
||||
return nil, nil |
||||
} |
||||
|
||||
to = env.Db().CreateAccount(addr) |
||||
} else { |
||||
to = env.Db().GetAccount(addr) |
||||
} |
||||
env.Transfer(from, to, value) |
||||
|
||||
// initialise a new contract and set the code that is to be used by the
|
||||
// EVM. The contract is a scoped environment for this execution context
|
||||
// only.
|
||||
contract := vm.NewContract(caller, to, value, gas, gasPrice) |
||||
contract.SetCallCode(&addr, env.Db().GetCodeHash(addr), env.Db().GetCode(addr)) |
||||
defer contract.Finalise() |
||||
|
||||
ret, err = env.Vm().Run(contract, input) |
||||
// When an error was returned by the EVM or when setting the creation code
|
||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||
// when we're in homestead this also counts for code storage gas errors.
|
||||
if err != nil { |
||||
contract.UseGas(contract.Gas) |
||||
|
||||
env.RevertToSnapshot(snapshotPreTransfer) |
||||
} |
||||
return ret, err |
||||
} |
||||
|
||||
// CallCode executes the given address' code as the given contract address
|
||||
func CallCode(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice, value *big.Int) (ret []byte, err error) { |
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
if env.Depth() > int(params.CallCreateDepth.Int64()) { |
||||
caller.ReturnGas(gas, gasPrice) |
||||
|
||||
return nil, vm.DepthError |
||||
} |
||||
if !env.CanTransfer(caller.Address(), value) { |
||||
caller.ReturnGas(gas, gasPrice) |
||||
|
||||
return nil, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", value, env.Db().GetBalance(caller.Address())) |
||||
} |
||||
|
||||
var ( |
||||
snapshotPreTransfer = env.SnapshotDatabase() |
||||
to = env.Db().GetAccount(caller.Address()) |
||||
) |
||||
// initialise a new contract and set the code that is to be used by the
|
||||
// EVM. The contract is a scoped environment for this execution context
|
||||
// only.
|
||||
contract := vm.NewContract(caller, to, value, gas, gasPrice) |
||||
contract.SetCallCode(&addr, env.Db().GetCodeHash(addr), env.Db().GetCode(addr)) |
||||
defer contract.Finalise() |
||||
|
||||
ret, err = env.Vm().Run(contract, input) |
||||
if err != nil { |
||||
contract.UseGas(contract.Gas) |
||||
|
||||
env.RevertToSnapshot(snapshotPreTransfer) |
||||
} |
||||
|
||||
return ret, err |
||||
} |
||||
|
||||
// Create creates a new contract with the given code
|
||||
func Create(env vm.Environment, caller vm.ContractRef, code []byte, gas, gasPrice, value *big.Int) (ret []byte, address common.Address, err error) { |
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
if env.Depth() > int(params.CallCreateDepth.Int64()) { |
||||
caller.ReturnGas(gas, gasPrice) |
||||
|
||||
return nil, common.Address{}, vm.DepthError |
||||
} |
||||
if !env.CanTransfer(caller.Address(), value) { |
||||
caller.ReturnGas(gas, gasPrice) |
||||
|
||||
return nil, common.Address{}, ValueTransferErr("insufficient funds to transfer value. Req %v, has %v", value, env.Db().GetBalance(caller.Address())) |
||||
} |
||||
|
||||
// Create a new account on the state
|
||||
nonce := env.Db().GetNonce(caller.Address()) |
||||
env.Db().SetNonce(caller.Address(), nonce+1) |
||||
|
||||
snapshotPreTransfer := env.SnapshotDatabase() |
||||
var ( |
||||
addr = crypto.CreateAddress(caller.Address(), nonce) |
||||
from = env.Db().GetAccount(caller.Address()) |
||||
to = env.Db().CreateAccount(addr) |
||||
) |
||||
if env.ChainConfig().IsEIP158(env.BlockNumber()) { |
||||
env.Db().SetNonce(addr, 1) |
||||
} |
||||
env.Transfer(from, to, value) |
||||
|
||||
// initialise a new contract and set the code that is to be used by the
|
||||
// EVM. The contract is a scoped environment for this execution context
|
||||
// only.
|
||||
contract := vm.NewContract(caller, to, value, gas, gasPrice) |
||||
contract.SetCallCode(&addr, crypto.Keccak256Hash(code), code) |
||||
defer contract.Finalise() |
||||
|
||||
ret, err = env.Vm().Run(contract, nil) |
||||
// check whether the max code size has been exceeded
|
||||
maxCodeSizeExceeded := len(ret) > params.MaxCodeSize |
||||
// if the contract creation ran successfully and no errors were returned
|
||||
// calculate the gas required to store the code. If the code could not
|
||||
// be stored due to not enough gas set an error and let it be handled
|
||||
// by the error checking condition below.
|
||||
if err == nil && !maxCodeSizeExceeded { |
||||
dataGas := big.NewInt(int64(len(ret))) |
||||
dataGas.Mul(dataGas, params.CreateDataGas) |
||||
if contract.UseGas(dataGas) { |
||||
env.Db().SetCode(addr, ret) |
||||
} else { |
||||
err = vm.CodeStoreOutOfGasError |
||||
} |
||||
} |
||||
|
||||
// When an error was returned by the EVM or when setting the creation code
|
||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||
// when we're in homestead this also counts for code storage gas errors.
|
||||
if maxCodeSizeExceeded || |
||||
(err != nil && (env.ChainConfig().IsHomestead(env.BlockNumber()) || err != vm.CodeStoreOutOfGasError)) { |
||||
contract.UseGas(contract.Gas) |
||||
env.RevertToSnapshot(snapshotPreTransfer) |
||||
|
||||
// Nothing should be returned when an error is thrown.
|
||||
return nil, addr, err |
||||
} |
||||
|
||||
return ret, addr, err |
||||
} |
||||
|
||||
// DelegateCall is equivalent to CallCode except that sender and value propagates from parent scope to child scope
|
||||
func DelegateCall(env vm.Environment, caller vm.ContractRef, addr common.Address, input []byte, gas, gasPrice *big.Int) (ret []byte, err error) { |
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
if env.Depth() > int(params.CallCreateDepth.Int64()) { |
||||
caller.ReturnGas(gas, gasPrice) |
||||
return nil, vm.DepthError |
||||
} |
||||
|
||||
var ( |
||||
snapshot = env.SnapshotDatabase() |
||||
to = env.Db().GetAccount(caller.Address()) |
||||
) |
||||
|
||||
// Iinitialise a new contract and make initialise the delegate values
|
||||
contract := vm.NewContract(caller, to, caller.Value(), gas, gasPrice).AsDelegate() |
||||
contract.SetCallCode(&addr, env.Db().GetCodeHash(addr), env.Db().GetCode(addr)) |
||||
defer contract.Finalise() |
||||
|
||||
ret, err = env.Vm().Run(contract, input) |
||||
if err != nil { |
||||
contract.UseGas(contract.Gas) |
||||
|
||||
env.RevertToSnapshot(snapshot) |
||||
} |
||||
|
||||
return ret, err |
||||
} |
||||
|
||||
// generic transfer method
|
||||
func Transfer(from, to vm.Account, amount *big.Int) { |
||||
from.SubBalance(amount) |
||||
to.AddBalance(amount) |
||||
} |
@ -0,0 +1,97 @@ |
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
) |
||||
|
||||
// Vm is the basic interface for an implementation of the EVM.
|
||||
type Vm interface { |
||||
// Run should execute the given contract with the input given in in
|
||||
// and return the contract execution return bytes or an error if it
|
||||
// failed.
|
||||
Run(c *Contract, in []byte) ([]byte, error) |
||||
} |
||||
|
||||
// StateDB is an EVM database for full state querying.
|
||||
type StateDB interface { |
||||
GetAccount(common.Address) Account |
||||
CreateAccount(common.Address) Account |
||||
|
||||
SubBalance(common.Address, *big.Int) |
||||
AddBalance(common.Address, *big.Int) |
||||
GetBalance(common.Address) *big.Int |
||||
|
||||
GetNonce(common.Address) uint64 |
||||
SetNonce(common.Address, uint64) |
||||
|
||||
GetCodeHash(common.Address) common.Hash |
||||
GetCode(common.Address) []byte |
||||
SetCode(common.Address, []byte) |
||||
GetCodeSize(common.Address) int |
||||
|
||||
AddRefund(*big.Int) |
||||
GetRefund() *big.Int |
||||
|
||||
GetState(common.Address, common.Hash) common.Hash |
||||
SetState(common.Address, common.Hash, common.Hash) |
||||
|
||||
Suicide(common.Address) bool |
||||
HasSuicided(common.Address) bool |
||||
|
||||
// Exist reports whether the given account exists in state.
|
||||
// Notably this should also return true for suicided accounts.
|
||||
Exist(common.Address) bool |
||||
// Empty returns whether the given account is empty. Empty
|
||||
// is defined according to EIP161 (balance = nonce = code = 0).
|
||||
Empty(common.Address) bool |
||||
|
||||
RevertToSnapshot(int) |
||||
Snapshot() int |
||||
|
||||
AddLog(*Log) |
||||
} |
||||
|
||||
// Account represents a contract or basic ethereum account.
|
||||
type Account interface { |
||||
SubBalance(amount *big.Int) |
||||
AddBalance(amount *big.Int) |
||||
SetBalance(*big.Int) |
||||
SetNonce(uint64) |
||||
Balance() *big.Int |
||||
Address() common.Address |
||||
ReturnGas(*big.Int) |
||||
SetCode(common.Hash, []byte) |
||||
ForEachStorage(cb func(key, value common.Hash) bool) |
||||
Value() *big.Int |
||||
} |
||||
|
||||
// CallContext provides a basic interface for the EVM calling conventions. The EVM Environment
|
||||
// depends on this context being implemented for doing subcalls and initialising new EVM contracts.
|
||||
type CallContext interface { |
||||
// Call another contract
|
||||
Call(env *Environment, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) |
||||
// Take another's contract code and execute within our own context
|
||||
CallCode(env *Environment, me ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) |
||||
// Same as CallCode except sender and value is propagated from parent to child scope
|
||||
DelegateCall(env *Environment, me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) |
||||
// Create a new contract
|
||||
Create(env *Environment, me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) |
||||
} |
@ -0,0 +1,68 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
) |
||||
|
||||
func NoopCanTransfer(db StateDB, from common.Address, balance *big.Int) bool { |
||||
return true |
||||
} |
||||
func NoopTransfer(db StateDB, from, to common.Address, amount *big.Int) {} |
||||
|
||||
type NoopEVMCallContext struct{} |
||||
|
||||
func (NoopEVMCallContext) Call(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { |
||||
return nil, nil |
||||
} |
||||
func (NoopEVMCallContext) CallCode(caller ContractRef, addr common.Address, data []byte, gas, value *big.Int) ([]byte, error) { |
||||
return nil, nil |
||||
} |
||||
func (NoopEVMCallContext) Create(caller ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) { |
||||
return nil, common.Address{}, nil |
||||
} |
||||
func (NoopEVMCallContext) DelegateCall(me ContractRef, addr common.Address, data []byte, gas *big.Int) ([]byte, error) { |
||||
return nil, nil |
||||
} |
||||
|
||||
type NoopStateDB struct{} |
||||
|
||||
func (NoopStateDB) GetAccount(common.Address) Account { return nil } |
||||
func (NoopStateDB) CreateAccount(common.Address) Account { return nil } |
||||
func (NoopStateDB) SubBalance(common.Address, *big.Int) {} |
||||
func (NoopStateDB) AddBalance(common.Address, *big.Int) {} |
||||
func (NoopStateDB) GetBalance(common.Address) *big.Int { return nil } |
||||
func (NoopStateDB) GetNonce(common.Address) uint64 { return 0 } |
||||
func (NoopStateDB) SetNonce(common.Address, uint64) {} |
||||
func (NoopStateDB) GetCodeHash(common.Address) common.Hash { return common.Hash{} } |
||||
func (NoopStateDB) GetCode(common.Address) []byte { return nil } |
||||
func (NoopStateDB) SetCode(common.Address, []byte) {} |
||||
func (NoopStateDB) GetCodeSize(common.Address) int { return 0 } |
||||
func (NoopStateDB) AddRefund(*big.Int) {} |
||||
func (NoopStateDB) GetRefund() *big.Int { return nil } |
||||
func (NoopStateDB) GetState(common.Address, common.Hash) common.Hash { return common.Hash{} } |
||||
func (NoopStateDB) SetState(common.Address, common.Hash, common.Hash) {} |
||||
func (NoopStateDB) Suicide(common.Address) bool { return false } |
||||
func (NoopStateDB) HasSuicided(common.Address) bool { return false } |
||||
func (NoopStateDB) Exist(common.Address) bool { return false } |
||||
func (NoopStateDB) Empty(common.Address) bool { return false } |
||||
func (NoopStateDB) RevertToSnapshot(int) {} |
||||
func (NoopStateDB) Snapshot() int { return 0 } |
||||
func (NoopStateDB) AddLog(*Log) {} |
@ -1,32 +0,0 @@ |
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm |
||||
|
||||
import ( |
||||
"math/big" |
||||
|
||||
"github.com/ethereum/go-ethereum/params" |
||||
) |
||||
|
||||
type ruleSet struct { |
||||
hs *big.Int |
||||
} |
||||
|
||||
func (r ruleSet) IsHomestead(n *big.Int) bool { return n.Cmp(r.hs) >= 0 } |
||||
func (r ruleSet) GasTable(*big.Int) params.GasTable { |
||||
return params.GasTableHomestead |
||||
} |
Loading…
Reference in new issue