Skip to content

Commit 122c51f

Browse files
committed
gas dimension live tracing: handle reverts and invalid opcodes
1 parent 56b5fa6 commit 122c51f

8 files changed

+220
-39
lines changed

eth/tracers/live/tx_gas_dimension_by_opcode.go

Lines changed: 85 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func NewTxGasDimensionByOpcodeLogger(
6464

6565
return &tracing.Hooks{
6666
OnOpcode: t.OnOpcode,
67+
OnFault: t.OnFault,
6768
OnTxStart: t.OnTxStart,
6869
OnTxEnd: t.OnTxEnd,
6970
OnBlockStart: t.OnBlockStart,
@@ -88,6 +89,17 @@ func (t *TxGasDimensionByOpcodeLiveTracer) OnTxStart(
8889
t.GasDimensionTracer.OnTxStart(vm, tx, from)
8990
}
9091

92+
func (t *TxGasDimensionByOpcodeLiveTracer) OnFault(
93+
pc uint64,
94+
op byte,
95+
gas, cost uint64,
96+
scope tracing.OpContext,
97+
depth int,
98+
err error,
99+
) {
100+
t.GasDimensionTracer.OnFault(pc, op, gas, cost, scope, depth, err)
101+
}
102+
91103
func (t *TxGasDimensionByOpcodeLiveTracer) OnOpcode(
92104
pc uint64,
93105
op byte,
@@ -107,34 +119,89 @@ func (t *TxGasDimensionByOpcodeLiveTracer) OnTxEnd(
107119
// first call the native tracer's OnTxEnd
108120
t.GasDimensionTracer.OnTxEnd(receipt, err)
109121

110-
// system transactions don't use any gas
111-
// they can be skipped
112-
if receipt.GasUsed != 0 {
113-
executionResultBytes, err := t.GasDimensionTracer.GetProtobufResult()
114-
if err != nil {
115-
fmt.Printf("Failed to get protobuf result: %v\n", err)
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)
116133
return
117134
}
118135

119-
blockNumber := receipt.BlockNumber.String()
120-
txHashString := receipt.TxHash.Hex()
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+
}
121155

122-
// Create the file path
123-
filename := fmt.Sprintf("%s.pb", txHashString)
124-
dirPath := filepath.Join(t.Path, blockNumber)
125-
filepath := filepath.Join(dirPath, filename)
156+
// Add tracer error if present
157+
if tracerErr != nil {
158+
errorInfo.TracerError = tracerErr.Error()
159+
}
126160

127-
// Ensure the directory exists (including block number subdirectory)
128-
if err := os.MkdirAll(dirPath, 0755); err != nil {
129-
fmt.Printf("Failed to create directory %s: %v\n", dirPath, err)
161+
// Try to get any available gas dimension data
162+
dimensions := t.GasDimensionTracer.GetOpcodeDimensionSummary()
163+
errorInfo.Dimensions = dimensions
164+
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)
130169
return
131170
}
132171

133-
// Write the file
134-
if err := os.WriteFile(filepath, executionResultBytes, 0644); err != nil {
135-
fmt.Printf("Failed to write file %s: %v\n", filepath, err)
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)
136176
return
137177
}
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+
}
138205
}
139206

140207
// reset the tracer

eth/tracers/live/tx_gas_dimension_logger.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func newTxGasDimensionLiveTraceLogger(cfg json.RawMessage) (*tracing.Hooks, erro
5050
return &tracing.Hooks{
5151
OnOpcode: t.OnOpcode,
5252
OnTxStart: t.OnTxStart,
53+
OnFault: t.OnFault,
5354
OnTxEnd: t.OnTxEnd,
5455
OnBlockStart: t.OnBlockStart,
5556
OnBlockEnd: t.OnBlockEnd,
@@ -75,6 +76,18 @@ func (t *txGasDimensionLiveTraceLogger) OnOpcode(
7576
) {
7677
t.GasDimensionTracer.OnOpcode(pc, op, gas, cost, scope, rData, depth, err)
7778
}
79+
80+
func (t *txGasDimensionLiveTraceLogger) OnFault(
81+
pc uint64,
82+
op byte,
83+
gas, cost uint64,
84+
scope tracing.OpContext,
85+
depth int,
86+
err error,
87+
) {
88+
t.GasDimensionTracer.OnFault(pc, op, gas, cost, scope, depth, err)
89+
}
90+
7891
func (t *txGasDimensionLiveTraceLogger) OnTxEnd(
7992
receipt *types.Receipt,
8093
err error,

eth/tracers/native/base_gas_dimension_tracer.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ type BaseGasDimensionTracer struct {
4141
executionGasAccumulated uint64
4242
// the amount of refund allowed at the end of the transaction, adjusted by EIP-3529
4343
refundAdjusted uint64
44-
// whether the transaction had an error, like out of gas
44+
// whether the transaction should be rejected for not following the rules of the VM
4545
err error
46+
// the transactions status, for a valid transaction that followed the rules,
47+
// but could have still failed for reasons inside the rules, like reverts, out of gas, etc.
48+
status uint64
4649
// whether the tracer itself was interrupted
4750
interrupt atomic.Bool
4851
// reason or error for the interruption in the tracer itself (as opposed to the transaction)
@@ -107,9 +110,12 @@ func (t *BaseGasDimensionTracer) onOpcodeStart(
107110
callStackInfo *CallGasDimensionInfo,
108111
opcode vm.OpCode,
109112
) {
113+
// First check if tracer has been interrupted
110114
if t.interrupt.Load() {
111115
return true, zeroGasesByDimension(), nil, vm.OpCode(op)
112116
}
117+
118+
// Depth validation - if depth doesn't match expectations, this is a tracer error
113119
if depth != t.depth && depth != t.depth-1 {
114120
t.interrupt.Store(true)
115121
t.reason = fmt.Errorf(
@@ -133,18 +139,21 @@ func (t *BaseGasDimensionTracer) onOpcodeStart(
133139
return true, zeroGasesByDimension(), nil, vm.OpCode(op)
134140
}
135141

136-
// get the gas dimension function
137-
// if it's not a call, directly calculate the gas dimensions for the opcode
142+
// Get the gas dimension function
143+
// If it's not a call, directly calculate the gas dimensions for the opcode
138144
f := GetCalcGasDimensionFunc(vm.OpCode(op))
139145
var fErr error
140146
gasesByDimension, callStackInfo, fErr = f(t, pc, op, gas, cost, scope, rData, depth, err)
147+
148+
// If there's a problem with our dimension calculation function, this is a tracer error
141149
if fErr != nil {
142150
t.interrupt.Store(true)
143151
t.reason = fErr
144152
return true, zeroGasesByDimension(), nil, vm.OpCode(op)
145153
}
146154
opcode = vm.OpCode(op)
147155

156+
// Logic validation check - another potential tracer error
148157
if WasCallOrCreate(opcode) && callStackInfo == nil && err == nil || !WasCallOrCreate(opcode) && callStackInfo != nil {
149158
t.interrupt.Store(true)
150159
t.reason = fmt.Errorf(
@@ -271,6 +280,7 @@ func (t *BaseGasDimensionTracer) OnTxEnd(receipt *types.Receipt, err error) {
271280
t.gasUsedForL1 = receipt.GasUsedForL1
272281
t.gasUsedForL2 = receipt.GasUsedForL2()
273282
t.txHash = receipt.TxHash
283+
t.status = receipt.Status
274284
t.refundAdjusted = t.adjustRefund(t.executionGasAccumulated+t.intrinsicGas, t.GetRefundAccumulated())
275285
}
276286

@@ -315,9 +325,16 @@ func (t *BaseGasDimensionTracer) GetPrevAccessList() (addresses map[common.Addre
315325
return t.prevAccessListAddresses, t.prevAccessListSlots
316326
}
317327

318-
// Error returns the VM error captured by the trace
328+
// Error returns the EVM execution error captured by the trace
319329
func (t *BaseGasDimensionTracer) Error() error { return t.err }
320330

331+
// Status returns the status of the transaction, e.g. 0 for failure, 1 for success
332+
// a transaction can revert, fail, and still be mined and included in a block
333+
func (t *BaseGasDimensionTracer) Status() uint64 { return t.status }
334+
335+
// Reason returns any errors that occurred in the tracer itself
336+
func (t *BaseGasDimensionTracer) Reason() error { return t.reason }
337+
321338
// Add to the execution gas accumulated, for tracking adjusted refund
322339
func (t *BaseGasDimensionTracer) AddToExecutionGasAccumulated(gas uint64) {
323340
t.executionGasAccumulated += gas
@@ -390,6 +407,7 @@ type BaseExecutionResult struct {
390407
TxHash string `json:"txHash"`
391408
BlockTimestamp uint64 `json:"blockTimestamp"`
392409
BlockNumber *big.Int `json:"blockNumber"`
410+
Status uint64 `json:"status"`
393411
}
394412

395413
// get the result of the transaction execution that we will hand to the json output
@@ -410,5 +428,6 @@ func (t *BaseGasDimensionTracer) GetBaseExecutionResult() (BaseExecutionResult,
410428
TxHash: t.txHash.Hex(),
411429
BlockTimestamp: t.env.Time,
412430
BlockNumber: t.env.BlockNumber,
431+
Status: t.status,
413432
}, nil
414433
}

eth/tracers/native/gas_dimension_calc.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ func calcSimpleSingleDimensionGas(
194194
err error,
195195
) (GasesByDimension, *CallGasDimensionInfo, error) {
196196
if err != nil {
197-
return outOfGas(gas)
197+
return consumeAllRemainingGas(gas)
198198
}
199199
return GasesByDimension{
200200
OneDimensionalGasCost: cost,
@@ -226,7 +226,7 @@ func calcSimpleAddressAccessSetGas(
226226
err error,
227227
) (GasesByDimension, *CallGasDimensionInfo, error) {
228228
if err != nil {
229-
return outOfGas(gas)
229+
return consumeAllRemainingGas(gas)
230230
}
231231
// for all these opcodes the address being checked is in stack position 0
232232
addressAsInt := scope.StackData()[len(scope.StackData())-1]
@@ -260,7 +260,7 @@ func calcSLOADGas(
260260
err error,
261261
) (GasesByDimension, *CallGasDimensionInfo, error) {
262262
if err != nil {
263-
return outOfGas(gas)
263+
return consumeAllRemainingGas(gas)
264264
}
265265
stackData := scope.StackData()
266266
stackLen := len(stackData)
@@ -304,7 +304,7 @@ func calcExtCodeCopyGas(
304304
// if state access is 2600, then have 2500 for state access
305305
// rest is computation.
306306
if err != nil {
307-
return outOfGas(gas)
307+
return consumeAllRemainingGas(gas)
308308
}
309309
stack := scope.StackData()
310310
lenStack := len(stack)
@@ -356,7 +356,7 @@ func calcStateReadCallGas(
356356
err error,
357357
) (GasesByDimension, *CallGasDimensionInfo, error) {
358358
if err != nil {
359-
return outOfGas(gas)
359+
return consumeAllRemainingGas(gas)
360360
}
361361
stack := scope.StackData()
362362
lenStack := len(stack)
@@ -480,7 +480,7 @@ func calcLogGas(
480480
// 32 bytes per topic is 256 gas per topic.
481481
// rest is computation (for the bloom filter computation, memory expansion, etc)
482482
if err != nil {
483-
return outOfGas(gas)
483+
return consumeAllRemainingGas(gas)
484484
}
485485
numTopics := uint64(0)
486486
switch vm.OpCode(op) {
@@ -537,7 +537,7 @@ func calcCreateGas(
537537
// static_gas = 32000
538538
// dynamic_gas = init_code_cost + memory_expansion_cost + deployment_code_execution_cost + code_deposit_cost
539539
if err != nil {
540-
return outOfGas(gas)
540+
return consumeAllRemainingGas(gas)
541541
}
542542
stack := scope.StackData()
543543
lenStack := len(stack)
@@ -633,7 +633,7 @@ func calcReadAndStoreCallGas(
633633
err error,
634634
) (GasesByDimension, *CallGasDimensionInfo, error) {
635635
if err != nil {
636-
return outOfGas(gas)
636+
return consumeAllRemainingGas(gas)
637637
}
638638
stack := scope.StackData()
639639
lenStack := len(stack)
@@ -816,7 +816,7 @@ func calcSStoreGas(
816816
// to find per-step changes, we track the accumulated refund
817817
// and compare it to the current refund
818818
if err != nil {
819-
return outOfGas(gas)
819+
return consumeAllRemainingGas(gas)
820820
}
821821
currentRefund := t.GetOpRefund()
822822
accumulatedRefund := t.GetRefundAccumulated()
@@ -884,7 +884,7 @@ func calcSelfDestructGas(
884884
// excepting 100 for the warm access set
885885
// doesn't actually delete anything from disk, it just marks it as deleted.
886886
if err != nil {
887-
return outOfGas(gas)
887+
return consumeAllRemainingGas(gas)
888888
}
889889
if cost == params.CreateBySelfdestructGas+params.SelfdestructGasEIP150 {
890890
// warm but funds target empty
@@ -1122,7 +1122,7 @@ func checkGasDimensionsEqualCallGas(
11221122
}
11231123

11241124
// helper function that purely makes the golang prettier for the out of gas case
1125-
func outOfGas(gas uint64) (GasesByDimension, *CallGasDimensionInfo, error) {
1125+
func consumeAllRemainingGas(gas uint64) (GasesByDimension, *CallGasDimensionInfo, error) {
11261126
return GasesByDimension{
11271127
OneDimensionalGasCost: gas,
11281128
Computation: gas,

eth/tracers/native/proto/gas_dimension_by_opcode.pb.go

Lines changed: 14 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)