Implement tracer interface

This commit is contained in:
Sina Mahmoodi 2026-02-16 19:42:56 +01:00
parent 324dc8f588
commit 2c316a8201
3 changed files with 52 additions and 30 deletions

View file

@ -56,27 +56,35 @@ func (l *fileWritingTracer) Write(p []byte) (n int, err error) {
return n, nil return n, nil
} }
// newFileWriter creates a set of hooks which wraps inner hooks (typically a logger), // newFileWriter creates a tracer which wraps inner hooks (typically a logger),
// and writes the output to a file, one file per transaction. // and writes the output to a file, one file per transaction.
func newFileWriter(baseDir string, innerFn func(out io.Writer) *tracing.Hooks) *tracing.Hooks { func newFileWriter(baseDir string, innerFn func(out io.Writer) *tracing.Hooks) *tracers.Tracer {
t := &fileWritingTracer{ t := &fileWritingTracer{
baseDir: baseDir, baseDir: baseDir,
suffix: "jsonl", suffix: "jsonl",
} }
t.inner = innerFn(t) // instantiate the inner tracer t.inner = innerFn(t) // instantiate the inner tracer
return t.hooks() return &tracers.Tracer{
Hooks: t.hooks(),
GetResult: func() (json.RawMessage, error) { return json.RawMessage("{}"), nil },
Stop: func(err error) {},
}
} }
// newResultWriter creates a set of hooks wraps and invokes an underlying tracer, // newResultWriter creates a tracer that wraps and invokes an underlying tracer,
// and writes the result (getResult-output) to file, one per transaction. // and writes the result (getResult-output) to file, one per transaction.
func newResultWriter(baseDir string, tracer *tracers.Tracer) *tracing.Hooks { func newResultWriter(baseDir string, tracer *tracers.Tracer) *tracers.Tracer {
t := &fileWritingTracer{ t := &fileWritingTracer{
baseDir: baseDir, baseDir: baseDir,
getResult: tracer.GetResult, getResult: tracer.GetResult,
inner: tracer.Hooks, inner: tracer.Hooks,
suffix: "json", suffix: "json",
} }
return t.hooks() return &tracers.Tracer{
Hooks: t.hooks(),
GetResult: func() (json.RawMessage, error) { return json.RawMessage("{}"), nil },
Stop: func(err error) {},
}
} }
// OnTxStart creates a new output-file specific for this transaction, and invokes // OnTxStart creates a new output-file specific for this transaction, and invokes

View file

@ -168,14 +168,15 @@ func Transition(ctx *cli.Context) error {
} }
// Configure tracer // Configure tracer
var tracer *tracers.Tracer
if ctx.IsSet(TraceTracerFlag.Name) { // Custom tracing if ctx.IsSet(TraceTracerFlag.Name) { // Custom tracing
config := json.RawMessage(ctx.String(TraceTracerConfigFlag.Name)) config := json.RawMessage(ctx.String(TraceTracerConfigFlag.Name))
tracer, err := tracers.DefaultDirectory.New(ctx.String(TraceTracerFlag.Name), innerTracer, err := tracers.DefaultDirectory.New(ctx.String(TraceTracerFlag.Name),
nil, config, chainConfig) nil, config, chainConfig)
if err != nil { if err != nil {
return NewError(ErrorConfig, fmt.Errorf("failed instantiating tracer: %v", err)) return NewError(ErrorConfig, fmt.Errorf("failed instantiating tracer: %v", err))
} }
vmConfig.Tracer = newResultWriter(baseDir, tracer) tracer = newResultWriter(baseDir, innerTracer)
} else if ctx.Bool(TraceFlag.Name) { // JSON opcode tracing } else if ctx.Bool(TraceFlag.Name) { // JSON opcode tracing
logConfig := &logger.Config{ logConfig := &logger.Config{
DisableStack: ctx.Bool(TraceDisableStackFlag.Name), DisableStack: ctx.Bool(TraceDisableStackFlag.Name),
@ -183,28 +184,28 @@ func Transition(ctx *cli.Context) error {
EnableReturnData: ctx.Bool(TraceEnableReturnDataFlag.Name), EnableReturnData: ctx.Bool(TraceEnableReturnDataFlag.Name),
} }
if ctx.Bool(TraceEnableCallFramesFlag.Name) { if ctx.Bool(TraceEnableCallFramesFlag.Name) {
vmConfig.Tracer = newFileWriter(baseDir, func(out io.Writer) *tracing.Hooks { tracer = newFileWriter(baseDir, func(out io.Writer) *tracing.Hooks {
return logger.NewJSONLoggerWithCallFrames(logConfig, out) return logger.NewJSONLoggerWithCallFrames(logConfig, out)
}) })
} else { } else {
vmConfig.Tracer = newFileWriter(baseDir, func(out io.Writer) *tracing.Hooks { tracer = newFileWriter(baseDir, func(out io.Writer) *tracing.Hooks {
return logger.NewJSONLogger(logConfig, out) return logger.NewJSONLogger(logConfig, out)
}) })
} }
} }
// Configure opcode counter // Configure opcode counter
var counter *native.OpcodeCounter var opcodeTracer *tracers.Tracer
if ctx.IsSet(OpcodeCountFlag.Name) && ctx.String(OpcodeCountFlag.Name) != "" { if ctx.IsSet(OpcodeCountFlag.Name) && ctx.String(OpcodeCountFlag.Name) != "" {
counter = new(native.OpcodeCounter) opcodeTracer = native.NewOpcodeCounter()
if vmConfig.Tracer != nil { if tracer != nil {
// If we have an existing tracer, multiplex with the opcode tracer // If we have an existing tracer, multiplex with the opcode tracer
tExisting := tracers.Tracer{Hooks: vmConfig.Tracer} mux, _ := native.NewMuxTracer([]string{"trace", "opcode"}, []*tracers.Tracer{tracer, opcodeTracer})
tCounter := tracers.Tracer{Hooks: counter.Hooks()} vmConfig.Tracer = mux.Hooks
muxTracer, _ := native.NewMuxTracer([]string{"existing", "opcode"}, []*tracers.Tracer{&tExisting, &tCounter})
vmConfig.Tracer = muxTracer.Hooks
} else { } else {
vmConfig.Tracer = counter.Hooks() vmConfig.Tracer = opcodeTracer.Hooks
} }
} else if tracer != nil {
vmConfig.Tracer = tracer.Hooks
} }
// Run the test and aggregate the result // Run the test and aggregate the result
s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, ctx.Int64(RewardFlag.Name)) s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, ctx.Int64(RewardFlag.Name))
@ -212,9 +213,13 @@ func Transition(ctx *cli.Context) error {
return err return err
} }
// Write opcode counts if enabled // Write opcode counts if enabled
if counter != nil { if opcodeTracer != nil {
fname := ctx.String(OpcodeCountFlag.Name) fname := ctx.String(OpcodeCountFlag.Name)
if err := saveFile(baseDir, fname, counter.Results()); err != nil { result, err := opcodeTracer.GetResult()
if err != nil {
return NewError(ErrorJson, fmt.Errorf("failed getting opcode counts: %v", err))
}
if err := saveFile(baseDir, fname, result); err != nil {
return err return err
} }
} }

View file

@ -17,30 +17,39 @@
package native package native
import ( import (
"encoding/json"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
) )
// OpcodeCounter is a simple tracer that counts how many times each opcode is executed. // opcodeCounter is a simple tracer that counts how many times each opcode is executed.
type OpcodeCounter struct { type opcodeCounter struct {
counts [256]uint64 counts [256]uint64
} }
func (c *OpcodeCounter) Hooks() *tracing.Hooks { // NewOpcodeCounter returns a new opcodeCounter tracer.
return &tracing.Hooks{ func NewOpcodeCounter() *tracers.Tracer {
c := &opcodeCounter{}
return &tracers.Tracer{
Hooks: &tracing.Hooks{
OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
c.counts[op]++ c.counts[op]++
}, },
},
GetResult: c.getResult,
Stop: func(err error) {},
} }
} }
// Results returns the opcode counts keyed by opcode name. // getResult returns the opcode counts keyed by opcode name.
func (c *OpcodeCounter) Results() map[string]uint64 { func (c *opcodeCounter) getResult() (json.RawMessage, error) {
out := make(map[string]uint64, len(c.counts)) out := make(map[string]uint64)
for op, count := range c.counts { for op, count := range c.counts {
if count != 0 { if count != 0 {
out[vm.OpCode(op).String()] = count out[vm.OpCode(op).String()] = count
} }
} }
return out return json.Marshal(out)
} }