@@ -15,6 +15,7 @@ import (
1515 "github.com/ethereum/go-ethereum/common"
1616 "github.com/ethereum/go-ethereum/core/types"
1717 _vm "github.com/ethereum/go-ethereum/core/vm"
18+ "github.com/ethereum/go-ethereum/log"
1819)
1920
2021// initializer for the tracer
@@ -29,9 +30,24 @@ type txGasDimensionByOpcodeLiveTraceConfig struct {
2930
3031// gasDimensionTracer struct
3132type TxGasDimensionByOpcodeLiveTracer struct {
32- Path string `json:"path"` // Path to directory for output
33- ChainConfig * params.ChainConfig
34- GasDimensionTracer * native.TxGasDimensionByOpcodeTracer
33+ Path string `json:"path"` // Path to directory for output
34+ ChainConfig * params.ChainConfig // chain config, needed for the tracer
35+ skip bool // skip hooking system transactions
36+ nativeGasByOpcodeTracer * native.TxGasDimensionByOpcodeTracer // the native tracer that does all the actual work
37+ }
38+
39+ // an struct to capture information about errors in tracer execution
40+ type TxGasDimensionByOpcodeLiveTraceErrorInfo struct {
41+ TxHash string `json:"txHash"`
42+ BlockNumber string `json:"blockNumber"`
43+ Error string `json:"error"`
44+ TracerError string `json:"tracerError,omitempty"`
45+ Status uint64 `json:"status"`
46+ GasUsed uint64 `json:"gasUsed"`
47+ GasUsedForL1 uint64 `json:"gasUsedForL1"`
48+ GasUsedForL2 uint64 `json:"gasUsedForL2"`
49+ IntrinsicGas uint64 `json:"intrinsicGas"`
50+ Dimensions interface {} `json:"dimensions,omitempty"`
3551}
3652
3753// gasDimensionTracer returns a new tracer that traces gas
@@ -48,6 +64,8 @@ func NewTxGasDimensionByOpcodeLogger(
4864 if config .Path == "" {
4965 return nil , fmt .Errorf ("tx gas dimension live tracer path for output is required: %v" , config )
5066 }
67+ // be sure path exists
68+ os .MkdirAll (config .Path , 0755 )
5169
5270 // if you get stuck here, look at
5371 // cmd/chaininfo/arbitrum_chain_info.json
@@ -57,9 +75,10 @@ func NewTxGasDimensionByOpcodeLogger(
5775 }
5876
5977 t := & TxGasDimensionByOpcodeLiveTracer {
60- Path : config .Path ,
61- ChainConfig : config .ChainConfig ,
62- GasDimensionTracer : nil ,
78+ Path : config .Path ,
79+ ChainConfig : config .ChainConfig ,
80+ skip : false ,
81+ nativeGasByOpcodeTracer : nil ,
6382 }
6483
6584 return & tracing.Hooks {
@@ -78,15 +97,21 @@ func (t *TxGasDimensionByOpcodeLiveTracer) OnTxStart(
7897 tx * types.Transaction ,
7998 from common.Address ,
8099) {
81- if t .GasDimensionTracer != nil {
82- fmt .Println ("Error seen in the gas dimension live tracer lifecycle!" )
100+ if t .nativeGasByOpcodeTracer != nil {
101+ log .Error ("Error seen in the gas dimension live tracer lifecycle!" )
102+ }
103+
104+ // we skip internal / system transactions
105+ if tx .Type () == types .ArbitrumInternalTxType {
106+ t .skip = true
107+ return
83108 }
84109
85- t .GasDimensionTracer = & native.TxGasDimensionByOpcodeTracer {
110+ t .nativeGasByOpcodeTracer = & native.TxGasDimensionByOpcodeTracer {
86111 BaseGasDimensionTracer : native .NewBaseGasDimensionTracer (t .ChainConfig ),
87112 OpcodeToDimensions : make (map [_vm.OpCode ]native.GasesByDimension ),
88113 }
89- t .GasDimensionTracer .OnTxStart (vm , tx , from )
114+ t .nativeGasByOpcodeTracer .OnTxStart (vm , tx , from )
90115}
91116
92117func (t * TxGasDimensionByOpcodeLiveTracer ) OnFault (
@@ -97,7 +122,10 @@ func (t *TxGasDimensionByOpcodeLiveTracer) OnFault(
97122 depth int ,
98123 err error ,
99124) {
100- t .GasDimensionTracer .OnFault (pc , op , gas , cost , scope , depth , err )
125+ if t .skip {
126+ return
127+ }
128+ t .nativeGasByOpcodeTracer .OnFault (pc , op , gas , cost , scope , depth , err )
101129}
102130
103131func (t * TxGasDimensionByOpcodeLiveTracer ) OnOpcode (
@@ -109,103 +137,37 @@ func (t *TxGasDimensionByOpcodeLiveTracer) OnOpcode(
109137 depth int ,
110138 err error ,
111139) {
112- t .GasDimensionTracer .OnOpcode (pc , op , gas , cost , scope , rData , depth , err )
140+ if t .skip {
141+ return
142+ }
143+ t .nativeGasByOpcodeTracer .OnOpcode (pc , op , gas , cost , scope , rData , depth , err )
113144}
114145
115146func (t * TxGasDimensionByOpcodeLiveTracer ) OnTxEnd (
116147 receipt * types.Receipt ,
117148 err error ,
118149) {
119- // first call the native tracer's OnTxEnd
120- t .GasDimensionTracer .OnTxEnd (receipt , err )
121-
122- blockNumber := receipt .BlockNumber .String ()
123- txHashString := receipt .TxHash .Hex ()
124-
125- // Handle errors by saving them to a separate errors directory
126- // Note: We only consider errors in the tracing process itself, not transaction reverts
127- tracerErr := t .GasDimensionTracer .Reason ()
128- if tracerErr != nil || err != nil {
129- // Create error directory path
130- errorDirPath := filepath .Join (t .Path , "errors" , blockNumber )
131- if err := os .MkdirAll (errorDirPath , 0755 ); err != nil {
132- fmt .Printf ("Failed to create error directory %s: %v\n " , errorDirPath , err )
133- return
134- }
135-
136- // Create error info structure
137- errorInfo := struct {
138- TxHash string `json:"txHash"`
139- BlockNumber string `json:"blockNumber"`
140- Error string `json:"error"`
141- TracerError string `json:"tracerError,omitempty"`
142- GasUsed uint64 `json:"gasUsed"`
143- GasUsedForL1 uint64 `json:"gasUsedForL1"`
144- GasUsedForL2 uint64 `json:"gasUsedForL2"`
145- IntrinsicGas uint64 `json:"intrinsicGas"`
146- Dimensions interface {} `json:"dimensions,omitempty"`
147- }{
148- TxHash : txHashString ,
149- BlockNumber : blockNumber ,
150- Error : err .Error (),
151- GasUsed : receipt .GasUsed ,
152- GasUsedForL1 : receipt .GasUsedForL1 ,
153- GasUsedForL2 : receipt .GasUsedForL2 (),
154- }
155-
156- // Add tracer error if present
157- if tracerErr != nil {
158- errorInfo .TracerError = tracerErr .Error ()
159- }
150+ // If we skipped this transaction, just reset and return
151+ if t .skip {
152+ t .nativeGasByOpcodeTracer = nil
153+ t .skip = false
154+ return
155+ }
160156
161- // Try to get any available gas dimension data
162- dimensions := t .GasDimensionTracer .GetOpcodeDimensionSummary ()
163- errorInfo .Dimensions = dimensions
157+ // first call the native tracer's OnTxEnd
158+ t .nativeGasByOpcodeTracer .OnTxEnd (receipt , err )
164159
165- // Marshal error info to JSON
166- errorData , err := json .MarshalIndent (errorInfo , "" , " " )
167- if err != nil {
168- fmt .Printf ("Failed to marshal error info: %v\n " , err )
169- return
170- }
160+ tracerErr := t .nativeGasByOpcodeTracer .Reason ()
171161
172- // Write error file
173- errorFilepath := filepath .Join (errorDirPath , fmt .Sprintf ("%s.json" , txHashString ))
174- if err := os .WriteFile (errorFilepath , errorData , 0644 ); err != nil {
175- fmt .Printf ("Failed to write error file %s: %v\n " , errorFilepath , err )
176- return
177- }
178- } else {
179- // system transactions don't use any gas
180- // they can be skipped
181- if receipt .GasUsed != 0 {
182- executionResultBytes , err := t .GasDimensionTracer .GetProtobufResult ()
183- if err != nil {
184- fmt .Printf ("Failed to get protobuf result: %v\n " , err )
185- return
186- }
187-
188- // Create the file path
189- filename := fmt .Sprintf ("%s.pb" , txHashString )
190- dirPath := filepath .Join (t .Path , blockNumber )
191- filepath := filepath .Join (dirPath , filename )
192-
193- // Ensure the directory exists (including block number subdirectory)
194- if err := os .MkdirAll (dirPath , 0755 ); err != nil {
195- fmt .Printf ("Failed to create directory %s: %v\n " , dirPath , err )
196- return
197- }
198-
199- // Write the file
200- if err := os .WriteFile (filepath , executionResultBytes , 0644 ); err != nil {
201- fmt .Printf ("Failed to write file %s: %v\n " , filepath , err )
202- return
203- }
204- }
162+ if tracerErr != nil || err != nil || receipt == nil {
163+ writeTxErrorToFile (t , receipt , err , tracerErr )
164+ } else { // tx did not have any errors
165+ writeTxSuccessToFile (t , receipt )
205166 }
206167
207168 // reset the tracer
208- t .GasDimensionTracer = nil
169+ t .nativeGasByOpcodeTracer = nil
170+ t .skip = false
209171}
210172
211173func (t * TxGasDimensionByOpcodeLiveTracer ) OnBlockStart (ev tracing.BlockEvent ) {
@@ -221,7 +183,7 @@ func (t *TxGasDimensionByOpcodeLiveTracer) OnBlockEndMetrics(blockNumber uint64,
221183
222184 // Ensure the directory exists
223185 if err := os .MkdirAll (dirPath , 0755 ); err != nil {
224- fmt . Printf ("Failed to create directory %s: %v \n " , dirPath , err )
186+ log . Error ("Failed to create directory" , "path" , dirPath , "error" , err )
225187 return
226188 }
227189
@@ -230,7 +192,120 @@ func (t *TxGasDimensionByOpcodeLiveTracer) OnBlockEndMetrics(blockNumber uint64,
230192
231193 // Write the file
232194 if err := os .WriteFile (filepath , fmt .Appendf (nil , "%d" , outData ), 0644 ); err != nil {
233- fmt . Printf ("Failed to write file %s: %v \n " , filepath , err )
195+ log . Error ("Failed to write file" , "path" , filepath , "error" , err )
234196 return
235197 }
236198}
199+
200+ // if the transaction has any kind of error, try to get as much information
201+ // as you can out of it, and then write that out to a file underneath
202+ // Path/errors/blocknumber/blocknumber_txhash.json
203+ func writeTxErrorToFile (t * TxGasDimensionByOpcodeLiveTracer , receipt * types.Receipt , err error , tracerError error ) {
204+ var txHashStr string = "no-tx-hash"
205+ var blockNumberStr string = "no-block-number"
206+ var errorInfo TxGasDimensionByOpcodeLiveTraceErrorInfo
207+
208+ var errStr string = ""
209+ var tracerErrStr string = ""
210+ if err != nil {
211+ errStr = err .Error ()
212+ }
213+ if tracerError != nil {
214+ tracerErrStr = tracerError .Error ()
215+ }
216+ // dimensions should not error, just return empty if there is no data
217+ dimensions := t .nativeGasByOpcodeTracer .GetOpcodeDimensionSummary ()
218+
219+ if receipt == nil {
220+ // we need something to use as a name for the error file,
221+ // and we have no tx to hash
222+ txHashStr += time .Now ().String ()
223+ outErrString := fmt .Sprintf ("receipt is nil, err: %s" , errStr )
224+ errorInfo = TxGasDimensionByOpcodeLiveTraceErrorInfo {
225+ Error : outErrString ,
226+ TracerError : tracerErrStr ,
227+ Dimensions : dimensions ,
228+ }
229+ } else {
230+ // if we errored in the tracer because we had an unexpected gas cost mismatch
231+ blockNumberStr = receipt .BlockNumber .String ()
232+ txHashStr = receipt .TxHash .Hex ()
233+
234+ var intrinsicGas uint64 = 0
235+ baseExecutionResult , err := t .nativeGasByOpcodeTracer .GetBaseExecutionResult ()
236+ if err == nil {
237+ intrinsicGas = baseExecutionResult .IntrinsicGas
238+ }
239+
240+ errorInfo = TxGasDimensionByOpcodeLiveTraceErrorInfo {
241+ TxHash : txHashStr ,
242+ BlockNumber : blockNumberStr ,
243+ Error : errStr ,
244+ TracerError : tracerErrStr ,
245+ Status : receipt .Status ,
246+ GasUsed : receipt .GasUsed ,
247+ GasUsedForL1 : receipt .GasUsedForL1 ,
248+ GasUsedForL2 : receipt .GasUsedForL2 (),
249+ IntrinsicGas : intrinsicGas ,
250+ Dimensions : dimensions ,
251+ }
252+ }
253+
254+ // Create error directory path
255+ errorDirPath := filepath .Join (t .Path , "errors" , blockNumberStr )
256+ if err := os .MkdirAll (errorDirPath , 0755 ); err != nil {
257+ log .Error ("Failed to create error directory" , "path" , errorDirPath , "error" , err )
258+ return
259+ }
260+ // Marshal error info to JSON
261+ errorData , err := json .MarshalIndent (errorInfo , "" , " " )
262+ if err != nil {
263+ log .Error ("Failed to marshal error info" , "error" , err )
264+ return
265+ }
266+
267+ // Write error file
268+ errorFilepath := filepath .Join (errorDirPath , fmt .Sprintf ("%s_%s.json" , blockNumberStr , txHashStr ))
269+ if err := os .WriteFile (errorFilepath , errorData , 0644 ); err != nil {
270+ log .Error ("Failed to write error file" , "path" , errorFilepath , "error" , err )
271+ return
272+ }
273+ }
274+
275+ // if the transaction is a non-erroring transaction, write it out to
276+ // the path specified when the tracer was created, under a folder organized by
277+ // every 1000 blocks (this avoids making a huge number of directories,
278+ // which makes analysis iteration over the entire dataset faster)
279+ // the individual filenames are where the filename is blocknumber_txhash.pb
280+ // so you have Path/block_group/blocknumber_txhash.pb
281+ // e.g. Path/1000/1890_0x123abc.pb
282+ func writeTxSuccessToFile (t * TxGasDimensionByOpcodeLiveTracer , receipt * types.Receipt ) {
283+ // system transactions don't use any gas
284+ // they can be skipped
285+ if receipt .GasUsed != 0 {
286+ txHashString := receipt .TxHash .Hex ()
287+ blockNumber := receipt .BlockNumber
288+ executionResultBytes , err := t .nativeGasByOpcodeTracer .GetProtobufResult ()
289+ if err != nil {
290+ log .Error ("Failed to get protobuf result" , "error" , err )
291+ return
292+ }
293+
294+ blockGroup := (blockNumber .Uint64 () / 1000 ) * 1000
295+ dirPath := filepath .Join (t .Path , fmt .Sprintf ("%d" , blockGroup ))
296+ filename := fmt .Sprintf ("%s_%s.pb" , blockNumber .String (), txHashString )
297+ filepath := filepath .Join (dirPath , filename )
298+
299+ // Ensure the directory exists (including block number subdirectory)
300+ if err := os .MkdirAll (dirPath , 0755 ); err != nil {
301+ log .Error ("Failed to create directory" , "path" , dirPath , "error" , err )
302+ return
303+ }
304+
305+ // Write the file
306+ if err := os .WriteFile (filepath , executionResultBytes , 0644 ); err != nil {
307+ log .Error ("Failed to write file" , "path" , filepath , "error" , err )
308+ return
309+ }
310+ }
311+ }
0 commit comments