Skip to content

Commit a618155

Browse files
authored
Merge pull request #384 from OffchainLabs/guard-tracer-empty-callstack
Guard callTracer and erc7562Tracer OnTxEnd against empty callstack
2 parents 32556fb + b002346 commit a618155

3 files changed

Lines changed: 57 additions & 1 deletion

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
### Fixed
2+
- `callTracer.OnTxEnd` now returns early if the top-level frame was never captured (e.g. on timeouts)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2025, Offchain Labs, Inc.
2+
// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md
3+
4+
package arbtest
5+
6+
import (
7+
"context"
8+
"encoding/json"
9+
"math/big"
10+
"strings"
11+
"testing"
12+
13+
"github.com/ethereum/go-ethereum/rpc"
14+
)
15+
16+
// TestReproTraceTimeoutEmptyCallstackPanic ensures a trace that times out before its
17+
// top-level frame is captured returns an error instead of panicking the RPC handler. A
18+
// 1ns timeout makes the interrupt win the race against OnEnter on otherwise normal txs.
19+
func TestReproTraceTimeoutEmptyCallstackPanic(t *testing.T) {
20+
ctx, cancel := context.WithCancel(context.Background())
21+
defer cancel()
22+
builder := NewNodeBuilder(ctx).DefaultConfig(t, false)
23+
cleanup := builder.Build(t)
24+
defer cleanup()
25+
26+
builder.L2Info.GenerateAccount("User2")
27+
var lastBlock uint64
28+
for i := 0; i < 5; i++ {
29+
tx := builder.L2Info.PrepareTx("Owner", "User2", builder.L2Info.TransferGas, big.NewInt(1e12), nil)
30+
err := builder.L2.Client.SendTransaction(ctx, tx)
31+
Require(t, err)
32+
receipt, err := builder.L2.EnsureTxSucceeded(tx)
33+
Require(t, err)
34+
lastBlock = receipt.BlockNumber.Uint64()
35+
}
36+
37+
l2rpc := builder.L2.Stack.Attach()
38+
for _, tracer := range []string{"callTracer", "flatCallTracer", "erc7562Tracer"} {
39+
for attempt := 0; attempt < 50; attempt++ {
40+
for bn := uint64(1); bn <= lastBlock; bn++ {
41+
var blockTrace json.RawMessage
42+
err := l2rpc.CallContext(ctx, &blockTrace, "debug_traceBlockByNumber",
43+
rpc.BlockNumber(bn), // #nosec G115
44+
map[string]interface{}{"tracer": tracer, "timeout": "1ns"})
45+
// A timed-out trace must either complete (nil) or report the timeout. It must
46+
// never crash the handler ("method handler crashed") or return the misleading
47+
// "incorrect number of top-level calls" when no top-level frame was captured.
48+
if err != nil && !strings.Contains(err.Error(), "execution timeout") {
49+
t.Fatalf("tracer %s: timed-out trace returned unexpected error (want nil or \"execution timeout\"): %v", tracer, err)
50+
}
51+
}
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)