cmd/evm: refactor file-output-handling for evm t8n

This commit is contained in:
Martin Holst Swende 2024-12-05 10:25:18 +01:00
parent 75f847390f
commit fa923e38eb
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
3 changed files with 193 additions and 85 deletions

View file

@ -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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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) {},
}
}

View file

@ -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
}