Skip to content

Commit f8fb608

Browse files
committed
gas dimension live tracing: improve live tracer error handling
1 parent 979f02a commit f8fb608

File tree

3 files changed

+184
-106
lines changed

3 files changed

+184
-106
lines changed

eth/tracers/live/tx_gas_dimension_by_opcode.go

Lines changed: 173 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -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
3132
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"`
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

92117
func (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

103131
func (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

115146
func (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

211173
func (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+
}

eth/tracers/live/tx_gas_dimension_logger.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/ethereum/go-ethereum/core/tracing"
1313
"github.com/ethereum/go-ethereum/eth/tracers"
1414
"github.com/ethereum/go-ethereum/eth/tracers/native"
15+
"github.com/ethereum/go-ethereum/log"
1516
)
1617

1718
type txGasDimensionLiveTraceLogger struct {
@@ -37,6 +38,9 @@ func newTxGasDimensionLiveTraceLogger(cfg json.RawMessage) (*tracing.Hooks, erro
3738
return nil, fmt.Errorf("gas dimension live tracer path for output is required: %v", config)
3839
}
3940

41+
// be sure path exists
42+
os.MkdirAll(config.Path, 0755)
43+
4044
gasDimensionTracer, err := native.NewTxGasDimensionLogger(nil, nil, nil)
4145
if err != nil {
4246
return nil, err
@@ -98,8 +102,7 @@ func (t *txGasDimensionLiveTraceLogger) OnTxEnd(
98102
// then get the json from the native tracer
99103
executionResultJsonBytes, errGettingResult := t.GasDimensionTracer.GetResult()
100104
if errGettingResult != nil {
101-
errorJsonString := fmt.Sprintf("{\"errorGettingResult\": \"%s\"}", errGettingResult.Error())
102-
fmt.Println(errorJsonString)
105+
log.Error("Failed to get result", "error", errGettingResult)
103106
return
104107
}
105108

@@ -112,13 +115,13 @@ func (t *txGasDimensionLiveTraceLogger) OnTxEnd(
112115

113116
// Ensure the directory exists
114117
if err := os.MkdirAll(t.Path, 0755); err != nil {
115-
fmt.Printf("Failed to create directory %s: %v\n", t.Path, err)
118+
log.Error("Failed to create directory", "path", t.Path, "error", err)
116119
return
117120
}
118121

119122
// Write the file
120123
if err := os.WriteFile(filepath, executionResultJsonBytes, 0644); err != nil {
121-
fmt.Printf("Failed to write file %s: %v\n", filepath, err)
124+
log.Error("Failed to write file", "path", filepath, "error", err)
122125
return
123126
}
124127
}

0 commit comments

Comments
 (0)