You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(evm-rpc): accept Scroll's empty call frame for a failed L1 message - #603
Scroll's l2geth call tracer answers {} for an L1 message (type 0x7e) that fails before execution. Example: scroll-mainnet block 35441201, tx 0xe398628cd2ea5c83d50b0e46e729caaf436f17a1adb3ddfb3a04837cc1f4dab5, a self-transfer with empty input, status 0x0, gasUsed 0x5208. Every provider we asked gives the same answer:
[{"txHash": "0xe398628c…f4dab5", "result": {}}]
DebugFrame requires type, from and gas, so the whole debug_traceBlockBy* response fails with invalid value at /0/result/type: undefined is not a string. Retrying can't help, so anything that fetches Scroll traces stops on this block. Older L1 messages that executed still trace normally.
Change
An empty frame ({}) is now read as "no call frame" rather than a schema error. This covers both the block-level call and the per-transaction fallback used for oversized responses.
ChainUtils.allowsEmptyCallFrame(tx) decides where that's legitimate. For now that's only Scroll (mainnet and Sepolia) L1 messages. The transaction is kept with no traces; normalization already skips missing frames.
Any other empty frame rejects the block with empty debug call frame for transaction <hash>. That's no looser than before, and the message now names the transaction.
Tests
rpc.empty-debug-frame.test.ts, using an unedited fixture of the Scroll block and its trace response. It covers three cases:
a Scroll L1 message is accepted;
a Scroll transaction of another type with an empty frame is rejected;
an empty frame on Sepolia is rejected.
All three fail without the fix.
verification.test.ts runs over every fixture, so it now skips transactionsRoot for Scroll. Scroll's 0x7e encoding (not an OP deposit) isn't implemented; that limitation already existed.
rushx test in evm/evm-rpc: 470 passed, 30 skipped. rush build --from @subsquid/evm-rpc passes.
Scroll's l2geth answers `{}` from the call tracer for an L1 message
(type 0x7e) that fails before execution, e.g. scroll-mainnet tx
0xe398628c...f4dab5 in block 35441201. The frame schema requires
`type`, `from` and `gas`, so the whole debug_traceBlockBy* response
failed validation and every consumer stopped on that block.
An empty frame is now read as "no call frame". On Scroll it is
accepted for an L1 message, and that transaction is stored with no
traces. Anywhere else the block is rejected with "empty debug call
frame for transaction <hash>", instead of a schema error that names
no transaction. The per-transaction fallback for oversized responses
gets the same handling.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… their label
On a chain whose tracer may answer `{}` (Scroll), a traces request
without full transactions now fetches transaction objects internally,
because only the type tells a legitimate empty frame from a broken
one. The caller still gets hashes back.
An empty frame labelled with another transaction now rejects the
block with the same message validateDebugFrames() uses, instead of
being dropped before that check runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Oversized block-level trace responses fall back to debug_traceTransaction,
which judges empty frames on its own; test Scroll acceptance and
non-Scroll rejection there too.
Skip the transactionsRoot check only for Scroll blocks that carry a 0x7e
L1 message, not for every Scroll fixture.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ation
Type 0x7e was always encoded as an OP deposit, so verifyTxRoot threw on
any Scroll block with an L1 message (no sourceHash). Since Scroll
traces now fetch full transactions even for hash-only requests, that
also hit hash-only trace requests with root verification on.
Encode a 0x7e transaction that carries queueIndex as l2geth's
L1MessageTx: 0x7e || rlp([queueIndex, gas, to, value, data, sender]).
It reproduces the root of scroll-mainnet block 35441201, so the
verification test no longer skips Scroll.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nullable(DebugFrame) accepts an actual JSON null from debug_traceBlockByNumber, and dropEmptyDebugFrames() treats it exactly like the internal null sentinel produced for {}. Before this change DebugFrame rejected null (the per-transaction fallback still does), so the block-level path now accepts a malformed response that is not the documented empty object. Keep the wire validator non-null and map only isEmptyFrame(result) to the internal missing-frame representation.
…r L1 messages
The block-level frame validator used nullable(DebugFrame), so a JSON
`null` frame was treated like the `{}` an L1 message legitimately
gets. Validate the non-empty frames with the strict schema and carry
only real `{}` frames forward as `result: null`.
Classify a Scroll L1 message by type 0x7e plus queueIndex, the same
shape the transactions-root encoder uses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(With Claude): About the review-body finding "Reject null debug frames in block trace responses": agreed, fixed in bcd238e. The block-level path validates the non-empty frames with the strict DebugFrameResult schema again. Only frames that are exactly {} go forward as result: null (their txHash is still validated), so a JSON null frame fails validation as it did before this PR. New test: keeps a JSON null call frame a validation error (fails without the fix).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Scroll's l2geth call tracer answers
{}for an L1 message (type0x7e) that fails before execution. Example: scroll-mainnet block 35441201, tx0xe398628cd2ea5c83d50b0e46e729caaf436f17a1adb3ddfb3a04837cc1f4dab5, a self-transfer with empty input,status 0x0, gasUsed0x5208. Every provider we asked gives the same answer:[{"txHash": "0xe398628c…f4dab5", "result": {}}]DebugFramerequirestype,fromandgas, so the wholedebug_traceBlockBy*response fails withinvalid value at /0/result/type: undefined is not a string. Retrying can't help, so anything that fetches Scroll traces stops on this block. Older L1 messages that executed still trace normally.Change
{}) is now read as "no call frame" rather than a schema error. This covers both the block-level call and the per-transaction fallback used for oversized responses.ChainUtils.allowsEmptyCallFrame(tx)decides where that's legitimate. For now that's only Scroll (mainnet and Sepolia) L1 messages. The transaction is kept with no traces; normalization already skips missing frames.empty debug call frame for transaction <hash>. That's no looser than before, and the message now names the transaction.Tests
rpc.empty-debug-frame.test.ts, using an unedited fixture of the Scroll block and its trace response. It covers three cases:All three fail without the fix.
verification.test.tsruns over every fixture, so it now skipstransactionsRootfor Scroll. Scroll's0x7eencoding (not an OP deposit) isn't implemented; that limitation already existed.rushx testinevm/evm-rpc: 470 passed, 30 skipped.rush build --from @subsquid/evm-rpcpasses.🤖 Generated with Claude Code