wallet: fix ancient wallets migration #34198

pull furszy wants to merge 2 commits into bitcoin:master from furszy:2026_wallet_migration_ancient_wallets changing 2 files +35 −5
  1. furszy commented at 8:03 PM on January 3, 2026: member

    We currently fail migration if the wallet does not contain the best block locator. This is a problem for wallets created before #152, which are not storing such record.

    Missing this record is not an error. it simply means the wallet will scan the chain prior to finish migration.

  2. DrahtBot added the label Wallet on Jan 3, 2026
  3. DrahtBot commented at 8:03 PM on January 3, 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/34198.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK w0xlt, sedited, achow101
    Stale ACK bensig

    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)

    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. bensig commented at 12:45 AM on January 5, 2026: contributor

    ACK 63a079e7761e8ef94e706eb2103ece4e2a117e50

    Code review ACK. The fix makes sense - ancient pre-#152 wallets shouldn't fail migration just because they lack the bestblock record.

    Couldn't run the test locally (requires previous releases for BDB wallet support), but the logic is straightforward.

  5. DrahtBot added the label Needs rebase on Jan 7, 2026
  6. furszy force-pushed on Jan 7, 2026
  7. DrahtBot removed the label Needs rebase on Jan 7, 2026
  8. DrahtBot added the label CI failed on Jan 7, 2026
  9. DrahtBot removed the label CI failed on Jan 19, 2026
  10. in src/wallet/wallet.cpp:3937 in 785eadf03f outdated
    3931 | @@ -3932,10 +3932,9 @@ util::Result<void> CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch,
    3932 |      }
    3933 |  
    3934 |      // Get best block locator so that we can copy it to the watchonly and solvables
    3935 | +    // Note: The best block locator was introduced in #152 so ancient wallets do not have it
    3936 |      CBlockLocator best_block_locator;
    3937 | -    if (!local_wallet_batch.ReadBestBlock(best_block_locator)) {
    3938 | -        return util::Error{_("Error: Unable to read wallet's best block locator record")};
    3939 | -    }
    3940 | +    (void)local_wallet_batch.ReadBestBlock(best_block_locator);
    


    luke-jr commented at 2:13 PM on January 22, 2026:

    This could result in a corrupt best_block_locator, later getting written in non-corrupt form. Is that the behaviour we want?


    furszy commented at 3:08 PM on January 22, 2026:

    Yes. The locator per se is not important. If it is missing or corrupted, the wallet will rescan the chain before completing migration and store the correct one. Its sole purpose is to track how far the wallet has scanned the chain.

  11. in test/functional/wallet_migration.py:1643 in 785eadf03f
    1638 | +        data = bytearray(wallet_dat_path.read_bytes())
    1639 | +        idx = data.find(key)
    1640 | +        assert idx != -1, f"{key!r} not found in wallet.dat"
    1641 | +
    1642 | +        # Zero the byte immediately after the key (CompactSize vector length)
    1643 | +        data[idx + len(key)] = 0x00
    


    luke-jr commented at 2:13 PM on January 22, 2026:

    Wouldn't it be better to change the key, so it's actually missing?


    furszy commented at 3:11 PM on January 22, 2026:

    Have you tried it? I think I tried it back then and it didn't work but.. maybe I'm mixing it with some other bug fix.


    achow101 commented at 9:15 PM on January 22, 2026:

    Seems to work:

    diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py
    index 404da94b8e0..aab3ac52c79 100755
    --- a/test/functional/wallet_migration.py
    +++ b/test/functional/wallet_migration.py
    @@ -1640,7 +1640,8 @@ class WalletMigrationTest(BitcoinTestFramework):
             assert idx != -1, f"{key!r} not found in wallet.dat"
     
             # Zero the byte immediately after the key (CompactSize vector length)
    -        data[idx + len(key)] = 0x00
    +        for i in range(idx, idx + len(key)):
    +            data[i] = 0
     
             wallet_dat_path.write_bytes(data)
    

    furszy commented at 9:46 PM on January 22, 2026:

    Done as suggested. Thx.

  12. in test/functional/wallet_migration.py:1657 in 785eadf03f
    1652 | +
    1653 | +        # Erase block locator record like if this would be a pre-#152 wallet
    1654 | +        self.erase_bdb_record(self.old_node.wallets_path / wallet_name / "wallet.dat", b"bestblock")
    1655 | +
    1656 | +        shutil.copytree(self.old_node.wallets_path / wallet_name, self.master_node.wallets_path / wallet_name, dirs_exist_ok=True)
    1657 | +        self.master_node.migratewallet(wallet_name)
    


    achow101 commented at 9:16 PM on January 22, 2026:

    Could check the debug.log that the rescan is actually happening


    furszy commented at 9:45 PM on January 22, 2026:

    Done as suggested.

  13. furszy force-pushed on Jan 22, 2026
  14. in test/functional/wallet_migration.py:1681 in 7740cd5996 outdated
    1676 | +        wallet_name = "nobestblock"
    1677 | +        wallet = self.create_legacy_wallet(wallet_name)
    1678 | +        wallet.unloadwallet()
    1679 | +
    1680 | +        # Erase block locator record like if this would be a pre-#152 wallet
    1681 | +        self.erase_bdb_record(self.old_node.wallets_path / wallet_name / "wallet.dat", b"bestblock")
    


    achow101 commented at 9:49 PM on January 26, 2026:

    In 7740cd59966a90a388ae93a4ba7a040f6f08a250 "test: add coverage for migrating ancient wallets"

    erase_bdb_record erases the first instance of bestblock here. However, there are actually 2 bestblock records, bestblock and bestblock_nomerkle. In most cases, this is probably erasing (the first part of) bestblock_nomerkle and leaving bestblock intact. So strictly speaking, this test is not testing pre #152 wallets, but rather a bit of a corruption case where bestblock is empty, but bestblock_nomerkle doesn't exist.

    This should probably erase bestblock_nomerkle first.


    furszy commented at 10:55 PM on January 26, 2026:

    Sure. Done as suggested.

  15. furszy force-pushed on Jan 26, 2026
  16. DrahtBot added the label CI failed on Jan 27, 2026
  17. DrahtBot removed the label CI failed on Jan 27, 2026
  18. sedited approved
  19. sedited commented at 8:57 AM on March 7, 2026: contributor

    ACK 0c8deee2ed45716a105485c6b02e19bc4dbebde0

  20. in test/functional/wallet_migration.py:1668 in 0c8deee2ed
    1663 | +    def erase_bdb_record(wallet_dat_path, key):
    1664 | +        data = bytearray(wallet_dat_path.read_bytes())
    1665 | +        idx = data.find(key)
    1666 | +        assert idx != -1, f"{key!r} not found in wallet.dat"
    1667 | +
    1668 | +        # Zero the byte immediately after the key (CompactSize vector length)
    


    achow101 commented at 11:10 PM on March 31, 2026:

    In f8b74f57636d8435fa4d4e67c55bc041b8fbb8fd "test: add coverage for migrating ancient wallets"

    The comment is no longer accurate. The entire key except for the compact size is being zero'd.


    furszy commented at 2:26 AM on April 1, 2026:

    Removed.

  21. furszy force-pushed on Apr 1, 2026
  22. sedited approved
  23. sedited commented at 9:03 PM on May 2, 2026: contributor

    Re-ACK 673b5bc7f74d0e3a1d6cbbde451c85b1bc5db1fe

  24. sedited requested review from achow101 on May 23, 2026
  25. in src/wallet/wallet.cpp:3981 in 673b5bc7f7


    w0xlt commented at 11:04 PM on May 27, 2026:

    nit: outdated comment.

    diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
    index 422a3661a8..7f2c88ac74 100644
    --- a/src/wallet/wallet.cpp
    +++ b/src/wallet/wallet.cpp
    @@ -3969,7 +3969,7 @@ util::Result<void> CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch,
             LOCK(data.watchonly_wallet->cs_wallet);
             data.watchonly_wallet->nOrderPosNext = nOrderPosNext;
             watchonly_batch->WriteOrderPosNext(data.watchonly_wallet->nOrderPosNext);
    -        // Write the best block locator to avoid rescanning on reload
    +        // Write the locator record. An empty locator is valid and triggers rescan on load.
             if (!watchonly_batch->WriteBestBlock(best_block_locator)) {
                 return util::Error{_("Error: Unable to write watchonly wallet best block locator record")};
             }
    @@ -3978,7 +3978,7 @@ util::Result<void> CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch,
         if (data.solvable_wallet) {
             solvables_batch = std::make_unique<WalletBatch>(data.solvable_wallet->GetDatabase());
             if (!solvables_batch->TxnBegin()) return util::Error{strprintf(_("Error: database transaction cannot be executed for wallet %s"), data.solvable_wallet->GetName())};
    -        // Write the best block locator to avoid rescanning on reload
    +        // Write the locator record. An empty locator is valid and triggers rescan on load.
             if (!solvables_batch->WriteBestBlock(best_block_locator)) {
                 return util::Error{_("Error: Unable to write solvable wallet best block locator record")};
             }
    

    furszy commented at 1:55 PM on May 28, 2026:

    Great, applied. Thanks.

  26. w0xlt commented at 11:04 PM on May 27, 2026: contributor

    ACK 673b5bc7f74d0e3a1d6cbbde451c85b1bc5db1fe

  27. wallet: fix ancient wallets migration
    The best block locator was introduced in #152, previously created
    wallets do not have these record.
    fd44d48b24
  28. test: add coverage for migrating ancient wallets
    Pre-#152 wallets have no best block stored. Test we
    can migrate them.
    b86c1c443d
  29. furszy force-pushed on May 28, 2026
  30. w0xlt commented at 5:27 PM on May 28, 2026: contributor

    reACK b86c1c443d89a50547efd422635e378b5e8f5e87

  31. DrahtBot requested review from sedited on May 28, 2026
  32. sedited approved
  33. sedited commented at 5:32 PM on May 28, 2026: contributor

    Re-ACK b86c1c443d89a50547efd422635e378b5e8f5e87

  34. achow101 commented at 6:42 PM on May 28, 2026: member

    ACK b86c1c443d89a50547efd422635e378b5e8f5e87

  35. achow101 merged this on May 28, 2026
  36. achow101 closed this on May 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-06-01 05:51 UTC

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