core/vm: less allocations for various call variants (#21222)

* core/vm/runtime/tests: add more benchmarks

* core/vm: initial work on improving alloc count for calls to precompiles

name                                  old time/op    new time/op    delta
SimpleLoop/identity-precompile-10M-6     117ms ±75%      43ms ± 1%  -63.09%  (p=0.008 n=5+5)
SimpleLoop/loop-10M-6                   79.6ms ± 4%    70.5ms ± 1%  -11.42%  (p=0.008 n=5+5)

name                                  old alloc/op   new alloc/op   delta
SimpleLoop/identity-precompile-10M-6    24.4MB ± 0%     4.9MB ± 0%  -79.94%  (p=0.008 n=5+5)
SimpleLoop/loop-10M-6                   13.2kB ± 0%    13.2kB ± 0%     ~     (p=0.357 n=5+5)

name                                  old allocs/op  new allocs/op  delta
SimpleLoop/identity-precompile-10M-6      382k ± 0%      153k ± 0%  -59.99%  (p=0.000 n=5+4)
SimpleLoop/loop-10M-6                     40.0 ± 0%      40.0 ± 0%     ~     (all equal)

* core/vm: don't allocate big.int for touch

name                                  old time/op    new time/op    delta
SimpleLoop/identity-precompile-10M-6    43.3ms ± 1%    42.4ms ± 7%     ~     (p=0.151 n=5+5)
SimpleLoop/loop-10M-6                   70.5ms ± 1%    76.7ms ± 1%   +8.67%  (p=0.008 n=5+5)

name                                  old alloc/op   new alloc/op   delta
SimpleLoop/identity-precompile-10M-6    4.90MB ± 0%    2.46MB ± 0%  -49.83%  (p=0.008 n=5+5)
SimpleLoop/loop-10M-6                   13.2kB ± 0%    13.2kB ± 1%     ~     (p=0.571 n=5+5)

name                                  old allocs/op  new allocs/op  delta
SimpleLoop/identity-precompile-10M-6      153k ± 0%       76k ± 0%  -49.98%  (p=0.029 n=4+4)
SimpleLoop/loop-10M-6                     40.0 ± 0%      40.0 ± 0%     ~     (all equal)

* core/vm: reduce allocs in staticcall

name                                  old time/op    new time/op    delta
SimpleLoop/identity-precompile-10M-6    42.4ms ± 7%    37.5ms ± 6%  -11.68%  (p=0.008 n=5+5)
SimpleLoop/loop-10M-6                   76.7ms ± 1%    69.1ms ± 1%   -9.82%  (p=0.008 n=5+5)

name                                  old alloc/op   new alloc/op   delta
SimpleLoop/identity-precompile-10M-6    2.46MB ± 0%    0.02MB ± 0%  -99.35%  (p=0.008 n=5+5)
SimpleLoop/loop-10M-6                   13.2kB ± 1%    13.2kB ± 0%     ~     (p=0.143 n=5+5)

name                                  old allocs/op  new allocs/op  delta
SimpleLoop/identity-precompile-10M-6     76.4k ± 0%      0.1k ± 0%     ~     (p=0.079 n=4+5)
SimpleLoop/loop-10M-6                     40.0 ± 0%      40.0 ± 0%     ~     (all equal)

* trie: better use of hasher keccakState

* core/state/statedb: reduce allocations in getDeletedStateObject

* core/vm: reduce allocations in all call derivates

* core/vm: reduce allocations in call variants

- Make returnstack `uint32`
- Use a `sync.Pool` of `stack`s

* core/vm: fix tests

* core/vm: goimports

* core/vm: tracer fix + staticcall gas fix

* core/vm: add back snapshot to staticcall

* core/vm: review concerns + make returnstack pooled + enable returndata in traces

* core/vm: fix some test tracer method signatures

* core/vm: run gencodec, minor comment polish

Co-authored-by: Péter Szilágyi <peterke@gmail.com>
This commit is contained in:
Martin Holst Swende 2020-07-16 14:06:19 +02:00 committed by GitHub
parent 240d1851db
commit 295693759e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 447 additions and 188 deletions

View File

@ -38,6 +38,10 @@ var (
Name: "trace.nostack", Name: "trace.nostack",
Usage: "Disable stack output in traces", Usage: "Disable stack output in traces",
} }
TraceDisableReturnDataFlag = cli.BoolFlag{
Name: "trace.noreturndata",
Usage: "Disable return data output in traces",
}
OutputAllocFlag = cli.StringFlag{ OutputAllocFlag = cli.StringFlag{
Name: "output.alloc", Name: "output.alloc",
Usage: "Determines where to put the `alloc` of the post-state.\n" + Usage: "Determines where to put the `alloc` of the post-state.\n" +

View File

@ -85,6 +85,7 @@ func Main(ctx *cli.Context) error {
logConfig := &vm.LogConfig{ logConfig := &vm.LogConfig{
DisableStack: ctx.Bool(TraceDisableStackFlag.Name), DisableStack: ctx.Bool(TraceDisableStackFlag.Name),
DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name), DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name),
DisableReturnData: ctx.Bool(TraceDisableReturnDataFlag.Name),
Debug: true, Debug: true,
} }
var prevFile *os.File var prevFile *os.File

View File

@ -121,6 +121,14 @@ var (
Name: "nostack", Name: "nostack",
Usage: "disable stack output", Usage: "disable stack output",
} }
DisableStorageFlag = cli.BoolFlag{
Name: "nostorage",
Usage: "disable storage output",
}
DisableReturnDataFlag = cli.BoolFlag{
Name: "noreturndata",
Usage: "disable return data output",
}
EVMInterpreterFlag = cli.StringFlag{ EVMInterpreterFlag = cli.StringFlag{
Name: "vm.evm", Name: "vm.evm",
Usage: "External EVM configuration (default = built-in interpreter)", Usage: "External EVM configuration (default = built-in interpreter)",
@ -137,6 +145,7 @@ var stateTransitionCommand = cli.Command{
t8ntool.TraceFlag, t8ntool.TraceFlag,
t8ntool.TraceDisableMemoryFlag, t8ntool.TraceDisableMemoryFlag,
t8ntool.TraceDisableStackFlag, t8ntool.TraceDisableStackFlag,
t8ntool.TraceDisableReturnDataFlag,
t8ntool.OutputAllocFlag, t8ntool.OutputAllocFlag,
t8ntool.OutputResultFlag, t8ntool.OutputResultFlag,
t8ntool.InputAllocFlag, t8ntool.InputAllocFlag,
@ -172,6 +181,8 @@ func init() {
ReceiverFlag, ReceiverFlag,
DisableMemoryFlag, DisableMemoryFlag,
DisableStackFlag, DisableStackFlag,
DisableStorageFlag,
DisableReturnDataFlag,
EVMInterpreterFlag, EVMInterpreterFlag,
} }
app.Commands = []cli.Command{ app.Commands = []cli.Command{

View File

@ -110,6 +110,8 @@ func runCmd(ctx *cli.Context) error {
logconfig := &vm.LogConfig{ logconfig := &vm.LogConfig{
DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
DisableStack: ctx.GlobalBool(DisableStackFlag.Name), DisableStack: ctx.GlobalBool(DisableStackFlag.Name),
DisableStorage: ctx.GlobalBool(DisableStorageFlag.Name),
DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name),
Debug: ctx.GlobalBool(DebugFlag.Name), Debug: ctx.GlobalBool(DebugFlag.Name),
} }

View File

@ -61,6 +61,8 @@ func stateTestCmd(ctx *cli.Context) error {
config := &vm.LogConfig{ config := &vm.LogConfig{
DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
DisableStack: ctx.GlobalBool(DisableStackFlag.Name), DisableStack: ctx.GlobalBool(DisableStackFlag.Name),
DisableStorage: ctx.GlobalBool(DisableStorageFlag.Name),
DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name),
} }
var ( var (
tracer vm.Tracer tracer vm.Tracer

View File

@ -505,7 +505,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
} }
// If no live objects are available, attempt to use snapshots // If no live objects are available, attempt to use snapshots
var ( var (
data Account data *Account
err error err error
) )
if s.snap != nil { if s.snap != nil {
@ -517,11 +517,15 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
if acc == nil { if acc == nil {
return nil return nil
} }
data.Nonce, data.Balance, data.CodeHash = acc.Nonce, acc.Balance, acc.CodeHash data = &Account{
Nonce: acc.Nonce,
Balance: acc.Balance,
CodeHash: acc.CodeHash,
Root: common.BytesToHash(acc.Root),
}
if len(data.CodeHash) == 0 { if len(data.CodeHash) == 0 {
data.CodeHash = emptyCodeHash data.CodeHash = emptyCodeHash
} }
data.Root = common.BytesToHash(acc.Root)
if data.Root == (common.Hash{}) { if data.Root == (common.Hash{}) {
data.Root = emptyRoot data.Root = emptyRoot
} }
@ -540,13 +544,14 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
if len(enc) == 0 { if len(enc) == 0 {
return nil return nil
} }
if err := rlp.DecodeBytes(enc, &data); err != nil { data = new(Account)
if err := rlp.DecodeBytes(enc, data); err != nil {
log.Error("Failed to decode state object", "addr", addr, "err", err) log.Error("Failed to decode state object", "addr", addr, "err", err)
return nil return nil
} }
} }
// Insert into the live set // Insert into the live set
obj := newObject(s, addr, data) obj := newObject(s, addr, *data)
s.setStateObject(obj) s.setStateObject(obj)
return obj return obj
} }

View File

@ -102,12 +102,18 @@ var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{
} }
// RunPrecompiledContract runs and evaluates the output of a precompiled contract. // RunPrecompiledContract runs and evaluates the output of a precompiled contract.
func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) { // It returns
gas := p.RequiredGas(input) // - the returned bytes,
if contract.UseGas(gas) { // - the _remaining_ gas,
return p.Run(input) // - any error that occurred
func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
gasCost := p.RequiredGas(input)
if suppliedGas < gasCost {
return nil, 0, ErrOutOfGas
} }
return nil, ErrOutOfGas suppliedGas -= gasCost
output, err := p.Run(input)
return output, suppliedGas, err
} }
// ECRECOVER implemented as a native contract. // ECRECOVER implemented as a native contract.
@ -197,6 +203,7 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) {
type bigModExp struct{} type bigModExp struct{}
var ( var (
big0 = big.NewInt(0)
big1 = big.NewInt(1) big1 = big.NewInt(1)
big4 = big.NewInt(4) big4 = big.NewInt(4)
big8 = big.NewInt(8) big8 = big.NewInt(8)

View File

@ -21,7 +21,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big"
"testing" "testing"
"time" "time"
@ -72,10 +71,9 @@ var blake2FMalformedInputTests = []precompiledFailureTest{
func testPrecompiled(addr string, test precompiledTest, t *testing.T) { func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
p := allPrecompiles[common.HexToAddress(addr)] p := allPrecompiles[common.HexToAddress(addr)]
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
contract := NewContract(AccountRef(common.HexToAddress("1337")), gas := p.RequiredGas(in)
nil, new(big.Int), p.RequiredGas(in)) t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) { if res, _, err := RunPrecompiledContract(p, in, gas); err != nil {
if res, err := RunPrecompiledContract(p, in, contract); err != nil {
t.Error(err) t.Error(err)
} else if common.Bytes2Hex(res) != test.Expected { } else if common.Bytes2Hex(res) != test.Expected {
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
@ -91,10 +89,10 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
p := allPrecompiles[common.HexToAddress(addr)] p := allPrecompiles[common.HexToAddress(addr)]
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
contract := NewContract(AccountRef(common.HexToAddress("1337")), gas := p.RequiredGas(in) - 1
nil, new(big.Int), p.RequiredGas(in)-1)
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) { t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
_, err := RunPrecompiledContract(p, in, contract) _, _, err := RunPrecompiledContract(p, in, gas)
if err.Error() != "out of gas" { if err.Error() != "out of gas" {
t.Errorf("Expected error [out of gas], got [%v]", err) t.Errorf("Expected error [out of gas], got [%v]", err)
} }
@ -109,11 +107,9 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing.T) { func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing.T) {
p := allPrecompiles[common.HexToAddress(addr)] p := allPrecompiles[common.HexToAddress(addr)]
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
contract := NewContract(AccountRef(common.HexToAddress("31337")), gas := p.RequiredGas(in)
nil, new(big.Int), p.RequiredGas(in))
t.Run(test.Name, func(t *testing.T) { t.Run(test.Name, func(t *testing.T) {
_, err := RunPrecompiledContract(p, in, contract) _, _, err := RunPrecompiledContract(p, in, gas)
if err.Error() != test.ExpectedError { if err.Error() != test.ExpectedError {
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
} }
@ -132,8 +128,6 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
p := allPrecompiles[common.HexToAddress(addr)] p := allPrecompiles[common.HexToAddress(addr)]
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
reqGas := p.RequiredGas(in) reqGas := p.RequiredGas(in)
contract := NewContract(AccountRef(common.HexToAddress("1337")),
nil, new(big.Int), reqGas)
var ( var (
res []byte res []byte
@ -141,14 +135,13 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
data = make([]byte, len(in)) data = make([]byte, len(in))
) )
bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(bench *testing.B) { bench.Run(fmt.Sprintf("%s-Gas=%d", test.Name, reqGas), func(bench *testing.B) {
bench.ReportAllocs() bench.ReportAllocs()
start := time.Now().Nanosecond() start := time.Now().Nanosecond()
bench.ResetTimer() bench.ResetTimer()
for i := 0; i < bench.N; i++ { for i := 0; i < bench.N; i++ {
contract.Gas = reqGas
copy(data, in) copy(data, in)
res, err = RunPrecompiledContract(p, data, contract) res, _, err = RunPrecompiledContract(p, data, reqGas)
} }
bench.StopTimer() bench.StopTimer()
elapsed := float64(time.Now().Nanosecond() - start) elapsed := float64(time.Now().Nanosecond() - start)

View File

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
) )
// emptyCodeHash is used by create to ensure deployment is disallowed to already // emptyCodeHash is used by create to ensure deployment is disallowed to already
@ -41,23 +42,24 @@ type (
GetHashFunc func(uint64) common.Hash GetHashFunc func(uint64) common.Hash
) )
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
var precompiles map[common.Address]PrecompiledContract
switch {
case evm.chainRules.IsYoloV1:
precompiles = PrecompiledContractsYoloV1
case evm.chainRules.IsIstanbul:
precompiles = PrecompiledContractsIstanbul
case evm.chainRules.IsByzantium:
precompiles = PrecompiledContractsByzantium
default:
precompiles = PrecompiledContractsHomestead
}
p, ok := precompiles[addr]
return p, ok
}
// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter. // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) { func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) {
if contract.CodeAddr != nil {
precompiles := PrecompiledContractsHomestead
if evm.chainRules.IsByzantium {
precompiles = PrecompiledContractsByzantium
}
if evm.chainRules.IsIstanbul {
precompiles = PrecompiledContractsIstanbul
}
if evm.chainRules.IsYoloV1 {
precompiles = PrecompiledContractsYoloV1
}
if p := precompiles[*contract.CodeAddr]; p != nil {
return RunPrecompiledContract(p, input, contract)
}
}
for _, interpreter := range evm.interpreters { for _, interpreter := range evm.interpreters {
if interpreter.CanRun(contract.Code) { if interpreter.CanRun(contract.Code) {
if evm.interpreter != interpreter { if evm.interpreter != interpreter {
@ -199,22 +201,14 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
return nil, gas, ErrDepth return nil, gas, ErrDepth
} }
// Fail if we're trying to transfer more than the available balance // Fail if we're trying to transfer more than the available balance
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, ErrInsufficientBalance return nil, gas, ErrInsufficientBalance
} }
var ( snapshot := evm.StateDB.Snapshot()
to = AccountRef(addr) p, isPrecompile := evm.precompile(addr)
snapshot = evm.StateDB.Snapshot()
)
if !evm.StateDB.Exist(addr) { if !evm.StateDB.Exist(addr) {
precompiles := PrecompiledContractsHomestead if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
if evm.chainRules.IsByzantium {
precompiles = PrecompiledContractsByzantium
}
if evm.chainRules.IsIstanbul {
precompiles = PrecompiledContractsIstanbul
}
if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer // Calling a non existing account, don't do anything, but ping the tracer
if evm.vmConfig.Debug && evm.depth == 0 { if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
@ -224,35 +218,47 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
} }
evm.StateDB.CreateAccount(addr) evm.StateDB.CreateAccount(addr)
} }
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value) evm.Transfer(evm.StateDB, caller.Address(), addr, 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 := NewContract(caller, to, value, gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
// Even if the account has no code, we need to continue because it might be a precompile
start := time.Now()
// Capture the tracer start/end events in debug mode // Capture the tracer start/end events in debug mode
if evm.vmConfig.Debug && evm.depth == 0 { if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
defer func() { // Lazy evaluation of the parameters evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) }(gas, time.Now())
}()
} }
ret, err = run(evm, contract, input, false)
if isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} else {
// 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.
code := evm.StateDB.GetCode(addr)
if len(code) == 0 {
ret, err = nil, nil // gas is unchanged
} else {
addrCopy := addr
// If the account has no code, we can abort here
// The depth-check is already done, and precompiles handled above
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
ret, err = run(evm, contract, input, false)
gas = contract.Gas
}
}
// When an error was returned by the EVM or when setting the creation code // 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 // 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. // when we're in homestead this also counts for code storage gas errors.
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
contract.UseGas(contract.Gas) gas = 0
} }
// TODO: consider clearing up unused snapshots:
//} else {
// evm.StateDB.DiscardSnapshot(snapshot)
} }
return ret, contract.Gas, err return ret, gas, err
} }
// CallCode executes the contract associated with the addr with the given input // CallCode executes the contract associated with the addr with the given input
@ -277,23 +283,27 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, ErrInsufficientBalance return nil, gas, ErrInsufficientBalance
} }
var ( var snapshot = evm.StateDB.Snapshot()
snapshot = evm.StateDB.Snapshot()
to = AccountRef(caller.Address()) // It is allowed to call precompiles, even via delegatecall
) if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} else {
addrCopy := addr
// Initialise a new contract and set the code that is to be used by the EVM. // 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. // The contract is a scoped environment for this execution context only.
contract := NewContract(caller, to, value, gas) contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = run(evm, contract, input, false) ret, err = run(evm, contract, input, false)
gas = contract.Gas
}
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
contract.UseGas(contract.Gas) gas = 0
} }
} }
return ret, contract.Gas, err return ret, gas, err
} }
// DelegateCall executes the contract associated with the addr with the given input // DelegateCall executes the contract associated with the addr with the given input
@ -309,22 +319,26 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
} }
var ( var snapshot = evm.StateDB.Snapshot()
snapshot = evm.StateDB.Snapshot()
to = AccountRef(caller.Address())
)
// Initialise a new contract and make initialise the delegate values
contract := NewContract(caller, to, nil, gas).AsDelegate()
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} else {
addrCopy := addr
// Initialise a new contract and make initialise the delegate values
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = run(evm, contract, input, false) ret, err = run(evm, contract, input, false)
gas = contract.Gas
}
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
contract.UseGas(contract.Gas) gas = 0
} }
} }
return ret, contract.Gas, err return ret, gas, err
} }
// StaticCall executes the contract associated with the addr with the given input // StaticCall executes the contract associated with the addr with the given input
@ -339,32 +353,43 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas, ErrDepth
} }
var ( // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped.
to = AccountRef(addr) // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced
snapshot = evm.StateDB.Snapshot() // after all empty accounts were deleted, so this is not required. However, if we omit this,
) // then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json.
// Initialise a new contract and set the code that is to be used by the EVM. // We could change this, but for now it's left for legacy reasons
// The contract is a scoped environment for this execution context only. var snapshot = evm.StateDB.Snapshot()
contract := NewContract(caller, to, new(big.Int), gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
// We do an AddBalance of zero here, just in order to trigger a touch. // We do an AddBalance of zero here, just in order to trigger a touch.
// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
// but is the correct thing to do and matters on other networks, in tests, and potential // but is the correct thing to do and matters on other networks, in tests, and potential
// future scenarios // future scenarios
evm.StateDB.AddBalance(addr, big.NewInt(0)) evm.StateDB.AddBalance(addr, big0)
if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(p, input, gas)
} else {
// At this point, we use a copy of address. If we don't, the go compiler will
// leak the 'contract' to the outer scope, and make allocation for 'contract'
// even if the actual execution ends on RunPrecompiled above.
addrCopy := addr
// 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 := NewContract(caller, AccountRef(addrCopy), new(big.Int), gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
// When an error was returned by the EVM or when setting the creation code // 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 // 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. // when we're in Homestead this also counts for code storage gas errors.
ret, err = run(evm, contract, input, true) ret, err = run(evm, contract, input, true)
gas = contract.Gas
}
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
contract.UseGas(contract.Gas) gas = 0
} }
} }
return ret, contract.Gas, err return ret, gas, err
} }
type codeAndHash struct { type codeAndHash struct {
@ -466,9 +491,9 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
// //
// The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:] // The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:]
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
codeAndHash := &codeAndHash{code: code} codeAndHash := &codeAndHash{code: code}
contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes()) contractAddr = crypto.CreateAddress2(caller.Address(), common.Hash(salt.Bytes32()), codeAndHash.Hash().Bytes())
return evm.create(caller, codeAndHash, gas, endowment, contractAddr) return evm.create(caller, codeAndHash, gas, endowment, contractAddr)
} }

View File

@ -24,6 +24,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
MemorySize int `json:"memSize"` MemorySize int `json:"memSize"`
Stack []*math.HexOrDecimal256 `json:"stack"` Stack []*math.HexOrDecimal256 `json:"stack"`
ReturnStack []math.HexOrDecimal64 `json:"returnStack"` ReturnStack []math.HexOrDecimal64 `json:"returnStack"`
ReturnData []byte `json:"returnData"`
Storage map[common.Hash]common.Hash `json:"-"` Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"` Depth int `json:"depth"`
RefundCounter uint64 `json:"refund"` RefundCounter uint64 `json:"refund"`
@ -50,6 +51,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
enc.ReturnStack[k] = math.HexOrDecimal64(v) enc.ReturnStack[k] = math.HexOrDecimal64(v)
} }
} }
enc.ReturnData = s.ReturnData
enc.Storage = s.Storage enc.Storage = s.Storage
enc.Depth = s.Depth enc.Depth = s.Depth
enc.RefundCounter = s.RefundCounter enc.RefundCounter = s.RefundCounter
@ -70,6 +72,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
MemorySize *int `json:"memSize"` MemorySize *int `json:"memSize"`
Stack []*math.HexOrDecimal256 `json:"stack"` Stack []*math.HexOrDecimal256 `json:"stack"`
ReturnStack []math.HexOrDecimal64 `json:"returnStack"` ReturnStack []math.HexOrDecimal64 `json:"returnStack"`
ReturnData []byte `json:"returnData"`
Storage map[common.Hash]common.Hash `json:"-"` Storage map[common.Hash]common.Hash `json:"-"`
Depth *int `json:"depth"` Depth *int `json:"depth"`
RefundCounter *uint64 `json:"refund"` RefundCounter *uint64 `json:"refund"`
@ -104,11 +107,14 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
} }
} }
if dec.ReturnStack != nil { if dec.ReturnStack != nil {
s.ReturnStack = make([]uint64, len(dec.ReturnStack)) s.ReturnStack = make([]uint32, len(dec.ReturnStack))
for k, v := range dec.ReturnStack { for k, v := range dec.ReturnStack {
s.ReturnStack[k] = uint64(v) s.ReturnStack[k] = uint32(v)
} }
} }
if dec.ReturnData != nil {
s.ReturnData = dec.ReturnData
}
if dec.Storage != nil { if dec.Storage != nil {
s.Storage = dec.Storage s.Storage = dec.Storage
} }

View File

@ -563,7 +563,7 @@ func opJumpSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([
if !callContext.contract.validJumpSubdest(posU64) { if !callContext.contract.validJumpSubdest(posU64) {
return nil, ErrInvalidJump return nil, ErrInvalidJump
} }
callContext.rstack.push(*pc) callContext.rstack.push(uint32(*pc))
*pc = posU64 + 1 *pc = posU64 + 1
return nil, nil return nil, nil
} }
@ -575,7 +575,7 @@ func opReturnSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx)
// Other than the check that the return stack is not empty, there is no // Other than the check that the return stack is not empty, there is no
// need to validate the pc from 'returns', since we only ever push valid // need to validate the pc from 'returns', since we only ever push valid
//values onto it via jumpsub. //values onto it via jumpsub.
*pc = callContext.rstack.pop() + 1 *pc = uint64(callContext.rstack.pop()) + 1
return nil, nil return nil, nil
} }
@ -608,7 +608,13 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]
stackvalue := size stackvalue := size
callContext.contract.UseGas(gas) callContext.contract.UseGas(gas)
res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, value.ToBig()) //TODO: use uint256.Int instead of converting with toBig()
var bigVal = big0
if !value.IsZero() {
bigVal = value.ToBig()
}
res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, bigVal)
// Push item on the stack based on the returned error. If the ruleset is // Push item on the stack based on the returned error. If the ruleset is
// homestead we must check for CodeStoreOutOfGasError (homestead only // homestead we must check for CodeStoreOutOfGasError (homestead only
// rule) and treat as an error, if the ruleset is frontier we must // rule) and treat as an error, if the ruleset is frontier we must
@ -643,8 +649,13 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([
callContext.contract.UseGas(gas) callContext.contract.UseGas(gas)
// reuse size int for stackvalue // reuse size int for stackvalue
stackvalue := size stackvalue := size
//TODO: use uint256.Int instead of converting with toBig()
bigEndowment := big0
if !endowment.IsZero() {
bigEndowment = endowment.ToBig()
}
res, addr, returnGas, suberr := interpreter.evm.Create2(callContext.contract, input, gas, res, addr, returnGas, suberr := interpreter.evm.Create2(callContext.contract, input, gas,
endowment.ToBig(), salt.ToBig()) bigEndowment, &salt)
// Push item on the stack based on the returned error. // Push item on the stack based on the returned error.
if suberr != nil { if suberr != nil {
stackvalue.Clear() stackvalue.Clear()
@ -672,10 +683,17 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by
// Get the arguments from the memory. // Get the arguments from the memory.
args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
var bigVal = big0
//TODO: use uint256.Int instead of converting with toBig()
// By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls),
// but it would make more sense to extend the usage of uint256.Int
if !value.IsZero() { if !value.IsZero() {
gas += params.CallStipend gas += params.CallStipend
bigVal = value.ToBig()
} }
ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, value.ToBig())
ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, bigVal)
if err != nil { if err != nil {
temp.Clear() temp.Clear()
} else { } else {
@ -702,10 +720,14 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) (
// Get arguments from the memory. // Get arguments from the memory.
args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) args := callContext.memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64()))
//TODO: use uint256.Int instead of converting with toBig()
var bigVal = big0
if !value.IsZero() { if !value.IsZero() {
gas += params.CallStipend gas += params.CallStipend
bigVal = value.ToBig()
} }
ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, value.ToBig())
ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, bigVal)
if err != nil { if err != nil {
temp.Clear() temp.Clear()
} else { } else {

View File

@ -182,13 +182,20 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
logged bool // deferred Tracer should ignore already logged steps logged bool // deferred Tracer should ignore already logged steps
res []byte // result of the opcode execution function res []byte // result of the opcode execution function
) )
// Don't move this deferrred function, it's placed before the capturestate-deferred method,
// so that it get's executed _after_: the capturestate needs the stacks before
// they are returned to the pools
defer func() {
returnStack(stack)
returnRStack(returns)
}()
contract.Input = input contract.Input = input
if in.cfg.Debug { if in.cfg.Debug {
defer func() { defer func() {
if err != nil { if err != nil {
if !logged { if !logged {
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, contract, in.evm.depth, err) in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err)
} else { } else {
in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, contract, in.evm.depth, err) in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, returns, contract, in.evm.depth, err)
} }
@ -272,7 +279,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
} }
if in.cfg.Debug { if in.cfg.Debug {
in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, returns, contract, in.evm.depth, err) in.cfg.Tracer.CaptureState(in.evm, pc, op, gasCopy, cost, mem, stack, returns, in.returnData, contract, in.evm.depth, err)
logged = true logged = true
} }
@ -281,7 +288,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
// if the operation clears the return data (e.g. it has returning data) // if the operation clears the return data (e.g. it has returning data)
// set the last return to the result of the operation. // set the last return to the result of the operation.
if operation.returns { if operation.returns {
in.returnData = res in.returnData = common.CopyBytes(res)
} }
switch { switch {

View File

@ -50,6 +50,7 @@ type LogConfig struct {
DisableMemory bool // disable memory capture DisableMemory bool // disable memory capture
DisableStack bool // disable stack capture DisableStack bool // disable stack capture
DisableStorage bool // disable storage capture DisableStorage bool // disable storage capture
DisableReturnData bool // disable return data capture
Debug bool // print output during capture end Debug bool // print output during capture end
Limit int // maximum length of output, but zero means unlimited Limit int // maximum length of output, but zero means unlimited
} }
@ -66,7 +67,8 @@ type StructLog struct {
Memory []byte `json:"memory"` Memory []byte `json:"memory"`
MemorySize int `json:"memSize"` MemorySize int `json:"memSize"`
Stack []*big.Int `json:"stack"` Stack []*big.Int `json:"stack"`
ReturnStack []uint64 `json:"returnStack"` ReturnStack []uint32 `json:"returnStack"`
ReturnData []byte `json:"returnData"`
Storage map[common.Hash]common.Hash `json:"-"` Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"` Depth int `json:"depth"`
RefundCounter uint64 `json:"refund"` RefundCounter uint64 `json:"refund"`
@ -104,7 +106,7 @@ func (s *StructLog) ErrorString() string {
// if you need to retain them beyond the current call. // if you need to retain them beyond the current call.
type Tracer interface { type Tracer interface {
CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error
CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error
} }
@ -142,7 +144,7 @@ func (l *StructLogger) CaptureStart(from common.Address, to common.Address, crea
// CaptureState logs a new structured log message and pushes it out to the environment // CaptureState logs a new structured log message and pushes it out to the environment
// //
// CaptureState also tracks SLOAD/SSTORE ops to track storage change. // CaptureState also tracks SLOAD/SSTORE ops to track storage change.
func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error {
// check if already accumulated the specified number of logs // check if already accumulated the specified number of logs
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
return errTraceLimitReached return errTraceLimitReached
@ -161,9 +163,9 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
stck[i] = new(big.Int).Set(item.ToBig()) stck[i] = new(big.Int).Set(item.ToBig())
} }
} }
var rstack []uint64 var rstack []uint32
if !l.cfg.DisableStack && rStack != nil { if !l.cfg.DisableStack && rStack != nil {
rstck := make([]uint64, len(rStack.data)) rstck := make([]uint32, len(rStack.data))
copy(rstck, rStack.data) copy(rstck, rStack.data)
} }
// Copy a snapshot of the current storage to a new container // Copy a snapshot of the current storage to a new container
@ -192,8 +194,13 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
} }
storage = l.storage[contract.Address()].Copy() storage = l.storage[contract.Address()].Copy()
} }
var rdata []byte
if !l.cfg.DisableReturnData {
rdata = make([]byte, len(rData))
copy(rdata, rData)
}
// create a new snapshot of the EVM. // create a new snapshot of the EVM.
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rstack, storage, depth, env.StateDB.GetRefund(), err} log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rstack, rdata, storage, depth, env.StateDB.GetRefund(), err}
l.logs = append(l.logs, log) l.logs = append(l.logs, log)
return nil return nil
} }
@ -257,6 +264,10 @@ func WriteTrace(writer io.Writer, logs []StructLog) {
fmt.Fprintf(writer, "%x: %x\n", h, item) fmt.Fprintf(writer, "%x: %x\n", h, item)
} }
} }
if len(log.ReturnData) > 0 {
fmt.Fprintln(writer, "ReturnData:")
fmt.Fprint(writer, hex.Dump(log.ReturnData))
}
fmt.Fprintln(writer) fmt.Fprintln(writer)
} }
} }
@ -308,7 +319,7 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b
return nil return nil
} }
func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error {
fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost) fmt.Fprintf(t.out, "| %4d | %10v | %3d |", pc, op, cost)
if !t.cfg.DisableStack { // format stack if !t.cfg.DisableStack { // format stack

View File

@ -46,7 +46,7 @@ func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create
} }
// CaptureState outputs state information on the logger. // CaptureState outputs state information on the logger.
func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, contract *Contract, depth int, err error) error { func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, rStack *ReturnStack, rData []byte, contract *Contract, depth int, err error) error {
log := StructLog{ log := StructLog{
Pc: pc, Pc: pc,
Op: op, Op: op,
@ -70,6 +70,9 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint
log.Stack = logstack log.Stack = logstack
log.ReturnStack = rStack.data log.ReturnStack = rStack.data
} }
if !l.cfg.DisableReturnData {
log.ReturnData = rData
}
return l.encoder.Encode(log) return l.encoder.Encode(log)
} }

View File

@ -61,7 +61,7 @@ func TestStoreCapture(t *testing.T) {
stack.push(uint256.NewInt().SetUint64(1)) stack.push(uint256.NewInt().SetUint64(1))
stack.push(uint256.NewInt()) stack.push(uint256.NewInt())
var index common.Hash var index common.Hash
logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, rstack, contract, 0, nil) logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, rstack, nil, contract, 0, nil)
if len(logger.storage[contract.Address()]) == 0 { if len(logger.storage[contract.Address()]) == 0 {
t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.storage[contract.Address()])) t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.storage[contract.Address()]))
} }

View File

@ -57,8 +57,15 @@ func setDefaults(cfg *Config) {
DAOForkBlock: new(big.Int), DAOForkBlock: new(big.Int),
DAOForkSupport: false, DAOForkSupport: false,
EIP150Block: new(big.Int), EIP150Block: new(big.Int),
EIP150Hash: common.Hash{},
EIP155Block: new(big.Int), EIP155Block: new(big.Int),
EIP158Block: new(big.Int), EIP158Block: new(big.Int),
ByzantiumBlock: new(big.Int),
ConstantinopleBlock: new(big.Int),
PetersburgBlock: new(big.Int),
IstanbulBlock: new(big.Int),
MuirGlacierBlock: new(big.Int),
YoloV1Block: nil,
} }
} }

View File

@ -321,34 +321,6 @@ func TestBlockhash(t *testing.T) {
} }
} }
// BenchmarkSimpleLoop test a pretty simple loop which loops
// 1M (1 048 575) times.
// Takes about 200 ms
func BenchmarkSimpleLoop(b *testing.B) {
// 0xfffff = 1048575 loops
code := []byte{
byte(vm.PUSH3), 0x0f, 0xff, 0xff,
byte(vm.JUMPDEST), // [ count ]
byte(vm.PUSH1), 1, // [count, 1]
byte(vm.SWAP1), // [1, count]
byte(vm.SUB), // [ count -1 ]
byte(vm.DUP1), // [ count -1 , count-1]
byte(vm.PUSH1), 4, // [count-1, count -1, label]
byte(vm.JUMPI), // [ 0 ]
byte(vm.STOP),
}
//tracer := vm.NewJSONLogger(nil, os.Stdout)
//Execute(code, nil, &Config{
// EVMConfig: vm.Config{
// Debug: true,
// Tracer: tracer,
// }})
for i := 0; i < b.N; i++ {
Execute(code, nil, nil)
}
}
type stepCounter struct { type stepCounter struct {
inner *vm.JSONLogger inner *vm.JSONLogger
steps int steps int
@ -358,7 +330,7 @@ func (s *stepCounter) CaptureStart(from common.Address, to common.Address, creat
return nil return nil
} }
func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, contract *vm.Contract, depth int, err error) error { func (s *stepCounter) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, rData []byte, contract *vm.Contract, depth int, err error) error {
s.steps++ s.steps++
// Enable this for more output // Enable this for more output
//s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err) //s.inner.CaptureState(env, pc, op, gas, cost, memory, stack, rStack, contract, depth, err)
@ -593,3 +565,160 @@ func DisabledTestEipExampleCases(t *testing.T) {
"allowed, and causes an error", code) "allowed, and causes an error", code)
} }
} }
// benchmarkNonModifyingCode benchmarks code, but if the code modifies the
// state, this should not be used, since it does not reset the state between runs.
func benchmarkNonModifyingCode(gas uint64, code []byte, name string, b *testing.B) {
cfg := new(Config)
setDefaults(cfg)
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
cfg.GasLimit = gas
var (
destination = common.BytesToAddress([]byte("contract"))
vmenv = NewEnv(cfg)
sender = vm.AccountRef(cfg.Origin)
)
cfg.State.CreateAccount(destination)
eoa := common.HexToAddress("E0")
{
cfg.State.CreateAccount(eoa)
cfg.State.SetNonce(eoa, 100)
}
reverting := common.HexToAddress("EE")
{
cfg.State.CreateAccount(reverting)
cfg.State.SetCode(reverting, []byte{
byte(vm.PUSH1), 0x00,
byte(vm.PUSH1), 0x00,
byte(vm.REVERT),
})
}
//cfg.State.CreateAccount(cfg.Origin)
// set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(destination, code)
vmenv.Call(sender, destination, nil, gas, cfg.Value)
b.Run(name, func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
vmenv.Call(sender, destination, nil, gas, cfg.Value)
}
})
}
// BenchmarkSimpleLoop test a pretty simple loop which loops until OOG
// 55 ms
func BenchmarkSimpleLoop(b *testing.B) {
staticCallIdentity := []byte{
byte(vm.JUMPDEST), // [ count ]
// push args for the call
byte(vm.PUSH1), 0, // out size
byte(vm.DUP1), // out offset
byte(vm.DUP1), // out insize
byte(vm.DUP1), // in offset
byte(vm.PUSH1), 0x4, // address of identity
byte(vm.GAS), // gas
byte(vm.STATICCALL),
byte(vm.POP), // pop return value
byte(vm.PUSH1), 0, // jumpdestination
byte(vm.JUMP),
}
callIdentity := []byte{
byte(vm.JUMPDEST), // [ count ]
// push args for the call
byte(vm.PUSH1), 0, // out size
byte(vm.DUP1), // out offset
byte(vm.DUP1), // out insize
byte(vm.DUP1), // in offset
byte(vm.DUP1), // value
byte(vm.PUSH1), 0x4, // address of identity
byte(vm.GAS), // gas
byte(vm.CALL),
byte(vm.POP), // pop return value
byte(vm.PUSH1), 0, // jumpdestination
byte(vm.JUMP),
}
callInexistant := []byte{
byte(vm.JUMPDEST), // [ count ]
// push args for the call
byte(vm.PUSH1), 0, // out size
byte(vm.DUP1), // out offset
byte(vm.DUP1), // out insize
byte(vm.DUP1), // in offset
byte(vm.DUP1), // value
byte(vm.PUSH1), 0xff, // address of existing contract
byte(vm.GAS), // gas
byte(vm.CALL),
byte(vm.POP), // pop return value
byte(vm.PUSH1), 0, // jumpdestination
byte(vm.JUMP),
}
callEOA := []byte{
byte(vm.JUMPDEST), // [ count ]
// push args for the call
byte(vm.PUSH1), 0, // out size
byte(vm.DUP1), // out offset
byte(vm.DUP1), // out insize
byte(vm.DUP1), // in offset
byte(vm.DUP1), // value
byte(vm.PUSH1), 0xE0, // address of EOA
byte(vm.GAS), // gas
byte(vm.CALL),
byte(vm.POP), // pop return value
byte(vm.PUSH1), 0, // jumpdestination
byte(vm.JUMP),
}
loopingCode := []byte{
byte(vm.JUMPDEST), // [ count ]
// push args for the call
byte(vm.PUSH1), 0, // out size
byte(vm.DUP1), // out offset
byte(vm.DUP1), // out insize
byte(vm.DUP1), // in offset
byte(vm.PUSH1), 0x4, // address of identity
byte(vm.GAS), // gas
byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP), byte(vm.POP),
byte(vm.PUSH1), 0, // jumpdestination
byte(vm.JUMP),
}
calllRevertingContractWithInput := []byte{
byte(vm.JUMPDEST), //
// push args for the call
byte(vm.PUSH1), 0, // out size
byte(vm.DUP1), // out offset
byte(vm.PUSH1), 0x20, // in size
byte(vm.PUSH1), 0x00, // in offset
byte(vm.PUSH1), 0x00, // value
byte(vm.PUSH1), 0xEE, // address of reverting contract
byte(vm.GAS), // gas
byte(vm.CALL),
byte(vm.POP), // pop return value
byte(vm.PUSH1), 0, // jumpdestination
byte(vm.JUMP),
}
//tracer := vm.NewJSONLogger(nil, os.Stdout)
//Execute(loopingCode, nil, &Config{
// EVMConfig: vm.Config{
// Debug: true,
// Tracer: tracer,
// }})
// 100M gas
benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", b)
benchmarkNonModifyingCode(100000000, callIdentity, "call-identity-100M", b)
benchmarkNonModifyingCode(100000000, loopingCode, "loop-100M", b)
benchmarkNonModifyingCode(100000000, callInexistant, "call-nonexist-100M", b)
benchmarkNonModifyingCode(100000000, callEOA, "call-EOA-100M", b)
benchmarkNonModifyingCode(100000000, calllRevertingContractWithInput, "call-reverting-100M", b)
//benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b)
//benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b)
}

View File

@ -18,10 +18,17 @@ package vm
import ( import (
"fmt" "fmt"
"sync"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
var stackPool = sync.Pool{
New: func() interface{} {
return &Stack{data: make([]uint256.Int, 0, 16)}
},
}
// Stack is an object for basic stack operations. Items popped to the stack are // Stack is an object for basic stack operations. Items popped to the stack are
// expected to be changed and modified. stack does not take care of adding newly // expected to be changed and modified. stack does not take care of adding newly
// initialised objects. // initialised objects.
@ -30,7 +37,12 @@ type Stack struct {
} }
func newstack() *Stack { func newstack() *Stack {
return &Stack{data: make([]uint256.Int, 0, 16)} return stackPool.Get().(*Stack)
}
func returnStack(s *Stack) {
s.data = s.data[:0]
stackPool.Put(s)
} }
// Data returns the underlying uint256.Int array. // Data returns the underlying uint256.Int array.
@ -87,20 +99,32 @@ func (st *Stack) Print() {
fmt.Println("#############") fmt.Println("#############")
} }
var rStackPool = sync.Pool{
New: func() interface{} {
return &ReturnStack{data: make([]uint32, 0, 10)}
},
}
// ReturnStack is an object for basic return stack operations. // ReturnStack is an object for basic return stack operations.
type ReturnStack struct { type ReturnStack struct {
data []uint64 data []uint32
} }
func newReturnStack() *ReturnStack { func newReturnStack() *ReturnStack {
return &ReturnStack{data: make([]uint64, 0, 1024)} return rStackPool.Get().(*ReturnStack)
} }
func (st *ReturnStack) push(d uint64) { func returnRStack(rs *ReturnStack) {
rs.data = rs.data[:0]
rStackPool.Put(rs)
}
func (st *ReturnStack) push(d uint32) {
st.data = append(st.data, d) st.data = append(st.data, d)
} }
func (st *ReturnStack) pop() (ret uint64) { // A uint32 is sufficient as for code below 4.2G
func (st *ReturnStack) pop() (ret uint32) {
ret = st.data[len(st.data)-1] ret = st.data[len(st.data)-1]
st.data = st.data[:len(st.data)-1] st.data = st.data[:len(st.data)-1]
return return

View File

@ -541,7 +541,7 @@ func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create b
} }
// CaptureState implements the Tracer interface to trace a single step of VM execution. // CaptureState implements the Tracer interface to trace a single step of VM execution.
func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, contract *vm.Contract, depth int, err error) error { func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, rStack *vm.ReturnStack, rdata []byte, contract *vm.Contract, depth int, err error) error {
if jst.err == nil { if jst.err == nil {
// Initialize the context if it wasn't done yet // Initialize the context if it wasn't done yet
if !jst.inited { if !jst.inited {

View File

@ -169,10 +169,10 @@ func TestHaltBetweenSteps(t *testing.T) {
env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) env := vm.NewEVM(vm.Context{BlockNumber: big.NewInt(1)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer})
contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0) contract := vm.NewContract(&account{}, &account{}, big.NewInt(0), 0)
tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil) tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, nil, contract, 0, nil)
timeout := errors.New("stahp") timeout := errors.New("stahp")
tracer.Stop(timeout) tracer.Stop(timeout)
tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, contract, 0, nil) tracer.CaptureState(env, 0, 0, 0, 0, nil, nil, nil, nil, contract, 0, nil)
if _, err := tracer.GetResult(); err.Error() != timeout.Error() { if _, err := tracer.GetResult(); err.Error() != timeout.Error() {
t.Errorf("Expected timeout error, got %v", err) t.Errorf("Expected timeout error, got %v", err)

View File

@ -179,9 +179,9 @@ func (t *SecureTrie) hashKey(key []byte) []byte {
h := newHasher(false) h := newHasher(false)
h.sha.Reset() h.sha.Reset()
h.sha.Write(key) h.sha.Write(key)
buf := h.sha.Sum(t.hashKeyBuf[:0]) h.sha.Read(t.hashKeyBuf[:])
returnHasherToPool(h) returnHasherToPool(h)
return buf return t.hashKeyBuf[:]
} }
// getSecKeyCache returns the current secure key cache, creating a new one if // getSecKeyCache returns the current secure key cache, creating a new one if