Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eth/tracers: support for golang tracers + add golang callTracer #23708

Merged
merged 31 commits into from
Nov 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
af67e1d
eth/tracers: add basic native loader
s1na Oct 11, 2021
57c993b
eth/tracers: add GetResult to tracer interface
s1na Oct 11, 2021
c9c1dc9
eth/tracers: add native call tracer
s1na Oct 11, 2021
340c773
eth/tracers: fix call tracer json result
s1na Oct 12, 2021
bf165bb
eth/tracers: minor fix
s1na Oct 12, 2021
4ea0d15
eth/tracers: fix
s1na Oct 12, 2021
75c6aff
eth/tracers: fix benchTracer
s1na Oct 13, 2021
7b10534
eth/tracers: test native call tracer
s1na Oct 13, 2021
ca5427a
eth/tracers: fix
s1na Oct 13, 2021
d9371f3
eth/tracers: rm extra make
s1na Oct 13, 2021
aff90d3
eth/tracers: rm extra make
s1na Oct 13, 2021
a2b0d3d
eth/tracers: make callFrame private
s1na Oct 13, 2021
18c4c17
eth/tracers: clean-up and comments
s1na Oct 13, 2021
0946623
eth/tracers: add license
s1na Oct 26, 2021
5db19c5
eth/tracers: rework the model a bit
holiman Oct 26, 2021
360e3ba
eth/tracers: move tracecall tests to subpackage
holiman Oct 28, 2021
c55a903
cmd/geth: load native tracers
s1na Nov 2, 2021
a9c2051
eth/tracers: minor fix
s1na Nov 2, 2021
c242c82
eth/tracers: impl stop
s1na Nov 2, 2021
60f5734
eth/tracers: add native noop tracer
s1na Nov 2, 2021
89bac76
renamings
s1na Nov 3, 2021
91a7c09
eth/tracers: more renamings
s1na Nov 3, 2021
2a69a37
eth/tracers: make jstracer non-exported, avoid cast
holiman Nov 3, 2021
4e2de46
eth/tracers, core/vm: rename vm.Tracer to vm.EVMLogger for clarity
holiman Nov 3, 2021
de953f5
eth/tracers: minor comment fix
s1na Nov 4, 2021
2af565b
eth/tracers/testing: lint nitpicks
holiman Nov 4, 2021
a839259
Merge branch 'master' into drop-in-plugin
holiman Nov 4, 2021
01bb908
core,eth: cancel evm on nativecalltracer stop
s1na Nov 4, 2021
2de2d6c
Revert "core,eth: cancel evm on nativecalltracer stop"
s1na Nov 4, 2021
a9b91d7
eth/tracers: linter nits
holiman Nov 4, 2021
8a5d5f0
eth/tracers: fix output on err
s1na Nov 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/evm/internal/t8ntool/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions cmd/evm/internal/t8ntool/transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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()
}
Expand All @@ -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
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/evm/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion cmd/evm/staterunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down
16 changes: 8 additions & 8 deletions core/vm/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand Down
10 changes: 5 additions & 5 deletions core/vm/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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) {
}

Expand Down Expand Up @@ -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) {
}
Expand Down
34 changes: 16 additions & 18 deletions eth/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -862,36 +862,34 @@ 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)
)
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 {
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
return nil, err
}
}
// Constuct the JavaScript tracer to execute with
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
}
// 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)

default:
tracer = vm.NewStructLogger(config.LogConfig)
}
Expand Down Expand Up @@ -921,7 +919,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
}, nil

case *Tracer:
case Tracer:
return tracer.GetResult()

default:
Expand Down
170 changes: 170 additions & 0 deletions eth/tracers/native/call.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright 2021 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package native
s1na marked this conversation as resolved.
Show resolved Hide resolved

import (
"encoding/json"
"errors"
"math/big"
"strconv"
"strings"
"sync/atomic"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
)

func init() {
tracers.RegisterNativeTracer("callTracerNative", NewCallTracer)
}

type callFrame struct {
Type string `json:"type"`
From string `json:"from"`
To string `json:"to,omitempty"`
Value string `json:"value,omitempty"`
Gas string `json:"gas"`
GasUsed string `json:"gasUsed"`
Input string `json:"input"`
Output string `json:"output,omitempty"`
Error string `json:"error,omitempty"`
Calls []callFrame `json:"calls,omitempty"`
}

type callTracer struct {
callstack []callFrame
interrupt uint32 // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
}

// NewCallTracer returns a native go tracer which tracks
// call frames of a tx, and implements vm.EVMLogger.
func NewCallTracer() tracers.Tracer {
// First callframe contains tx context info
// and is populated on start and end.
t := &callTracer{callstack: make([]callFrame, 1)}
return t
}

func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.callstack[0] = callFrame{
Type: "CALL",
From: addrToHex(from),
To: addrToHex(to),
Input: bytesToHex(input),
Gas: uintToHex(gas),
Value: bigToHex(value),
}
if create {
t.callstack[0].Type = "CREATE"
}
}

func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
t.callstack[0].GasUsed = uintToHex(gasUsed)
if err != nil {
t.callstack[0].Error = err.Error()
if err.Error() == "execution reverted" && len(output) > 0 {
t.callstack[0].Output = bytesToHex(output)
}
} else {
t.callstack[0].Output = bytesToHex(output)
Comment on lines +83 to +87
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. However, apparently this is not covered by the tests, think you can add a test that flags this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not required for this PR though

}
}

func (t *callTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}

func (t *callTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
}

func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
// Skip if tracing was interrupted
if atomic.LoadUint32(&t.interrupt) > 0 {
// TODO: env.Cancel()
return
}

call := callFrame{
Type: typ.String(),
From: addrToHex(from),
To: addrToHex(to),
Input: bytesToHex(input),
Gas: uintToHex(gas),
Value: bigToHex(value),
}
t.callstack = append(t.callstack, call)
}

func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
size := len(t.callstack)
if size <= 1 {
return
}
// pop call
call := t.callstack[size-1]
t.callstack = t.callstack[:size-1]
size -= 1

call.GasUsed = uintToHex(gasUsed)
if err == nil {
call.Output = bytesToHex(output)
} else {
call.Error = err.Error()
if call.Type == "CREATE" || call.Type == "CREATE2" {
call.To = ""
}
}
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
}

func (t *callTracer) GetResult() (json.RawMessage, error) {
if len(t.callstack) != 1 {
return nil, errors.New("incorrect number of top-level calls")
}
res, err := json.Marshal(t.callstack[0])
if err != nil {
return nil, err
}
return json.RawMessage(res), t.reason
}

func (t *callTracer) Stop(err error) {
t.reason = err
atomic.StoreUint32(&t.interrupt, 1)
}

func bytesToHex(s []byte) string {
return "0x" + common.Bytes2Hex(s)
}

func bigToHex(n *big.Int) string {
if n == nil {
return ""
}
return "0x" + n.Text(16)
}

func uintToHex(n uint64) string {
return "0x" + strconv.FormatUint(n, 16)
}

func addrToHex(a common.Address) string {
return strings.ToLower(a.Hex())
}
Loading