rpc: fail scanblocks when block filter index is not caught up #35837

pull MicSm wants to merge 1 commits into bitcoin:master from MicSm:rpc/scanblocks-require-synced-index changing 2 files +72 −13
  1. MicSm commented at 6:17 PM on July 29, 2026: none

    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

    1. Start a node with -blockfilterindex=1 and let it mine/receive some blocks.
    2. Stop the node, delete <datadir>/<chain>/indexes/blockfilter, restart with -blockfilterindex=1.
    3. 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 that getblockfilter uses. 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 LookupFilterRange failure as RPC_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 - new test_scanblocks_requires_synced_index covers the modified code: it wipes the filter index, restarts, and calls scanblocks in the window before the index catches up, asserting the indexing error (and that the scan never reports completed=true over 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_test cases (which wait_until(... synced ...) before scanning) continue to exercise the normal synced path and the RPC_INTERNAL_ERROR branch 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.

  2. rpc: fail scanblocks when block filter index is not caught up
    Previously LookupFilterRange failures were ignored and the scan
    continued, so an unsynced filter index could return completed:true
    with silent gaps. Require the index to be caught up before scanning
    (matching getblockfilter), and treat remaining lookup failures as
    internal errors rather than skipping ranges.
    9467446a9f
  3. DrahtBot added the label RPC/REST/ZMQ on Jul 29, 2026
  4. DrahtBot commented at 6:18 PM on July 29, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/35837.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    Concept ACK maflcko

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #35582 (rpc: reject null for optional parameters by RuslanProgrammer)
    • #35229 (refactor: Use CBlockIndex parameters as reference by optout21)
    • #24230 (indexes: Stop using node internal types and locking cs_main, improve sync logic by ryanofsky)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  5. maflcko commented at 6:52 AM on July 30, 2026: member

    I found this regression while reviewing the RPC with an AI-assisted tool I maintain, Boffin 0.3.2.

    Thx for following the projects AI policy, and Concept ACK. However, I think the link to the tool can be removed and this can just say "I found this regression while reviewing the RPC with an AI-assisted tool". The tool is "a layer for agents", but I am sure any vanilla agent will find the same issue with the same prompt. E.g:

    <details><summary>LLM output</summary>

    cdx -c model=gpt-5.4-mini exec 'Review the scanblocks RPC for correctness' 2>/dev/null 
    Findings:
    
    1. [`src/rpc/blockchain.cpp`](src\/rpc\/blockchain.cpp#L2683) silently ignores `LookupFilterRange()` failures. If the block filter DB read fails or the range is otherwise unreadable, the RPC just skips that chunk, keeps going, and can still return `completed: true` with missing `relevant_blocks`. That is a correctness bug, not just an error-path detail. It should surface the failure to the caller or at least mark the scan incomplete.
    
    2. [`src/rpc/blockchain.cpp`](src\/rpc\/blockchain.cpp#L2664) has an abort race during startup. `g_scanfilter_should_abort_scan` is cleared only after the scan has already reserved the global slot and finished descriptor/index setup. If another thread issues `scanblocks("abort")` in that window, it can set the flag and then have it overwritten by the startup path, causing the scan to ignore a valid abort request. The reset needs to happen before any long setup, or the state transition needs to be synchronized.
    
    No other correctness issue stood out in the range logic itself.
    

    </details>


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-07-31 20:50 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me