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 }