rpc: Manual prune lock management (Take 2) #34534

pull fjahr wants to merge 4 commits into bitcoin:master from fjahr:2026-02-prunelocks-rpc-v2 changing 8 files +350 −1
  1. fjahr commented at 2:01 pm on February 8, 2026: contributor

    This is a refreshed version of #19463, which was closed due to inactivity.

    Motivation: On pruned nodes, if a wallet is not loaded while the node is running, blocks necessary for rescanning when the wallet is loaded may be pruned. This PR allows users and external wallet software to manually manage prune locks via RPC, so they can protect blocks needed for future rescans and also clean up locks that are no longer relevant (e.g. after permanently unloading a wallet from a node). This is useful both for Bitcoin Core’s own wallet and for external wallets that use Bitcoin Core as a backend. Usage of temporary prune locking may also be helpful in other functional test scenarios but I didn’t spend time to check where might apply.

    This PR leverages the existing prune locks system we already have. The interface of the RPCs of the old PR has been slightly updated, it now matches the pattern used by setban/listbanned exactly. The locks are prefixed to distinguish them from the systems locks and prevent naming colissions. They are not persisted across restarts.

  2. DrahtBot added the label RPC/REST/ZMQ on Feb 8, 2026
  3. DrahtBot commented at 2:01 pm on February 8, 2026: contributor

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

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK sedited, stickies-v

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34435 (refactor: use _MiB/_GiB consistently for byte conversions by l0rinc)
    • #33477 (Rollback for dumptxoutset without invalidating blocks by fjahr)

    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.

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • # Blocks needed to generate at minimum to allow pruning to work on regtest (with fastprune). -> # Blocks needed to generate at least to allow pruning to work on regtest (with fastprune). [“at minimum” is grammatically incorrect/awkward and can momentarily obscure the intended meaning.]

    2026-03-31 21:56:04

  4. sedited commented at 2:15 pm on February 8, 2026: contributor
    Concept ACK
  5. DrahtBot added the label CI failed on Feb 8, 2026
  6. DrahtBot commented at 3:19 pm on February 8, 2026: contributor

    🚧 At least one of the CI tasks failed. Task Windows native, fuzz, VS 2022: https://github.com/bitcoin/bitcoin/actions/runs/21799390662/job/62892243983 LLM reason (✨ experimental): Fuzz test failed because the RPC command “listprunelocks” is not registered as safe or unsafe for fuzzing in rpc.cpp.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  7. fjahr force-pushed on Feb 8, 2026
  8. fjahr commented at 3:29 pm on February 8, 2026: contributor
    Fixing CI failure: Forgot to set RPC_COMMANDS_[NOT_]SAFE_FOR_FUZZING for the new RPCs.
  9. DrahtBot removed the label CI failed on Feb 8, 2026
  10. in src/rpc/blockchain.cpp:886 in f192b06a3b
    881+        "Returns a list of all active prune locks.\n"
    882+        "Prune locks prevent block data at specific heights from being pruned.\n"
    883+        "These locks are used internally by indexes and can also be set manually via the setprunelock RPC.\n",
    884+        {},
    885+        RPCResult{
    886+            RPCResult::Type::ARR, "", "",
    


    luke-jr commented at 1:51 am on February 10, 2026:
    This was an Object for future extensibility. No reason to lose that.

    fjahr commented at 1:54 pm on February 18, 2026:
    The RPC is called listprunelocks, I don’t see how it could be extended to return anything else aside from a list of locks without that other thing feeling out of place. Can you give some specific ideas that you have? At least I would want to give the RPC a different name then (getpruneinfo or so) but potentially these ideas should rather be in a separate RPC. I prefer consistency and so I really like that we could be consistenty with the banning RPC.

    maflcko commented at 2:28 pm on February 18, 2026:
    listprunelocks could return a “blockman epoch” in the future, if there was need to further distinguish rpc results. (Yes, this is made up, but with an array, this is impossible, with an object, this is trivial and harmless)

    fjahr commented at 2:35 pm on February 18, 2026:
    What is a “blockman epoch”? I don’t think I have heard of that before.

    fjahr commented at 3:40 pm on February 19, 2026:
    I guess TIL per this comment that the JSON object thing is in the dev notes, fair enough, I have changed it to return an object.
  11. in src/rpc/blockchain.cpp:890 in f192b06a3b
    885+        RPCResult{
    886+            RPCResult::Type::ARR, "", "",
    887+            {
    888+                {RPCResult::Type::OBJ, "", "",
    889+                {
    890+                    {RPCResult::Type::STR, "name", "The identifier of the prune lock"},
    


    luke-jr commented at 1:51 am on February 10, 2026:
    0                    {RPCResult::Type::STR, "id", "The identifier of the prune lock"},
    

    fjahr commented at 1:54 pm on February 18, 2026:
    Done
  12. luke-jr changes_requested
  13. luke-jr commented at 1:53 am on February 10, 2026: member
    Incomplete review. Basically, the original PR interface was way better.
  14. in src/rpc/blockchain.cpp:905 in f192b06a3b
    900+{
    901+    ChainstateManager& chainman = EnsureAnyChainman(request.context);
    902+    UniValue locks_arr(UniValue::VARR);
    903+    {
    904+        LOCK(::cs_main);
    905+        const auto& prune_locks = chainman.m_blockman.GetPruneLocks();
    


    bvbfan commented at 6:56 am on February 10, 2026:

    You could get it as copy and don’t keep lock while iterating, performance wise.

    0const auto prune_locks = WITH_LOCK(::cs_main, return chainman.m_blockman.GetPruneLocks());
    

    fjahr commented at 1:54 pm on February 18, 2026:
    Done
  15. fjahr force-pushed on Feb 18, 2026
  16. fjahr commented at 1:55 pm on February 18, 2026: contributor

    @luke-jr

    Basically, the original PR interface was way better.

    Is this just about #34534 (review) or did you mean something else?

  17. fjahr commented at 1:55 pm on February 18, 2026: contributor
    Addressed feedback from @luke-jr and @bvbfan , thanks for the review!
  18. fjahr force-pushed on Feb 19, 2026
  19. sedited commented at 3:57 pm on March 18, 2026: contributor

    I can not recall who was mentioned as a potential user but I would appreciate if someone could ping them to take a look if this is still relevant to them.

    Do you remember at least who mentioned these users/use cases?

  20. sedited requested review from stickies-v on Mar 19, 2026
  21. stickies-v commented at 5:30 pm on March 19, 2026: contributor

    Unfortunately, I can not recall who was mentioned as a potential user but I would appreciate if someone could ping them to take a look if this is still relevant to them.

    This looks potentially interesting, but I don’t think we should be implementing RPCs when we don’t know anyone that would actually be using them? Since the previous PR has been open since 2020 already without users chiming in, it seems like there’s no real demand for this?

  22. achow101 commented at 5:47 pm on March 19, 2026: member
    This is generally useful in the wallet and for any wallets that leverage Bitcoin Core. The fundamental issue is that if the wallet is not loaded while the node is running, blocks necessary for rescanning when the wallet is loaded may be pruned away. The wallet definitely should be using prune locks, but it’s also necessary to have a way to for users to remove prune locks that are no longer relevant, e.g. if they unload a wallet that they don’t plan to load on that node again. External wallets that use Bitcoin Core should also find this useful as they can then set prune locks that match the scan state of the external wallet.
  23. stickies-v commented at 10:10 am on March 20, 2026: contributor
    Thanks for the explanation @achow101 . Concept ACK. Enabling wallets to build on top of a pruned node via RPC seems enough of a use case to move this forward. @fjahr I think this might be useful to include in PR description to give it more motivation?
  24. fjahr commented at 11:54 am on March 20, 2026: contributor

    Thanks for the explanation @achow101 . Concept ACK. Enabling wallets to build on top of a pruned node via RPC seems enough of a use case to move this forward. @fjahr I think this might be useful to include in PR description to give it more motivation?

    Done, replace that vague part of the description with a better motivation paragraph.

  25. in src/rpc/blockchain.cpp:907 in 1f9cfa1a88
    902+    [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
    903+{
    904+    ChainstateManager& chainman = EnsureAnyChainman(request.context);
    905+    UniValue locks_arr(UniValue::VARR);
    906+    {
    907+        const auto prune_locks = WITH_LOCK(::cs_main, return chainman.m_blockman.GetPruneLocks());
    


    sedited commented at 11:41 am on March 21, 2026:
    Don’t we either have to hold this lock while iterating through the prune locks, or copy them? I think my preference would be to just take a copy here.

    fjahr commented at 1:41 pm on March 22, 2026:
    Hm, it certainly seems like I addressed this comment without thinking about it enough because the surrounding parentesis were unnecessary as well. I am now using a explicit unordered_map constructor now to ensure the copy happens before the lock scope ends and I have added a comment and removed the parenthesis.
  26. in src/node/blockstorage.h:461 in 22709b0adf outdated
    453@@ -454,6 +454,12 @@ class BlockManager
    454     //! Create or update a prune lock identified by its name
    455     void UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
    456 
    457+    //! Delete a prune lock identified by its name. Returns true if the lock existed.
    458+    bool DeletePruneLock(const std::string& name) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
    459+
    460+    //! Return the current set of prune locks.
    461+    const std::unordered_map<std::string, PruneLockInfo>& GetPruneLocks() const EXCLUSIVE_LOCKS_REQUIRED(::cs_main) { return m_prune_locks; }
    


    sedited commented at 12:02 pm on March 21, 2026:
    Is there a reason for introducing this helper? Existing validation code that iterates through the locks directly.

    fjahr commented at 1:41 pm on March 22, 2026:
    m_prune_locks is private and the direct access to it in validation.cpp is only possible because ChainState is a friend class of BlockManager. Of course we could also just make it public but a getter method seemed nicer to me.
  27. sedited commented at 12:17 pm on March 21, 2026: contributor
    One thing that is still not clear to me is how this is actually useful to external applications if the locks are not persisted. I could see how this might be interesting for our own wallets, by setting prune locks for wallets even though they are not loaded yet. But for outside users this seems like a limitation that is difficult to deal with. If you want to add a wallet on startup, wouldn’t that require racing to set a lock again if you restart? What is the exact scenario here?
  28. fjahr force-pushed on Mar 22, 2026
  29. fjahr commented at 1:41 pm on March 22, 2026: contributor

    Addressed latest @sedited feedback, thank you!

    One thing that is still not clear to me is how this is actually useful to external applications if the locks are not persisted. I could see how this might be interesting for our own wallets, by setting prune locks for wallets even though they are not loaded yet. But for outside users this seems like a limitation that is difficult to deal with. If you want to add a wallet on startup, wouldn’t that require racing to set a lock again if you restart? What is the exact scenario here?

    First, I think we can agree that it’s useful for long-running nodes and the internal wallet, right? So for the following I am still unsure if it should be included here or part of follow-up PR.

    For a node that is relaunched only every few weeks for usage of (cold-ish) wallet the persisted lock sounds useful. But I am not sure if just making the locks persistent is actually the best UI for this use-case. The pattern would probably be that the user, before shutdown, sets a prune lock to the last scan height (potentially the tip) and then shuts down. Then on restart the prune lock is respected. But then maybe it would actually be more convenient if there was a startup option -prunelocked or so where the node can be started with the current tip locked. It could get a height parameter too if an earlier height should be locked. I haven’t spent much time thinking if this covers all the use-cases but so far I tend to think the startup option covers what I have in mind and comes with a few upsides: If the node crashes and the user couldn’t set the lock before, with persistence they might be still screwed but with the startup option they are safe. And since we could avoid persistence this should be much easier to implement and have less code to maintain.

    EDIT: Also, for certain wallets that really need a lot of control, there is always the option to manually prune as well.

  30. sedited commented at 10:28 am on March 25, 2026: contributor

    First, I think we can agree that it’s useful for long-running nodes and the internal wallet, right? So for the following I am still unsure if it should be included here or part of follow-up PR.

    The case is still not quite clear to me to be honest. Would the wallet start tracking prune locks for unloaded wallets? Afaict it doesn’t currently hold a lock during rescan - that does indeed seem like something useful. I can also see how deleting / cleaning up existing locks could be useful, though it also feels like bit of a footgun. I’m having a hard time thinking of a scenario where you can robustly race a node pruning and important an existing wallet / descriptor. The startup option does seem better to me too.

  31. fjahr commented at 1:34 pm on March 25, 2026: contributor

    The startup option does seem better to me too.

    Thanks, I have added a minimal implementation of the startup option because so far seems like a clearly useful feature to me if outside wallets want to use prune locks in any capacity. But conceptual feedback still very welcome!

    Would the wallet start tracking prune locks for unloaded wallets?

    I think so? I could see something like this become more common in the future when more users switch on pruning to avoid having to get a larger disk. I would expect there are wallet options that market themselves being compatible with (possibly aggressive) pruning on the node and that this option will become more common in the future. But of course it would be a stronger statement to hear from an implementer directly that they would use such a feature. I just looked at the sparrow docs as a start and they are rather “hands-off” so far and just warn about potential issues: https://sparrowwallet.com/docs/connect-node.html#pruned-nodes

  32. DrahtBot added the label Needs rebase on Mar 31, 2026
  33. blockstorage: Add prune locks helper methods
    These are helpful for RPC methods to manage prune locks manually.
    f43358d035
  34. rpc: Add setprunelock and listprunelocks
    These allow for manual management of prune locks by users and follow the UX pattern of setban/listbanned.
    
    Co-authored-by: Luke Dashjr <luke-jr+git@utopios.org>
    a489c682e1
  35. test: Add manual prune lock RPC coverage 5413a2ed86
  36. init: Add -prunelockheight init option
    This allows to start a node with a prune lock already set in order to avoid racing with any unwanted pruning.
    b4895d5222
  37. fjahr force-pushed on Mar 31, 2026
  38. fjahr commented at 9:55 pm on March 31, 2026: contributor
    Rebased
  39. DrahtBot removed the label Needs rebase on Mar 31, 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-04-12 03:13 UTC

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