diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go index 2e57d72589..d110af2c30 100644 --- a/cmd/evm/internal/t8ntool/flags.go +++ b/cmd/evm/internal/t8ntool/flags.go @@ -38,6 +38,10 @@ var ( Name: "trace.nostack", Usage: "Disable stack output in traces", } + TraceDisableReturnDataFlag = cli.BoolFlag{ + Name: "trace.noreturndata", + Usage: "Disable return data output in traces", + } OutputAllocFlag = cli.StringFlag{ Name: "output.alloc", Usage: "Determines where to put the `alloc` of the post-state.\n" + diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index a4908d763e..079307b975 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -83,9 +83,10 @@ func Main(ctx *cli.Context) error { if ctx.Bool(TraceFlag.Name) { // Configure the EVM logger logConfig := &vm.LogConfig{ - DisableStack: ctx.Bool(TraceDisableStackFlag.Name), - DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name), - Debug: true, + DisableStack: ctx.Bool(TraceDisableStackFlag.Name), + DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name), + DisableReturnData: ctx.Bool(TraceDisableReturnDataFlag.Name), + Debug: true, } var prevFile *os.File // This one closes the last file diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 2d5e9763df..7b472350d9 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -121,6 +121,14 @@ var ( Name: "nostack", 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{ Name: "vm.evm", Usage: "External EVM configuration (default = built-in interpreter)", @@ -137,6 +145,7 @@ var stateTransitionCommand = cli.Command{ t8ntool.TraceFlag, t8ntool.TraceDisableMemoryFlag, t8ntool.TraceDisableStackFlag, + t8ntool.TraceDisableReturnDataFlag, t8ntool.OutputAllocFlag, t8ntool.OutputResultFlag, t8ntool.InputAllocFlag, @@ -172,6 +181,8 @@ func init() { ReceiverFlag, DisableMemoryFlag, DisableStackFlag, + DisableStorageFlag, + DisableReturnDataFlag, EVMInterpreterFlag, } app.Commands = []cli.Command{ diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 639f0c0ac9..d0be6ca1e1 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -108,9 +108,11 @@ func runCmd(ctx *cli.Context) error { glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name))) log.Root().SetHandler(glogger) logconfig := &vm.LogConfig{ - DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), - DisableStack: ctx.GlobalBool(DisableStackFlag.Name), - Debug: ctx.GlobalBool(DebugFlag.Name), + DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), + DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + DisableStorage: ctx.GlobalBool(DisableStorageFlag.Name), + DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name), + Debug: ctx.GlobalBool(DebugFlag.Name), } var ( diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index 6f9e47cf50..f9a6b06b8f 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -59,8 +59,10 @@ func stateTestCmd(ctx *cli.Context) error { // Configure the EVM logger config := &vm.LogConfig{ - DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), - DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), + DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + DisableStorage: ctx.GlobalBool(DisableStorageFlag.Name), + DisableReturnData: ctx.GlobalBool(DisableReturnDataFlag.Name), } var ( tracer vm.Tracer diff --git a/core/state/statedb.go b/core/state/statedb.go index 45cf1f7c42..17dd474314 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -505,7 +505,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { } // If no live objects are available, attempt to use snapshots var ( - data Account + data *Account err error ) if s.snap != nil { @@ -517,11 +517,15 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { if acc == 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 { data.CodeHash = emptyCodeHash } - data.Root = common.BytesToHash(acc.Root) if data.Root == (common.Hash{}) { data.Root = emptyRoot } @@ -540,13 +544,14 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { if len(enc) == 0 { 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) return nil } } // Insert into the live set - obj := newObject(s, addr, data) + obj := newObject(s, addr, *data) s.setStateObject(obj) return obj } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 6493d45895..8930a06266 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -102,12 +102,18 @@ var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{ } // RunPrecompiledContract runs and evaluates the output of a precompiled contract. -func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) { - gas := p.RequiredGas(input) - if contract.UseGas(gas) { - return p.Run(input) +// It returns +// - the returned bytes, +// - the _remaining_ gas, +// - 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. @@ -197,6 +203,7 @@ func (c *dataCopy) Run(in []byte) ([]byte, error) { type bigModExp struct{} var ( + big0 = big.NewInt(0) big1 = big.NewInt(1) big4 = big.NewInt(4) big8 = big.NewInt(8) diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 65a9f8b797..5bc365949d 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -21,7 +21,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "math/big" "testing" "time" @@ -72,10 +71,9 @@ var blake2FMalformedInputTests = []precompiledFailureTest{ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { p := allPrecompiles[common.HexToAddress(addr)] in := common.Hex2Bytes(test.Input) - contract := NewContract(AccountRef(common.HexToAddress("1337")), - nil, new(big.Int), p.RequiredGas(in)) - t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) { - if res, err := RunPrecompiledContract(p, in, contract); err != nil { + gas := p.RequiredGas(in) + t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { + if res, _, err := RunPrecompiledContract(p, in, gas); err != nil { t.Error(err) } else if common.Bytes2Hex(res) != test.Expected { 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) { p := allPrecompiles[common.HexToAddress(addr)] in := common.Hex2Bytes(test.Input) - contract := NewContract(AccountRef(common.HexToAddress("1337")), - nil, new(big.Int), p.RequiredGas(in)-1) - t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, contract.Gas), func(t *testing.T) { - _, err := RunPrecompiledContract(p, in, contract) + gas := p.RequiredGas(in) - 1 + + t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { + _, _, err := RunPrecompiledContract(p, in, gas) if err.Error() != "out of gas" { 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) { p := allPrecompiles[common.HexToAddress(addr)] in := common.Hex2Bytes(test.Input) - contract := NewContract(AccountRef(common.HexToAddress("31337")), - nil, new(big.Int), p.RequiredGas(in)) - + gas := p.RequiredGas(in) t.Run(test.Name, func(t *testing.T) { - _, err := RunPrecompiledContract(p, in, contract) + _, _, err := RunPrecompiledContract(p, in, gas) if err.Error() != test.ExpectedError { 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)] in := common.Hex2Bytes(test.Input) reqGas := p.RequiredGas(in) - contract := NewContract(AccountRef(common.HexToAddress("1337")), - nil, new(big.Int), reqGas) var ( res []byte @@ -141,14 +135,13 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { 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() start := time.Now().Nanosecond() bench.ResetTimer() for i := 0; i < bench.N; i++ { - contract.Gas = reqGas copy(data, in) - res, err = RunPrecompiledContract(p, data, contract) + res, _, err = RunPrecompiledContract(p, data, reqGas) } bench.StopTimer() elapsed := float64(time.Now().Nanosecond() - start) diff --git a/core/vm/evm.go b/core/vm/evm.go index 880198bd78..f5469c500c 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" ) // emptyCodeHash is used by create to ensure deployment is disallowed to already @@ -41,23 +42,24 @@ type ( 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. 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 { if interpreter.CanRun(contract.Code) { if evm.interpreter != interpreter { @@ -199,22 +201,14 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas return nil, gas, ErrDepth } // 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 } - var ( - to = AccountRef(addr) - snapshot = evm.StateDB.Snapshot() - ) + snapshot := evm.StateDB.Snapshot() + p, isPrecompile := evm.precompile(addr) + if !evm.StateDB.Exist(addr) { - precompiles := PrecompiledContractsHomestead - if evm.chainRules.IsByzantium { - precompiles = PrecompiledContractsByzantium - } - if evm.chainRules.IsIstanbul { - precompiles = PrecompiledContractsIstanbul - } - if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 { + if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { // Calling a non existing account, don't do anything, but ping the tracer if evm.vmConfig.Debug && evm.depth == 0 { 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.Transfer(evm.StateDB, caller.Address(), to.Address(), 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() + evm.Transfer(evm.StateDB, caller.Address(), addr, value) // Capture the tracer start/end events in debug mode if evm.vmConfig.Debug && evm.depth == 0 { evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) - - defer func() { // Lazy evaluation of the parameters - evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) - }() + defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters + evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), 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 // 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 { evm.StateDB.RevertToSnapshot(snapshot) 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 @@ -277,23 +283,27 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, ErrInsufficientBalance } - var ( - snapshot = evm.StateDB.Snapshot() - to = AccountRef(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 := NewContract(caller, to, value, gas) - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) - - ret, err = run(evm, contract, input, false) + var snapshot = evm.StateDB.Snapshot() + + // 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. + // The contract is a scoped environment for this execution context only. + contract := NewContract(caller, AccountRef(caller.Address()), value, gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + ret, err = run(evm, contract, input, false) + gas = contract.Gas + } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) 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 @@ -309,22 +319,26 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } - var ( - 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)) - - ret, err = run(evm, contract, input, false) + var snapshot = evm.StateDB.Snapshot() + + // 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) + gas = contract.Gas + } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) 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 @@ -339,32 +353,43 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } - var ( - to = AccountRef(addr) - snapshot = evm.StateDB.Snapshot() - ) - // 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, new(big.Int), gas) - contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) + // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped. + // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced + // after all empty accounts were deleted, so this is not required. However, if we omit this, + // then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json. + // We could change this, but for now it's left for legacy reasons + var snapshot = evm.StateDB.Snapshot() // 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, // but is the correct thing to do and matters on other networks, in tests, and potential // future scenarios - evm.StateDB.AddBalance(addr, big.NewInt(0)) - - // 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. - ret, err = run(evm, contract, input, true) + 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 + // 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. + ret, err = run(evm, contract, input, true) + gas = contract.Gas + } if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != ErrExecutionReverted { - contract.UseGas(contract.Gas) + gas = 0 } } - return ret, contract.Gas, err + return ret, gas, err } 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:] // 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} - 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) } diff --git a/core/vm/gen_structlog.go b/core/vm/gen_structlog.go index 7ef909954a..ac1a9070c8 100644 --- a/core/vm/gen_structlog.go +++ b/core/vm/gen_structlog.go @@ -24,6 +24,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { MemorySize int `json:"memSize"` Stack []*math.HexOrDecimal256 `json:"stack"` ReturnStack []math.HexOrDecimal64 `json:"returnStack"` + ReturnData []byte `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` RefundCounter uint64 `json:"refund"` @@ -50,6 +51,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { enc.ReturnStack[k] = math.HexOrDecimal64(v) } } + enc.ReturnData = s.ReturnData enc.Storage = s.Storage enc.Depth = s.Depth enc.RefundCounter = s.RefundCounter @@ -70,6 +72,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { MemorySize *int `json:"memSize"` Stack []*math.HexOrDecimal256 `json:"stack"` ReturnStack []math.HexOrDecimal64 `json:"returnStack"` + ReturnData []byte `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth *int `json:"depth"` RefundCounter *uint64 `json:"refund"` @@ -104,11 +107,14 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { } } if dec.ReturnStack != nil { - s.ReturnStack = make([]uint64, len(dec.ReturnStack)) + s.ReturnStack = make([]uint32, len(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 { s.Storage = dec.Storage } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 38d0d09e04..adf44b7f48 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -563,7 +563,7 @@ func opJumpSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([ if !callContext.contract.validJumpSubdest(posU64) { return nil, ErrInvalidJump } - callContext.rstack.push(*pc) + callContext.rstack.push(uint32(*pc)) *pc = posU64 + 1 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 // need to validate the pc from 'returns', since we only ever push valid //values onto it via jumpsub. - *pc = callContext.rstack.pop() + 1 + *pc = uint64(callContext.rstack.pop()) + 1 return nil, nil } @@ -608,7 +608,13 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([] stackvalue := size 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 // homestead we must check for CodeStoreOutOfGasError (homestead only // 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) // reuse size int for stackvalue 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, - endowment.ToBig(), salt.ToBig()) + bigEndowment, &salt) // Push item on the stack based on the returned error. if suberr != nil { stackvalue.Clear() @@ -672,10 +683,17 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]by // Get the arguments from the memory. 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() { 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 { temp.Clear() } else { @@ -702,10 +720,14 @@ func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ( // Get arguments from the memory. 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() { 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 { temp.Clear() } else { diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 9c7c2b4100..89feab0e2f 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -182,13 +182,20 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( logged bool // deferred Tracer should ignore already logged steps 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 if in.cfg.Debug { defer func() { if err != nil { 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 { 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 { - 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 } @@ -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) // set the last return to the result of the operation. if operation.returns { - in.returnData = res + in.returnData = common.CopyBytes(res) } switch { diff --git a/core/vm/logger.go b/core/vm/logger.go index 2c90399aca..e1d7c67ef1 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -47,11 +47,12 @@ func (s Storage) Copy() Storage { // LogConfig are the configuration options for structured logger the EVM type LogConfig struct { - DisableMemory bool // disable memory capture - DisableStack bool // disable stack capture - DisableStorage bool // disable storage capture - Debug bool // print output during capture end - Limit int // maximum length of output, but zero means unlimited + DisableMemory bool // disable memory capture + DisableStack bool // disable stack capture + DisableStorage bool // disable storage capture + DisableReturnData bool // disable return data capture + Debug bool // print output during capture end + Limit int // maximum length of output, but zero means unlimited } //go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go @@ -66,7 +67,8 @@ type StructLog struct { Memory []byte `json:"memory"` MemorySize int `json:"memSize"` Stack []*big.Int `json:"stack"` - ReturnStack []uint64 `json:"returnStack"` + ReturnStack []uint32 `json:"returnStack"` + ReturnData []byte `json:"returnData"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` RefundCounter uint64 `json:"refund"` @@ -104,7 +106,7 @@ func (s *StructLog) ErrorString() string { // if you need to retain them beyond the current call. type Tracer interface { 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 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 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 if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) { 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()) } } - var rstack []uint64 + var rstack []uint32 if !l.cfg.DisableStack && rStack != nil { - rstck := make([]uint64, len(rStack.data)) + rstck := make([]uint32, len(rStack.data)) copy(rstck, rStack.data) } // 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() } + var rdata []byte + if !l.cfg.DisableReturnData { + rdata = make([]byte, len(rData)) + copy(rdata, rData) + } // 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) return nil } @@ -257,6 +264,10 @@ func WriteTrace(writer io.Writer, logs []StructLog) { 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) } } @@ -308,7 +319,7 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b 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) if !t.cfg.DisableStack { // format stack diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go index e37c3ce2bd..5f3f2c42f7 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -46,7 +46,7 @@ func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create } // 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{ Pc: pc, Op: op, @@ -70,6 +70,9 @@ func (l *JSONLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint log.Stack = logstack log.ReturnStack = rStack.data } + if !l.cfg.DisableReturnData { + log.ReturnData = rData + } return l.encoder.Encode(log) } diff --git a/core/vm/logger_test.go b/core/vm/logger_test.go index 572edf9807..e287f0c7aa 100644 --- a/core/vm/logger_test.go +++ b/core/vm/logger_test.go @@ -61,7 +61,7 @@ func TestStoreCapture(t *testing.T) { stack.push(uint256.NewInt().SetUint64(1)) stack.push(uint256.NewInt()) 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 { t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.storage[contract.Address()])) } diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 9cb492786b..7ebaa9a7e3 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -52,13 +52,20 @@ type Config struct { func setDefaults(cfg *Config) { if cfg.ChainConfig == nil { cfg.ChainConfig = ¶ms.ChainConfig{ - ChainID: big.NewInt(1), - HomesteadBlock: new(big.Int), - DAOForkBlock: new(big.Int), - DAOForkSupport: false, - EIP150Block: new(big.Int), - EIP155Block: new(big.Int), - EIP158Block: new(big.Int), + ChainID: big.NewInt(1), + HomesteadBlock: new(big.Int), + DAOForkBlock: new(big.Int), + DAOForkSupport: false, + EIP150Block: new(big.Int), + EIP150Hash: common.Hash{}, + EIP155Block: 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, } } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 991813bf81..108ee80e41 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -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 { inner *vm.JSONLogger steps int @@ -358,7 +330,7 @@ func (s *stepCounter) CaptureStart(from common.Address, to common.Address, creat 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++ // Enable this for more output //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) } } + +// 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) +} diff --git a/core/vm/stack.go b/core/vm/stack.go index 99de4d79c8..af27d6552c 100644 --- a/core/vm/stack.go +++ b/core/vm/stack.go @@ -18,10 +18,17 @@ package vm import ( "fmt" + "sync" "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 // expected to be changed and modified. stack does not take care of adding newly // initialised objects. @@ -30,7 +37,12 @@ type Stack struct { } 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. @@ -87,20 +99,32 @@ func (st *Stack) Print() { 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. type ReturnStack struct { - data []uint64 + data []uint32 } func newReturnStack() *ReturnStack { - return &ReturnStack{data: make([]uint64, 0, 1024)} + return rStackPool.Get().(*ReturnStack) +} + +func returnRStack(rs *ReturnStack) { + rs.data = rs.data[:0] + rStackPool.Put(rs) } -func (st *ReturnStack) push(d uint64) { +func (st *ReturnStack) push(d uint32) { 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] st.data = st.data[:len(st.data)-1] return diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index a1394920f4..050fb05159 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -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. -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 { // Initialize the context if it wasn't done yet if !jst.inited { diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 911431a3fd..b4de998651 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -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}) 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") 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() { t.Errorf("Expected timeout error, got %v", err) diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 955771495b..bd8e51d989 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -179,9 +179,9 @@ func (t *SecureTrie) hashKey(key []byte) []byte { h := newHasher(false) h.sha.Reset() h.sha.Write(key) - buf := h.sha.Sum(t.hashKeyBuf[:0]) + h.sha.Read(t.hashKeyBuf[:]) returnHasherToPool(h) - return buf + return t.hashKeyBuf[:] } // getSecKeyCache returns the current secure key cache, creating a new one if