Skip to content

fix(evm-rpc): treat an omitted withdrawals array as empty when verifying withdrawalsRoot - #604

Open
SQD-Trevor-Agent wants to merge 1 commit into
masterfrom
alert-fix/gUhtWn-cronos-withdrawals
Open

SQD-Trevor-Agent wants to merge 1 commit into
masterfrom
alert-fix/gUhtWn-cronos-withdrawals

Conversation

@SQD-Trevor-Agent

Copy link
Copy Markdown

Cause (proven)

dump-cronos-mainnet-traceless-0 (evm-archive namespace) was in CrashLoopBackOff (17+ restarts). The fatal error, from the pod's previous-container logs, is a non-retryable AssertionError in Rpc.mapBlock:

AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
  (val != null, msg)
    at assertNotNull (/squid/util/util-internal/lib/misc.js:52:26)
    at Rpc.mapBlock (/squid/evm/evm-rpc/lib/rpc.js:177:65)   // = assertNotNull(block.withdrawals)
  { blockNumber: 101503527, blockHash: 0x9a64993c364da9321a4801c74a188e31e042e95d15105817a76b1a7f0fb692c8 }

Cronos runs with --verify-withdrawals-root. In mapBlock, when block.withdrawalsRoot != null the code did assertNotNull(block.withdrawals). Cronos (a Cosmos-EVM chain) has no withdrawals: its header withdrawalsRoot is the canonical empty-trie root 0x56e81f17…b421 and the block body carries withdrawals: []. Fetched directly, both independent providers (dwellir and uniblock) return withdrawals: [] with that empty root — the on-chain data is well-formed. But the response occasionally arrives with the empty withdrawals array omitted (undefined), and assertNotNull then turns that transient omission into a process crash — a crash-loop, since the dumper re-reaches the same block on every restart.

This was confirmed as transient, not corrupt data: after the eRPC cache for block 101503527 was re-warmed, the dumper processed it cleanly and advanced past it (restart count held steady) — i.e. the field was present again.

Fix

Treat an omitted withdrawals array as [] (block.withdrawals ?? []). The subsequent assert.equal(block.withdrawalsRoot, computedRoot) still guards correctness: an empty list computes to the empty root (so Cronos verifies), while a genuinely missing non-empty set computes to the empty root and fails verification instead of being silently accepted.

Test (red→green)

test/rpc.test.ts:

  • tolerates an omitted empty withdrawals array — a block with the empty withdrawalsRoot and no withdrawals field verifies and returns 1 block.

  • still rejects a missing non-empty withdrawals set — a block with a non-empty withdrawalsRoot and no withdrawals field throws failed to verify withdrawals root.

  • pre-fix: Tests 2 failed (both throw the assertNotNull falsy-value AssertionError)

  • post-fix: Tests 2 passed

  • full @subsquid/evm-rpc suite green (rush build -t @subsquid/evm-rpc clean; tsc --noEmit -p tsconfig.test.json clean). One unrelated base-mainnet sender-recovery test hits the 5 s default timeout on slow hardware and passes at --testTimeout=60000.

Falsification

If dump-cronos-mainnet-traceless-0 still exits with this assertNotNull AssertionError after this change, the omission is not what crashes it. If it instead starts failing failed to verify withdrawals root, a provider is returning a non-empty withdrawalsRoot without the matching withdrawals — a genuine provider data-quality issue to raise upstream.

Related to #551 (same crash-loop class — a transient provider/proxy response killing the dumper) but a distinct path: #551 converts schema-validation errors to retries; this block passes schema validation (withdrawals is optional) and crashes later in mapBlock.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant