rpc, wallet: add an option to not load the wallet after migrating #35266

pull polespinasa wants to merge 3 commits into bitcoin:master from polespinasa:2026-05-11-addoptiontonorescanwhenwalletmigrate changing 6 files +86 −22
  1. polespinasa commented at 8:47 PM on May 11, 2026: member

    This PR is motivated by this Stack Exchange question.

    Long story short, someone who has a node pruned before his legacy wallet birthday, is unable to migrate the wallet as it is not possible to load it.

    Loading is not necessary for migration, and migrating without wanting to use the wallet in that node is a valid use-case.

    This PR adds a new RPC argument to migratewallet that allow the user disabling the wallet loading. Second commits adds tests for it.

  2. DrahtBot commented at 8:47 PM on May 11, 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/35266.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK w0xlt

    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:

    • #34909 (wallet, refactor: modularise wallet by extracting out legacy wallet migration by rkrux)
    • #25722 (refactor: Use util::Result class for wallet loading 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-->

  3. w0xlt commented at 9:00 PM on May 11, 2026: contributor

    Concept ACK.

  4. polespinasa force-pushed on May 11, 2026
  5. DrahtBot added the label CI failed on May 11, 2026
  6. polespinasa force-pushed on May 11, 2026
  7. in src/wallet/rpc/wallet.cpp:596 in 525b30dcb2
     592 | @@ -593,6 +593,7 @@ static RPCMethod migratewallet()
     593 |          {
     594 |              {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to migrate. If provided both here and in the RPC endpoint, the two must be identical."},
     595 |              {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
     596 | +            {"load_wallet", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Load the wallet after migration. If not specified the wallet will be loaded."}
    


    achow101 commented at 9:34 PM on May 11, 2026:

    In 6d3916f03206cb123da342da1200259e554bae46 "rpc, wallet: add option to not load the wallet after migration"

                {"load_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "Load the wallet after migration."}
    

    polespinasa commented at 10:21 PM on May 11, 2026:

    done

  8. in src/wallet/rpc/wallet.cpp:624 in 525b30dcb2
     617 | @@ -617,8 +618,13 @@ static RPCMethod migratewallet()
     618 |                  wallet_pass = std::string_view{request.params[1].get_str()};
     619 |              }
     620 |  
     621 | +            bool loadwallet{true};
     622 | +            if (!request.params[2].isNull()) {
     623 | +                loadwallet = request.params[2].get_bool();
     624 | +            }
    


    achow101 commented at 9:37 PM on May 11, 2026:

    In 6d3916f03206cb123da342da1200259e554bae46 "rpc, wallet: add option to not load the wallet after migration"

                bool loadwallet = self.Arg<bool>("load_wallet");
    

    polespinasa commented at 10:21 PM on May 11, 2026:

    done

  9. in src/wallet/wallet.cpp:4461 in 525b30dcb2 outdated
    4465 | -                    LogError("Failed to load wallet '%s' after migration. Rolling back migration to preserve consistency. "
    4466 | -                             "Error cause: %s\n", wallet_name, error.original);
    4467 | -                    success = false;
    4468 | -                    break;
    4469 | +                if (load_wallet) {
    4470 | +                    wallet = LoadWallet(context, wallet_name, /*load_on_start=*/std::nullopt, options, status, error, warnings);
    


    achow101 commented at 9:52 PM on May 11, 2026:

    In 6d3916f03206cb123da342da1200259e554bae46 "rpc, wallet: add option to not load the wallet after migration"

    For watchonly_wallet and solvables_wallet, without loading those wallets, we lose the output to the user that those wallets were created. MigrationResult should be updated to include the watchonly and solvables wallets names so we can return that info to the user when those wallets are not being loaded.


    polespinasa commented at 2:12 PM on May 12, 2026:

    We only lose that output when not loading the wallet? I mean is it something this PR broke?


    achow101 commented at 5:51 PM on May 12, 2026:

    When those wallets are not loaded, we no longer inform the user that those wallets have been created in the RPC result. But we should still do that, so MigrationResult should have fields for those wallet names.

  10. DrahtBot removed the label CI failed on May 11, 2026
  11. polespinasa force-pushed on May 11, 2026
  12. polespinasa commented at 10:38 PM on May 11, 2026: member

    note to self - need release note

  13. in src/wallet/rpc/wallet.cpp:596 in d77b45e9c9
     592 | @@ -593,6 +593,7 @@ static RPCMethod migratewallet()
     593 |          {
     594 |              {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to migrate. If provided both here and in the RPC endpoint, the two must be identical."},
     595 |              {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
     596 | +            {"load_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "Load the wallet after migration. If not specified the wallet will be loaded."}
    


    stickies-v commented at 1:11 PM on May 12, 2026:

    nit: the Default value is already encoded in the help string, no need to manually repeat it with "if not specified..."

    bitcoin-cli help migratewallet
    ...
    3. load_wallet    (boolean, optional, default=true) Load the wallet after migration. If not specified the wallet will be loaded.`
    

    polespinasa commented at 2:14 PM on May 12, 2026:

    wops, true! forgot to remove that part of the sentence when added the Default value :)

    fixed!

  14. rpc, wallet: add option to not load the wallet after migration
    After migrating from legacy wallet to a descriptor based wallet
    the wallet is loaded into the node by default.
    This commit adds an RPC argumment to disable wallet loading.
    4be09f232d
  15. test: tests wallet migration with load_wallet disabled 3644101f36
  16. add release note 4f9002bfe2
  17. polespinasa force-pushed on May 12, 2026
  18. w0xlt commented at 9:30 PM on May 12, 2026: contributor

    The current code fails when migration creates watchonly and/or solvables wallets.

    This happens in the post-migration loop inside MigrateLegacyToDescriptor():

    for (std::shared_ptr<CWallet>* wallet_ptr : {&local_wallet, &res.watchonly_wallet, &res.solvables_wallet}) { ... }
    

    Inside the loop, the wallet pointer is reset before the optional reload:

    wallet.reset();
    

    When load_wallet=false, the wallet is not reloaded, so res.wallet remains null. The code then uses !res.wallet to decide whether the primary wallet name still needs to be set:

    if (!res.wallet) {
        res.wallet_name = wallet_name;
    }
    

    Because res.wallet remains null throughout the loop, this condition is true for the primary wallet, the watchonly wallet, and the solvables wallet. As a result, res.wallet_name is overwritten and ends up containing the last auxiliary wallet name, e.g. <wallet>_solvables, instead of the primary wallet name.

    The same no-load path also clears res.watchonly_wallet and res.solvables_wallet, so the RPC response cannot derive watchonly_name or solvables_name from those pointers.

    Test:

    diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py
    index 48ae1fb051..78730aa3fe 100755
    --- a/test/functional/wallet_migration.py
    +++ b/test/functional/wallet_migration.py
    @@ -1537,6 +1537,27 @@ class WalletMigrationTest(BitcoinTestFramework):
             assert_equal(loaded_wallet.getbalance(), bals["mine"]["trusted"])
             loaded_wallet.unloadwallet()
     
    +    def test_no_load_reports_auxiliary_wallet_names(self):
    +        self.log.info("Test no-load migration reports auxiliary wallet names")
    +        wallet_name = "no_load_auxiliary_names"
    +        wallet = self.create_legacy_wallet(wallet_name)
    +
    +        wallet.importaddress(address=self.master_node.get_wallet_rpc(self.default_wallet_name).getnewaddress(), rescan=False)
    +        _, pubkey = generate_keypair(compressed=True, wif=True)
    +        wallet.addmultisigaddress(nrequired=1, keys=[pubkey.hex()])
    +
    +        self.old_node.unloadwallet(wallet_name)
    +        shutil.copytree(self.old_node.wallets_path / wallet_name, self.master_node.wallets_path / wallet_name)
    +
    +        migrate_info = self.master_node.migratewallet(wallet_name=wallet_name, load_wallet=False)
    +
    +        assert_equal(migrate_info["wallet_name"], wallet_name)
    +        assert_equal(migrate_info["watchonly_name"], f"{wallet_name}_watchonly")
    +        assert_equal(migrate_info["solvables_name"], f"{wallet_name}_solvables")
    +        assert wallet_name not in self.master_node.listwallets()
    +        assert f"{wallet_name}_watchonly" not in self.master_node.listwallets()
    +        assert f"{wallet_name}_solvables" not in self.master_node.listwallets()
    +
         def test_solvable_no_privs(self):
             self.log.info("Test migrating a multisig that we do not have any private keys for")
             wallet = self.create_legacy_wallet("multisig_noprivs")
    @@ -1669,6 +1690,7 @@ class WalletMigrationTest(BitcoinTestFramework):
             self.test_miniscript()
             self.test_taproot()
             self.test_no_load_after_migration()
    +        self.test_no_load_reports_auxiliary_wallet_names()
             self.test_solvable_no_privs()
             self.test_loading_failure_after_migration()
    

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-05-19 06:51 UTC