From 2bf4a8ff73fc387592383dcba4175c422825b6b9 Mon Sep 17 00:00:00 2001
From: Martin HS <martin@swende.se>
Date: Tue, 21 Jan 2025 08:35:03 +0100
Subject: [PATCH] cmd/evm: refactor handling output-files for `t8n` (#30854)

As part of trying to make the inputs and outputs of the evm subcommands
more streamlined and aligned, this PR modifies how `evm t8n` manages
output-files.

Previously, we do a kind of wonky thing where between each transaction,
we invoke a `getTracer` closure. In that closure, we create a new
output-file, a tracer, and then make the tracer stream output to the
file. We also fiddle a bit to ensure that the file becomes properly
closed.

It is a kind of hacky solution we have in place. This PR changes it, so
that from the execution-pipeline point of view, we have just a regular
tracer. No fiddling with re-setting it or closing files.

That particular tracer, however, is a bit special: it takes care of
creating new files per transaction (in the tx-start-hook) and closing
(on tx-end-hook). Also instantiating the right type of underlying
tracer, which can be a json-logger or a custom tracer.

---------

Co-authored-by: Gary Rong <garyrong0905@gmail.com>
---
 cmd/evm/internal/t8ntool/execution.go         |  52 +-----
 cmd/evm/internal/t8ntool/file_tracer.go       | 152 ++++++++++++++++++
 cmd/evm/internal/t8ntool/transaction.go       |   6 +-
 cmd/evm/internal/t8ntool/transition.go        |  77 ++++-----
 cmd/evm/main.go                               |   4 +-
 cmd/evm/t8n_test.go                           |  32 ++--
 cmd/evm/testdata/31/README.md                 |  10 ++
 ...47543268a5aaf2a6b32a69d2c6d978c45dcfb.json |   2 +-
 ...3641ef5a8bbdb10ac69f466083a6789c77fb8.json |   1 +
 ...641ef5a8bbdb10ac69f466083a6789c77fb8.jsonl |   6 +
 ...86f5a9a08f7ef183da72c23bb3b2374530128.json |   1 +
 ...6f5a9a08f7ef183da72c23bb3b2374530128.jsonl |   6 +
 cmd/evm/testdata/31/txs.json                  |  24 +++
 core/vm/evm.go                                |   5 -
 14 files changed, 260 insertions(+), 118 deletions(-)
 create mode 100644 cmd/evm/internal/t8ntool/file_tracer.go
 create mode 100644 cmd/evm/testdata/31/trace-1-0x03a7b0a91e61a170d64ea94b8263641ef5a8bbdb10ac69f466083a6789c77fb8.json
 create mode 100644 cmd/evm/testdata/31/trace-1-0x03a7b0a91e61a170d64ea94b8263641ef5a8bbdb10ac69f466083a6789c77fb8.jsonl
 create mode 100644 cmd/evm/testdata/31/trace-2-0xd96e0ce6418ee3360e11d3c7b6886f5a9a08f7ef183da72c23bb3b2374530128.json
 create mode 100644 cmd/evm/testdata/31/trace-2-0xd96e0ce6418ee3360e11d3c7b6886f5a9a08f7ef183da72c23bb3b2374530128.jsonl

diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
index 7c17a251f0..9ff5a05290 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"
@@ -35,7 +33,6 @@ import (
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/core/vm"
 	"github.com/ethereum/go-ethereum/crypto"
-	"github.com/ethereum/go-ethereum/eth/tracers"
 	"github.com/ethereum/go-ethereum/ethdb"
 	"github.com/ethereum/go-ethereum/log"
 	"github.com/ethereum/go-ethereum/params"
@@ -130,9 +127,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) {
+func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, txIt txIterator, miningReward int64) (*state.StateDB, *ExecutionResult, []byte, error) {
 	// Capture errors for BLOCKHASH operation, if we haven't been supplied the
 	// required blockhashes
 	var hashError error
@@ -241,23 +236,13 @@ 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 {
-			evm.SetTracer(tracer.Hooks)
-		}
 		statedb.SetTxContext(tx.Hash(), txIndex)
-
 		var (
 			snapshot = statedb.Snapshot()
 			prevGas  = gaspool.Gas()
 		)
-		if tracer != nil && tracer.OnTxStart != nil {
-			tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
+		if evm.Config.Tracer != nil && evm.Config.Tracer.OnTxStart != nil {
+			evm.Config.Tracer.OnTxStart(evm.GetVMContext(), tx, msg.From)
 		}
 		// (ret []byte, usedGas uint64, failed bool, err error)
 		msgResult, err := core.ApplyMessage(evm, msg, gaspool)
@@ -266,13 +251,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 evm.Config.Tracer != nil && evm.Config.Tracer.OnTxEnd != nil {
+				evm.Config.Tracer.OnTxEnd(nil, err)
 			}
 			continue
 		}
@@ -316,13 +296,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 evm.Config.Tracer != nil && evm.Config.Tracer.OnTxEnd != nil {
+				evm.Config.Tracer.OnTxEnd(receipt, nil)
 			}
 		}
 
@@ -468,16 +443,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..38fc35bd32
--- /dev/null
+++ b/cmd/evm/internal/t8ntool/file_tracer.go
@@ -0,0 +1,152 @@
+// 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 <http://www.gnu.org/licenses/>.
+
+package t8ntool
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"math/big"
+	"os"
+	"path/filepath"
+
+	"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/log"
+)
+
+// fileWritingTracer wraps either a tracer or a logger. On tx start,
+// it instantiates a tracer/logger, creates a new file to direct output to,
+// and on tx end it closes the file.
+type fileWritingTracer struct {
+	txIndex     int            // transaction counter
+	inner       *tracing.Hooks // inner hooks
+	destination io.WriteCloser // the currently open file (if any)
+	baseDir     string         // baseDir to write output-files to
+	suffix      string         // suffix is the suffix to use when creating files
+
+	// for custom tracing
+	getResult func() (json.RawMessage, error)
+}
+
+func (l *fileWritingTracer) Write(p []byte) (n int, err error) {
+	if l.destination != nil {
+		return l.destination.Write(p)
+	}
+	log.Warn("Tracer wrote to non-existing output")
+	// It is tempting to return an error here, however, the json encoder
+	// will no retry writing to an io.Writer once it has returned an error once.
+	// Therefore, we must squash the error.
+	return n, nil
+}
+
+// newFileWriter creates a set of hooks 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 {
+	t := &fileWritingTracer{
+		baseDir: baseDir,
+		suffix:  "jsonl",
+	}
+	t.inner = innerFn(t) // instantiate the inner tracer
+	return t.hooks()
+}
+
+// newResultWriter creates a set of hooks 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 {
+	t := &fileWritingTracer{
+		baseDir:   baseDir,
+		getResult: tracer.GetResult,
+		inner:     tracer.Hooks,
+		suffix:    "json",
+	}
+	return t.hooks()
+}
+
+// OnTxStart creates a new output-file specific for this transaction, and invokes
+// the inner OnTxStart handler.
+func (l *fileWritingTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
+	// Open a new file, or print a warning log if it's failed
+	fname := filepath.Join(l.baseDir, fmt.Sprintf("trace-%d-%v.%v", l.txIndex, tx.Hash().String(), l.suffix))
+	traceFile, err := os.Create(fname)
+	if err != nil {
+		log.Warn("Failed creating trace-file", "err", err)
+	} else {
+		log.Info("Created tracing-file", "path", fname)
+		l.destination = traceFile
+	}
+	if l.inner != nil && l.inner.OnTxStart != nil {
+		l.inner.OnTxStart(env, tx, from)
+	}
+}
+
+// OnTxEnd writes result (if getResult exist), closes any currently open output-file,
+// and invokes the inner OnTxEnd handler.
+func (l *fileWritingTracer) OnTxEnd(receipt *types.Receipt, err error) {
+	if l.inner != nil && l.inner.OnTxEnd != nil {
+		l.inner.OnTxEnd(receipt, err)
+	}
+	if l.getResult != nil && l.destination != nil {
+		if result, err := l.getResult(); result != nil {
+			json.NewEncoder(l.destination).Encode(result)
+		} else {
+			log.Warn("Error obtaining tracer result", "err", err)
+		}
+		l.destination.Close()
+		l.destination = nil
+	}
+	l.txIndex++
+}
+
+func (l *fileWritingTracer) hooks() *tracing.Hooks {
+	return &tracing.Hooks{
+		OnTxStart: l.OnTxStart,
+		OnTxEnd:   l.OnTxEnd,
+		OnEnter: func(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+			if l.inner != nil && 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 != nil && 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 != nil && 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 != nil && l.inner.OnFault != nil {
+				l.inner.OnFault(pc, op, gas, cost, scope, depth, err)
+			}
+		},
+		OnSystemCallStart: func() {
+			if l.inner != nil && l.inner.OnSystemCallStart != nil {
+				l.inner.OnSystemCallStart()
+			}
+		},
+		OnSystemCallEnd: func() {
+			if l.inner != nil && l.inner.OnSystemCallEnd != nil {
+				l.inner.OnSystemCallEnd()
+			}
+		},
+	}
+}
diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go
index 6dac4301dd..d8650beca6 100644
--- a/cmd/evm/internal/t8ntool/transaction.go
+++ b/cmd/evm/internal/t8ntool/transaction.go
@@ -64,12 +64,10 @@ func (r *result) MarshalJSON() ([]byte, error) {
 }
 
 func Transaction(ctx *cli.Context) error {
-	var (
-		err error
-	)
 	// We need to load the transactions. May be either in stdin input or in files.
 	// Check if anything needs to be read from stdin
 	var (
+		err         error
 		txStr       = ctx.String(InputTxsFlag.Name)
 		inputData   = &input{}
 		chainConfig *params.ChainConfig
@@ -82,6 +80,7 @@ func Transaction(ctx *cli.Context) error {
 	}
 	// Set the chain id
 	chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name))
+
 	var body hexutil.Bytes
 	if txStr == stdinSelector {
 		decoder := json.NewDecoder(os.Stdin)
@@ -107,6 +106,7 @@ func Transaction(ctx *cli.Context) error {
 		}
 	}
 	signer := types.MakeSigner(chainConfig, new(big.Int), 0)
+
 	// We now have the transactions in 'body', which is supposed to be an
 	// rlp list of transactions
 	it, err := rlp.NewListIterator([]byte(body))
diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go
index 231576fa42..e946ccddd5 100644
--- a/cmd/evm/internal/t8ntool/transition.go
+++ b/cmd/evm/internal/t8ntool/transition.go
@@ -82,58 +82,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 +131,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 +150,34 @@ func Transition(ctx *cli.Context) error {
 	if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil {
 		return err
 	}
+
+	// Configure tracer
+	if ctx.IsSet(TraceTracerFlag.Name) { // Custom tracing
+		config := json.RawMessage(ctx.String(TraceTracerConfigFlag.Name))
+		tracer, 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)
+	} 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),
+		}
+		if ctx.Bool(TraceEnableCallFramesFlag.Name) {
+			vmConfig.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 {
+				return logger.NewJSONLogger(logConfig, out)
+			})
+		}
+	}
 	// 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))
 	if err != nil {
 		return err
 	}
diff --git a/cmd/evm/main.go b/cmd/evm/main.go
index 2079425416..61e46aa50e 100644
--- a/cmd/evm/main.go
+++ b/cmd/evm/main.go
@@ -83,8 +83,8 @@ var (
 	}
 	TraceFormatFlag = &cli.StringFlag{
 		Name:     "trace.format",
-		Usage:    "Trace output format to use (struct|json)",
-		Value:    "struct",
+		Usage:    "Trace output format to use (json|struct|md)",
+		Value:    "json",
 		Category: traceCategory,
 	}
 	TraceDisableMemoryFlag = &cli.BoolFlag{
diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go
index 27c6c43164..985dac968d 100644
--- a/cmd/evm/t8n_test.go
+++ b/cmd/evm/t8n_test.go
@@ -606,18 +606,18 @@ func TestEvmRun(t *testing.T) {
 			wantStdout: "./testdata/evmrun/1.out.1.txt",
 			wantStderr: "./testdata/evmrun/1.out.2.txt",
 		},
-		{ // default tracing (struct)
-			input:      []string{"run", "--trace", "0x6040"},
+		{ // Struct tracing
+			input:      []string{"run", "--trace", "--trace.format=struct", "0x6040"},
 			wantStdout: "./testdata/evmrun/2.out.1.txt",
 			wantStderr: "./testdata/evmrun/2.out.2.txt",
 		},
-		{ // default tracing (struct), plus alloc-dump
-			input:      []string{"run", "--trace", "--dump", "0x6040"},
+		{ // struct-tracing, plus alloc-dump
+			input:      []string{"run", "--trace", "--trace.format=struct", "--dump", "0x6040"},
 			wantStdout: "./testdata/evmrun/3.out.1.txt",
 			//wantStderr: "./testdata/evmrun/3.out.2.txt",
 		},
-		{ // json-tracing, plus alloc-dump
-			input:      []string{"run", "--trace", "--trace.format=json", "--dump", "0x6040"},
+		{ // json-tracing (default), plus alloc-dump
+			input:      []string{"run", "--trace", "--dump", "0x6040"},
 			wantStdout: "./testdata/evmrun/4.out.1.txt",
 			//wantStderr: "./testdata/evmrun/4.out.2.txt",
 		},
@@ -698,7 +698,10 @@ func TestEVMTracing(t *testing.T) {
 				"--input.env=./testdata/31/env.json", "--state.fork=Cancun",
 				"--trace",
 			},
-			expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl"},
+			//expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl"},
+			expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl",
+				"trace-1-0x03a7b0a91e61a170d64ea94b8263641ef5a8bbdb10ac69f466083a6789c77fb8.jsonl",
+				"trace-2-0xd96e0ce6418ee3360e11d3c7b6886f5a9a08f7ef183da72c23bb3b2374530128.jsonl"},
 		},
 		{
 			base: "./testdata/31",
@@ -706,14 +709,17 @@ func TestEVMTracing(t *testing.T) {
 				"--input.alloc=./testdata/31/alloc.json", "--input.txs=./testdata/31/txs.json",
 				"--input.env=./testdata/31/env.json", "--state.fork=Cancun",
 				"--trace.tracer", `
-{ 
-	result: function(){ 
-		return "hello world"
-	}, 
-	fault: function(){} 
+{   count: 0,
+	result: function(){
+		this.count = this.count + 1;
+		return "hello world " + this.count
+	},
+	fault: function(){}
 }`,
 			},
-			expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json"},
+			expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json",
+				"trace-1-0x03a7b0a91e61a170d64ea94b8263641ef5a8bbdb10ac69f466083a6789c77fb8.json",
+				"trace-2-0xd96e0ce6418ee3360e11d3c7b6886f5a9a08f7ef183da72c23bb3b2374530128.json"},
 		},
 		{
 			base: "./testdata/32",
diff --git a/cmd/evm/testdata/31/README.md b/cmd/evm/testdata/31/README.md
index 305e4f52da..b337c1eb62 100644
--- a/cmd/evm/testdata/31/README.md
+++ b/cmd/evm/testdata/31/README.md
@@ -1 +1,11 @@
 This test does some EVM execution, and can be used to test the tracers and trace-outputs.
+This test should yield three output-traces, in separate files
+
+For example:
+```
+[user@work evm]$ go  run . t8n --input.alloc ./testdata/31/alloc.json --input.txs ./testdata/31/txs.json --input.env ./testdata/31/env.json --state.fork Cancun --output.basedir /tmp --trace
+INFO [12-06|09:53:32.123] Created tracing-file                     path=/tmp/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl
+INFO [12-06|09:53:32.124] Created tracing-file                     path=/tmp/trace-1-0x03a7b0a91e61a170d64ea94b8263641ef5a8bbdb10ac69f466083a6789c77fb8.jsonl
+INFO [12-06|09:53:32.125] Created tracing-file                     path=/tmp/trace-2-0xd96e0ce6418ee3360e11d3c7b6886f5a9a08f7ef183da72c23bb3b2374530128.jsonl
+```
+
diff --git a/cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json b/cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json
index cd4bc1ab64..dfa1d7c7cb 100644
--- a/cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json
+++ b/cmd/evm/testdata/31/trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json
@@ -1 +1 @@
-"hello world"
+"hello world 1"
diff --git a/cmd/evm/testdata/31/trace-1-0x03a7b0a91e61a170d64ea94b8263641ef5a8bbdb10ac69f466083a6789c77fb8.json b/cmd/evm/testdata/31/trace-1-0x03a7b0a91e61a170d64ea94b8263641ef5a8bbdb10ac69f466083a6789c77fb8.json
new file mode 100644
index 0000000000..25980291c7
--- /dev/null
+++ b/cmd/evm/testdata/31/trace-1-0x03a7b0a91e61a170d64ea94b8263641ef5a8bbdb10ac69f466083a6789c77fb8.json
@@ -0,0 +1 @@
+"hello world 2"
diff --git a/cmd/evm/testdata/31/trace-1-0x03a7b0a91e61a170d64ea94b8263641ef5a8bbdb10ac69f466083a6789c77fb8.jsonl b/cmd/evm/testdata/31/trace-1-0x03a7b0a91e61a170d64ea94b8263641ef5a8bbdb10ac69f466083a6789c77fb8.jsonl
new file mode 100644
index 0000000000..26e5c7ee4e
--- /dev/null
+++ b/cmd/evm/testdata/31/trace-1-0x03a7b0a91e61a170d64ea94b8263641ef5a8bbdb10ac69f466083a6789c77fb8.jsonl
@@ -0,0 +1,6 @@
+{"pc":0,"op":96,"gas":"0x13498","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
+{"pc":2,"op":96,"gas":"0x13495","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
+{"pc":4,"op":96,"gas":"0x13492","gasCost":"0x3","memSize":0,"stack":["0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
+{"pc":6,"op":96,"gas":"0x1348f","gasCost":"0x3","memSize":0,"stack":["0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
+{"pc":8,"op":0,"gas":"0x1348c","gasCost":"0x0","memSize":0,"stack":["0x40","0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"STOP"}
+{"output":"","gasUsed":"0xc"}
diff --git a/cmd/evm/testdata/31/trace-2-0xd96e0ce6418ee3360e11d3c7b6886f5a9a08f7ef183da72c23bb3b2374530128.json b/cmd/evm/testdata/31/trace-2-0xd96e0ce6418ee3360e11d3c7b6886f5a9a08f7ef183da72c23bb3b2374530128.json
new file mode 100644
index 0000000000..242587bd83
--- /dev/null
+++ b/cmd/evm/testdata/31/trace-2-0xd96e0ce6418ee3360e11d3c7b6886f5a9a08f7ef183da72c23bb3b2374530128.json
@@ -0,0 +1 @@
+"hello world 3"
diff --git a/cmd/evm/testdata/31/trace-2-0xd96e0ce6418ee3360e11d3c7b6886f5a9a08f7ef183da72c23bb3b2374530128.jsonl b/cmd/evm/testdata/31/trace-2-0xd96e0ce6418ee3360e11d3c7b6886f5a9a08f7ef183da72c23bb3b2374530128.jsonl
new file mode 100644
index 0000000000..26e5c7ee4e
--- /dev/null
+++ b/cmd/evm/testdata/31/trace-2-0xd96e0ce6418ee3360e11d3c7b6886f5a9a08f7ef183da72c23bb3b2374530128.jsonl
@@ -0,0 +1,6 @@
+{"pc":0,"op":96,"gas":"0x13498","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
+{"pc":2,"op":96,"gas":"0x13495","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
+{"pc":4,"op":96,"gas":"0x13492","gasCost":"0x3","memSize":0,"stack":["0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
+{"pc":6,"op":96,"gas":"0x1348f","gasCost":"0x3","memSize":0,"stack":["0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"PUSH1"}
+{"pc":8,"op":0,"gas":"0x1348c","gasCost":"0x0","memSize":0,"stack":["0x40","0x40","0x40","0x40"],"depth":1,"refund":0,"opName":"STOP"}
+{"output":"","gasUsed":"0xc"}
diff --git a/cmd/evm/testdata/31/txs.json b/cmd/evm/testdata/31/txs.json
index 473c1526f4..da8f5761f6 100644
--- a/cmd/evm/testdata/31/txs.json
+++ b/cmd/evm/testdata/31/txs.json
@@ -10,5 +10,29 @@
     "r" : "0x0",
     "s" : "0x0",
     "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"
+  },
+  {
+    "gas": "0x186a0",
+    "gasPrice": "0x600",
+    "input": "0x",
+    "nonce": "0x1",
+    "to": "0x1111111111111111111111111111111111111111",
+    "value": "0x1",
+    "v" : "0x0",
+    "r" : "0x0",
+    "s" : "0x0",
+    "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"
+  },
+  {
+    "gas": "0x186a0",
+    "gasPrice": "0x600",
+    "input": "0x",
+    "nonce": "0x2",
+    "to": "0x1111111111111111111111111111111111111111",
+    "value": "0x1",
+    "v" : "0x0",
+    "r" : "0x0",
+    "s" : "0x0",
+    "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8"
   }
 ]
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 1a0215459c..1e52215b7e 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -133,11 +133,6 @@ func NewEVM(blockCtx BlockContext, statedb StateDB, chainConfig *params.ChainCon
 	return evm
 }
 
-// SetTracer sets the tracer for following state transition.
-func (evm *EVM) SetTracer(tracer *tracing.Hooks) {
-	evm.Config.Tracer = tracer
-}
-
 // SetPrecompiles sets the precompiled contracts for the EVM.
 // This method is only used through RPC calls.
 // It is not thread-safe.