diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 7a0de86a11..410764aa4c 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -17,9 +17,7 @@ package t8ntool import ( - "encoding/json" "fmt" - "io" "math/big" "github.com/ethereum/go-ethereum/common" @@ -132,7 +130,7 @@ type rejectedTx struct { // Apply applies a set of transactions to a pre-state func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, txIt txIterator, miningReward int64, - getTracerFn func(txIndex int, txHash common.Hash, chainConfig *params.ChainConfig) (*tracers.Tracer, io.WriteCloser, error)) (*state.StateDB, *ExecutionResult, []byte, error) { + tracer *tracers.Tracer) (*state.StateDB, *ExecutionResult, []byte, error) { // Capture errors for BLOCKHASH operation, if we haven't been supplied the // required blockhashes var hashError error @@ -241,10 +239,6 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, continue } } - tracer, traceOutput, err := getTracerFn(txIndex, tx.Hash(), chainConfig) - if err != nil { - return nil, nil, nil, err - } // TODO (rjl493456442) it's a bit weird to reset the tracer in the // middle of block execution, please improve it somehow. if tracer != nil { @@ -266,13 +260,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err) rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) gaspool.SetGas(prevGas) - if tracer != nil { - if tracer.OnTxEnd != nil { - tracer.OnTxEnd(nil, err) - } - if err := writeTraceResult(tracer, traceOutput); err != nil { - log.Warn("Error writing tracer output", "err", err) - } + if tracer != nil && tracer.OnTxEnd != nil { + tracer.OnTxEnd(nil, err) } continue } @@ -316,13 +305,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, //receipt.BlockNumber receipt.TransactionIndex = uint(txIndex) receipts = append(receipts, receipt) - if tracer != nil { - if tracer.Hooks.OnTxEnd != nil { - tracer.Hooks.OnTxEnd(receipt, nil) - } - if err = writeTraceResult(tracer, traceOutput); err != nil { - log.Warn("Error writing tracer output", "err", err) - } + if tracer != nil && tracer.Hooks.OnTxEnd != nil { + tracer.Hooks.OnTxEnd(receipt, nil) } } @@ -468,16 +452,3 @@ func calcDifficulty(config *params.ChainConfig, number, currentTime, parentTime } return ethash.CalcDifficulty(config, currentTime, parent) } - -func writeTraceResult(tracer *tracers.Tracer, f io.WriteCloser) error { - defer f.Close() - result, err := tracer.GetResult() - if err != nil || result == nil { - return err - } - err = json.NewEncoder(f).Encode(result) - if err != nil { - return err - } - return nil -} diff --git a/cmd/evm/internal/t8ntool/file_tracer.go b/cmd/evm/internal/t8ntool/file_tracer.go new file mode 100644 index 0000000000..942e34269d --- /dev/null +++ b/cmd/evm/internal/t8ntool/file_tracer.go @@ -0,0 +1,171 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package t8ntool + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/eth/tracers/logger" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + + "encoding/json" + "fmt" + "io" + "math/big" + "os" + "path/filepath" +) + +// fileWritingTracer is a tracer which wraps either a different tracer, +// or a logger. On tx start, it creates a new file to direct output to, +// and on tx end it closes the file. +type fileWritingTracer struct { + txIndex int + inner *tracing.Hooks + destination io.WriteCloser + baseDir string + + // for json-tracing + logConfig *logger.Config + callFrames bool + + // for custom tracing + tracerName string + tracerConf json.RawMessage + chainConfig *params.ChainConfig + getResult func() (json.RawMessage, error) +} + +func newFileWritingTracer(baseDir string, logConfig *logger.Config, callFrames bool) *fileWritingTracer { + return &fileWritingTracer{ + baseDir: baseDir, + logConfig: logConfig, + callFrames: callFrames, + } +} + +func newFileWritingCustomTracer(baseDir, tracerName string, traceConfig json.RawMessage, chainConfig *params.ChainConfig) *fileWritingTracer { + return &fileWritingTracer{ + baseDir: baseDir, + tracerName: tracerName, + chainConfig: chainConfig, + } +} + +// OnTxStartJSONL is the OnTxStart-handler for jsonl logger. +func (l *fileWritingTracer) OnTxStartJSONL(env *tracing.VMContext, tx *types.Transaction, from common.Address) { + // Open a new file, + fname := filepath.Join(l.baseDir, fmt.Sprintf("trace-%d-%v.jsonl", l.txIndex, tx.Hash().String())) + traceFile, err := os.Create(fname) + if err != nil { + log.Warn("Failed creating trace-file", "err", err) + } + log.Debug("Created tracing-file", "path", fname) + l.destination = traceFile + if !l.callFrames { + l.inner = logger.NewJSONLogger(l.logConfig, traceFile) + } else { + l.inner = logger.NewJSONLoggerWithCallFrames(l.logConfig, traceFile) + } + if l.inner.OnTxStart != nil { + l.inner.OnTxStart(env, tx, from) + } +} + +// OnTxStartJSONL is the OnTxStart-handler for custom tracer. +func (l *fileWritingTracer) OnTxStartJSON(env *tracing.VMContext, tx *types.Transaction, from common.Address) { + // Open a new file, + fname := filepath.Join(l.baseDir, fmt.Sprintf("trace-%d-%v.json", l.txIndex, tx.Hash().String())) + traceFile, err := os.Create(fname) + if err != nil { + log.Warn("Failed creating trace-file", "err", err) + } + fmt.Printf("Created tracing-file %v\n", fname) + log.Info("Created tracing-file", "path", fname) + l.destination = traceFile + inner, err := tracers.DefaultDirectory.New(l.tracerName, nil, l.tracerConf, l.chainConfig) + if err != nil { + log.Warn("Failed instantiating tracer", "err", err) + return + } + l.getResult = inner.GetResult + l.inner = inner.Hooks + if l.inner.OnTxStart != nil { + l.inner.OnTxStart(env, tx, from) + } +} + +func (l *fileWritingTracer) OnTxEnd(receipt *types.Receipt, err error) { + if l.inner.OnTxEnd != nil { + l.inner.OnTxEnd(receipt, err) + } + if l.getResult != nil { + if result, err := l.getResult(); result != nil { + json.NewEncoder(l.destination).Encode(result) + } else { + log.Warn("Error obtaining tracer result", "err", err) + } + } + if l.destination != nil { // Close old file + l.destination.Close() + l.destination = nil + } + l.txIndex++ +} + +func (l *fileWritingTracer) Tracer() *tracers.Tracer { + hooks := &tracing.Hooks{ + OnTxStart: l.OnTxStartJSONL, + OnTxEnd: l.OnTxEnd, + OnSystemCallStart: func() { + if l.inner.OnSystemCallStart != nil { + l.inner.OnSystemCallStart() + } + }, + OnEnter: func(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + if l.inner.OnEnter != nil { + l.inner.OnEnter(depth, typ, from, to, input, gas, value) + } + }, + OnExit: func(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if l.inner.OnExit != nil { + l.inner.OnExit(depth, output, gasUsed, err, reverted) + } + }, + OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + if l.inner.OnOpcode != nil { + l.inner.OnOpcode(pc, op, gas, cost, scope, rData, depth, err) + } + }, + OnFault: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { + if l.inner.OnFault != nil { + l.inner.OnFault(pc, op, gas, cost, scope, depth, err) + } + }, + } + if len(l.tracerName) > 0 { // a custom tracer + hooks.OnTxStart = l.OnTxStartJSON + } + return &tracers.Tracer{ + Hooks: hooks, + GetResult: func() (json.RawMessage, error) { return nil, nil }, + Stop: func(err error) {}, + } +} diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 231576fa42..c9ebb75b82 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -20,7 +20,6 @@ import ( "encoding/json" "errors" "fmt" - "io" "math/big" "os" "path/filepath" @@ -29,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "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/vm" "github.com/ethereum/go-ethereum/eth/tracers" @@ -82,58 +80,10 @@ type input struct { } func Transition(ctx *cli.Context) error { - var getTracer = func(txIndex int, txHash common.Hash, chainConfig *params.ChainConfig) (*tracers.Tracer, io.WriteCloser, error) { - return nil, nil, nil - } - baseDir, err := createBasedir(ctx) if err != nil { return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err)) } - - if ctx.Bool(TraceFlag.Name) { // JSON opcode tracing - // Configure the EVM logger - logConfig := &logger.Config{ - DisableStack: ctx.Bool(TraceDisableStackFlag.Name), - EnableMemory: ctx.Bool(TraceEnableMemoryFlag.Name), - EnableReturnData: ctx.Bool(TraceEnableReturnDataFlag.Name), - } - getTracer = func(txIndex int, txHash common.Hash, _ *params.ChainConfig) (*tracers.Tracer, io.WriteCloser, error) { - traceFile, err := os.Create(filepath.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String()))) - if err != nil { - return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) - } - var l *tracing.Hooks - if ctx.Bool(TraceEnableCallFramesFlag.Name) { - l = logger.NewJSONLoggerWithCallFrames(logConfig, traceFile) - } else { - l = logger.NewJSONLogger(logConfig, traceFile) - } - tracer := &tracers.Tracer{ - Hooks: l, - // jsonLogger streams out result to file. - GetResult: func() (json.RawMessage, error) { return nil, nil }, - Stop: func(err error) {}, - } - return tracer, traceFile, nil - } - } else if ctx.IsSet(TraceTracerFlag.Name) { - var config json.RawMessage - if ctx.IsSet(TraceTracerConfigFlag.Name) { - config = []byte(ctx.String(TraceTracerConfigFlag.Name)) - } - getTracer = func(txIndex int, txHash common.Hash, chainConfig *params.ChainConfig) (*tracers.Tracer, io.WriteCloser, error) { - traceFile, err := os.Create(filepath.Join(baseDir, fmt.Sprintf("trace-%d-%v.json", txIndex, txHash.String()))) - if err != nil { - return nil, nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) - } - tracer, err := tracers.DefaultDirectory.New(ctx.String(TraceTracerFlag.Name), nil, config, chainConfig) - if err != nil { - return nil, nil, NewError(ErrorConfig, fmt.Errorf("failed instantiating tracer: %w", err)) - } - return tracer, traceFile, nil - } - } // We need to load three things: alloc, env and transactions. May be either in // stdin input or in files. // Check if anything needs to be read from stdin @@ -179,6 +129,7 @@ func Transition(ctx *cli.Context) error { chainConfig = cConf vmConfig.ExtraEips = extraEips } + // Set the chain id chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name)) @@ -197,8 +148,23 @@ func Transition(ctx *cli.Context) error { if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil { return err } + + // Configure tracer + var tracer *tracers.Tracer + if ctx.IsSet(TraceTracerFlag.Name) { // Custom tracing + config := json.RawMessage(ctx.String(TraceTracerConfigFlag.Name)) + tracer = newFileWritingCustomTracer(baseDir, ctx.String(TraceTracerFlag.Name), config, chainConfig).Tracer() + } else if ctx.Bool(TraceFlag.Name) { // JSON opcode tracing + logConfig := &logger.Config{ + DisableStack: ctx.Bool(TraceDisableStackFlag.Name), + EnableMemory: ctx.Bool(TraceEnableMemoryFlag.Name), + EnableReturnData: ctx.Bool(TraceEnableReturnDataFlag.Name), + } + tracer = newFileWritingTracer(baseDir, logConfig, ctx.Bool(TraceEnableCallFramesFlag.Name)).Tracer() + } + // Run the test and aggregate the result - s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, ctx.Int64(RewardFlag.Name), getTracer) + s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, ctx.Int64(RewardFlag.Name), tracer) if err != nil { return err }