Skip to content

Commit 8d7e606

Browse files
s1naholiman
andauthored
eth/tracers: support for golang tracers + add golang callTracer (#23708)
* eth/tracers: add basic native loader * eth/tracers: add GetResult to tracer interface * eth/tracers: add native call tracer * eth/tracers: fix call tracer json result * eth/tracers: minor fix * eth/tracers: fix * eth/tracers: fix benchTracer * eth/tracers: test native call tracer * eth/tracers: fix * eth/tracers: rm extra make Co-authored-by: Martin Holst Swende <[email protected]> * eth/tracers: rm extra make * eth/tracers: make callFrame private * eth/tracers: clean-up and comments * eth/tracers: add license * eth/tracers: rework the model a bit * eth/tracers: move tracecall tests to subpackage * cmd/geth: load native tracers * eth/tracers: minor fix * eth/tracers: impl stop * eth/tracers: add native noop tracer * renamings Co-authored-by: Martin Holst Swende <[email protected]> * eth/tracers: more renamings * eth/tracers: make jstracer non-exported, avoid cast * eth/tracers, core/vm: rename vm.Tracer to vm.EVMLogger for clarity * eth/tracers: minor comment fix * eth/tracers/testing: lint nitpicks * core,eth: cancel evm on nativecalltracer stop * Revert "core,eth: cancel evm on nativecalltracer stop" This reverts commit 01bb908. * eth/tracers: linter nits * eth/tracers: fix output on err Co-authored-by: Martin Holst Swende <[email protected]>
1 parent 3bbeb94 commit 8d7e606

File tree

15 files changed

+560
-250
lines changed

15 files changed

+560
-250
lines changed

cmd/evm/internal/t8ntool/execution.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ type rejectedTx struct {
9696
// Apply applies a set of transactions to a pre-state
9797
func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
9898
txs types.Transactions, miningReward int64,
99-
getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.Tracer, err error)) (*state.StateDB, *ExecutionResult, error) {
99+
getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error)) (*state.StateDB, *ExecutionResult, error) {
100100

101101
// Capture errors for BLOCKHASH operation, if we haven't been supplied the
102102
// required blockhashes

cmd/evm/internal/t8ntool/transition.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ func Transition(ctx *cli.Context) error {
8989

9090
var (
9191
err error
92-
tracer vm.Tracer
92+
tracer vm.EVMLogger
9393
baseDir = ""
9494
)
95-
var getTracer func(txIndex int, txHash common.Hash) (vm.Tracer, error)
95+
var getTracer func(txIndex int, txHash common.Hash) (vm.EVMLogger, error)
9696

9797
// If user specified a basedir, make sure it exists
9898
if ctx.IsSet(OutputBasedir.Name) {
@@ -119,7 +119,7 @@ func Transition(ctx *cli.Context) error {
119119
prevFile.Close()
120120
}
121121
}()
122-
getTracer = func(txIndex int, txHash common.Hash) (vm.Tracer, error) {
122+
getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) {
123123
if prevFile != nil {
124124
prevFile.Close()
125125
}
@@ -131,7 +131,7 @@ func Transition(ctx *cli.Context) error {
131131
return vm.NewJSONLogger(logConfig, traceFile), nil
132132
}
133133
} else {
134-
getTracer = func(txIndex int, txHash common.Hash) (tracer vm.Tracer, err error) {
134+
getTracer = func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error) {
135135
return nil, nil
136136
}
137137
}

cmd/evm/runner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func runCmd(ctx *cli.Context) error {
116116
}
117117

118118
var (
119-
tracer vm.Tracer
119+
tracer vm.EVMLogger
120120
debugLogger *vm.StructLogger
121121
statedb *state.StateDB
122122
chainConfig *params.ChainConfig

cmd/evm/staterunner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func stateTestCmd(ctx *cli.Context) error {
6565
EnableReturnData: !ctx.GlobalBool(DisableReturnDataFlag.Name),
6666
}
6767
var (
68-
tracer vm.Tracer
68+
tracer vm.EVMLogger
6969
debugger *vm.StructLogger
7070
)
7171
switch {

cmd/geth/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ import (
3939
"github.com/ethereum/go-ethereum/log"
4040
"github.com/ethereum/go-ethereum/metrics"
4141
"github.com/ethereum/go-ethereum/node"
42+
43+
// Force-load the native, to trigger registration
44+
_ "github.com/ethereum/go-ethereum/eth/tracers/native"
45+
4246
"gopkg.in/urfave/cli.v1"
4347
)
4448

core/vm/interpreter.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ import (
2727

2828
// Config are the configuration options for the Interpreter
2929
type Config struct {
30-
Debug bool // Enables debugging
31-
Tracer Tracer // Opcode logger
32-
NoRecursion bool // Disables call, callcode, delegate call and create
33-
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
34-
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
30+
Debug bool // Enables debugging
31+
Tracer EVMLogger // Opcode logger
32+
NoRecursion bool // Disables call, callcode, delegate call and create
33+
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
34+
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
3535

3636
JumpTable [256]*operation // EVM instruction table, automatically populated if unset
3737

@@ -152,9 +152,9 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
152152
pc = uint64(0) // program counter
153153
cost uint64
154154
// copies used by tracer
155-
pcCopy uint64 // needed for the deferred Tracer
156-
gasCopy uint64 // for Tracer to log gas remaining before execution
157-
logged bool // deferred Tracer should ignore already logged steps
155+
pcCopy uint64 // needed for the deferred EVMLogger
156+
gasCopy uint64 // for EVMLogger to log gas remaining before execution
157+
logged bool // deferred EVMLogger should ignore already logged steps
158158
res []byte // result of the opcode execution function
159159
)
160160
// Don't move this deferrred function, it's placed before the capturestate-deferred method,

core/vm/logger.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,12 @@ func (s *StructLog) ErrorString() string {
9898
return ""
9999
}
100100

101-
// Tracer is used to collect execution traces from an EVM transaction
101+
// EVMLogger is used to collect execution traces from an EVM transaction
102102
// execution. CaptureState is called for each step of the VM with the
103103
// current VM state.
104104
// Note that reference types are actual VM data structures; make copies
105105
// if you need to retain them beyond the current call.
106-
type Tracer interface {
106+
type EVMLogger interface {
107107
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
108108
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
109109
CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
@@ -112,7 +112,7 @@ type Tracer interface {
112112
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error)
113113
}
114114

115-
// StructLogger is an EVM state logger and implements Tracer.
115+
// StructLogger is an EVM state logger and implements EVMLogger.
116116
//
117117
// StructLogger can capture state based on the given Log configuration and also keeps
118118
// a track record of modified storage which is used in reporting snapshots of the
@@ -145,7 +145,7 @@ func (l *StructLogger) Reset() {
145145
l.err = nil
146146
}
147147

148-
// CaptureStart implements the Tracer interface to initialize the tracing operation.
148+
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
149149
func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
150150
}
151151

@@ -210,7 +210,7 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
210210
l.logs = append(l.logs, log)
211211
}
212212

213-
// CaptureFault implements the Tracer interface to trace an execution fault
213+
// CaptureFault implements the EVMLogger interface to trace an execution fault
214214
// while running an opcode.
215215
func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
216216
}

eth/tracers/api.go

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -862,36 +862,34 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
862862
func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
863863
// Assemble the structured logger or the JavaScript tracer
864864
var (
865-
tracer vm.Tracer
865+
tracer vm.EVMLogger
866866
err error
867867
txContext = core.NewEVMTxContext(message)
868868
)
869869
switch {
870-
case config != nil && config.Tracer != nil:
870+
case config == nil:
871+
tracer = vm.NewStructLogger(nil)
872+
case config.Tracer != nil:
871873
// Define a meaningful timeout of a single transaction trace
872874
timeout := defaultTraceTimeout
873875
if config.Timeout != nil {
874876
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
875877
return nil, err
876878
}
877879
}
878-
// Constuct the JavaScript tracer to execute with
879-
if tracer, err = New(*config.Tracer, txctx); err != nil {
880+
if t, err := New(*config.Tracer, txctx); err != nil {
880881
return nil, err
882+
} else {
883+
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
884+
go func() {
885+
<-deadlineCtx.Done()
886+
if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
887+
t.Stop(errors.New("execution timeout"))
888+
}
889+
}()
890+
defer cancel()
891+
tracer = t
881892
}
882-
// Handle timeouts and RPC cancellations
883-
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
884-
go func() {
885-
<-deadlineCtx.Done()
886-
if deadlineCtx.Err() == context.DeadlineExceeded {
887-
tracer.(*Tracer).Stop(errors.New("execution timeout"))
888-
}
889-
}()
890-
defer cancel()
891-
892-
case config == nil:
893-
tracer = vm.NewStructLogger(nil)
894-
895893
default:
896894
tracer = vm.NewStructLogger(config.LogConfig)
897895
}
@@ -921,7 +919,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
921919
StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
922920
}, nil
923921

924-
case *Tracer:
922+
case Tracer:
925923
return tracer.GetResult()
926924

927925
default:

eth/tracers/native/call.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// Copyright 2021 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package native
18+
19+
import (
20+
"encoding/json"
21+
"errors"
22+
"math/big"
23+
"strconv"
24+
"strings"
25+
"sync/atomic"
26+
"time"
27+
28+
"github.com/ethereum/go-ethereum/common"
29+
"github.com/ethereum/go-ethereum/core/vm"
30+
"github.com/ethereum/go-ethereum/eth/tracers"
31+
)
32+
33+
func init() {
34+
tracers.RegisterNativeTracer("callTracerNative", NewCallTracer)
35+
}
36+
37+
type callFrame struct {
38+
Type string `json:"type"`
39+
From string `json:"from"`
40+
To string `json:"to,omitempty"`
41+
Value string `json:"value,omitempty"`
42+
Gas string `json:"gas"`
43+
GasUsed string `json:"gasUsed"`
44+
Input string `json:"input"`
45+
Output string `json:"output,omitempty"`
46+
Error string `json:"error,omitempty"`
47+
Calls []callFrame `json:"calls,omitempty"`
48+
}
49+
50+
type callTracer struct {
51+
callstack []callFrame
52+
interrupt uint32 // Atomic flag to signal execution interruption
53+
reason error // Textual reason for the interruption
54+
}
55+
56+
// NewCallTracer returns a native go tracer which tracks
57+
// call frames of a tx, and implements vm.EVMLogger.
58+
func NewCallTracer() tracers.Tracer {
59+
// First callframe contains tx context info
60+
// and is populated on start and end.
61+
t := &callTracer{callstack: make([]callFrame, 1)}
62+
return t
63+
}
64+
65+
func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
66+
t.callstack[0] = callFrame{
67+
Type: "CALL",
68+
From: addrToHex(from),
69+
To: addrToHex(to),
70+
Input: bytesToHex(input),
71+
Gas: uintToHex(gas),
72+
Value: bigToHex(value),
73+
}
74+
if create {
75+
t.callstack[0].Type = "CREATE"
76+
}
77+
}
78+
79+
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
80+
t.callstack[0].GasUsed = uintToHex(gasUsed)
81+
if err != nil {
82+
t.callstack[0].Error = err.Error()
83+
if err.Error() == "execution reverted" && len(output) > 0 {
84+
t.callstack[0].Output = bytesToHex(output)
85+
}
86+
} else {
87+
t.callstack[0].Output = bytesToHex(output)
88+
}
89+
}
90+
91+
func (t *callTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
92+
}
93+
94+
func (t *callTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
95+
}
96+
97+
func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
98+
// Skip if tracing was interrupted
99+
if atomic.LoadUint32(&t.interrupt) > 0 {
100+
// TODO: env.Cancel()
101+
return
102+
}
103+
104+
call := callFrame{
105+
Type: typ.String(),
106+
From: addrToHex(from),
107+
To: addrToHex(to),
108+
Input: bytesToHex(input),
109+
Gas: uintToHex(gas),
110+
Value: bigToHex(value),
111+
}
112+
t.callstack = append(t.callstack, call)
113+
}
114+
115+
func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
116+
size := len(t.callstack)
117+
if size <= 1 {
118+
return
119+
}
120+
// pop call
121+
call := t.callstack[size-1]
122+
t.callstack = t.callstack[:size-1]
123+
size -= 1
124+
125+
call.GasUsed = uintToHex(gasUsed)
126+
if err == nil {
127+
call.Output = bytesToHex(output)
128+
} else {
129+
call.Error = err.Error()
130+
if call.Type == "CREATE" || call.Type == "CREATE2" {
131+
call.To = ""
132+
}
133+
}
134+
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
135+
}
136+
137+
func (t *callTracer) GetResult() (json.RawMessage, error) {
138+
if len(t.callstack) != 1 {
139+
return nil, errors.New("incorrect number of top-level calls")
140+
}
141+
res, err := json.Marshal(t.callstack[0])
142+
if err != nil {
143+
return nil, err
144+
}
145+
return json.RawMessage(res), t.reason
146+
}
147+
148+
func (t *callTracer) Stop(err error) {
149+
t.reason = err
150+
atomic.StoreUint32(&t.interrupt, 1)
151+
}
152+
153+
func bytesToHex(s []byte) string {
154+
return "0x" + common.Bytes2Hex(s)
155+
}
156+
157+
func bigToHex(n *big.Int) string {
158+
if n == nil {
159+
return ""
160+
}
161+
return "0x" + n.Text(16)
162+
}
163+
164+
func uintToHex(n uint64) string {
165+
return "0x" + strconv.FormatUint(n, 16)
166+
}
167+
168+
func addrToHex(a common.Address) string {
169+
return strings.ToLower(a.Hex())
170+
}

0 commit comments

Comments
 (0)