|
| 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