github.com/ethereum/go-ethereum@v1.16.1/cmd/evm/internal/t8ntool/file_tracer.go (about)

     1  // Copyright 2024 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // go-ethereum is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package t8ntool
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"math/big"
    24  	"os"
    25  	"path/filepath"
    26  
    27  	"github.com/ethereum/go-ethereum/common"
    28  	"github.com/ethereum/go-ethereum/core/tracing"
    29  	"github.com/ethereum/go-ethereum/core/types"
    30  	"github.com/ethereum/go-ethereum/eth/tracers"
    31  	"github.com/ethereum/go-ethereum/log"
    32  )
    33  
    34  // fileWritingTracer wraps either a tracer or a logger. On tx start,
    35  // it instantiates a tracer/logger, creates a new file to direct output to,
    36  // and on tx end it closes the file.
    37  type fileWritingTracer struct {
    38  	txIndex     int            // transaction counter
    39  	inner       *tracing.Hooks // inner hooks
    40  	destination io.WriteCloser // the currently open file (if any)
    41  	baseDir     string         // baseDir to write output-files to
    42  	suffix      string         // suffix is the suffix to use when creating files
    43  
    44  	// for custom tracing
    45  	getResult func() (json.RawMessage, error)
    46  }
    47  
    48  func (l *fileWritingTracer) Write(p []byte) (n int, err error) {
    49  	if l.destination != nil {
    50  		return l.destination.Write(p)
    51  	}
    52  	log.Warn("Tracer wrote to non-existing output")
    53  	// It is tempting to return an error here, however, the json encoder
    54  	// will no retry writing to an io.Writer once it has returned an error once.
    55  	// Therefore, we must squash the error.
    56  	return n, nil
    57  }
    58  
    59  // newFileWriter creates a set of hooks which wraps inner hooks (typically a logger),
    60  // and writes the output to a file, one file per transaction.
    61  func newFileWriter(baseDir string, innerFn func(out io.Writer) *tracing.Hooks) *tracing.Hooks {
    62  	t := &fileWritingTracer{
    63  		baseDir: baseDir,
    64  		suffix:  "jsonl",
    65  	}
    66  	t.inner = innerFn(t) // instantiate the inner tracer
    67  	return t.hooks()
    68  }
    69  
    70  // newResultWriter creates a set of hooks wraps and invokes an underlying tracer,
    71  // and writes the result (getResult-output) to file, one per transaction.
    72  func newResultWriter(baseDir string, tracer *tracers.Tracer) *tracing.Hooks {
    73  	t := &fileWritingTracer{
    74  		baseDir:   baseDir,
    75  		getResult: tracer.GetResult,
    76  		inner:     tracer.Hooks,
    77  		suffix:    "json",
    78  	}
    79  	return t.hooks()
    80  }
    81  
    82  // OnTxStart creates a new output-file specific for this transaction, and invokes
    83  // the inner OnTxStart handler.
    84  func (l *fileWritingTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
    85  	// Open a new file, or print a warning log if it's failed
    86  	fname := filepath.Join(l.baseDir, fmt.Sprintf("trace-%d-%v.%v", l.txIndex, tx.Hash().String(), l.suffix))
    87  	traceFile, err := os.Create(fname)
    88  	if err != nil {
    89  		log.Warn("Failed creating trace-file", "err", err)
    90  	} else {
    91  		log.Info("Created tracing-file", "path", fname)
    92  		l.destination = traceFile
    93  	}
    94  	if l.inner != nil && l.inner.OnTxStart != nil {
    95  		l.inner.OnTxStart(env, tx, from)
    96  	}
    97  }
    98  
    99  // OnTxEnd writes result (if getResult exist), closes any currently open output-file,
   100  // and invokes the inner OnTxEnd handler.
   101  func (l *fileWritingTracer) OnTxEnd(receipt *types.Receipt, err error) {
   102  	if l.inner != nil && l.inner.OnTxEnd != nil {
   103  		l.inner.OnTxEnd(receipt, err)
   104  	}
   105  	if l.getResult != nil && l.destination != nil {
   106  		if result, err := l.getResult(); result != nil {
   107  			json.NewEncoder(l.destination).Encode(result)
   108  		} else {
   109  			log.Warn("Error obtaining tracer result", "err", err)
   110  		}
   111  		l.destination.Close()
   112  		l.destination = nil
   113  	}
   114  	l.txIndex++
   115  }
   116  
   117  func (l *fileWritingTracer) hooks() *tracing.Hooks {
   118  	return &tracing.Hooks{
   119  		OnTxStart: l.OnTxStart,
   120  		OnTxEnd:   l.OnTxEnd,
   121  		OnEnter: func(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
   122  			if l.inner != nil && l.inner.OnEnter != nil {
   123  				l.inner.OnEnter(depth, typ, from, to, input, gas, value)
   124  			}
   125  		},
   126  		OnExit: func(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
   127  			if l.inner != nil && l.inner.OnExit != nil {
   128  				l.inner.OnExit(depth, output, gasUsed, err, reverted)
   129  			}
   130  		},
   131  		OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
   132  			if l.inner != nil && l.inner.OnOpcode != nil {
   133  				l.inner.OnOpcode(pc, op, gas, cost, scope, rData, depth, err)
   134  			}
   135  		},
   136  		OnFault: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) {
   137  			if l.inner != nil && l.inner.OnFault != nil {
   138  				l.inner.OnFault(pc, op, gas, cost, scope, depth, err)
   139  			}
   140  		},
   141  		OnSystemCallStart: func() {
   142  			if l.inner != nil && l.inner.OnSystemCallStart != nil {
   143  				l.inner.OnSystemCallStart()
   144  			}
   145  		},
   146  		OnSystemCallEnd: func() {
   147  			if l.inner != nil && l.inner.OnSystemCallEnd != nil {
   148  				l.inner.OnSystemCallEnd()
   149  			}
   150  		},
   151  	}
   152  }