From af67e1def6dd1fc0843374817d42903c0f7feae0 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 11 Oct 2021 17:09:41 +0200 Subject: [PATCH 01/30] eth/tracers: add basic native loader --- eth/tracers/native/native.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 eth/tracers/native/native.go diff --git a/eth/tracers/native/native.go b/eth/tracers/native/native.go new file mode 100644 index 000000000000..2b2d67392552 --- /dev/null +++ b/eth/tracers/native/native.go @@ -0,0 +1,18 @@ +package native + +import "github.com/ethereum/go-ethereum/core/vm" + +type Constructor func() vm.Tracer + +var tracers map[string]Constructor = make(map[string]Constructor) + +func Register(name string, fn Constructor) { + tracers[name] = fn +} + +func New(name string) (vm.Tracer, bool) { + if fn, ok := tracers[name]; ok { + return fn(), true + } + return nil, false +} From 57c993b6cbe2ccc6e893cb047a26a4876e8df3fb Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 11 Oct 2021 17:13:54 +0200 Subject: [PATCH 02/30] eth/tracers: add GetResult to tracer interface --- eth/tracers/native/native.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/eth/tracers/native/native.go b/eth/tracers/native/native.go index 2b2d67392552..10c607fc43c7 100644 --- a/eth/tracers/native/native.go +++ b/eth/tracers/native/native.go @@ -1,8 +1,17 @@ package native -import "github.com/ethereum/go-ethereum/core/vm" +import ( + "encoding/json" -type Constructor func() vm.Tracer + "github.com/ethereum/go-ethereum/core/vm" +) + +type Tracer interface { + vm.Tracer + GetResult() (json.RawMessage, error) +} + +type Constructor func() Tracer var tracers map[string]Constructor = make(map[string]Constructor) @@ -10,7 +19,7 @@ func Register(name string, fn Constructor) { tracers[name] = fn } -func New(name string) (vm.Tracer, bool) { +func New(name string) (Tracer, bool) { if fn, ok := tracers[name]; ok { return fn(), true } From c9c1dc9d5c94aceb965a2601c03fdc2360e74041 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Mon, 11 Oct 2021 18:20:45 +0200 Subject: [PATCH 03/30] eth/tracers: add native call tracer --- eth/tracers/api.go | 31 +++++---- eth/tracers/native/call.go | 125 +++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 13 deletions(-) create mode 100644 eth/tracers/native/call.go diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 5019fb6f7357..07764bc4ac7d 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/native" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" @@ -858,19 +859,23 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex return nil, err } } - // Constuct the JavaScript tracer to execute with - if tracer, err = New(*config.Tracer, txctx); err != nil { - return nil, err - } - // Handle timeouts and RPC cancellations - deadlineCtx, cancel := context.WithTimeout(ctx, timeout) - go func() { - <-deadlineCtx.Done() - if deadlineCtx.Err() == context.DeadlineExceeded { - tracer.(*Tracer).Stop(errors.New("execution timeout")) + // Native tracers take precedence + var ok bool + if tracer, ok = native.New(*config.Tracer); !ok { + if tracer, err = New(*config.Tracer, txctx); err != nil { + return nil, err } - }() - defer cancel() + // TODO(s1na): do we need timeout for native tracers? + // Handle timeouts and RPC cancellations + deadlineCtx, cancel := context.WithTimeout(ctx, timeout) + go func() { + <-deadlineCtx.Done() + if deadlineCtx.Err() == context.DeadlineExceeded { + tracer.(*Tracer).Stop(errors.New("execution timeout")) + } + }() + defer cancel() + } case config == nil: tracer = vm.NewStructLogger(nil) @@ -904,7 +909,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex StructLogs: ethapi.FormatLogs(tracer.StructLogs()), }, nil - case *Tracer: + case native.Tracer: return tracer.GetResult() default: diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go new file mode 100644 index 000000000000..923fd052c1d5 --- /dev/null +++ b/eth/tracers/native/call.go @@ -0,0 +1,125 @@ +package native + +import ( + "encoding/json" + "math/big" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" +) + +func init() { + Register("callTracerNative", NewCallTracer) +} + +type CallFrame struct { + Type string `json:"type"` + From string `json:"from"` + To string `json:"to"` + Input string `json:"input"` + Gas string `json:"gas"` + Value string `json:"value,omitempty"` + GasUsed string `json:"gasUsed"` + Output string `json:"output"` + Error string `json:"error,omitempty"` + Calls []CallFrame `json:"calls,omitempty"` +} + +type CallTracer struct { + callstack []CallFrame +} + +func NewCallTracer() Tracer { + 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), + Calls: make([]CallFrame, 0), + } + if create { + t.callstack[0].Type = "CREATE" + } +} + +func (t *CallTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { + t.callstack[0].Output = bytesToHex(output) + t.callstack[0].GasUsed = uintToHex(gasUsed) + if err != nil { + t.callstack[0].Error = err.Error() + } +} + +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) { + call := CallFrame{ + Type: typ.String(), + From: addrToHex(from), + To: addrToHex(to), + Input: bytesToHex(input), + Gas: uintToHex(gas), + Value: bigToHex(value), + Calls: make([]CallFrame, 0), + } + t.callstack = append(t.callstack, call) +} + +func (t *CallTracer) CaptureExit(output []byte, gasUsed uint64, err error) { + size := len(t.callstack) + if size > 1 { + // 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) { + res, err := json.Marshal(t.callstack) + if err != nil { + return nil, err + } + return json.RawMessage(res), nil +} + +func bytesToHex(s []byte) string { + return "0x" + common.Bytes2Hex(s) +} + +func bigToHex(n *big.Int) string { + 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()) +} From 340c7732016d5c429ede22751d90d98d0609601b Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 12 Oct 2021 12:43:34 +0200 Subject: [PATCH 04/30] eth/tracers: fix call tracer json result --- eth/tracers/native/call.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 923fd052c1d5..22f0f2ccafaa 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -2,6 +2,7 @@ package native import ( "encoding/json" + "errors" "math/big" "strconv" "strings" @@ -101,7 +102,10 @@ func (t *CallTracer) CaptureExit(output []byte, gasUsed uint64, err error) { } func (t *CallTracer) GetResult() (json.RawMessage, error) { - res, err := json.Marshal(t.callstack) + 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 } From bf165bba68bf2fed9ce101cb7f733860d6f523e5 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 12 Oct 2021 12:48:23 +0200 Subject: [PATCH 05/30] eth/tracers: minor fix --- eth/tracers/native/call.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 22f0f2ccafaa..a85a1b5fab08 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -20,10 +20,10 @@ type CallFrame struct { Type string `json:"type"` From string `json:"from"` To string `json:"to"` - Input string `json:"input"` - Gas string `json:"gas"` Value string `json:"value,omitempty"` + Gas string `json:"gas"` GasUsed string `json:"gasUsed"` + Input string `json:"input"` Output string `json:"output"` Error string `json:"error,omitempty"` Calls []CallFrame `json:"calls,omitempty"` From 4ea0d159667aab243a4d22f8d8c4a91b0d7512d7 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 12 Oct 2021 12:52:11 +0200 Subject: [PATCH 06/30] eth/tracers: fix --- eth/tracers/native/call.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index a85a1b5fab08..8f67a91a6acf 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -24,7 +24,7 @@ type CallFrame struct { Gas string `json:"gas"` GasUsed string `json:"gasUsed"` Input string `json:"input"` - Output string `json:"output"` + Output string `json:"output,omitempty"` Error string `json:"error,omitempty"` Calls []CallFrame `json:"calls,omitempty"` } @@ -117,6 +117,9 @@ func bytesToHex(s []byte) string { } func bigToHex(n *big.Int) string { + if n == nil { + return "" + } return "0x" + n.Text(16) } From 75c6affab4440d65d2882c8b81dee343a4dfdf23 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 13 Oct 2021 13:32:37 +0200 Subject: [PATCH 07/30] eth/tracers: fix benchTracer --- eth/tracers/tracers_test.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index fb817fbc5667..17ce271c4cc0 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -35,6 +35,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers/native" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/tests" @@ -406,12 +407,19 @@ func BenchmarkTracers(b *testing.B) { if err := json.Unmarshal(blob, test); err != nil { b.Fatalf("failed to parse testcase: %v", err) } - benchTracer("callTracer", test, b) + newTracer := func() native.Tracer { + tracer, err := New("callTracer", new(Context)) + if err != nil { + b.Fatalf("failed to create call tracer: %v", err) + } + return tracer + } + benchTracer(newTracer, test, b) }) } } -func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { +func benchTracer(newTracer func() native.Tracer, 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 { @@ -438,21 +446,21 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { } _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) - // Create the tracer, the EVM environment and run it - tracer, err := New(tracerName, new(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}) - b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { + tracer := newTracer() + 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) } + _, err := tracer.GetResult() + if err != nil { + b.Fatal(err) + } + statedb.RevertToSnapshot(snap) } } From 7b105349c5fa98062fb12df4f747027220fff92f Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 13 Oct 2021 14:57:05 +0200 Subject: [PATCH 08/30] eth/tracers: test native call tracer --- eth/tracers/tracers_test.go | 46 ++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 17ce271c4cc0..7f5f19f1eb2d 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -205,10 +205,42 @@ func TestPrestateTracerCreate2(t *testing.T) { // 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) + newTracer := func() native.Tracer { + tracer, err := New("callTracerLegacy", new(Context)) + if err != nil { + t.Fatalf("failed to create call tracer: %v", err) + } + return tracer + } + + testCallTracer(newTracer, "call_tracer_legacy", t) +} + +func TestCallTracer(t *testing.T) { + newTracer := func() native.Tracer { + tracer, err := New("callTracer", new(Context)) + if err != nil { + t.Fatalf("failed to create call tracer: %v", err) + } + return tracer + } + + testCallTracer(newTracer, "call_tracer", t) } -func testCallTracer(tracer string, dirPath string, t *testing.T) { +func TestCallTracerNative(t *testing.T) { + newTracer := func() native.Tracer { + tracer, ok := native.New("callTracerNative") + if !ok { + t.Fatal("failed to create native call tracer") + } + return tracer + } + + testCallTracer(newTracer, "call_tracer", t) +} + +func testCallTracer(newTracer func() native.Tracer, 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) @@ -252,11 +284,7 @@ func testCallTracer(tracer string, dirPath string, t *testing.T) { } _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) - // Create the tracer, the EVM environment and run it - tracer, err := New(tracer, new(Context)) - if err != nil { - t.Fatalf("failed to create call tracer: %v", err) - } + tracer := newTracer() evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) msg, err := tx.AsMessage(signer, nil) @@ -288,10 +316,6 @@ func testCallTracer(tracer string, dirPath string, t *testing.T) { } } -func TestCallTracer(t *testing.T) { - testCallTracer("callTracer", "call_tracer", t) -} - // jsonEqual is similar to reflect.DeepEqual, but does a 'bounce' via json prior to // comparison func jsonEqual(x, y interface{}) bool { From ca5427ade094ca8220692ba1e5ffcfb91aa8ffa3 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 13 Oct 2021 15:03:38 +0200 Subject: [PATCH 09/30] eth/tracers: fix --- eth/tracers/native/call.go | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 8f67a91a6acf..43be50020e73 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -19,7 +19,7 @@ func init() { type CallFrame struct { Type string `json:"type"` From string `json:"from"` - To string `json:"to"` + To string `json:"to,omitempty"` Value string `json:"value,omitempty"` Gas string `json:"gas"` GasUsed string `json:"gasUsed"` @@ -82,23 +82,24 @@ func (t *CallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common. func (t *CallTracer) CaptureExit(output []byte, gasUsed uint64, err error) { size := len(t.callstack) - if size > 1 { - // 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 = "" - } + 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) } + t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) } func (t *CallTracer) GetResult() (json.RawMessage, error) { From d9371f300fe03ab12073a85c3c7eaa58a910527c Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Wed, 13 Oct 2021 15:06:31 +0200 Subject: [PATCH 10/30] eth/tracers: rm extra make Co-authored-by: Martin Holst Swende --- eth/tracers/native/call.go | 1 - 1 file changed, 1 deletion(-) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 43be50020e73..70b49f1ccd54 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -46,7 +46,6 @@ func (t *CallTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad Input: bytesToHex(input), Gas: uintToHex(gas), Value: bigToHex(value), - Calls: make([]CallFrame, 0), } if create { t.callstack[0].Type = "CREATE" From aff90d3f88cc18c9674a646ba031bcdb20cb8d2b Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 13 Oct 2021 15:07:39 +0200 Subject: [PATCH 11/30] eth/tracers: rm extra make --- eth/tracers/native/call.go | 1 - 1 file changed, 1 deletion(-) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 70b49f1ccd54..d990ac65c99d 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -74,7 +74,6 @@ func (t *CallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common. Input: bytesToHex(input), Gas: uintToHex(gas), Value: bigToHex(value), - Calls: make([]CallFrame, 0), } t.callstack = append(t.callstack, call) } From a2b0d3d48ed8ba789f170b1be88788bc38b68dff Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 13 Oct 2021 16:59:05 +0200 Subject: [PATCH 12/30] eth/tracers: make callFrame private --- eth/tracers/native/call.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index d990ac65c99d..a9bad83648ba 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -16,7 +16,7 @@ func init() { Register("callTracerNative", NewCallTracer) } -type CallFrame struct { +type callFrame struct { Type string `json:"type"` From string `json:"from"` To string `json:"to,omitempty"` @@ -26,20 +26,20 @@ type CallFrame struct { Input string `json:"input"` Output string `json:"output,omitempty"` Error string `json:"error,omitempty"` - Calls []CallFrame `json:"calls,omitempty"` + Calls []callFrame `json:"calls,omitempty"` } type CallTracer struct { - callstack []CallFrame + callstack []callFrame } func NewCallTracer() Tracer { - t := &CallTracer{callstack: make([]CallFrame, 1)} + 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{ + t.callstack[0] = callFrame{ Type: "CALL", From: addrToHex(from), To: addrToHex(to), @@ -67,7 +67,7 @@ func (t *CallTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos } func (t *CallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { - call := CallFrame{ + call := callFrame{ Type: typ.String(), From: addrToHex(from), To: addrToHex(to), From 18c4c17ba509f73c2662b2dd534499cb34a53f54 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 13 Oct 2021 17:14:06 +0200 Subject: [PATCH 13/30] eth/tracers: clean-up and comments --- eth/tracers/native/call.go | 6 +++++- eth/tracers/native/native.go | 14 +++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index a9bad83648ba..792c106f7d69 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -13,7 +13,7 @@ import ( ) func init() { - Register("callTracerNative", NewCallTracer) + register("callTracerNative", NewCallTracer) } type callFrame struct { @@ -33,7 +33,11 @@ type CallTracer struct { callstack []callFrame } +// NewCallTracer returns a native go tracer which tracks +// call frames of a tx, and implements vm.Tracer. func NewCallTracer() Tracer { + // First callframe contains tx context info + // and is populated on start and end. t := &CallTracer{callstack: make([]callFrame, 1)} return t } diff --git a/eth/tracers/native/native.go b/eth/tracers/native/native.go index 10c607fc43c7..3382d6b927cd 100644 --- a/eth/tracers/native/native.go +++ b/eth/tracers/native/native.go @@ -6,19 +6,27 @@ import ( "github.com/ethereum/go-ethereum/core/vm" ) +// Tracer interface extends vm.Tracer and additionally +// allows collecting the tracing result. type Tracer interface { vm.Tracer GetResult() (json.RawMessage, error) } -type Constructor func() Tracer +// constructor creates a new instance of a Tracer. +type constructor func() Tracer -var tracers map[string]Constructor = make(map[string]Constructor) +var tracers map[string]constructor = make(map[string]constructor) -func Register(name string, fn Constructor) { +// register makes native tracers in this directory which adhere +// to the `Tracer` interface available to the rest of the codebase. +// It is typically invoked in the `init()` function. +func register(name string, fn constructor) { tracers[name] = fn } +// New returns a new instance of a tracer, if one was +// registered under the given name. func New(name string) (Tracer, bool) { if fn, ok := tracers[name]; ok { return fn(), true From 0946623acce231333b8ede4e60c2614345cc8c4e Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 26 Oct 2021 12:27:26 +0200 Subject: [PATCH 14/30] eth/tracers: add license --- eth/tracers/native/call.go | 16 ++++++++++++++++ eth/tracers/native/native.go | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 792c106f7d69..98e1b35373d0 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -1,3 +1,19 @@ +// 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 . + package native import ( diff --git a/eth/tracers/native/native.go b/eth/tracers/native/native.go index 3382d6b927cd..4148c52c7c12 100644 --- a/eth/tracers/native/native.go +++ b/eth/tracers/native/native.go @@ -1,3 +1,19 @@ +// 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 . + package native import ( From 5db19c5922c4850bc2bdd6d50e5fbd46c8bd5e36 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 26 Oct 2021 14:34:59 +0200 Subject: [PATCH 15/30] eth/tracers: rework the model a bit --- core/vm/runtime/runtime_test.go | 2 +- eth/tracers/api.go | 28 +++++++---------- eth/tracers/native/call.go | 27 +++++++++------- eth/tracers/native/native.go | 51 ------------------------------ eth/tracers/tracer.go | 42 ++++++++++++------------- eth/tracers/tracer_test.go | 8 ++--- eth/tracers/tracers.go | 55 ++++++++++++++++++++++++++------- eth/tracers/tracers_test.go | 21 ++++++------- 8 files changed, 105 insertions(+), 129 deletions(-) delete mode 100644 eth/tracers/native/native.go diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 9f4bafbc7fd5..1e54c40f40a4 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -494,7 +494,7 @@ func BenchmarkSimpleLoop(b *testing.B) { //Execute(loopingCode, nil, &Config{ // EVMConfig: vm.Config{ // Debug: true, - // Tracer: tracer, + // JSTracer: tracer, // }}) // 100M gas benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", "", b) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 07764bc4ac7d..41bae92a523f 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -36,7 +36,6 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/eth/tracers/native" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" @@ -860,22 +859,17 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex } } // Native tracers take precedence - var ok bool - if tracer, ok = native.New(*config.Tracer); !ok { - if tracer, err = New(*config.Tracer, txctx); err != nil { - return nil, err - } - // TODO(s1na): do we need timeout for native tracers? - // Handle timeouts and RPC cancellations - deadlineCtx, cancel := context.WithTimeout(ctx, timeout) - go func() { - <-deadlineCtx.Done() - if deadlineCtx.Err() == context.DeadlineExceeded { - tracer.(*Tracer).Stop(errors.New("execution timeout")) - } - }() - defer cancel() + if tracer, err = New(*config.Tracer, txctx); err != nil { + return nil, err } + deadlineCtx, cancel := context.WithTimeout(ctx, timeout) + go func() { + <-deadlineCtx.Done() + if deadlineCtx.Err() == context.DeadlineExceeded { + tracer.(*JSTracer).Stop(errors.New("execution timeout")) + } + }() + defer cancel() case config == nil: tracer = vm.NewStructLogger(nil) @@ -909,7 +903,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex StructLogs: ethapi.FormatLogs(tracer.StructLogs()), }, nil - case native.Tracer: + case Tracer: return tracer.GetResult() default: diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 98e1b35373d0..fb7b0a198223 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -26,10 +26,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" ) func init() { - register("callTracerNative", NewCallTracer) + tracers.RegisterNativeTracer("callTracerNative", NewCallTracer) } type callFrame struct { @@ -45,20 +46,20 @@ type callFrame struct { Calls []callFrame `json:"calls,omitempty"` } -type CallTracer struct { +type callTracer struct { callstack []callFrame } // NewCallTracer returns a native go tracer which tracks // call frames of a tx, and implements vm.Tracer. -func NewCallTracer() Tracer { +func NewCallTracer() tracers.Tracer { // First callframe contains tx context info // and is populated on start and end. - t := &CallTracer{callstack: make([]callFrame, 1)} + 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) { +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), @@ -72,7 +73,7 @@ func (t *CallTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad } } -func (t *CallTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { +func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { t.callstack[0].Output = bytesToHex(output) t.callstack[0].GasUsed = uintToHex(gasUsed) if err != nil { @@ -80,13 +81,13 @@ func (t *CallTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, } } -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) 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) 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) { +func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { call := callFrame{ Type: typ.String(), From: addrToHex(from), @@ -98,7 +99,7 @@ func (t *CallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common. t.callstack = append(t.callstack, call) } -func (t *CallTracer) CaptureExit(output []byte, gasUsed uint64, err error) { +func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { size := len(t.callstack) if size <= 1 { return @@ -120,7 +121,7 @@ func (t *CallTracer) CaptureExit(output []byte, gasUsed uint64, err error) { t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) } -func (t *CallTracer) GetResult() (json.RawMessage, error) { +func (t *callTracer) GetResult() (json.RawMessage, error) { if len(t.callstack) != 1 { return nil, errors.New("incorrect number of top-level calls") } @@ -131,6 +132,10 @@ func (t *CallTracer) GetResult() (json.RawMessage, error) { return json.RawMessage(res), nil } +func (t *callTracer) Stop(err error) { + // TODO +} + func bytesToHex(s []byte) string { return "0x" + common.Bytes2Hex(s) } diff --git a/eth/tracers/native/native.go b/eth/tracers/native/native.go deleted file mode 100644 index 4148c52c7c12..000000000000 --- a/eth/tracers/native/native.go +++ /dev/null @@ -1,51 +0,0 @@ -// 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 . - -package native - -import ( - "encoding/json" - - "github.com/ethereum/go-ethereum/core/vm" -) - -// Tracer interface extends vm.Tracer and additionally -// allows collecting the tracing result. -type Tracer interface { - vm.Tracer - GetResult() (json.RawMessage, error) -} - -// constructor creates a new instance of a Tracer. -type constructor func() Tracer - -var tracers map[string]constructor = make(map[string]constructor) - -// register makes native tracers in this directory which adhere -// to the `Tracer` interface available to the rest of the codebase. -// It is typically invoked in the `init()` function. -func register(name string, fn constructor) { - tracers[name] = fn -} - -// New returns a new instance of a tracer, if one was -// registered under the given name. -func New(name string) (Tracer, bool) { - if fn, ok := tracers[name]; ok { - return fn(), true - } - return nil, false -} diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index ed5600453345..23a0221d102d 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -100,13 +100,13 @@ func (mw *memoryWrapper) slice(begin, end int64) []byte { if end < begin || begin < 0 { // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // runtime goes belly up https://github.com/golang/go/issues/15639. - log.Warn("Tracer accessed out of bound memory", "offset", begin, "end", end) + log.Warn("JSTracer accessed out of bound memory", "offset", begin, "end", end) return nil } if mw.memory.Len() < int(end) { // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // runtime goes belly up https://github.com/golang/go/issues/15639. - log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", begin, "size", end-begin) + log.Warn("JSTracer accessed out of bound memory", "available", mw.memory.Len(), "offset", begin, "size", end-begin) return nil } return mw.memory.GetCopy(begin, end-begin) @@ -117,7 +117,7 @@ func (mw *memoryWrapper) getUint(addr int64) *big.Int { if mw.memory.Len() < int(addr)+32 || addr < 0 { // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // runtime goes belly up https://github.com/golang/go/issues/15639. - log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32) + log.Warn("JSTracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32) return new(big.Int) } return new(big.Int).SetBytes(mw.memory.GetPtr(addr, 32)) @@ -160,7 +160,7 @@ func (sw *stackWrapper) peek(idx int) *big.Int { if len(sw.stack.Data()) <= idx || idx < 0 { // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // runtime goes belly up https://github.com/golang/go/issues/15639. - log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx) + log.Warn("JSTracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx) return new(big.Int) } return sw.stack.Back(idx).ToBig() @@ -365,7 +365,7 @@ func (r *frameResult) pushObject(vm *duktape.Context) { // Tracer provides an implementation of Tracer that evaluates a Javascript // function for each VM execution step. -type Tracer struct { +type JSTracer struct { vm *duktape.Context // Javascript VM instance tracerObject int // Stack index of the tracer JavaScript object @@ -409,12 +409,8 @@ type Context struct { // New instantiates a new tracer instance. code specifies a Javascript snippet, // which must evaluate to an expression returning an object with 'step', 'fault' // and 'result' functions. -func New(code string, ctx *Context) (*Tracer, error) { - // Resolve any tracers by name and assemble the tracer object - if tracer, ok := tracer(code); ok { - code = tracer - } - tracer := &Tracer{ +func newJsTracer(code string, ctx *Context) (*JSTracer, error) { + tracer := &JSTracer{ vm: duktape.New(), ctx: make(map[string]interface{}), opWrapper: new(opWrapper), @@ -522,7 +518,7 @@ func New(code string, ctx *Context) (*Tracer, error) { if start < 0 || start > end || end > len(blob) { // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // runtime goes belly up https://github.com/golang/go/issues/15639. - log.Warn("Tracer accessed out of bound memory", "available", len(blob), "offset", start, "size", size) + log.Warn("JSTracer accessed out of bound memory", "available", len(blob), "offset", start, "size", size) ctx.PushFixedBuffer(0) return 1 } @@ -566,7 +562,7 @@ func New(code string, ctx *Context) (*Tracer, error) { tracer.traceCallFrames = hasEnter tracer.traceSteps = hasStep - // Tracer is valid, inject the big int library to access large numbers + // JSTracer is valid, inject the big int library to access large numbers tracer.vm.EvalString(bigIntegerJS) tracer.vm.PutGlobalString("bigInt") @@ -627,14 +623,14 @@ func New(code string, ctx *Context) (*Tracer, error) { } // Stop terminates execution of the tracer at the first opportune moment. -func (jst *Tracer) Stop(err error) { +func (jst *JSTracer) Stop(err error) { jst.reason = err atomic.StoreUint32(&jst.interrupt, 1) } // call executes a method on a JS object, catching any errors, formatting and // returning them as error objects. -func (jst *Tracer) call(noret bool, method string, args ...string) (json.RawMessage, error) { +func (jst *JSTracer) call(noret bool, method string, args ...string) (json.RawMessage, error) { // Execute the JavaScript call and return any error jst.vm.PushString(method) for _, arg := range args { @@ -670,7 +666,7 @@ func wrapError(context string, err error) error { } // CaptureStart implements the Tracer interface to initialize the tracing operation. -func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { +func (jst *JSTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { jst.ctx["type"] = "CALL" if create { jst.ctx["type"] = "CREATE" @@ -700,7 +696,7 @@ func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr } // 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, scope *vm.ScopeContext, rData []byte, depth int, err error) { +func (jst *JSTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { if !jst.traceSteps { return } @@ -736,7 +732,7 @@ func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost } // CaptureFault implements the Tracer interface to trace an execution fault -func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { +func (jst *JSTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { if jst.err != nil { return } @@ -750,7 +746,7 @@ func (jst *Tracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost } // CaptureEnd is called after the call finishes to finalize the tracing. -func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { +func (jst *JSTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { jst.ctx["output"] = output jst.ctx["time"] = t.String() jst.ctx["gasUsed"] = gasUsed @@ -761,7 +757,7 @@ func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, er } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (jst *Tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (jst *JSTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { if !jst.traceCallFrames { return } @@ -791,7 +787,7 @@ func (jst *Tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Ad // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (jst *Tracer) CaptureExit(output []byte, gasUsed uint64, err error) { +func (jst *JSTracer) CaptureExit(output []byte, gasUsed uint64, err error) { if !jst.traceCallFrames { return } @@ -815,7 +811,7 @@ func (jst *Tracer) CaptureExit(output []byte, gasUsed uint64, err error) { } // GetResult calls the Javascript 'result' function and returns its value, or any accumulated error -func (jst *Tracer) GetResult() (json.RawMessage, error) { +func (jst *JSTracer) GetResult() (json.RawMessage, error) { // Transform the context into a JavaScript object and inject into the state obj := jst.vm.PushObject() @@ -837,7 +833,7 @@ func (jst *Tracer) GetResult() (json.RawMessage, error) { } // addToObj pushes a field to a JS object. -func (jst *Tracer) addToObj(obj int, key string, val interface{}) { +func (jst *JSTracer) addToObj(obj int, key string, val interface{}) { pushValue(jst.vm, val) jst.vm.PutPropString(obj, key) } diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 3decca225d7a..6834947f0542 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -58,7 +58,7 @@ func testCtx() *vmContext { return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} } -func runTrace(tracer *Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) { +func runTrace(tracer Tracer, vmctx *vmContext, chaincfg *params.ChainConfig) (json.RawMessage, error) { env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, chaincfg, vm.Config{Debug: true, Tracer: tracer}) var ( startGas uint64 = 10000 @@ -168,7 +168,7 @@ func TestHaltBetweenSteps(t *testing.T) { // TestNoStepExec tests a regular value transfer (no exec), and accessing the statedb // in 'result' func TestNoStepExec(t *testing.T) { - runEmptyTrace := func(tracer *Tracer, vmctx *vmContext) (json.RawMessage, error) { + runEmptyTrace := func(tracer Tracer, vmctx *vmContext) (json.RawMessage, error) { env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) startGas := uint64(10000) contract := vm.NewContract(account{}, account{}, big.NewInt(0), startGas) @@ -223,7 +223,7 @@ func TestIsPrecompile(t *testing.T) { t.Error(err) } if string(res) != "false" { - t.Errorf("Tracer should not consider blake2f as precompile in byzantium") + t.Errorf("JSTracer should not consider blake2f as precompile in byzantium") } tracer, _ = New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context)) @@ -233,7 +233,7 @@ func TestIsPrecompile(t *testing.T) { t.Error(err) } if string(res) != "true" { - t.Errorf("Tracer should consider blake2f as precompile in istanbul") + t.Errorf("JSTracer should consider blake2f as precompile in istanbul") } } diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go index 4e1ef23ad2f8..9eef6bfb10f0 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/tracers.go @@ -18,14 +18,55 @@ package tracers import ( + "encoding/json" + "fmt" "strings" "unicode" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers/internal/tracers" ) -// all contains all the built in JavaScript tracers by name. -var all = make(map[string]string) +// Tracer interface extends vm.JSTracer and additionally +// allows collecting the tracing result. +type Tracer interface { + vm.Tracer + GetResult() (json.RawMessage, error) + // Stop terminates execution of the tracer at the first opportune moment. + Stop(err error) +} + +var ( + nativeTracers map[string]func() Tracer = make(map[string]func() Tracer) + jsTracers = make(map[string]string) +) + +// RegisterNativeTracer makes native tracers in this directory which adhere +// to the `JSTracer` interface available to the rest of the codebase. +// It is typically invoked in the `init()` function. +func RegisterNativeTracer(name string, ctor func() Tracer) { + nativeTracers[name] = ctor +} + +// New returns a new instance of a tracer, +// 1. If 'code' is the name of a registered native tracer, then that tracer +// is instantiated and returned +// 2. If 'code' is the name of a registered js-tracer, then that tracer is +// instantiated and returned +// 3. Otherwise, the code is interpreted as the js code of a js-tracer, and +// is evaluated and returned. +func New(code string, ctx *Context) (Tracer, error) { + // Resolve native tracer + if fn, ok := nativeTracers[code]; ok { + return fn(), nil + } + panic(fmt.Sprintf("no native tracer %v found", code)) + // Resolve js-tracers by name and assemble the tracer object + if tracer, ok := jsTracers[code]; ok { + code = tracer + } + return newJsTracer(code, ctx) +} // camel converts a snake cased input string into a camel cased output. func camel(str string) string { @@ -40,14 +81,6 @@ func camel(str string) string { func init() { for _, file := range tracers.AssetNames() { name := camel(strings.TrimSuffix(file, ".js")) - all[name] = string(tracers.MustAsset(file)) - } -} - -// tracer retrieves a specific JavaScript tracer by name. -func tracer(name string) (string, bool) { - if tracer, ok := all[name]; ok { - return tracer, true + jsTracers[name] = string(tracers.MustAsset(file)) } - return "", false } diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 7f5f19f1eb2d..c0b43029443c 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -35,7 +35,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/tracers/native" + //"github.com/ethereum/go-ethereum/eth/tracers/native" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/tests" @@ -205,7 +205,7 @@ func TestPrestateTracerCreate2(t *testing.T) { // Iterates over all the input-output datasets in the tracer test harness and // runs the JavaScript tracers against them. func TestCallTracerLegacy(t *testing.T) { - newTracer := func() native.Tracer { + newTracer := func() Tracer { tracer, err := New("callTracerLegacy", new(Context)) if err != nil { t.Fatalf("failed to create call tracer: %v", err) @@ -217,7 +217,7 @@ func TestCallTracerLegacy(t *testing.T) { } func TestCallTracer(t *testing.T) { - newTracer := func() native.Tracer { + newTracer := func() Tracer { tracer, err := New("callTracer", new(Context)) if err != nil { t.Fatalf("failed to create call tracer: %v", err) @@ -229,18 +229,17 @@ func TestCallTracer(t *testing.T) { } func TestCallTracerNative(t *testing.T) { - newTracer := func() native.Tracer { - tracer, ok := native.New("callTracerNative") - if !ok { - t.Fatal("failed to create native call tracer") + newTracer := func() Tracer { + tracer, err := New("callTracerNative", nil /* TODO? */) + if err != nil { + t.Fatalf("failed to create native call tracer: %v", err) } return tracer } - testCallTracer(newTracer, "call_tracer", t) } -func testCallTracer(newTracer func() native.Tracer, dirPath string, t *testing.T) { +func testCallTracer(newTracer func() Tracer, 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) @@ -431,7 +430,7 @@ func BenchmarkTracers(b *testing.B) { if err := json.Unmarshal(blob, test); err != nil { b.Fatalf("failed to parse testcase: %v", err) } - newTracer := func() native.Tracer { + newTracer := func() Tracer { tracer, err := New("callTracer", new(Context)) if err != nil { b.Fatalf("failed to create call tracer: %v", err) @@ -443,7 +442,7 @@ func BenchmarkTracers(b *testing.B) { } } -func benchTracer(newTracer func() native.Tracer, test *callTracerTest, b *testing.B) { +func benchTracer(newTracer func() Tracer, 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 { From 360e3ba352c4b729eadc0dd52358996039203535 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 28 Oct 2021 11:47:53 +0200 Subject: [PATCH 16/30] eth/tracers: move tracecall tests to subpackage --- eth/tracers/testing/calltrace_test.go | 242 ++++++++++++++++++++++++++ eth/tracers/tracers.go | 2 - eth/tracers/tracers_test.go | 195 --------------------- 3 files changed, 242 insertions(+), 197 deletions(-) create mode 100644 eth/tracers/testing/calltrace_test.go diff --git a/eth/tracers/testing/calltrace_test.go b/eth/tracers/testing/calltrace_test.go new file mode 100644 index 000000000000..fb96e495f169 --- /dev/null +++ b/eth/tracers/testing/calltrace_test.go @@ -0,0 +1,242 @@ +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, 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) + } +} diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go index 9eef6bfb10f0..356cd012b1c0 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/tracers.go @@ -19,7 +19,6 @@ package tracers import ( "encoding/json" - "fmt" "strings" "unicode" @@ -60,7 +59,6 @@ func New(code string, ctx *Context) (Tracer, error) { if fn, ok := nativeTracers[code]; ok { return fn(), nil } - panic(fmt.Sprintf("no native tracer %v found", code)) // Resolve js-tracers by name and assemble the tracer object if tracer, ok := jsTracers[code]; ok { code = tracer diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index c0b43029443c..6ad3abe605fc 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -20,11 +20,8 @@ import ( "crypto/ecdsa" "crypto/rand" "encoding/json" - "io/ioutil" "math/big" - "path/filepath" "reflect" - "strings" "testing" "github.com/ethereum/go-ethereum/common" @@ -35,9 +32,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" - //"github.com/ethereum/go-ethereum/eth/tracers/native" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/tests" ) @@ -202,119 +197,6 @@ func TestPrestateTracerCreate2(t *testing.T) { } } -// Iterates over all the input-output datasets in the tracer test harness and -// runs the JavaScript tracers against them. -func TestCallTracerLegacy(t *testing.T) { - newTracer := func() Tracer { - tracer, err := New("callTracerLegacy", new(Context)) - if err != nil { - t.Fatalf("failed to create call tracer: %v", err) - } - return tracer - } - - testCallTracer(newTracer, "call_tracer_legacy", t) -} - -func TestCallTracer(t *testing.T) { - newTracer := func() Tracer { - tracer, err := New("callTracer", new(Context)) - if err != nil { - t.Fatalf("failed to create call tracer: %v", err) - } - return tracer - } - - testCallTracer(newTracer, "call_tracer", t) -} - -func TestCallTracerNative(t *testing.T) { - newTracer := func() Tracer { - tracer, err := New("callTracerNative", nil /* TODO? */) - if err != nil { - t.Fatalf("failed to create native call tracer: %v", err) - } - return tracer - } - testCallTracer(newTracer, "call_tracer", t) -} - -func testCallTracer(newTracer func() Tracer, 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() - - // Call tracer test found, read if from disk - blob, err := ioutil.ReadFile(filepath.Join("testdata", dirPath, file.Name())) - if err != nil { - t.Fatalf("failed to read testcase: %v", err) - } - test := new(callTracerTest) - if err := json.Unmarshal(blob, test); err != nil { - t.Fatalf("failed to parse testcase: %v", err) - } - // Configure a blockchain with the given prestate - tx := new(types.Transaction) - if err := rlp.DecodeBytes(common.FromHex(test.Input), tx); err != nil { - t.Fatalf("failed to parse testcase input: %v", err) - } - 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 := newTracer() - 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 { @@ -410,80 +292,3 @@ func BenchmarkTransactionTrace(b *testing.B) { tracer.Reset() } } - -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) - } - newTracer := func() Tracer { - tracer, err := New("callTracer", new(Context)) - if err != nil { - b.Fatalf("failed to create call tracer: %v", err) - } - return tracer - } - benchTracer(newTracer, test, b) - }) - } -} - -func benchTracer(newTracer func() Tracer, 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 := newTracer() - 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) - } - _, err := tracer.GetResult() - if err != nil { - b.Fatal(err) - } - - statedb.RevertToSnapshot(snap) - } -} From c55a903e7736ea874622f6bd67b835e9e2363d22 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 2 Nov 2021 16:45:03 +0100 Subject: [PATCH 17/30] cmd/geth: load native tracers --- cmd/geth/main.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index d003d590ed28..ecf784085916 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -39,6 +39,10 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" + + // Force-load the native, to trigger registration + _ "github.com/ethereum/go-ethereum/eth/tracers/native" + "gopkg.in/urfave/cli.v1" ) From a9c2051d3f5befedd953a6f3f3ac40c57ba216d8 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 2 Nov 2021 16:49:25 +0100 Subject: [PATCH 18/30] eth/tracers: minor fix --- eth/tracers/tracers.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go index 356cd012b1c0..e9ac5112a388 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/tracers.go @@ -26,7 +26,7 @@ import ( "github.com/ethereum/go-ethereum/eth/tracers/internal/tracers" ) -// Tracer interface extends vm.JSTracer and additionally +// Tracer interface extends vm.Tracer and additionally // allows collecting the tracing result. type Tracer interface { vm.Tracer @@ -40,9 +40,9 @@ var ( jsTracers = make(map[string]string) ) -// RegisterNativeTracer makes native tracers in this directory which adhere -// to the `JSTracer` interface available to the rest of the codebase. -// It is typically invoked in the `init()` function. +// RegisterNativeTracer makes native tracers which adhere +// to the `Tracer` interface available to the rest of the codebase. +// It is typically invoked in the `init()` function, e.g. see the `native/call.go`. func RegisterNativeTracer(name string, ctor func() Tracer) { nativeTracers[name] = ctor } From c242c82e07b1c287dc89d7b0e1ad9fe1cd6f2a1b Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 2 Nov 2021 17:16:57 +0100 Subject: [PATCH 19/30] eth/tracers: impl stop --- eth/tracers/native/call.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index fb7b0a198223..599de654c17f 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -22,6 +22,7 @@ import ( "math/big" "strconv" "strings" + "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" @@ -48,6 +49,8 @@ type callFrame struct { 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 @@ -88,6 +91,12 @@ func (t *callTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos } 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), @@ -129,11 +138,12 @@ func (t *callTracer) GetResult() (json.RawMessage, error) { if err != nil { return nil, err } - return json.RawMessage(res), nil + return json.RawMessage(res), t.reason } func (t *callTracer) Stop(err error) { - // TODO + t.reason = err + atomic.StoreUint32(&t.interrupt, 1) } func bytesToHex(s []byte) string { From 60f5734e2678f516154c131c83d2bb60d87b65af Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Tue, 2 Nov 2021 19:52:46 +0100 Subject: [PATCH 20/30] eth/tracers: add native noop tracer --- eth/tracers/native/noop.go | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 eth/tracers/native/noop.go diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go new file mode 100644 index 000000000000..554bb18f14a6 --- /dev/null +++ b/eth/tracers/native/noop.go @@ -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) { +} From 89bac76e0cb5c6f157bb65531f038fc44b814d9b Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi <1591639+s1na@users.noreply.github.com> Date: Wed, 3 Nov 2021 17:03:55 +0100 Subject: [PATCH 21/30] renamings Co-authored-by: Martin Holst Swende --- core/vm/runtime/runtime_test.go | 2 +- eth/tracers/api.go | 2 +- eth/tracers/tracer_test.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 1e54c40f40a4..9f4bafbc7fd5 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -494,7 +494,7 @@ func BenchmarkSimpleLoop(b *testing.B) { //Execute(loopingCode, nil, &Config{ // EVMConfig: vm.Config{ // Debug: true, - // JSTracer: tracer, + // Tracer: tracer, // }}) // 100M gas benchmarkNonModifyingCode(100000000, staticCallIdentity, "staticcall-identity-100M", "", b) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 41bae92a523f..dd66996d4337 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -866,7 +866,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex go func() { <-deadlineCtx.Done() if deadlineCtx.Err() == context.DeadlineExceeded { - tracer.(*JSTracer).Stop(errors.New("execution timeout")) + tracer.Stop(errors.New("execution timeout")) } }() defer cancel() diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 6834947f0542..defd12d3985a 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -223,7 +223,7 @@ func TestIsPrecompile(t *testing.T) { t.Error(err) } if string(res) != "false" { - t.Errorf("JSTracer should not consider blake2f as precompile in byzantium") + t.Errorf("Tracer should not consider blake2f as precompile in byzantium") } tracer, _ = New("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", new(Context)) @@ -233,7 +233,7 @@ func TestIsPrecompile(t *testing.T) { t.Error(err) } if string(res) != "true" { - t.Errorf("JSTracer should consider blake2f as precompile in istanbul") + t.Errorf("Tracer should consider blake2f as precompile in istanbul") } } From 91a7c094b570af11ef3e6a159f3f30201730db84 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Wed, 3 Nov 2021 17:26:10 +0100 Subject: [PATCH 22/30] eth/tracers: more renamings --- eth/tracers/api.go | 2 +- eth/tracers/tracer.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index dd66996d4337..6d15b5bc6a2a 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -866,7 +866,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex go func() { <-deadlineCtx.Done() if deadlineCtx.Err() == context.DeadlineExceeded { - tracer.Stop(errors.New("execution timeout")) + tracer.(Tracer).Stop(errors.New("execution timeout")) } }() defer cancel() diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 23a0221d102d..769c0d34b54b 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -100,13 +100,13 @@ func (mw *memoryWrapper) slice(begin, end int64) []byte { if end < begin || begin < 0 { // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // runtime goes belly up https://github.com/golang/go/issues/15639. - log.Warn("JSTracer accessed out of bound memory", "offset", begin, "end", end) + log.Warn("Tracer accessed out of bound memory", "offset", begin, "end", end) return nil } if mw.memory.Len() < int(end) { // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // runtime goes belly up https://github.com/golang/go/issues/15639. - log.Warn("JSTracer accessed out of bound memory", "available", mw.memory.Len(), "offset", begin, "size", end-begin) + log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", begin, "size", end-begin) return nil } return mw.memory.GetCopy(begin, end-begin) @@ -117,7 +117,7 @@ func (mw *memoryWrapper) getUint(addr int64) *big.Int { if mw.memory.Len() < int(addr)+32 || addr < 0 { // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // runtime goes belly up https://github.com/golang/go/issues/15639. - log.Warn("JSTracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32) + log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32) return new(big.Int) } return new(big.Int).SetBytes(mw.memory.GetPtr(addr, 32)) @@ -160,7 +160,7 @@ func (sw *stackWrapper) peek(idx int) *big.Int { if len(sw.stack.Data()) <= idx || idx < 0 { // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // runtime goes belly up https://github.com/golang/go/issues/15639. - log.Warn("JSTracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx) + log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx) return new(big.Int) } return sw.stack.Back(idx).ToBig() @@ -518,7 +518,7 @@ func newJsTracer(code string, ctx *Context) (*JSTracer, error) { if start < 0 || start > end || end > len(blob) { // TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go // runtime goes belly up https://github.com/golang/go/issues/15639. - log.Warn("JSTracer accessed out of bound memory", "available", len(blob), "offset", start, "size", size) + log.Warn("Tracer accessed out of bound memory", "available", len(blob), "offset", start, "size", size) ctx.PushFixedBuffer(0) return 1 } @@ -562,7 +562,7 @@ func newJsTracer(code string, ctx *Context) (*JSTracer, error) { tracer.traceCallFrames = hasEnter tracer.traceSteps = hasStep - // JSTracer is valid, inject the big int library to access large numbers + // Tracer is valid, inject the big int library to access large numbers tracer.vm.EvalString(bigIntegerJS) tracer.vm.PutGlobalString("bigInt") From 2a69a3707eb389fd47c0d5a5cd36fe26db272cc5 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 3 Nov 2021 20:54:48 +0100 Subject: [PATCH 23/30] eth/tracers: make jstracer non-exported, avoid cast --- eth/tracers/api.go | 29 ++++++++++++++--------------- eth/tracers/tracer.go | 28 ++++++++++++++-------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 6d15b5bc6a2a..561134cc4a7c 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -850,7 +850,9 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex txContext = core.NewEVMTxContext(message) ) switch { - case config != nil && config.Tracer != nil: + case config == nil: + tracer = vm.NewStructLogger(nil) + case config.Tracer != nil: // Define a meaningful timeout of a single transaction trace timeout := defaultTraceTimeout if config.Timeout != nil { @@ -858,22 +860,19 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex return nil, err } } - // Native tracers take precedence - if tracer, err = New(*config.Tracer, txctx); err != nil { + if t, err := New(*config.Tracer, txctx); err != nil { return nil, err + } else { + deadlineCtx, cancel := context.WithTimeout(ctx, timeout) + go func() { + <-deadlineCtx.Done() + if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) { + t.Stop(errors.New("execution timeout")) + } + }() + defer cancel() + tracer = t } - deadlineCtx, cancel := context.WithTimeout(ctx, timeout) - go func() { - <-deadlineCtx.Done() - if deadlineCtx.Err() == context.DeadlineExceeded { - tracer.(Tracer).Stop(errors.New("execution timeout")) - } - }() - defer cancel() - - case config == nil: - tracer = vm.NewStructLogger(nil) - default: tracer = vm.NewStructLogger(config.LogConfig) } diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 769c0d34b54b..c85923f69931 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -363,9 +363,9 @@ func (r *frameResult) pushObject(vm *duktape.Context) { vm.PutPropString(obj, "getError") } -// Tracer provides an implementation of Tracer that evaluates a Javascript +// jsTracer provides an implementation of Tracer that evaluates a Javascript // function for each VM execution step. -type JSTracer struct { +type jsTracer struct { vm *duktape.Context // Javascript VM instance tracerObject int // Stack index of the tracer JavaScript object @@ -409,8 +409,8 @@ type Context struct { // New instantiates a new tracer instance. code specifies a Javascript snippet, // which must evaluate to an expression returning an object with 'step', 'fault' // and 'result' functions. -func newJsTracer(code string, ctx *Context) (*JSTracer, error) { - tracer := &JSTracer{ +func newJsTracer(code string, ctx *Context) (*jsTracer, error) { + tracer := &jsTracer{ vm: duktape.New(), ctx: make(map[string]interface{}), opWrapper: new(opWrapper), @@ -623,14 +623,14 @@ func newJsTracer(code string, ctx *Context) (*JSTracer, error) { } // Stop terminates execution of the tracer at the first opportune moment. -func (jst *JSTracer) Stop(err error) { +func (jst *jsTracer) Stop(err error) { jst.reason = err atomic.StoreUint32(&jst.interrupt, 1) } // call executes a method on a JS object, catching any errors, formatting and // returning them as error objects. -func (jst *JSTracer) call(noret bool, method string, args ...string) (json.RawMessage, error) { +func (jst *jsTracer) call(noret bool, method string, args ...string) (json.RawMessage, error) { // Execute the JavaScript call and return any error jst.vm.PushString(method) for _, arg := range args { @@ -666,7 +666,7 @@ func wrapError(context string, err error) error { } // CaptureStart implements the Tracer interface to initialize the tracing operation. -func (jst *JSTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { +func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { jst.ctx["type"] = "CALL" if create { jst.ctx["type"] = "CREATE" @@ -696,7 +696,7 @@ func (jst *JSTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad } // CaptureState implements the Tracer interface to trace a single step of VM execution. -func (jst *JSTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { +func (jst *jsTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { if !jst.traceSteps { return } @@ -732,7 +732,7 @@ func (jst *JSTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos } // CaptureFault implements the Tracer interface to trace an execution fault -func (jst *JSTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { +func (jst *jsTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { if jst.err != nil { return } @@ -746,7 +746,7 @@ func (jst *JSTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos } // CaptureEnd is called after the call finishes to finalize the tracing. -func (jst *JSTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { +func (jst *jsTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { jst.ctx["output"] = output jst.ctx["time"] = t.String() jst.ctx["gasUsed"] = gasUsed @@ -757,7 +757,7 @@ func (jst *JSTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (jst *JSTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (jst *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { if !jst.traceCallFrames { return } @@ -787,7 +787,7 @@ func (jst *JSTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common. // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (jst *JSTracer) CaptureExit(output []byte, gasUsed uint64, err error) { +func (jst *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) { if !jst.traceCallFrames { return } @@ -811,7 +811,7 @@ func (jst *JSTracer) CaptureExit(output []byte, gasUsed uint64, err error) { } // GetResult calls the Javascript 'result' function and returns its value, or any accumulated error -func (jst *JSTracer) GetResult() (json.RawMessage, error) { +func (jst *jsTracer) GetResult() (json.RawMessage, error) { // Transform the context into a JavaScript object and inject into the state obj := jst.vm.PushObject() @@ -833,7 +833,7 @@ func (jst *JSTracer) GetResult() (json.RawMessage, error) { } // addToObj pushes a field to a JS object. -func (jst *JSTracer) addToObj(obj int, key string, val interface{}) { +func (jst *jsTracer) addToObj(obj int, key string, val interface{}) { pushValue(jst.vm, val) jst.vm.PutPropString(obj, key) } From 4e2de461d7c166682b81772d5b4836ea18e089a9 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 3 Nov 2021 20:58:50 +0100 Subject: [PATCH 24/30] eth/tracers, core/vm: rename vm.Tracer to vm.EVMLogger for clarity --- cmd/evm/internal/t8ntool/execution.go | 2 +- cmd/evm/internal/t8ntool/transition.go | 8 ++++---- cmd/evm/runner.go | 2 +- cmd/evm/staterunner.go | 2 +- core/vm/interpreter.go | 16 ++++++++-------- core/vm/logger.go | 10 +++++----- eth/tracers/api.go | 2 +- eth/tracers/native/call.go | 2 +- eth/tracers/tracers.go | 6 +++--- 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index fae65767be7f..cedf9662727b 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -96,7 +96,7 @@ type rejectedTx struct { // Apply applies a set of transactions to a pre-state func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, txs types.Transactions, miningReward int64, - getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.Tracer, err error)) (*state.StateDB, *ExecutionResult, error) { + getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error)) (*state.StateDB, *ExecutionResult, error) { // Capture errors for BLOCKHASH operation, if we haven't been supplied the // required blockhashes diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 7407ed0a44b5..f106b537b9f7 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -89,10 +89,10 @@ func Transition(ctx *cli.Context) error { var ( err error - tracer vm.Tracer + tracer vm.EVMLogger baseDir = "" ) - var getTracer func(txIndex int, txHash common.Hash) (vm.Tracer, error) + var getTracer func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) // If user specified a basedir, make sure it exists if ctx.IsSet(OutputBasedir.Name) { @@ -119,7 +119,7 @@ func Transition(ctx *cli.Context) error { prevFile.Close() } }() - getTracer = func(txIndex int, txHash common.Hash) (vm.Tracer, error) { + getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { if prevFile != nil { prevFile.Close() } @@ -131,7 +131,7 @@ func Transition(ctx *cli.Context) error { return vm.NewJSONLogger(logConfig, traceFile), nil } } else { - getTracer = func(txIndex int, txHash common.Hash) (tracer vm.Tracer, err error) { + getTracer = func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error) { return nil, nil } } diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index cedbd228151d..447bb2c2e66d 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -116,7 +116,7 @@ func runCmd(ctx *cli.Context) error { } var ( - tracer vm.Tracer + tracer vm.EVMLogger debugLogger *vm.StructLogger statedb *state.StateDB chainConfig *params.ChainConfig diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index ab27046091ef..5e9bf696b669 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -65,7 +65,7 @@ func stateTestCmd(ctx *cli.Context) error { EnableReturnData: !ctx.GlobalBool(DisableReturnDataFlag.Name), } var ( - tracer vm.Tracer + tracer vm.EVMLogger debugger *vm.StructLogger ) switch { diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 9fb83799c98c..92d33388f62c 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -27,11 +27,11 @@ import ( // Config are the configuration options for the Interpreter type Config struct { - Debug bool // Enables debugging - Tracer Tracer // Opcode logger - NoRecursion bool // Disables call, callcode, delegate call and create - NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) - EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages + Debug bool // Enables debugging + Tracer EVMLogger // Opcode logger + NoRecursion bool // Disables call, callcode, delegate call and create + NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) + EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages JumpTable [256]*operation // EVM instruction table, automatically populated if unset @@ -152,9 +152,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( pc = uint64(0) // program counter cost uint64 // copies used by tracer - pcCopy uint64 // needed for the deferred Tracer - gasCopy uint64 // for Tracer to log gas remaining before execution - logged bool // deferred Tracer should ignore already logged steps + pcCopy uint64 // needed for the deferred EVMLogger + gasCopy uint64 // for EVMLogger to log gas remaining before execution + logged bool // deferred EVMLogger 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, diff --git a/core/vm/logger.go b/core/vm/logger.go index 52dc0b8a0ec1..048b84ff6f45 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -98,12 +98,12 @@ func (s *StructLog) ErrorString() string { return "" } -// Tracer is used to collect execution traces from an EVM transaction +// EVMLogger is used to collect execution traces from an EVM transaction // execution. CaptureState is called for each step of the VM with the // current VM state. // Note that reference types are actual VM data structures; make copies // if you need to retain them beyond the current call. -type Tracer interface { +type EVMLogger interface { CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) @@ -112,7 +112,7 @@ type Tracer interface { CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) } -// StructLogger is an EVM state logger and implements Tracer. +// StructLogger is an EVM state logger and implements EVMLogger. // // StructLogger can capture state based on the given Log configuration and also keeps // a track record of modified storage which is used in reporting snapshots of the @@ -145,7 +145,7 @@ func (l *StructLogger) Reset() { l.err = nil } -// CaptureStart implements the Tracer interface to initialize the tracing operation. +// CaptureStart implements the EVMLogger interface to initialize the tracing operation. func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { } @@ -210,7 +210,7 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui l.logs = append(l.logs, log) } -// CaptureFault implements the Tracer interface to trace an execution fault +// CaptureFault implements the EVMLogger interface to trace an execution fault // while running an opcode. func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 561134cc4a7c..7bc1de439abb 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -845,7 +845,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { // Assemble the structured logger or the JavaScript tracer var ( - tracer vm.Tracer + tracer vm.EVMLogger err error txContext = core.NewEVMTxContext(message) ) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 599de654c17f..60426891a372 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -54,7 +54,7 @@ type callTracer struct { } // NewCallTracer returns a native go tracer which tracks -// call frames of a tx, and implements vm.Tracer. +// 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. diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go index e9ac5112a388..03ab8f7beee2 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/tracers.go @@ -26,10 +26,10 @@ import ( "github.com/ethereum/go-ethereum/eth/tracers/internal/tracers" ) -// Tracer interface extends vm.Tracer and additionally +// Tracer interface extends vm.EVMLogger and additionally // allows collecting the tracing result. type Tracer interface { - vm.Tracer + vm.EVMLogger GetResult() (json.RawMessage, error) // Stop terminates execution of the tracer at the first opportune moment. Stop(err error) @@ -41,7 +41,7 @@ var ( ) // RegisterNativeTracer makes native tracers which adhere -// to the `Tracer` interface available to the rest of the codebase. +// to the `EVMLogger` interface available to the rest of the codebase. // It is typically invoked in the `init()` function, e.g. see the `native/call.go`. func RegisterNativeTracer(name string, ctor func() Tracer) { nativeTracers[name] = ctor From de953f57144fbe9729a9dd55405da05c44e00b36 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 4 Nov 2021 17:42:35 +0100 Subject: [PATCH 25/30] eth/tracers: minor comment fix --- eth/tracers/tracers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/tracers/tracers.go b/eth/tracers/tracers.go index 03ab8f7beee2..79534c63655d 100644 --- a/eth/tracers/tracers.go +++ b/eth/tracers/tracers.go @@ -41,7 +41,7 @@ var ( ) // RegisterNativeTracer makes native tracers which adhere -// to the `EVMLogger` interface available to the rest of the codebase. +// to the `Tracer` interface available to the rest of the codebase. // It is typically invoked in the `init()` function, e.g. see the `native/call.go`. func RegisterNativeTracer(name string, ctor func() Tracer) { nativeTracers[name] = ctor From 2af565b18ba11d84c3bc43e5874b3d493bdf8f63 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 4 Nov 2021 18:56:41 +0100 Subject: [PATCH 26/30] eth/tracers/testing: lint nitpicks --- eth/tracers/testing/calltrace_test.go | 1 + eth/tracers/tracers_test.go | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/eth/tracers/testing/calltrace_test.go b/eth/tracers/testing/calltrace_test.go index fb96e495f169..d34821d41d4c 100644 --- a/eth/tracers/testing/calltrace_test.go +++ b/eth/tracers/testing/calltrace_test.go @@ -20,6 +20,7 @@ import ( "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" ) diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 6ad3abe605fc..9f5fcd3fd512 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -108,14 +108,6 @@ type callContext struct { Miner common.Address `json:"miner"` } -// 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"` -} - func TestPrestateTracerCreate2(t *testing.T) { unsignedTx := types.NewTransaction(1, common.HexToAddress("0x00000000000000000000000000000000deadbeef"), new(big.Int), 5000000, big.NewInt(1), []byte{}) From 01bb908790a369c1bb9d3937df9325c6857bf855 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 4 Nov 2021 21:01:41 +0100 Subject: [PATCH 27/30] core,eth: cancel evm on nativecalltracer stop --- core/vm/access_list_tracer.go | 2 +- core/vm/evm.go | 12 ++++++------ core/vm/instructions.go | 2 +- core/vm/logger.go | 6 +++--- core/vm/logger_json.go | 2 +- eth/tracers/native/call.go | 4 ++-- eth/tracers/native/noop.go | 2 +- eth/tracers/tracer.go | 2 +- eth/tracers/tracer_test.go | 4 +++- 9 files changed, 19 insertions(+), 17 deletions(-) diff --git a/core/vm/access_list_tracer.go b/core/vm/access_list_tracer.go index 11b4e294263e..b63bc4efc2b2 100644 --- a/core/vm/access_list_tracer.go +++ b/core/vm/access_list_tracer.go @@ -166,7 +166,7 @@ func (*AccessListTracer) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {} -func (*AccessListTracer) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (*AccessListTracer) CaptureEnter(env *EVM, typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { } func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {} diff --git a/core/vm/evm.go b/core/vm/evm.go index 07e96191588a..4c5a309cee0d 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -187,7 +187,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) evm.Config.Tracer.CaptureEnd(ret, 0, 0, nil) } else { - evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) + evm.Config.Tracer.CaptureEnter(evm, CALL, caller.Address(), addr, input, gas, value) evm.Config.Tracer.CaptureExit(ret, 0, nil) } } @@ -206,7 +206,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas }(gas, time.Now()) } else { // Handle tracer events for entering and exiting a call frame - evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) + evm.Config.Tracer.CaptureEnter(evm, CALL, caller.Address(), addr, input, gas, value) defer func(startGas uint64) { evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) }(gas) @@ -272,7 +272,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Debug { - evm.Config.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value) + evm.Config.Tracer.CaptureEnter(evm, CALLCODE, caller.Address(), addr, input, gas, value) defer func(startGas uint64) { evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) }(gas) @@ -316,7 +316,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Debug { - evm.Config.Tracer.CaptureEnter(DELEGATECALL, caller.Address(), addr, input, gas, nil) + evm.Config.Tracer.CaptureEnter(evm, DELEGATECALL, caller.Address(), addr, input, gas, nil) defer func(startGas uint64) { evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) }(gas) @@ -369,7 +369,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Debug { - evm.Config.Tracer.CaptureEnter(STATICCALL, caller.Address(), addr, input, gas, nil) + evm.Config.Tracer.CaptureEnter(evm, STATICCALL, caller.Address(), addr, input, gas, nil) defer func(startGas uint64) { evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) }(gas) @@ -456,7 +456,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.depth == 0 { evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value) } else { - evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value) + evm.Config.Tracer.CaptureEnter(evm, typ, caller.Address(), address, codeAndHash.code, gas, value) } } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index bda480f083d4..32eef57927f0 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -792,7 +792,7 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) interpreter.evm.StateDB.Suicide(scope.Contract.Address()) if interpreter.cfg.Debug { - interpreter.cfg.Tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) + interpreter.cfg.Tracer.CaptureEnter(interpreter.evm, SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) interpreter.cfg.Tracer.CaptureExit([]byte{}, 0, nil) } return nil, nil diff --git a/core/vm/logger.go b/core/vm/logger.go index 048b84ff6f45..ecb0fe263cd8 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -106,7 +106,7 @@ func (s *StructLog) ErrorString() string { type EVMLogger interface { CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) - CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) + CaptureEnter(env *EVM, typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) CaptureExit(output []byte, gasUsed uint64, err error) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) @@ -227,7 +227,7 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration } } -func (l *StructLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (l *StructLogger) CaptureEnter(env *EVM, typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { } func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} @@ -350,7 +350,7 @@ func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, e output, gasUsed, err) } -func (t *mdLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *mdLogger) CaptureEnter(env *EVM, typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { } func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go index 479a00c0acad..0a9e57d51453 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -88,7 +88,7 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, errMsg}) } -func (l *JSONLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (l *JSONLogger) CaptureEnter(env *EVM, typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { } func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 60426891a372..b8fe0adc6256 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -90,10 +90,10 @@ func (t *callTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos 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) { +func (t *callTracer) CaptureEnter(env *vm.EVM, 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() + env.Cancel() return } diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go index 554bb18f14a6..b505c9b5d4bb 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/native/noop.go @@ -32,7 +32,7 @@ func (t *noopTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos 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) CaptureEnter(env *vm.EVM, 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) { diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 4fee7ed9668c..69d373037b84 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -750,7 +750,7 @@ func (jst *jsTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (jst *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (jst *jsTracer) CaptureEnter(env *vm.EVM, typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { if !jst.traceCallFrames { return } diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 0e78f34b62ab..398be835ef1f 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -255,8 +255,10 @@ func TestEnterExit(t *testing.T) { scope := &vm.ScopeContext{ Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), } + vmctx := testCtx() + env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) - tracer.CaptureEnter(vm.CALL, scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int)) + tracer.CaptureEnter(env, vm.CALL, scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int)) tracer.CaptureExit([]byte{}, 400, nil) have, err := tracer.GetResult() From 2de2d6cbb60421531927f540a61fdeef3cb5adb3 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Thu, 4 Nov 2021 21:30:07 +0100 Subject: [PATCH 28/30] Revert "core,eth: cancel evm on nativecalltracer stop" This reverts commit 01bb908790a369c1bb9d3937df9325c6857bf855. --- core/vm/access_list_tracer.go | 2 +- core/vm/evm.go | 12 ++++++------ core/vm/instructions.go | 2 +- core/vm/logger.go | 6 +++--- core/vm/logger_json.go | 2 +- eth/tracers/native/call.go | 4 ++-- eth/tracers/native/noop.go | 2 +- eth/tracers/tracer.go | 2 +- eth/tracers/tracer_test.go | 4 +--- 9 files changed, 17 insertions(+), 19 deletions(-) diff --git a/core/vm/access_list_tracer.go b/core/vm/access_list_tracer.go index b63bc4efc2b2..11b4e294263e 100644 --- a/core/vm/access_list_tracer.go +++ b/core/vm/access_list_tracer.go @@ -166,7 +166,7 @@ func (*AccessListTracer) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost func (*AccessListTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {} -func (*AccessListTracer) CaptureEnter(env *EVM, typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (*AccessListTracer) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { } func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {} diff --git a/core/vm/evm.go b/core/vm/evm.go index 4c5a309cee0d..07e96191588a 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -187,7 +187,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) evm.Config.Tracer.CaptureEnd(ret, 0, 0, nil) } else { - evm.Config.Tracer.CaptureEnter(evm, CALL, caller.Address(), addr, input, gas, value) + evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) evm.Config.Tracer.CaptureExit(ret, 0, nil) } } @@ -206,7 +206,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas }(gas, time.Now()) } else { // Handle tracer events for entering and exiting a call frame - evm.Config.Tracer.CaptureEnter(evm, CALL, caller.Address(), addr, input, gas, value) + evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) defer func(startGas uint64) { evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) }(gas) @@ -272,7 +272,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Debug { - evm.Config.Tracer.CaptureEnter(evm, CALLCODE, caller.Address(), addr, input, gas, value) + evm.Config.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value) defer func(startGas uint64) { evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) }(gas) @@ -316,7 +316,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Debug { - evm.Config.Tracer.CaptureEnter(evm, DELEGATECALL, caller.Address(), addr, input, gas, nil) + evm.Config.Tracer.CaptureEnter(DELEGATECALL, caller.Address(), addr, input, gas, nil) defer func(startGas uint64) { evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) }(gas) @@ -369,7 +369,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // Invoke tracer hooks that signal entering/exiting a call frame if evm.Config.Debug { - evm.Config.Tracer.CaptureEnter(evm, STATICCALL, caller.Address(), addr, input, gas, nil) + evm.Config.Tracer.CaptureEnter(STATICCALL, caller.Address(), addr, input, gas, nil) defer func(startGas uint64) { evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) }(gas) @@ -456,7 +456,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.depth == 0 { evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value) } else { - evm.Config.Tracer.CaptureEnter(evm, typ, caller.Address(), address, codeAndHash.code, gas, value) + evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value) } } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 32eef57927f0..bda480f083d4 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -792,7 +792,7 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) interpreter.evm.StateDB.Suicide(scope.Contract.Address()) if interpreter.cfg.Debug { - interpreter.cfg.Tracer.CaptureEnter(interpreter.evm, SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) + interpreter.cfg.Tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) interpreter.cfg.Tracer.CaptureExit([]byte{}, 0, nil) } return nil, nil diff --git a/core/vm/logger.go b/core/vm/logger.go index ecb0fe263cd8..048b84ff6f45 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -106,7 +106,7 @@ func (s *StructLog) ErrorString() string { type EVMLogger interface { CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) - CaptureEnter(env *EVM, typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) + CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) CaptureExit(output []byte, gasUsed uint64, err error) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) @@ -227,7 +227,7 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration } } -func (l *StructLogger) CaptureEnter(env *EVM, typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (l *StructLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { } func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} @@ -350,7 +350,7 @@ func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, e output, gasUsed, err) } -func (t *mdLogger) CaptureEnter(env *EVM, typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *mdLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { } func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} diff --git a/core/vm/logger_json.go b/core/vm/logger_json.go index 0a9e57d51453..479a00c0acad 100644 --- a/core/vm/logger_json.go +++ b/core/vm/logger_json.go @@ -88,7 +88,7 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t, errMsg}) } -func (l *JSONLogger) CaptureEnter(env *EVM, typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (l *JSONLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { } func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index b8fe0adc6256..60426891a372 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -90,10 +90,10 @@ func (t *callTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos 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(env *vm.EVM, typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +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 { - env.Cancel() + // TODO: env.Cancel() return } diff --git a/eth/tracers/native/noop.go b/eth/tracers/native/noop.go index b505c9b5d4bb..554bb18f14a6 100644 --- a/eth/tracers/native/noop.go +++ b/eth/tracers/native/noop.go @@ -32,7 +32,7 @@ func (t *noopTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos 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(env *vm.EVM, typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +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) { diff --git a/eth/tracers/tracer.go b/eth/tracers/tracer.go index 69d373037b84..4fee7ed9668c 100644 --- a/eth/tracers/tracer.go +++ b/eth/tracers/tracer.go @@ -750,7 +750,7 @@ func (jst *jsTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). -func (jst *jsTracer) CaptureEnter(env *vm.EVM, typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (jst *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { if !jst.traceCallFrames { return } diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 398be835ef1f..0e78f34b62ab 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -255,10 +255,8 @@ func TestEnterExit(t *testing.T) { scope := &vm.ScopeContext{ Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), } - vmctx := testCtx() - env := vm.NewEVM(vmctx.blockCtx, vmctx.txCtx, &dummyStatedb{}, params.TestChainConfig, vm.Config{Debug: true, Tracer: tracer}) - tracer.CaptureEnter(env, vm.CALL, scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int)) + tracer.CaptureEnter(vm.CALL, scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int)) tracer.CaptureExit([]byte{}, 400, nil) have, err := tracer.GetResult() From a9b91d79f0c812cbf9f0a2e769a5e9b97903636e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 4 Nov 2021 22:59:11 +0100 Subject: [PATCH 29/30] eth/tracers: linter nits --- eth/tracers/testing/calltrace_test.go | 7 +++++-- eth/tracers/tracers_test.go | 9 --------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/eth/tracers/testing/calltrace_test.go b/eth/tracers/testing/calltrace_test.go index d34821d41d4c..3423e7a58a39 100644 --- a/eth/tracers/testing/calltrace_test.go +++ b/eth/tracers/testing/calltrace_test.go @@ -99,8 +99,11 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { var ( signer = types.MakeSigner(test.Genesis.Config, new(big.Int).SetUint64(uint64(test.Context.Number))) origin, _ = signer.Sender(tx) - txContext = vm.TxContext{origin, tx.GasPrice()} - context = vm.BlockContext{ + txContext = vm.TxContext{ + Origin: origin, + GasPrice: tx.GasPrice(), + } + context = vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, Coinbase: test.Context.Miner, diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index a894a6d4dae8..ba445fd5ae1c 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -26,7 +26,6 @@ import ( "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" @@ -100,14 +99,6 @@ type callTrace struct { Calls []callTrace `json:"calls,omitempty"` } -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"` -} - // TestZeroValueToNotExitCall tests the calltracer(s) on the following: // Tx to A, A calls B with zero value. B does not already exist. // Expected: that enter/exit is invoked and the inner call is shown in the result From 8a5d5f0cde829f9645296dc445966cf672298fc1 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 5 Nov 2021 11:06:23 +0100 Subject: [PATCH 30/30] eth/tracers: fix output on err --- eth/tracers/native/call.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 60426891a372..a7ca57566d38 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -77,10 +77,14 @@ func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Ad } func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) { - t.callstack[0].Output = bytesToHex(output) 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) } }