rpc: fix dumptxoutset rollback crash after loadtxoutset on pruned node #36351

pull FlashWayne wants to merge 3 commits into bitcoin:master from FlashWayne:fix-dumptxoutset-rollback-snapshot-tip changing 2 files +18 −0
  1. FlashWayne commented at 10:33 PM on September 26, 2026: contributor

    On a pruned node, dumptxoutset with rollback checks that the blocks it needs haven't been pruned by calling GetFirstBlock(*current_tip, BLOCK_HAVE_MASK) (blockchain.cpp:3194). GetFirstBlock asserts that the block it starts from has both block and undo data (blockstorage.cpp:638).

    Right after loadtxoutset, the active tip is the snapshot base block, and that has neither until it's downloaded. So on a -prune node:

    loadtxoutset <snapshot>
    dumptxoutset <path> rollback=<height>
    

    aborts bitcoind:

    Assertion failed: ((last_block->nStatus & status_mask) == status_mask), function GetFirstBlock, file blockstorage.cpp, line 638.
    

    The GetPruneHeight helper already guards against a tip without data (blockchain.cpp:934), this call site doesn't.

    This returns an RPC error ("Could not roll back to requested height since block data for the current tip is not available.") when the tip itself has no data, before calling GetFirstBlock. The check runs for every node, not only in prune mode: without -prune the same scenario doesn't abort, but it would otherwise copy the whole UTXO set into a temporary database before failing with an internal error. The existing "already pruned" error is still returned when GetFirstBlock finds a gap.

    The test goes in feature_assumeutxo.py: right after the snapshot is loaded on the pruned node (node 1), and again on the non-pruned node 2, dumptxoutset with a rollback to the start height must return that error. On master the pruned node aborts and the RPC connection drops (RemoteDisconnected). With the fix feature_assumeutxo.py and rpc_dumptxoutset.py pass.

  2. rpc: fix dumptxoutset rollback crash after loadtxoutset on pruned node
    On a pruned node, dumptxoutset with rollback calls GetFirstBlock on the
    active chain tip to check that the needed block data hasn't been
    pruned. GetFirstBlock asserts that the block it starts from has block
    and undo data. Right after loadtxoutset the active tip is the snapshot
    base block, which has neither until it is downloaded, so the call
    aborts bitcoind:
    
      Assertion failed: ((last_block->nStatus & status_mask) == status_mask),
      function GetFirstBlock, file blockstorage.cpp
    
    Return the existing "necessary block data is already pruned" error when
    the tip itself has no data.
    a9e79810b2
  3. DrahtBot added the label RPC/REST/ZMQ on Sep 26, 2026
  4. DrahtBot commented at 10:33 PM on September 26, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

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

    Type Reviewers
    Stale ACK cyb3ralbert

    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.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  5. in test/functional/feature_assumeutxo.py:532 in a9e79810b2
     527 | @@ -528,6 +528,10 @@ def check_dump_output(output):
     528 |          assert_equal(loaded['coins_loaded'], SNAPSHOT_BASE_HEIGHT)
     529 |          assert_equal(loaded['base_height'], SNAPSHOT_BASE_HEIGHT)
     530 |  
     531 | +        self.log.info("Check that dumptxoutset rollback fails cleanly on a pruned node without the snapshot base block")
     532 | +        assert_raises_rpc_error(-1, "Could not roll back to requested height since necessary block data is already pruned.",
    


    fjahr commented at 11:31 PM on September 26, 2026:

    This error message is not reporting the actual reason for the failure


    FlashWayne commented at 7:31 AM on September 27, 2026:

    Right, nothing was pruned in that case, the base block just hasn't been downloaded yet. Added a separate error for it in 8fa8ed0111: "Could not roll back to requested height since block data for the current tip is not available." The "already pruned" message is now only returned when GetFirstBlock actually finds a gap.

  6. fjahr commented at 11:33 PM on September 26, 2026: contributor

    Concept ~0

    This is such a esoteric edge case that I don't really think a user could ever realistically hit it.

  7. rpc: use a dedicated error when the tip has no block data
    The "already pruned" error is misleading when the tip is the snapshot
    base block right after loadtxoutset: nothing was pruned, the block just
    has not been downloaded yet.
    8fa8ed0111
  8. FlashWayne commented at 7:31 AM on September 27, 2026: contributor

    Agreed it's unlikely to be hit in practice. The reason I still think it's worth fixing is that it's an RPC call aborting the node instead of returning an error, and the fix is just a status check before GetFirstBlock. If you think it's not worth the review time I'm fine closing it.

  9. DrahtBot added the label CI failed on Sep 28, 2026
  10. DrahtBot removed the label CI failed on Sep 28, 2026
  11. cyb3ralbert commented at 3:23 AM on October 8, 2026: contributor

    One question: the new tip check is only inside if (IsPruneMode()). Without -prune the same scenario does not crash the node, but it copies the whole UTXO set into a temporary LevelDB and then fails with -32603. Why not check the tip for any rollback, before the copy?

    tested ACK 8fa8ed01119d32536862c385c7286e588b1bfdd0

    • Linux x86_64, gcc 13.3, Release build
    • PR test on merge-base ed7dd7cf4e15 and on master aef8a04966d7: fails with RemoteDisconnected; node1 aborts with SIGABRT, assert in GetFirstBlock (blockstorage.cpp:638)
    • PR head, and PR merged onto master (merge is clean): feature_assumeutxo.py and rpc_dumptxoutset.py pass, the node stays up
    • pruned node without a snapshot: rollback still works; after pruneblockchain the "already pruned" error is still returned
    • nit: description is stale: it still says the existing "already pruned" error is returned, and that master times out (the connection drops right away)
  12. rpc: check the dumptxoutset rollback tip data on non-pruned nodes too
    Without -prune the missing tip data is only found after the UTXO set has been copied into a temporary database, and the RPC then fails with an internal error. Check it before the copy for every node.
    7c7a482ce5
  13. FlashWayne commented at 7:49 AM on October 8, 2026: contributor

    Thanks, good point. I moved the tip check out of the IsPruneMode() block in 7c7a482ce5, so it now runs for every node before the UTXO set is copied. The test now also covers the non-pruned node (n2) right after loadtxoutset. I also updated the description: the connection drops right away on master rather than timing out, and the "already pruned" error is only returned when GetFirstBlock finds a gap.

  14. sedited commented at 12:40 PM on October 8, 2026: contributor

    This is such a esoteric edge case that I don't really think a user could ever realistically hit it.

    I agree with fjahr here, this doesn't seem worth fixing. I also don't think this provides better defense in depth. For these reasons, I'm closing this again.

  15. sedited closed this on Oct 8, 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-10-24 08:51 UTC

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