mirror of https://github.com/ethereum/go-ethereum
eth/tracers: support for golang tracers + add golang callTracer (#23708)
* eth/tracers: add basic native loader
* eth/tracers: add GetResult to tracer interface
* eth/tracers: add native call tracer
* eth/tracers: fix call tracer json result
* eth/tracers: minor fix
* eth/tracers: fix
* eth/tracers: fix benchTracer
* eth/tracers: test native call tracer
* eth/tracers: fix
* eth/tracers: rm extra make
Co-authored-by: Martin Holst Swende <martin@swende.se>
* eth/tracers: rm extra make
* eth/tracers: make callFrame private
* eth/tracers: clean-up and comments
* eth/tracers: add license
* eth/tracers: rework the model a bit
* eth/tracers: move tracecall tests to subpackage
* cmd/geth: load native tracers
* eth/tracers: minor fix
* eth/tracers: impl stop
* eth/tracers: add native noop tracer
* renamings
Co-authored-by: Martin Holst Swende <martin@swende.se>
* eth/tracers: more renamings
* eth/tracers: make jstracer non-exported, avoid cast
* eth/tracers, core/vm: rename vm.Tracer to vm.EVMLogger for clarity
* eth/tracers: minor comment fix
* eth/tracers/testing: lint nitpicks
* core,eth: cancel evm on nativecalltracer stop
* Revert "core,eth: cancel evm on nativecalltracer stop"
This reverts commit 01bb908790
.
* eth/tracers: linter nits
* eth/tracers: fix output on err
Co-authored-by: Martin Holst Swende <martin@swende.se>
pull/23861/head
parent
3bbeb94c1c
commit
8d7e6062ec
@ -0,0 +1,170 @@ |
|||||||
|
// Copyright 2021 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package native |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"errors" |
||||||
|
"math/big" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
"sync/atomic" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/core/vm" |
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers" |
||||||
|
) |
||||||
|
|
||||||
|
func init() { |
||||||
|
tracers.RegisterNativeTracer("callTracerNative", NewCallTracer) |
||||||
|
} |
||||||
|
|
||||||
|
type callFrame struct { |
||||||
|
Type string `json:"type"` |
||||||
|
From string `json:"from"` |
||||||
|
To string `json:"to,omitempty"` |
||||||
|
Value string `json:"value,omitempty"` |
||||||
|
Gas string `json:"gas"` |
||||||
|
GasUsed string `json:"gasUsed"` |
||||||
|
Input string `json:"input"` |
||||||
|
Output string `json:"output,omitempty"` |
||||||
|
Error string `json:"error,omitempty"` |
||||||
|
Calls []callFrame `json:"calls,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
type callTracer struct { |
||||||
|
callstack []callFrame |
||||||
|
interrupt uint32 // Atomic flag to signal execution interruption
|
||||||
|
reason error // Textual reason for the interruption
|
||||||
|
} |
||||||
|
|
||||||
|
// NewCallTracer returns a native go tracer which tracks
|
||||||
|
// call frames of a tx, and implements vm.EVMLogger.
|
||||||
|
func NewCallTracer() tracers.Tracer { |
||||||
|
// First callframe contains tx context info
|
||||||
|
// and is populated on start and end.
|
||||||
|
t := &callTracer{callstack: make([]callFrame, 1)} |
||||||
|
return t |
||||||
|
} |
||||||
|
|
||||||
|
func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { |
||||||
|
t.callstack[0] = callFrame{ |
||||||
|
Type: "CALL", |
||||||
|
From: addrToHex(from), |
||||||
|
To: addrToHex(to), |
||||||
|
Input: bytesToHex(input), |
||||||
|
Gas: uintToHex(gas), |
||||||
|
Value: bigToHex(value), |
||||||
|
} |
||||||
|
if create { |
||||||
|
t.callstack[0].Type = "CREATE" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { |
||||||
|
t.callstack[0].GasUsed = uintToHex(gasUsed) |
||||||
|
if err != nil { |
||||||
|
t.callstack[0].Error = err.Error() |
||||||
|
if err.Error() == "execution reverted" && len(output) > 0 { |
||||||
|
t.callstack[0].Output = bytesToHex(output) |
||||||
|
} |
||||||
|
} else { |
||||||
|
t.callstack[0].Output = bytesToHex(output) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (t *callTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { |
||||||
|
} |
||||||
|
|
||||||
|
func (t *callTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { |
||||||
|
} |
||||||
|
|
||||||
|
func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { |
||||||
|
// Skip if tracing was interrupted
|
||||||
|
if atomic.LoadUint32(&t.interrupt) > 0 { |
||||||
|
// TODO: env.Cancel()
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
call := callFrame{ |
||||||
|
Type: typ.String(), |
||||||
|
From: addrToHex(from), |
||||||
|
To: addrToHex(to), |
||||||
|
Input: bytesToHex(input), |
||||||
|
Gas: uintToHex(gas), |
||||||
|
Value: bigToHex(value), |
||||||
|
} |
||||||
|
t.callstack = append(t.callstack, call) |
||||||
|
} |
||||||
|
|
||||||
|
func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { |
||||||
|
size := len(t.callstack) |
||||||
|
if size <= 1 { |
||||||
|
return |
||||||
|
} |
||||||
|
// pop call
|
||||||
|
call := t.callstack[size-1] |
||||||
|
t.callstack = t.callstack[:size-1] |
||||||
|
size -= 1 |
||||||
|
|
||||||
|
call.GasUsed = uintToHex(gasUsed) |
||||||
|
if err == nil { |
||||||
|
call.Output = bytesToHex(output) |
||||||
|
} else { |
||||||
|
call.Error = err.Error() |
||||||
|
if call.Type == "CREATE" || call.Type == "CREATE2" { |
||||||
|
call.To = "" |
||||||
|
} |
||||||
|
} |
||||||
|
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) |
||||||
|
} |
||||||
|
|
||||||
|
func (t *callTracer) GetResult() (json.RawMessage, error) { |
||||||
|
if len(t.callstack) != 1 { |
||||||
|
return nil, errors.New("incorrect number of top-level calls") |
||||||
|
} |
||||||
|
res, err := json.Marshal(t.callstack[0]) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return json.RawMessage(res), t.reason |
||||||
|
} |
||||||
|
|
||||||
|
func (t *callTracer) Stop(err error) { |
||||||
|
t.reason = err |
||||||
|
atomic.StoreUint32(&t.interrupt, 1) |
||||||
|
} |
||||||
|
|
||||||
|
func bytesToHex(s []byte) string { |
||||||
|
return "0x" + common.Bytes2Hex(s) |
||||||
|
} |
||||||
|
|
||||||
|
func bigToHex(n *big.Int) string { |
||||||
|
if n == nil { |
||||||
|
return "" |
||||||
|
} |
||||||
|
return "0x" + n.Text(16) |
||||||
|
} |
||||||
|
|
||||||
|
func uintToHex(n uint64) string { |
||||||
|
return "0x" + strconv.FormatUint(n, 16) |
||||||
|
} |
||||||
|
|
||||||
|
func addrToHex(a common.Address) string { |
||||||
|
return strings.ToLower(a.Hex()) |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
package native |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"math/big" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/core/vm" |
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers" |
||||||
|
) |
||||||
|
|
||||||
|
func init() { |
||||||
|
tracers.RegisterNativeTracer("noopTracerNative", NewNoopTracer) |
||||||
|
} |
||||||
|
|
||||||
|
type noopTracer struct{} |
||||||
|
|
||||||
|
func NewNoopTracer() tracers.Tracer { |
||||||
|
return &noopTracer{} |
||||||
|
} |
||||||
|
|
||||||
|
func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { |
||||||
|
} |
||||||
|
|
||||||
|
func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { |
||||||
|
} |
||||||
|
|
||||||
|
func (t *noopTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { |
||||||
|
} |
||||||
|
|
||||||
|
func (t *noopTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { |
||||||
|
} |
||||||
|
|
||||||
|
func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { |
||||||
|
} |
||||||
|
|
||||||
|
func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) { |
||||||
|
} |
||||||
|
|
||||||
|
func (t *noopTracer) GetResult() (json.RawMessage, error) { |
||||||
|
return json.RawMessage(`{}`), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (t *noopTracer) Stop(err error) { |
||||||
|
} |
@ -0,0 +1,246 @@ |
|||||||
|
package testing |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"io/ioutil" |
||||||
|
"math/big" |
||||||
|
"path/filepath" |
||||||
|
"reflect" |
||||||
|
"strings" |
||||||
|
"testing" |
||||||
|
"unicode" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil" |
||||||
|
"github.com/ethereum/go-ethereum/common/math" |
||||||
|
"github.com/ethereum/go-ethereum/core" |
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb" |
||||||
|
"github.com/ethereum/go-ethereum/core/types" |
||||||
|
"github.com/ethereum/go-ethereum/core/vm" |
||||||
|
"github.com/ethereum/go-ethereum/eth/tracers" |
||||||
|
"github.com/ethereum/go-ethereum/rlp" |
||||||
|
"github.com/ethereum/go-ethereum/tests" |
||||||
|
|
||||||
|
// Force-load the native, to trigger registration
|
||||||
|
_ "github.com/ethereum/go-ethereum/eth/tracers/native" |
||||||
|
) |
||||||
|
|
||||||
|
type callContext struct { |
||||||
|
Number math.HexOrDecimal64 `json:"number"` |
||||||
|
Difficulty *math.HexOrDecimal256 `json:"difficulty"` |
||||||
|
Time math.HexOrDecimal64 `json:"timestamp"` |
||||||
|
GasLimit math.HexOrDecimal64 `json:"gasLimit"` |
||||||
|
Miner common.Address `json:"miner"` |
||||||
|
} |
||||||
|
|
||||||
|
// callTrace is the result of a callTracer run.
|
||||||
|
type callTrace struct { |
||||||
|
Type string `json:"type"` |
||||||
|
From common.Address `json:"from"` |
||||||
|
To common.Address `json:"to"` |
||||||
|
Input hexutil.Bytes `json:"input"` |
||||||
|
Output hexutil.Bytes `json:"output"` |
||||||
|
Gas *hexutil.Uint64 `json:"gas,omitempty"` |
||||||
|
GasUsed *hexutil.Uint64 `json:"gasUsed,omitempty"` |
||||||
|
Value *hexutil.Big `json:"value,omitempty"` |
||||||
|
Error string `json:"error,omitempty"` |
||||||
|
Calls []callTrace `json:"calls,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
// callTracerTest defines a single test to check the call tracer against.
|
||||||
|
type callTracerTest struct { |
||||||
|
Genesis *core.Genesis `json:"genesis"` |
||||||
|
Context *callContext `json:"context"` |
||||||
|
Input string `json:"input"` |
||||||
|
Result *callTrace `json:"result"` |
||||||
|
} |
||||||
|
|
||||||
|
// Iterates over all the input-output datasets in the tracer test harness and
|
||||||
|
// runs the JavaScript tracers against them.
|
||||||
|
func TestCallTracerLegacy(t *testing.T) { |
||||||
|
testCallTracer("callTracerLegacy", "call_tracer_legacy", t) |
||||||
|
} |
||||||
|
|
||||||
|
func TestCallTracer(t *testing.T) { |
||||||
|
testCallTracer("callTracer", "call_tracer", t) |
||||||
|
} |
||||||
|
|
||||||
|
func TestCallTracerNative(t *testing.T) { |
||||||
|
testCallTracer("callTracerNative", "call_tracer", t) |
||||||
|
} |
||||||
|
|
||||||
|
func testCallTracer(tracerName string, dirPath string, t *testing.T) { |
||||||
|
files, err := ioutil.ReadDir(filepath.Join("..", "testdata", dirPath)) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to retrieve tracer test suite: %v", err) |
||||||
|
} |
||||||
|
for _, file := range files { |
||||||
|
if !strings.HasSuffix(file.Name(), ".json") { |
||||||
|
continue |
||||||
|
} |
||||||
|
file := file // capture range variable
|
||||||
|
t.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(t *testing.T) { |
||||||
|
t.Parallel() |
||||||
|
|
||||||
|
var ( |
||||||
|
test = new(callTracerTest) |
||||||
|
tx = new(types.Transaction) |
||||||
|
) |
||||||
|
// Call tracer test found, read if from disk
|
||||||
|
if blob, err := ioutil.ReadFile(filepath.Join("..", "testdata", dirPath, file.Name())); err != nil { |
||||||
|
t.Fatalf("failed to read testcase: %v", err) |
||||||
|
} else if err := json.Unmarshal(blob, test); err != nil { |
||||||
|
t.Fatalf("failed to parse testcase: %v", err) |
||||||
|
} |
||||||
|
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { |
||||||
|
t.Fatalf("failed to parse testcase input: %v", err) |
||||||
|
} |
||||||
|
// Configure a blockchain with the given prestate
|
||||||
|
var ( |
||||||
|
signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) |
||||||
|
origin, _ = signer.Sender(tx) |
||||||
|
txContext = vm.TxContext{ |
||||||
|
Origin: origin, |
||||||
|
GasPrice: tx.GasPrice(), |
||||||
|
} |
||||||
|
context = vm.BlockContext{ |
||||||
|
CanTransfer: core.CanTransfer, |
||||||
|
Transfer: core.Transfer, |
||||||
|
Coinbase: test.Context.Miner, |
||||||
|
BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), |
||||||
|
Time: new(big.Int).SetUint64(uint64(test.Context.Time)), |
||||||
|
Difficulty: (*big.Int)(test.Context.Difficulty), |
||||||
|
GasLimit: uint64(test.Context.GasLimit), |
||||||
|
} |
||||||
|
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) |
||||||
|
) |
||||||
|
tracer, err := tracers.New(tracerName, new(tracers.Context)) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to create call tracer: %v", err) |
||||||
|
} |
||||||
|
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) |
||||||
|
msg, err := tx.AsMessage(signer, nil) |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to prepare transaction for tracing: %v", err) |
||||||
|
} |
||||||
|
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) |
||||||
|
if _, err = st.TransitionDb(); err != nil { |
||||||
|
t.Fatalf("failed to execute transaction: %v", err) |
||||||
|
} |
||||||
|
// Retrieve the trace result and compare against the etalon
|
||||||
|
res, err := tracer.GetResult() |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("failed to retrieve trace result: %v", err) |
||||||
|
} |
||||||
|
ret := new(callTrace) |
||||||
|
if err := json.Unmarshal(res, ret); err != nil { |
||||||
|
t.Fatalf("failed to unmarshal trace result: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
if !jsonEqual(ret, test.Result) { |
||||||
|
// uncomment this for easier debugging
|
||||||
|
//have, _ := json.MarshalIndent(ret, "", " ")
|
||||||
|
//want, _ := json.MarshalIndent(test.Result, "", " ")
|
||||||
|
//t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", string(have), string(want))
|
||||||
|
t.Fatalf("trace mismatch: \nhave %+v\nwant %+v", ret, test.Result) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to
|
||||||
|
// comparison
|
||||||
|
func jsonEqual(x, y interface{}) bool { |
||||||
|
xTrace := new(callTrace) |
||||||
|
yTrace := new(callTrace) |
||||||
|
if xj, err := json.Marshal(x); err == nil { |
||||||
|
json.Unmarshal(xj, xTrace) |
||||||
|
} else { |
||||||
|
return false |
||||||
|
} |
||||||
|
if yj, err := json.Marshal(y); err == nil { |
||||||
|
json.Unmarshal(yj, yTrace) |
||||||
|
} else { |
||||||
|
return false |
||||||
|
} |
||||||
|
return reflect.DeepEqual(xTrace, yTrace) |
||||||
|
} |
||||||
|
|
||||||
|
// camel converts a snake cased input string into a camel cased output.
|
||||||
|
func camel(str string) string { |
||||||
|
pieces := strings.Split(str, "_") |
||||||
|
for i := 1; i < len(pieces); i++ { |
||||||
|
pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:] |
||||||
|
} |
||||||
|
return strings.Join(pieces, "") |
||||||
|
} |
||||||
|
func BenchmarkTracers(b *testing.B) { |
||||||
|
files, err := ioutil.ReadDir(filepath.Join("..", "testdata", "call_tracer")) |
||||||
|
if err != nil { |
||||||
|
b.Fatalf("failed to retrieve tracer test suite: %v", err) |
||||||
|
} |
||||||
|
for _, file := range files { |
||||||
|
if !strings.HasSuffix(file.Name(), ".json") { |
||||||
|
continue |
||||||
|
} |
||||||
|
file := file // capture range variable
|
||||||
|
b.Run(camel(strings.TrimSuffix(file.Name(), ".json")), func(b *testing.B) { |
||||||
|
blob, err := ioutil.ReadFile(filepath.Join("..", "testdata", "call_tracer", file.Name())) |
||||||
|
if err != nil { |
||||||
|
b.Fatalf("failed to read testcase: %v", err) |
||||||
|
} |
||||||
|
test := new(callTracerTest) |
||||||
|
if err := json.Unmarshal(blob, test); err != nil { |
||||||
|
b.Fatalf("failed to parse testcase: %v", err) |
||||||
|
} |
||||||
|
benchTracer("callTracerNative", test, b) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { |
||||||
|
// Configure a blockchain with the given prestate
|
||||||
|
tx := new(types.Transaction) |
||||||
|
if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { |
||||||
|
b.Fatalf("failed to parse testcase input: %v", err) |
||||||
|
} |
||||||
|
signer := types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) |
||||||
|
msg, err := tx.AsMessage(signer, nil) |
||||||
|
if err != nil { |
||||||
|
b.Fatalf("failed to prepare transaction for tracing: %v", err) |
||||||
|
} |
||||||
|
origin, _ := signer.Sender(tx) |
||||||
|
txContext := vm.TxContext{ |
||||||
|
Origin: origin, |
||||||
|
GasPrice: tx.GasPrice(), |
||||||
|
} |
||||||
|
context := vm.BlockContext{ |
||||||
|
CanTransfer: core.CanTransfer, |
||||||
|
Transfer: core.Transfer, |
||||||
|
Coinbase: test.Context.Miner, |
||||||
|
BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), |
||||||
|
Time: new(big.Int).SetUint64(uint64(test.Context.Time)), |
||||||
|
Difficulty: (*big.Int)(test.Context.Difficulty), |
||||||
|
GasLimit: uint64(test.Context.GasLimit), |
||||||
|
} |
||||||
|
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) |
||||||
|
|
||||||
|
b.ReportAllocs() |
||||||
|
b.ResetTimer() |
||||||
|
for i := 0; i < b.N; i++ { |
||||||
|
tracer, err := tracers.New(tracerName, new(tracers.Context)) |
||||||
|
if err != nil { |
||||||
|
b.Fatalf("failed to create call tracer: %v", err) |
||||||
|
} |
||||||
|
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) |
||||||
|
snap := statedb.Snapshot() |
||||||
|
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) |
||||||
|
if _, err = st.TransitionDb(); err != nil { |
||||||
|
b.Fatalf("failed to execute transaction: %v", err) |
||||||
|
} |
||||||
|
if _, err = tracer.GetResult(); err != nil { |
||||||
|
b.Fatal(err) |
||||||
|
} |
||||||
|
statedb.RevertToSnapshot(snap) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue