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
}
// 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.
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{
baseDir: baseDir,
suffix: "jsonl",
}
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.
func newResultWriter(baseDir string, tracer *tracers.Tracer) *tracing.Hooks {
func newResultWriter(baseDir string, tracer *tracers.Tracer) *tracers.Tracer {
t := &fileWritingTracer{
baseDir: baseDir,
getResult: tracer.GetResult,
inner: tracer.Hooks,
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

View file

@ -168,14 +168,15 @@ func Transition(ctx *cli.Context) error {
}
// Configure tracer
var tracer *tracers.Tracer
if ctx.IsSet(TraceTracerFlag.Name) { // Custom tracing
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)
if err != nil {
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
logConfig := &logger.Config{
DisableStack: ctx.Bool(TraceDisableStackFlag.Name),
@ -183,28 +184,28 @@ func Transition(ctx *cli.Context) error {
EnableReturnData: ctx.Bool(TraceEnableReturnDataFlag.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)
})
} 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)
})
}
}
// Configure opcode counter
var counter *native.OpcodeCounter
var opcodeTracer *tracers.Tracer
if ctx.IsSet(OpcodeCountFlag.Name) && ctx.String(OpcodeCountFlag.Name) != "" {
counter = new(native.OpcodeCounter)
if vmConfig.Tracer != nil {
opcodeTracer = native.NewOpcodeCounter()
if tracer != nil {
// If we have an existing tracer, multiplex with the opcode tracer
tExisting := tracers.Tracer{Hooks: vmConfig.Tracer}
tCounter := tracers.Tracer{Hooks: counter.Hooks()}
muxTracer, _ := native.NewMuxTracer([]string{"existing", "opcode"}, []*tracers.Tracer{&tExisting, &tCounter})
vmConfig.Tracer = muxTracer.Hooks
mux, _ := native.NewMuxTracer([]string{"trace", "opcode"}, []*tracers.Tracer{tracer, opcodeTracer})
vmConfig.Tracer = mux.Hooks
} else {
vmConfig.Tracer = counter.Hooks()
vmConfig.Tracer = opcodeTracer.Hooks
}
} else if tracer != nil {
vmConfig.Tracer = tracer.Hooks
}
// Run the test and aggregate the result
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
}
// Write opcode counts if enabled
if counter != nil {
if opcodeTracer != nil {
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
}
}

View file

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