The issue
scanblocks reads block filters over a range via LookupFilterRange. When the
block filter index is behind the active chain (e.g. right after startup, while
it is still syncing in the background), the lookup for the not yet indexed range
fails. The old code ignored that failure, advanced start_index to the end of
the chunk, and kept going - so the scan skipped every unindexed block and still
returned "completed": true.
The result is a silent gap: a caller gets a relevant_blocks list that looks
fine but is missing any match in the unindexed range, with no way to
distinguish "no matches" from "range was never scanned". getblockfilter
already guards against this state and returns an error; scanblocks did not.
Note that "completed" only means the scan was not aborted - it does not mean
every filter in the requested range was actually read.
Steps to reproduce
- Start a node with
-blockfilterindex=1and let it mine/receive some blocks. - Stop the node, delete
<datadir>/<chain>/indexes/blockfilter, restart with-blockfilterindex=1. - Immediately (before the index finishes rebuilding) call
scanblocks start '["addr(<addr>)"]'.
Before this change the call returns "completed": true with an empty/partial
relevant_blocks, silently skipping the range the index had not rebuilt yet.
How it is fixed and why
- Wait for the index before scanning: if
BlockUntilSyncedToCurrentChain()reports the index is still behind, throw the same "still in the process of being indexed" error thatgetblockfilteruses. This is the correct layer to fail at - the RPC cannot produce a complete answer until the index covers the requested range, so it should say so instead of returning a partial one. - Treat a remaining
LookupFilterRangefailure asRPC_INTERNAL_ERROR. Once the index is confirmed synced, a failed range read is unexpected and indicates corruption, so failing loudly is preferable to skipping data.
The diff also reindents the match loop: inverting if (LookupFilterRange(...))
into an early throw removes one nesting level from the existing body. That
reindentation is a consequence of the bug fix, not a standalone style change.
Tests
test/functional/rpc_scanblocks.py- newtest_scanblocks_requires_synced_indexcovers the modified code: it wipes the filter index, restarts, and callsscanblocksin the window before the index catches up, asserting the indexing error (and that the scan never reportscompleted=trueover a skipped range); after the index syncs it checks the same scan completes and finds the match. It retries the restart to reliably hit the unsynced window on faster machines.- The existing
run_testcases (whichwait_until(... synced ...)before scanning) continue to exercise the normal synced path and theRPC_INTERNAL_ERRORbranch stays on the corruption-only path.
AI / tooling note
I found this regression while reviewing the RPC with an AI-assisted tool I
maintain, Boffin 0.3.2. I reproduced the
unsynced-index behavior myself, chose to mirror getblockfilter's existing
guard, and verified the fix with the functional test above. This PR text and
any review replies are my own.