indexes: set prune lock to genesis before first block #36150

pull andrewtoth wants to merge 2 commits into bitcoin:master from andrewtoth:fix_prune changing 2 files +7 −2
  1. andrewtoth commented at 4:47 PM on September 2, 2026: contributor

    When setting both a new index and prune size and restarting an unpruned node, the node will prune the block store first and then the index will fail to start syncing.

    Fix this by setting the prune lock to 0 if the index does not yet have a best block.

  2. DrahtBot added the label UTXO Db and Indexes on Sep 2, 2026
  3. DrahtBot commented at 4:47 PM on September 2, 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/36150.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

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

    Type Reviewers
    ACK sedited, fjahr
    Concept ACK l0rinc, mzumsande
    Stale ACK achow101, willcl-ark

    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:

    • #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-->

  4. l0rinc commented at 5:47 PM on September 2, 2026: contributor

    Concept ACK! Since #34897 the locator can lag the in-memory tip while SetBestBlockIndex() still advances the lock on every update, so should we maybe derive the lock from the committed locator instead (initialize in Init(), advance in Commit(), cap at the chain height on reorg, flush and drain before pruneblockchain) and cover it with a prune-mode index test?

  5. andrewtoth commented at 5:51 PM on September 2, 2026: contributor

    Since #34897 the locator can lag the in-memory tip while SetBestBlockIndex() still advances the lock on every update, so should we maybe derive the lock from the committed locator instead (initialize in Init(), advance in Commit(), cap at the chain height on reorg, flush and drain before pruneblockchain) and cover it with a prune-mode index test?

    That seems out of scope for what this is fixing. If you have an unpruned node, and then add both a new index and a prune value to the config and restart, it will break on indexing because it will prune first before syncing the index. This is not about a lagging locator, but startup failing to lock the prune height for an empty index.

  6. mzumsande commented at 3:47 PM on September 3, 2026: contributor

    Concept ACK

  7. sedited approved
  8. sedited commented at 7:05 PM on September 3, 2026: contributor

    ACK 39e4a6bb41749547c983456ce05281ca9eae16fe

  9. DrahtBot requested review from mzumsande on Sep 3, 2026
  10. DrahtBot requested review from l0rinc on Sep 3, 2026
  11. andrewtoth commented at 4:36 PM on September 8, 2026: contributor

    I hope this can get in before v32 cutoff. If you restart your non-pruned node with a prune target and a new index, you will first prune then crash on starting to index. Then you are S.O.L. with your new index and your only recourse is to do a full -reindex. I came across this while testing #36002.

    It's a very simple fix. I assume the original check for block was intended as a null guard, but it should really set it to genesis instead of skipping the prune lock.

    Here's a reproducer. I'm not sure it's worth putting it in the codebase though.

    #!/usr/bin/env python3
    from feature_pruning import mine_large_blocks
    from test_framework.test_framework import BitcoinTestFramework
    from test_framework.util import assert_greater_than
    
    
    class IndexPruneStartupTest(BitcoinTestFramework):
        def set_test_params(self):
            self.setup_clean_chain = True
            self.num_nodes = 1
    
        def run_test(self):
            node = self.nodes[0]
            self.log.info("Build an unpruned chain exceeding the minimum automatic prune target")
            mine_large_blocks(node, 1020)
            height = node.getblockcount()
    
            self.log.info("Enable automatic pruning and a fresh blockfilterindex on the same restart")
            self.restart_node(0, extra_args=["-prune=550", "-blockfilterindex=1"])
            self.wait_until(lambda: node.getindexinfo() == {
                "basic block filter index": {"synced": True, "best_block_height": height},
            })
    
    
    if __name__ == "__main__":
        IndexPruneStartupTest(__file__).main()
    
  12. sedited added this to the milestone 32.0 on Sep 8, 2026
  13. l0rinc changes_requested
  14. l0rinc commented at 11:09 PM on September 8, 2026: contributor

    This is not about a lagging locator, but startup failing to lock the prune height for an empty index.

    Sure, let’s fix startup here and address the lock advancing beyond the committed index state separately.


    The problem we're fixing here was introduced in #21726, which included the genesis fallback at one stage, but lost it without a regression test catching it, so we should learn from that and add one here.

    Luckily, DeletePruneLock() already returns whether the lock existed, so we can check that and just reinitialize the index in blockfilter_index_initial_sync. I’ve prepared characterization + fix commits in l0rinc/bitcoin#296 as a demo.

  15. DrahtBot requested review from l0rinc on Sep 8, 2026
  16. test: characterize startup with newly added prune and index 0ae3b40c27
  17. andrewtoth force-pushed on Sep 9, 2026
  18. andrewtoth commented at 1:32 AM on September 9, 2026: contributor

    Thanks @l0rinc, I added the characterization test in the first commit and updated to the regression test in the second commit. I think the functional test suite is more appropriate for this, since we want to make sure we can startup and sync a new index when we introduce pruning at the same time. This functionality could possibly fail in other ways in the future, even if the underlying prune locks are correctly applied.

  19. in test/functional/feature_pruning.py:474 in 6d7caa1c23 outdated
     470 | @@ -472,15 +471,12 @@ def run_test(self):
     471 |          self.sync_blocks([self.nodes[0], self.nodes[5]], wait=5, timeout=300)
     472 |  
     473 |          self.log.info("Test prune with a new index")
     474 | -        self.stop_node(0)
     475 | +        self.restart_node(0, extra_args=["-prune=550", "-blockfilterindex=1"])
    


    l0rinc commented at 1:34 AM on September 9, 2026:

    6d7caa1 indexes: set prune lock to genesis before first block:

    We're changing the test conditions in the fix commit, so it's not obvious what the behavior was before the fix. Can you adjust the characterization commit so that ideally only the assertions change while the conditions stay the same (so that we're actually exercising the same use case)?


    andrewtoth commented at 1:38 AM on September 9, 2026:

    it's not obvious what the behavior was before the fix.

    What specifically is not obvious about it? I'd prefer to leave it like this.


    l0rinc commented at 1:43 AM on September 9, 2026:

    you're changing the conditions, this is a different test, so you haven't characterized the behavior before the fix


    andrewtoth commented at 1:44 AM on September 9, 2026:

    But which part is not obvious to you?


    andrewtoth commented at 1:39 PM on September 9, 2026:

    The conditions are the same. The node is restarted with with a prune target and a new index. It's not obvious to me how to make this more obvious.


    l0rinc commented at 6:07 AM on September 10, 2026:

    Can you please unresolve this comment


    l0rinc commented at 7:00 AM on September 10, 2026:

    The characterization test could do a start, wait_until_stopped, start_node as well, so that the fix commit only changes the wait to wait_until - instead of changing assert_start_raises_init_error to wait_until which is hard to interpret, it's not obvious how this changes the behavior, or what we're even testing before.


    andrewtoth commented at 6:14 PM on September 10, 2026:

    I think I will just keep it as is for now.

  20. sedited approved
  21. sedited commented at 2:27 PM on September 9, 2026: contributor

    Re-ACK 6d7caa1c23346fadd15183db0f57385207db113b

  22. DrahtBot requested review from l0rinc on Sep 9, 2026
  23. achow101 commented at 7:57 PM on September 9, 2026: member

    ACK 6d7caa1c23346fadd15183db0f57385207db113b

  24. in test/functional/feature_pruning.py:477 in 6d7caa1c23
     480 | -            expected_msg="basic block filter index best block of the index goes beyond pruned data",
     481 | -            match=ErrorMatch.PARTIAL_REGEX,
     482 | -        )
     483 | -        self.start_node(0, extra_args=["-prune=550"])
     484 | +        height = node.getblockcount()
     485 | +        self.wait_until(lambda: node.getindexinfo() == {
    


    l0rinc commented at 6:58 AM on September 10, 2026:

    6d7caa1 indexes: set prune lock to genesis before first block:

    Is it expected that this took 11 minutes to run locally?

    feature_pruning.py | ✓ Passed | 663 s


    willcl-ark commented at 7:19 AM on September 10, 2026:

    This is an extended (runtime) script, so can take longer and isn't run by all jobs. That said, for me it runs in:

    1/1 - feature_pruning.py passed, Duration: 95 s
    
    TEST               | STATUS    | DURATION
    
    feature_pruning.py | ✓ Passed  | 95 s
    
    ALL                | ✓ Passed  | 95 s (accumulated)
    Runtime: 95 s
    

    What hardware are you on?


    willcl-ark commented at 7:23 AM on September 10, 2026:

    In the CI this PR took:

    alpine: feature_pruning.py | ✓ Passed | 301 s

    previous releases: feature_pruning.py | ✓ Passed | 121 s

    most recent merge to master: alpine: feature_pruning.py | ✓ Passed | 173 s

    previous releases: feature_pruning.py | ✓ Passed | 90 s

    So it's certainly adding to the runtime, but I'd say that's expected, as we are pruning and indexing instead of init erroring...


    sedited commented at 8:02 AM on September 10, 2026:

    I'm surprised it makes this much of a difference in the CI, not seeing much of a difference locally.

    EDIT: It's probably debug mode again.


    sedited commented at 8:10 AM on September 10, 2026:

    Instead of syncing completely might it be enough to sync a few blocks instead?


    andrewtoth commented at 6:14 PM on September 10, 2026:

    Fixed. We only wait until at least the first 10 blocks are synced.

  25. l0rinc changes_requested
  26. DrahtBot requested review from l0rinc on Sep 10, 2026
  27. willcl-ark approved
  28. willcl-ark commented at 7:09 AM on September 10, 2026: member

    Code review ACK 6d7caa1c23346fadd15183db0f57385207db113b

    The genesis prune lock is set before startup pruning, preserving the blocks the index needs when they're available.

    Did not run the reproducer from the comments.

  29. indexes: set prune lock to genesis before first block
    When setting both a new index and prune size and restarting
    an unpruned node, the node will prune the block store first
    and then the index will fail to start syncing.
    
    Fix this by setting the prune lock to 0 if the index does not
    yet have a best block.
    9b22995140
  30. andrewtoth force-pushed on Sep 10, 2026
  31. sedited approved
  32. sedited commented at 8:16 PM on September 10, 2026: contributor

    Re-ACK 9b2299514028f5055d4314e402776cbb45c5f538

  33. DrahtBot requested review from willcl-ark on Sep 10, 2026
  34. DrahtBot requested review from achow101 on Sep 10, 2026
  35. in src/index/base.cpp:509 in 9b22995140
     503 | @@ -504,9 +504,9 @@ void BaseIndex::SetBestBlockIndex(const CBlockIndex* block)
     504 |  {
     505 |      assert(!m_chainstate->m_blockman.IsPruneMode() || AllowPrune());
     506 |  
     507 | -    if (AllowPrune() && block) {
     508 | +    if (AllowPrune()) {
     509 |          node::PruneLockInfo prune_lock;
     510 | -        prune_lock.height_first = block->nHeight;
     511 | +        prune_lock.height_first = block ? block->nHeight : 0;
    


    fjahr commented at 10:15 PM on September 10, 2026:

    nit: Wouldn't have hurt to add a comment here, to the effect of what's in the commit message.

  36. fjahr commented at 10:15 PM on September 10, 2026: contributor

    Code review ACK 9b2299514028f5055d4314e402776cbb45c5f538

  37. sedited merged this on Sep 11, 2026
  38. sedited closed this on Sep 11, 2026

  39. willcl-ark commented at 8:20 AM on September 11, 2026: member

    Post-merge reACK 9b2299514028f5055d4314e402776cbb45c5f538

  40. Kino1994 referenced this in commit 026413251c on Sep 13, 2026
  41. andrewtoth deleted the branch on Sep 28, 2026

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-09-28 19:51 UTC

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