@@ -15,6 +15,7 @@ import (
15
15
"github.com/ethereum/go-ethereum/common"
16
16
"github.com/ethereum/go-ethereum/core/types"
17
17
_vm "github.com/ethereum/go-ethereum/core/vm"
18
+ "github.com/ethereum/go-ethereum/log"
18
19
)
19
20
20
21
// initializer for the tracer
@@ -29,9 +30,24 @@ type txGasDimensionByOpcodeLiveTraceConfig struct {
29
30
30
31
// gasDimensionTracer struct
31
32
type 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"`
35
51
}
36
52
37
53
// gasDimensionTracer returns a new tracer that traces gas
@@ -48,6 +64,8 @@ func NewTxGasDimensionByOpcodeLogger(
48
64
if config .Path == "" {
49
65
return nil , fmt .Errorf ("tx gas dimension live tracer path for output is required: %v" , config )
50
66
}
67
+ // be sure path exists
68
+ os .MkdirAll (config .Path , 0755 )
51
69
52
70
// if you get stuck here, look at
53
71
// cmd/chaininfo/arbitrum_chain_info.json
@@ -57,9 +75,10 @@ func NewTxGasDimensionByOpcodeLogger(
57
75
}
58
76
59
77
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 ,
63
82
}
64
83
65
84
return & tracing.Hooks {
@@ -78,15 +97,21 @@ func (t *TxGasDimensionByOpcodeLiveTracer) OnTxStart(
78
97
tx * types.Transaction ,
79
98
from common.Address ,
80
99
) {
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
83
108
}
84
109
85
- t .GasDimensionTracer = & native.TxGasDimensionByOpcodeTracer {
110
+ t .nativeGasByOpcodeTracer = & native.TxGasDimensionByOpcodeTracer {
86
111
BaseGasDimensionTracer : native .NewBaseGasDimensionTracer (t .ChainConfig ),
87
112
OpcodeToDimensions : make (map [_vm.OpCode ]native.GasesByDimension ),
88
113
}
89
- t .GasDimensionTracer .OnTxStart (vm , tx , from )
114
+ t .nativeGasByOpcodeTracer .OnTxStart (vm , tx , from )
90
115
}
91
116
92
117
func (t * TxGasDimensionByOpcodeLiveTracer ) OnFault (
@@ -97,7 +122,10 @@ func (t *TxGasDimensionByOpcodeLiveTracer) OnFault(
97
122
depth int ,
98
123
err error ,
99
124
) {
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 )
101
129
}
102
130
103
131
func (t * TxGasDimensionByOpcodeLiveTracer ) OnOpcode (
@@ -109,103 +137,37 @@ func (t *TxGasDimensionByOpcodeLiveTracer) OnOpcode(
109
137
depth int ,
110
138
err error ,
111
139
) {
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 )
113
144
}
114
145
115
146
func (t * TxGasDimensionByOpcodeLiveTracer ) OnTxEnd (
116
147
receipt * types.Receipt ,
117
148
err error ,
118
149
) {
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
+ }
160
156
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 )
164
159
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 ()
171
161
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 )
205
166
}
206
167
207
168
// reset the tracer
208
- t .GasDimensionTracer = nil
169
+ t .nativeGasByOpcodeTracer = nil
170
+ t .skip = false
209
171
}
210
172
211
173
func (t * TxGasDimensionByOpcodeLiveTracer ) OnBlockStart (ev tracing.BlockEvent ) {
@@ -221,7 +183,7 @@ func (t *TxGasDimensionByOpcodeLiveTracer) OnBlockEndMetrics(blockNumber uint64,
221
183
222
184
// Ensure the directory exists
223
185
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 )
225
187
return
226
188
}
227
189
@@ -230,7 +192,120 @@ func (t *TxGasDimensionByOpcodeLiveTracer) OnBlockEndMetrics(blockNumber uint64,
230
192
231
193
// Write the file
232
194
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 )
234
196
return
235
197
}
236
198
}
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