fix: handle nil block result in EmptyBlocksSanitizer#19
Merged
Conversation
A spec-compliant JSON-RPC server returns `result: null` for blocks it
cannot find (e.g. pruned or reorged out). The previous implementation of
`classify_blocks_from_result/1` assumed every batch response carried a
block map, so a `nil` result crashed the GenServer with `BadMapError` at
`Map.get(nil, "transactions", nil)`. Because the fetcher uses
`restart: :permanent`, repeated crashes eventually exhausted the
supervisor's restart intensity and brought down the `indexer`
application, taking the whole BEAM VM with it.
Add a match clause for `%{id: _, result: nil}` that logs a warning and
skips the block. Include a regression test that mocks a nil batch
response and asserts the sanitizer does not mutate the DB record.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The initial nil-handling commit silently skipped blocks for which the JSON-RPC returned `result: null`, which left those blocks with `is_empty: nil, refetch_needed: false` — the exact predicate that `consensus_blocks_with_nil_is_empty_query/1` selects for. The sanitizer would then re-query the same blocks every cycle (every 10s), producing perpetual RPC traffic and log spam. Reconcile requested vs returned block numbers in the caller and call `Block.set_refetch_needed/1` on the difference. The refetch_needed predicate then excludes them from subsequent sanitizer cycles and hands them off to the regular refetch path — consistent with how the fetcher already handles blocks whose RPC view disagrees with the DB. Also strengthen the regression test so it actually discriminates the fixed code from the buggy code: - Replace `Process.sleep/1` with `wait_for_results/1`, which polls for `refetch_needed == true` and fails the test if the sanitizer never updates the row (as happens on the pre-fix `BadMapError` crash path). - Assert both `is_empty == nil` (unchanged) and `refetch_needed == true` (positive signal only the fix can produce). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rswanson
added a commit
that referenced
this pull request
Apr 17, 2026
When the JSON-RPC returns \`result: null\` for a block (pruned, reorged
out, or genuinely missing), \`classify_blocks_from_result/1\` crashed
with \`BadMapError\` on \`Map.get(nil, "transactions", nil)\`. Because the
fetcher is \`restart: :permanent\`, each crash exhausted the supervisor's
restart intensity and brought down the \`indexer\` application + BEAM VM,
producing a K8s crashloop against Signet's sidecar RPC.
- Add a \`%{id: _, result: nil}\` match clause that skips without
crashing.
- Reconcile requested vs. returned block numbers in the caller and
flag the missing ones with \`Block.set_refetch_needed/1\` so
\`consensus_blocks_with_nil_is_empty_query\` stops re-selecting them
every cycle.
- Regression test stubs a nil batch response and uses
\`wait_for_results\` to assert \`refetch_needed == true\`; on the
un-fixed code the GenServer crashes and the assertion times out.
Merged equivalent of master PR #19 onto signet-main. Preserves the
existing defensive \`Map.get(block, "transactions") || []\` guard from
this branch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Indexer.Fetcher.EmptyBlocksSanitizer.classify_blocks_from_result/1assumed everyeth_getBlockByNumberbatch entry carried a block map, so a spec-compliantresult: null(returned for pruned/reorged blocks) crashed the GenServer withBadMapErroratMap.get(nil, "transactions", nil).restart: :permanent, each bad block kept crashing the process until the supervisor exhausted its restart intensity, shut down theindexerapplication, and took the whole BEAM VM with it — producing a K8s crashloop.%{id: _, result: nil}that logs a warning and skips the block, plus a regression test that mocks a nil batch response.Context
Observed in prod against an upstream RPC (Signet sidecar) that correctly returns
nullfor blocks that have been truncated out of cold storage after a host-chain reorg. The previous logs showed:Test plan
skips blocks for which JSON-RPC returns nil resultpassesempty_blocks_sanitizer_test.exscases still passLocal validation
mix format --check-formattedcouldn't be run in isolation (project.formatter.exsrequires a fullmix deps.get). Files were checked against the default Elixir formatter withline_length: 120(matching.formatter.exs) and reported as already formatted.🤖 Generated with Claude Code