t8ntool: add optional call frames to json logger

Co-authored-by: Mario Vega <marioevz@gmail.com>
This commit is contained in:
Sina Mahmoodi 2024-03-26 15:46:58 +01:00
parent 738b5a586e
commit 931a320e48
4 changed files with 140 additions and 3 deletions

View file

@ -50,6 +50,10 @@ var (
Name: "trace.returndata", Name: "trace.returndata",
Usage: "Enable return data output in traces", Usage: "Enable return data output in traces",
} }
TraceEnableCallFramesFlag = &cli.BoolFlag{
Name: "trace.callframes",
Usage: "Enable call frames output in traces",
}
OutputBasedir = &cli.StringFlag{ OutputBasedir = &cli.StringFlag{
Name: "output.basedir", Name: "output.basedir",
Usage: "Specifies where output files are placed. Will be created if it does not exist.", Usage: "Specifies where output files are placed. Will be created if it does not exist.",

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
@ -101,9 +102,14 @@ func Transition(ctx *cli.Context) error {
if err != nil { if err != nil {
return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err))
} }
logger := logger.NewJSONLogger(logConfig, traceFile) var l *tracing.Hooks
if ctx.Bool(TraceEnableCallFramesFlag.Name) {
l = logger.NewJSONLoggerWithCallFrames(logConfig, traceFile)
} else {
l = logger.NewJSONLogger(logConfig, traceFile)
}
tracer := &tracers.Tracer{ tracer := &tracers.Tracer{
Hooks: logger, Hooks: l,
// jsonLogger streams out result to file. // jsonLogger streams out result to file.
GetResult: func() (json.RawMessage, error) { return nil, nil }, GetResult: func() (json.RawMessage, error) { return nil, nil },
Stop: func(err error) {}, Stop: func(err error) {},

View file

@ -0,0 +1,65 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package logger
import (
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
)
var _ = (*callFrameMarshaling)(nil)
// MarshalJSON marshals as JSON.
func (c callFrame) MarshalJSON() ([]byte, error) {
type callFrame struct {
From common.Address `json:"from"`
To common.Address `json:"to"`
Input hexutil.Bytes `json:"input,omitempty"`
Gas math.HexOrDecimal64 `json:"gas"`
Value *hexutil.Big `json:"value"`
Type string `json:"type"`
}
var enc callFrame
enc.From = c.From
enc.To = c.To
enc.Input = c.Input
enc.Gas = math.HexOrDecimal64(c.Gas)
enc.Value = (*hexutil.Big)(c.Value)
enc.Type = c.Type()
return json.Marshal(&enc)
}
// UnmarshalJSON unmarshals from JSON.
func (c *callFrame) UnmarshalJSON(input []byte) error {
type callFrame struct {
From *common.Address `json:"from"`
To *common.Address `json:"to"`
Input *hexutil.Bytes `json:"input,omitempty"`
Gas *math.HexOrDecimal64 `json:"gas"`
Value *hexutil.Big `json:"value"`
}
var dec callFrame
if err := json.Unmarshal(input, &dec); err != nil {
return err
}
if dec.From != nil {
c.From = *dec.From
}
if dec.To != nil {
c.To = *dec.To
}
if dec.Input != nil {
c.Input = *dec.Input
}
if dec.Gas != nil {
c.Gas = uint64(*dec.Gas)
}
if dec.Value != nil {
c.Value = (*big.Int)(dec.Value)
}
return nil
}

View file

@ -19,14 +19,41 @@ package logger
import ( import (
"encoding/json" "encoding/json"
"io" "io"
"math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
) )
//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe.go
// overrides for gencodec
type callFrameMarshaling struct {
Input hexutil.Bytes
Gas math.HexOrDecimal64
Value *hexutil.Big
Type string `json:"type"` // adds call to Type() in MarshalJSON
}
// callFrame is emitted every call frame entered.
type callFrame struct {
op vm.OpCode
From common.Address `json:"from"`
To common.Address `json:"to"`
Input []byte `json:"input,omitempty"`
Gas uint64 `json:"gas"`
Value *big.Int `json:"value"`
}
// Type formats the call type in a human-readable format.
func (c *callFrame) Type() string {
return c.op.String()
}
type jsonLogger struct { type jsonLogger struct {
encoder *json.Encoder encoder *json.Encoder
cfg *Config cfg *Config
@ -48,6 +75,22 @@ func NewJSONLogger(cfg *Config, writer io.Writer) *tracing.Hooks {
} }
} }
// NewJSONLoggerWithCallFrames creates a new EVM tracer that prints execution steps as JSON objects
// into the provided stream. It also includes call frames in the output.
func NewJSONLoggerWithCallFrames(cfg *Config, writer io.Writer) *tracing.Hooks {
l := &jsonLogger{encoder: json.NewEncoder(writer), cfg: cfg}
if l.cfg == nil {
l.cfg = &Config{}
}
return &tracing.Hooks{
OnTxStart: l.OnTxStart,
OnEnter: l.OnEnter,
OnExit: l.OnExit,
OnOpcode: l.OnOpcode,
OnFault: l.OnFault,
}
}
func (l *jsonLogger) OnFault(pc uint64, op byte, gas uint64, cost uint64, scope tracing.OpContext, depth int, err error) { func (l *jsonLogger) OnFault(pc uint64, op byte, gas uint64, cost uint64, scope tracing.OpContext, depth int, err error) {
// TODO: Add rData to this interface as well // TODO: Add rData to this interface as well
l.OnOpcode(pc, op, gas, cost, scope, nil, depth, err) l.OnOpcode(pc, op, gas, cost, scope, nil, depth, err)
@ -79,10 +122,29 @@ func (l *jsonLogger) OnOpcode(pc uint64, op byte, gas, cost uint64, scope tracin
l.encoder.Encode(log) l.encoder.Encode(log)
} }
func (l *jsonLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) { // OnEnter is not enabled by default.
func (l *jsonLogger) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
frame := callFrame{
op: vm.OpCode(typ),
From: from,
To: to,
Gas: gas,
Value: value,
}
if l.cfg.EnableMemory {
frame.Input = input
}
l.encoder.Encode(frame)
}
func (l *jsonLogger) OnEnd(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
if depth > 0 { if depth > 0 {
return return
} }
l.OnExit(depth, output, gasUsed, err, false)
}
func (l *jsonLogger) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
type endLog struct { type endLog struct {
Output string `json:"output"` Output string `json:"output"`
GasUsed math.HexOrDecimal64 `json:"gasUsed"` GasUsed math.HexOrDecimal64 `json:"gasUsed"`