Skip to content

Commit caa200d

Browse files
authored
Merge pull request #175 from morph-l2/ryan/tracer-with-logs
feat(tracer): add withLog to callTracer
2 parents fd9eed1 + 099f66d commit caa200d

22 files changed

+621
-226
lines changed

core/state_transition.go

+7
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
355355
return nil, err
356356
}
357357

358+
if st.evm.Config.Debug {
359+
st.evm.Config.Tracer.CaptureTxStart(st.initialGas)
360+
defer func() {
361+
st.evm.Config.Tracer.CaptureTxEnd(st.gas)
362+
}()
363+
}
364+
358365
var (
359366
msg = st.msg
360367
sender = vm.AccountRef(msg.From())

core/types/l2trace.go

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ type StructLogRes struct {
8585
Depth int `json:"depth"`
8686
Error string `json:"error,omitempty"`
8787
Stack []string `json:"stack,omitempty"`
88+
ReturnData string `json:"returnData,omitempty"`
8889
Memory []string `json:"memory,omitempty"`
8990
Storage map[string]string `json:"storage,omitempty"`
9091
RefundCounter uint64 `json:"refund,omitempty"`

core/vm/access_list_tracer.go

+4
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ func (*AccessListTracer) CaptureEnter(typ OpCode, from common.Address, to common
175175

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

178+
func (t *AccessListTracer) CaptureTxStart(gasLimit uint64) {}
179+
180+
func (t *AccessListTracer) CaptureTxEnd(restGas uint64) {}
181+
178182
// AccessList returns the current accesslist maintained by the tracer.
179183
func (a *AccessListTracer) AccessList() types.AccessList {
180184
return a.list.accessList()

core/vm/logger.go

+98
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ package vm
1919
import (
2020
"bytes"
2121
"encoding/hex"
22+
"encoding/json"
2223
"fmt"
2324
"io"
2425
"math/big"
2526
"strings"
27+
"sync/atomic"
2628
"time"
2729

2830
"github.com/holiman/uint256"
@@ -127,6 +129,9 @@ func (s *StructLog) ErrorString() string {
127129
// Note that reference types are actual VM data structures; make copies
128130
// if you need to retain them beyond the current call.
129131
type EVMLogger interface {
132+
// Transaction level
133+
CaptureTxStart(gasLimit uint64)
134+
CaptureTxEnd(restGas uint64)
130135
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
131136
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
132137
CaptureStateAfter(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
@@ -162,6 +167,14 @@ type StructLogger struct {
162167
logs []*StructLog
163168
output []byte
164169
err error
170+
171+
gasLimit uint64
172+
usedGas uint64
173+
174+
interrupt atomic.Bool // Atomic flag to signal execution interruption
175+
reason error // Textual reason for the interruption
176+
177+
ResultL1DataFee *big.Int
165178
}
166179

167180
// NewStructLogger returns a new logger
@@ -214,6 +227,11 @@ func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Add
214227
//
215228
// CaptureState also tracks SLOAD/SSTORE ops to track storage change.
216229
func (l *StructLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, opErr error) {
230+
// If tracing was interrupted, set the error and stop
231+
if l.interrupt.Load() {
232+
return
233+
}
234+
217235
memory := scope.Memory
218236
stack := scope.Stack
219237
contract := scope.Contract
@@ -340,6 +358,14 @@ func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {
340358

341359
}
342360

361+
func (l *StructLogger) CaptureTxStart(gasLimit uint64) {
362+
l.gasLimit = gasLimit
363+
}
364+
365+
func (l *StructLogger) CaptureTxEnd(restGas uint64) {
366+
l.usedGas = l.gasLimit - restGas
367+
}
368+
343369
// UpdatedAccounts is used to collect all "touched" accounts
344370
func (l *StructLogger) UpdatedAccounts() map[common.Address]struct{} {
345371
return l.statesAffected
@@ -367,6 +393,33 @@ func (l *StructLogger) Error() error { return l.err }
367393
// Output returns the VM return value captured by the trace.
368394
func (l *StructLogger) Output() []byte { return l.output }
369395

396+
func (l *StructLogger) GetResult() (json.RawMessage, error) {
397+
// Tracing aborted
398+
if l.reason != nil {
399+
return nil, l.reason
400+
}
401+
failed := l.err != nil
402+
returnData := common.CopyBytes(l.output)
403+
// Return data when successful and revert reason when reverted, otherwise empty.
404+
returnVal := fmt.Sprintf("%x", returnData)
405+
if failed && l.err != ErrExecutionReverted {
406+
returnVal = ""
407+
}
408+
return json.Marshal(&types.ExecutionResult{
409+
Gas: l.usedGas,
410+
Failed: failed,
411+
ReturnValue: returnVal,
412+
StructLogs: formatLogs(l.StructLogs()),
413+
L1DataFee: (*hexutil.Big)(l.ResultL1DataFee),
414+
})
415+
}
416+
417+
// Stop terminates execution of the tracer at the first opportune moment.
418+
func (l *StructLogger) Stop(err error) {
419+
l.reason = err
420+
l.interrupt.Store(true)
421+
}
422+
370423
// WriteTrace writes a formatted trace to the given writer
371424
func WriteTrace(writer io.Writer, logs []*StructLog) {
372425
for _, log := range logs {
@@ -487,6 +540,10 @@ func (t *mdLogger) CaptureEnter(typ OpCode, from common.Address, to common.Addre
487540

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

543+
func (t *mdLogger) CaptureTxStart(gasLimit uint64) {}
544+
545+
func (t *mdLogger) CaptureTxEnd(restGas uint64) {}
546+
490547
// FormatLogs formats EVM returned structured logs for json output
491548
func FormatLogs(logs []*StructLog) []*types.StructLogRes {
492549
formatted := make([]*types.StructLogRes, 0, len(logs))
@@ -511,3 +568,44 @@ func FormatLogs(logs []*StructLog) []*types.StructLogRes {
511568
}
512569
return formatted
513570
}
571+
572+
// formatLogs formats EVM returned structured logs for json output
573+
func formatLogs(logs []*StructLog) []*types.StructLogRes {
574+
formatted := make([]*types.StructLogRes, len(logs))
575+
for index, trace := range logs {
576+
formatted[index] = &types.StructLogRes{
577+
Pc: trace.Pc,
578+
Op: trace.Op.String(),
579+
Gas: trace.Gas,
580+
GasCost: trace.GasCost,
581+
Depth: trace.Depth,
582+
Error: trace.ErrorString(),
583+
RefundCounter: trace.RefundCounter,
584+
}
585+
if trace.Stack != nil {
586+
stack := make([]string, len(trace.Stack))
587+
for i, stackValue := range trace.Stack {
588+
stack[i] = stackValue.Hex()
589+
}
590+
formatted[index].Stack = stack
591+
}
592+
if trace.ReturnData.Len() > 0 {
593+
formatted[index].ReturnData = hexutil.Bytes(trace.ReturnData.Bytes()).String()
594+
}
595+
if trace.Memory.Len() > 0 {
596+
memory := make([]string, 0, (trace.Memory.Len()+31)/32)
597+
for i := 0; i+32 <= trace.Memory.Len(); i += 32 {
598+
memory = append(memory, fmt.Sprintf("%x", trace.Memory.Bytes()[i:i+32]))
599+
}
600+
formatted[index].Memory = memory
601+
}
602+
if trace.Storage != nil {
603+
storage := make(map[string]string)
604+
for i, storageValue := range trace.Storage {
605+
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
606+
}
607+
formatted[index].Storage = storage
608+
}
609+
}
610+
return formatted
611+
}

core/vm/logger_json.go

+4
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,7 @@ func (l *JSONLogger) CaptureEnter(typ OpCode, from common.Address, to common.Add
9898
}
9999

100100
func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
101+
102+
func (t *JSONLogger) CaptureTxStart(gasLimit uint64) {}
103+
104+
func (t *JSONLogger) CaptureTxEnd(restGas uint64) {}

core/vm/runtime/runtime_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode
376376
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
377377
cfg.GasLimit = gas
378378
if len(tracerCode) > 0 {
379-
tracer, err := tracers.New(tracerCode, new(tracers.Context))
379+
tracer, err := tracers.New(tracerCode, new(tracers.Context), nil)
380380
if err != nil {
381381
b.Fatal(err)
382382
}
@@ -877,7 +877,7 @@ func TestRuntimeJSTracer(t *testing.T) {
877877
statedb.SetCode(common.HexToAddress("0xee"), calleeCode)
878878
statedb.SetCode(common.HexToAddress("0xff"), depressedCode)
879879

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

914914
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
915-
tracer, err := tracers.New(jsTracer, new(tracers.Context))
915+
tracer, err := tracers.New(jsTracer, new(tracers.Context), nil)
916916
if err != nil {
917917
t.Fatal(err)
918918
}

eth/tracers/api.go

+37-58
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"bufio"
2121
"bytes"
2222
"context"
23+
"encoding/json"
2324
"errors"
2425
"fmt"
2526
"io/ioutil"
@@ -173,15 +174,15 @@ type TraceConfig struct {
173174
Tracer *string
174175
Timeout *string
175176
Reexec *uint64
177+
// Config specific to given tracer. Note struct logger
178+
// config are historically embedded in main object.
179+
TracerConfig json.RawMessage
176180
}
177181

178182
// TraceCallConfig is the config for traceCall API. It holds one more
179183
// field to override the state for tracing.
180184
type TraceCallConfig struct {
181-
*vm.LogConfig
182-
Tracer *string
183-
Timeout *string
184-
Reexec *uint64
185+
TraceConfig
185186
StateOverrides *ethapi.StateOverride
186187
}
187188

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

899900
var traceConfig *TraceConfig
900901
if config != nil {
901-
traceConfig = &TraceConfig{
902-
LogConfig: config.LogConfig,
903-
Tracer: config.Tracer,
904-
Timeout: config.Timeout,
905-
Reexec: config.Reexec,
906-
}
902+
traceConfig = &config.TraceConfig
907903
}
908904

909905
signer := types.MakeSigner(api.backend.ChainConfig(), block.Number())
@@ -921,75 +917,58 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
921917
func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig, l1DataFee *big.Int) (interface{}, error) {
922918
// Assemble the structured logger or the JavaScript tracer
923919
var (
924-
tracer vm.EVMLogger
920+
tracer Tracer
925921
err error
922+
timeout = defaultTraceTimeout
926923
txContext = core.NewEVMTxContext(message)
927924
)
928-
switch {
929-
case config == nil:
930-
tracer = vm.NewStructLogger(nil)
931-
case config.Tracer != nil:
932-
// Define a meaningful timeout of a single transaction trace
933-
timeout := defaultTraceTimeout
934-
if config.Timeout != nil {
935-
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
936-
return nil, err
937-
}
938-
}
939-
if t, err := New(*config.Tracer, txctx); err != nil {
925+
if config == nil {
926+
config = &TraceConfig{}
927+
}
928+
// Default tracer is the struct logger
929+
tracer = vm.NewStructLogger(config.LogConfig)
930+
if config.Tracer != nil {
931+
tracer, err = New(*config.Tracer, txctx, config.TracerConfig)
932+
if err != nil {
940933
return nil, err
941-
} else {
942-
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
943-
go func() {
944-
<-deadlineCtx.Done()
945-
if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
946-
t.Stop(errors.New("execution timeout"))
947-
}
948-
}()
949-
defer cancel()
950-
tracer = t
951934
}
952-
default:
953-
tracer = vm.NewStructLogger(config.LogConfig)
954935
}
955936
// Run the transaction with tracing enabled.
956937
vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true})
957938

939+
// Define a meaningful timeout of a single transaction trace
940+
if config.Timeout != nil {
941+
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
942+
return nil, err
943+
}
944+
}
945+
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
946+
go func() {
947+
<-deadlineCtx.Done()
948+
if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
949+
tracer.Stop(errors.New("execution timeout"))
950+
// Stop evm execution. Note cancellation is not necessarily immediate.
951+
vmenv.Cancel()
952+
}
953+
}()
954+
defer cancel()
955+
958956
// If gasPrice is 0, make sure that the account has sufficient balance to cover `l1DataFee`.
959957
if message.GasPrice().Cmp(big.NewInt(0)) == 0 {
960958
statedb.AddBalance(message.From(), l1DataFee)
961959
}
962-
963960
// Call Prepare to clear out the statedb access list
964961
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
965-
966962
result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), l1DataFee)
967963
if err != nil {
968964
return nil, fmt.Errorf("tracing failed: %w", err)
969965
}
970966

971-
// Depending on the tracer type, format and return the output.
972-
switch tracer := tracer.(type) {
973-
case *vm.StructLogger:
974-
// If the result contains a revert reason, return it.
975-
returnVal := fmt.Sprintf("%x", result.Return())
976-
if len(result.Revert()) > 0 {
977-
returnVal = fmt.Sprintf("%x", result.Revert())
978-
}
979-
return &types.ExecutionResult{
980-
Gas: result.UsedGas,
981-
Failed: result.Failed(),
982-
ReturnValue: returnVal,
983-
StructLogs: vm.FormatLogs(tracer.StructLogs()),
984-
L1DataFee: (*hexutil.Big)(result.L1DataFee),
985-
}, nil
986-
987-
case Tracer:
988-
return tracer.GetResult()
989-
990-
default:
991-
panic(fmt.Sprintf("bad tracer type %T", tracer))
967+
l, ok := tracer.(*vm.StructLogger)
968+
if ok {
969+
l.ResultL1DataFee = result.L1DataFee
992970
}
971+
return tracer.GetResult()
993972
}
994973

995974
// APIs return the collection of RPC services the tracer package offers.

eth/tracers/internal/tracetest/calltrace_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
184184
}
185185
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
186186
)
187-
tracer, err := tracers.New(tracerName, new(tracers.Context))
187+
tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
188188
if err != nil {
189189
t.Fatalf("failed to create call tracer: %v", err)
190190
}
@@ -302,7 +302,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
302302
b.ReportAllocs()
303303
b.ResetTimer()
304304
for i := 0; i < b.N; i++ {
305-
tracer, err := tracers.New(tracerName, new(tracers.Context))
305+
tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
306306
if err != nil {
307307
b.Fatalf("failed to create call tracer: %v", err)
308308
}
@@ -372,7 +372,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
372372
}
373373
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
374374
// Create the tracer, the EVM environment and run it
375-
tracer, err := tracers.New("callTracer", nil)
375+
tracer, err := tracers.New("callTracer", nil, nil)
376376
if err != nil {
377377
t.Fatalf("failed to create call tracer: %v", err)
378378
}

0 commit comments

Comments
 (0)