Skip to content

Commit

Permalink
feat(tracer): add withLog to callTracer
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanmorphl2 committed Jan 13, 2025
1 parent fd9eed1 commit 787260e
Show file tree
Hide file tree
Showing 19 changed files with 501 additions and 177 deletions.
7 changes: 7 additions & 0 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
return nil, err
}

if st.evm.Config.Debug {
st.evm.Config.Tracer.CaptureTxStart(st.initialGas)
defer func() {
st.evm.Config.Tracer.CaptureTxEnd(st.gas)
}()
}

var (
msg = st.msg
sender = vm.AccountRef(msg.From())
Expand Down
4 changes: 4 additions & 0 deletions core/vm/access_list_tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ func (*AccessListTracer) CaptureEnter(typ OpCode, from common.Address, to common

func (*AccessListTracer) CaptureExit(output []byte, gasUsed uint64, err error) {}

func (t *AccessListTracer) CaptureTxStart(gasLimit uint64) {}

func (t *AccessListTracer) CaptureTxEnd(restGas uint64) {}

// AccessList returns the current accesslist maintained by the tracer.
func (a *AccessListTracer) AccessList() types.AccessList {
return a.list.accessList()
Expand Down
11 changes: 11 additions & 0 deletions core/vm/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ func (s *StructLog) ErrorString() string {
// Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call.
type EVMLogger interface {
// Transaction level
CaptureTxStart(gasLimit uint64)
CaptureTxEnd(restGas uint64)
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureStateAfter(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
Expand Down Expand Up @@ -340,6 +343,10 @@ func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {

}

func (t *StructLogger) CaptureTxStart(gasLimit uint64) {}

func (t *StructLogger) CaptureTxEnd(restGas uint64) {}

// UpdatedAccounts is used to collect all "touched" accounts
func (l *StructLogger) UpdatedAccounts() map[common.Address]struct{} {
return l.statesAffected
Expand Down Expand Up @@ -487,6 +494,10 @@ func (t *mdLogger) CaptureEnter(typ OpCode, from common.Address, to common.Addre

func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}

func (t *mdLogger) CaptureTxStart(gasLimit uint64) {}

func (t *mdLogger) CaptureTxEnd(restGas uint64) {}

// FormatLogs formats EVM returned structured logs for json output
func FormatLogs(logs []*StructLog) []*types.StructLogRes {
formatted := make([]*types.StructLogRes, 0, len(logs))
Expand Down
4 changes: 4 additions & 0 deletions core/vm/logger_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,7 @@ func (l *JSONLogger) CaptureEnter(typ OpCode, from common.Address, to common.Add
}

func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}

func (t *JSONLogger) CaptureTxStart(gasLimit uint64) {}

func (t *JSONLogger) CaptureTxEnd(restGas uint64) {}
6 changes: 3 additions & 3 deletions core/vm/runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
cfg.GasLimit = gas
if len(tracerCode) > 0 {
tracer, err := tracers.New(tracerCode, new(tracers.Context))
tracer, err := tracers.New(tracerCode, new(tracers.Context), nil)
if err != nil {
b.Fatal(err)
}
Expand Down Expand Up @@ -877,7 +877,7 @@ func TestRuntimeJSTracer(t *testing.T) {
statedb.SetCode(common.HexToAddress("0xee"), calleeCode)
statedb.SetCode(common.HexToAddress("0xff"), depressedCode)

tracer, err := tracers.New(jsTracer, new(tracers.Context))
tracer, err := tracers.New(jsTracer, new(tracers.Context), nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -912,7 +912,7 @@ func TestJSTracerCreateTx(t *testing.T) {
code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)}

statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
tracer, err := tracers.New(jsTracer, new(tracers.Context))
tracer, err := tracers.New(jsTracer, new(tracers.Context), nil)
if err != nil {
t.Fatal(err)
}
Expand Down
18 changes: 7 additions & 11 deletions eth/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"bufio"
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -173,15 +174,15 @@ type TraceConfig struct {
Tracer *string
Timeout *string
Reexec *uint64
// Config specific to given tracer. Note struct logger
// config are historically embedded in main object.
TracerConfig json.RawMessage
}

// TraceCallConfig is the config for traceCall API. It holds one more
// field to override the state for tracing.
type TraceCallConfig struct {
*vm.LogConfig
Tracer *string
Timeout *string
Reexec *uint64
TraceConfig
StateOverrides *ethapi.StateOverride
}

Expand Down Expand Up @@ -898,12 +899,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc

var traceConfig *TraceConfig
if config != nil {
traceConfig = &TraceConfig{
LogConfig: config.LogConfig,
Tracer: config.Tracer,
Timeout: config.Timeout,
Reexec: config.Reexec,
}
traceConfig = &config.TraceConfig
}

signer := types.MakeSigner(api.backend.ChainConfig(), block.Number())
Expand Down Expand Up @@ -936,7 +932,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
return nil, err
}
}
if t, err := New(*config.Tracer, txctx); err != nil {
if t, err := New(*config.Tracer, txctx, config.TracerConfig); err != nil {
return nil, err
} else {
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
Expand Down
6 changes: 3 additions & 3 deletions eth/tracers/internal/tracetest/calltrace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
}
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
)
tracer, err := tracers.New(tracerName, new(tracers.Context))
tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
Expand Down Expand Up @@ -302,7 +302,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
tracer, err := tracers.New(tracerName, new(tracers.Context))
tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
if err != nil {
b.Fatalf("failed to create call tracer: %v", err)
}
Expand Down Expand Up @@ -372,7 +372,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
}
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
// Create the tracer, the EVM environment and run it
tracer, err := tracers.New("callTracer", nil)
tracer, err := tracers.New("callTracer", nil, nil)
if err != nil {
t.Fatalf("failed to create call tracer: %v", err)
}
Expand Down
6 changes: 5 additions & 1 deletion eth/tracers/js/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ type jsTracer 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 *tracers2.Context) (tracers2.Tracer, error) {
func newJsTracer(code string, ctx *tracers2.Context, cfg json.RawMessage) (tracers2.Tracer, error) {
if c, ok := assetTracers[code]; ok {
code = c
}
Expand Down Expand Up @@ -831,6 +831,10 @@ func (jst *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
}
}

func (t *jsTracer) CaptureTxStart(gasLimit uint64) {}

func (t *jsTracer) CaptureTxEnd(restGas uint64) {}

// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
func (jst *jsTracer) GetResult() (json.RawMessage, error) {
// Transform the context into a JavaScript object and inject into the state
Expand Down
26 changes: 13 additions & 13 deletions eth/tracers/js/tracer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func runTrace(tracer tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCon
func TestTracer(t *testing.T) {
execTracer := func(code string) ([]byte, string) {
t.Helper()
tracer, err := newJsTracer(code, nil)
tracer, err := newJsTracer(code, nil, nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -130,7 +130,7 @@ func TestTracer(t *testing.T) {
func TestHalt(t *testing.T) {
t.Skip("duktape doesn't support abortion")
timeout := errors.New("stahp")
tracer, err := newJsTracer("{step: function() { while(1); }, result: function() { return null; }, fault: function(){}}", nil)
tracer, err := newJsTracer("{step: function() { while(1); }, result: function() { return null; }, fault: function(){}}", nil, nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -144,7 +144,7 @@ func TestHalt(t *testing.T) {
}

func TestHaltBetweenSteps(t *testing.T) {
tracer, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }}", nil)
tracer, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }}", nil, nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -168,7 +168,7 @@ func TestHaltBetweenSteps(t *testing.T) {
func TestNoStepExec(t *testing.T) {
execTracer := func(code string) []byte {
t.Helper()
tracer, err := newJsTracer(code, nil)
tracer, err := newJsTracer(code, nil, nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -203,7 +203,7 @@ func TestIsPrecompile(t *testing.T) {
chaincfg.BerlinBlock = big.NewInt(300)
chaincfg.ArchimedesBlock = big.NewInt(400)
txCtx := vm.TxContext{GasPrice: big.NewInt(100000)}
tracer, err := newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
tracer, err := newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -217,7 +217,7 @@ func TestIsPrecompile(t *testing.T) {
t.Errorf("Tracer should not consider blake2f as precompile in byzantium")
}

tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
blockCtx = vm.BlockContext{BlockNumber: big.NewInt(250)}
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil {
Expand All @@ -228,7 +228,7 @@ func TestIsPrecompile(t *testing.T) {
}

// test sha disabled in archimedes
tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000002'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000002'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
blockCtx = vm.BlockContext{BlockNumber: big.NewInt(450)}
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil {
Expand All @@ -238,7 +238,7 @@ func TestIsPrecompile(t *testing.T) {
t.Errorf("Tracer should not consider blake2f as precompile in archimedes")
}

tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000003'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000003'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
blockCtx = vm.BlockContext{BlockNumber: big.NewInt(450)}
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil {
Expand All @@ -249,7 +249,7 @@ func TestIsPrecompile(t *testing.T) {
}

// test blake2f disabled in archimedes
tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000009'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil {
t.Error(err)
Expand All @@ -259,7 +259,7 @@ func TestIsPrecompile(t *testing.T) {
}

// test ecrecover enabled in archimedes
tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000001'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil)
tracer, _ = newJsTracer("{addr: toAddress('0000000000000000000000000000000000000001'), res: null, step: function() { this.res = isPrecompiled(this.addr); }, fault: function() {}, result: function() { return this.res; }}", nil, nil)
res, err = runTrace(tracer, &vmContext{blockCtx, txCtx}, chaincfg)
if err != nil {
t.Error(err)
Expand All @@ -271,14 +271,14 @@ func TestIsPrecompile(t *testing.T) {

func TestEnterExit(t *testing.T) {
// test that either both or none of enter() and exit() are defined
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.Context)); err == nil {
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(tracers.Context), nil); err == nil {
t.Fatal("tracer creation should've failed without exit() definition")
}
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context)); err != nil {
if _, err := newJsTracer("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(tracers.Context), nil); err != nil {
t.Fatal(err)
}
// test that the enter and exit method are correctly invoked and the values passed
tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context))
tracer, err := newJsTracer("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(tracers.Context), nil)
if err != nil {
t.Fatal(err)
}
Expand Down
37 changes: 6 additions & 31 deletions eth/tracers/native/4byte.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"math/big"
"strconv"
"sync/atomic"
"time"

"github.com/morph-l2/go-ethereum/common"
"github.com/morph-l2/go-ethereum/core/vm"
Expand All @@ -47,20 +46,20 @@ func init() {
// 0xc281d19e-0: 1
// }
type fourByteTracer struct {
env *vm.EVM
noopTracer
ids map[string]int // ids aggregates the 4byte ids found
interrupt uint32 // Atomic flag to signal execution interruption
interrupt atomic.Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
activePrecompiles []common.Address // Updated on CaptureStart based on given rules
}

// newFourByteTracer returns a native go tracer which collects
// 4 byte-identifiers of a tx, and implements vm.EVMLogger.
func newFourByteTracer() tracers.Tracer {
func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
t := &fourByteTracer{
ids: make(map[string]int),
}
return t
return t, nil
}

// isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go
Expand All @@ -81,8 +80,6 @@ func (t *fourByteTracer) store(id []byte, size int) {

// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.env = env

// Update list of precompiles based on current block
rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Time.Uint64())
t.activePrecompiles = vm.ActivePrecompiles(rules)
Expand All @@ -93,19 +90,10 @@ func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
}
}

// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *fourByteTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}

// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (t *fourByteTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}

// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *fourByteTracer) CaptureEnter(op 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 {
t.env.Cancel()
if t.interrupt.Load() {
return
}
if len(input) < 4 {
Expand All @@ -123,19 +111,6 @@ func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to comm
t.store(input[0:4], len(input)-4)
}

// CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code.
func (t *fourByteTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
}

// CaptureFault implements the EVMLogger interface to trace an execution fault.
func (t *fourByteTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
}

// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *fourByteTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
}

// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
func (t *fourByteTracer) GetResult() (json.RawMessage, error) {
Expand All @@ -149,5 +124,5 @@ func (t *fourByteTracer) GetResult() (json.RawMessage, error) {
// Stop terminates execution of the tracer at the first opportune moment.
func (t *fourByteTracer) Stop(err error) {
t.reason = err
atomic.StoreUint32(&t.interrupt, 1)
t.interrupt.Store(true)
}
Loading

0 comments on commit 787260e

Please sign in to comment.