diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 77d0a395c93..5ff8abf3077 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4010,10 +4010,9 @@ util::Result CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch, } // Get best block locator so that we can copy it to the watchonly and solvables + // Note: The best block locator was introduced in #152 so ancient wallets do not have it CBlockLocator best_block_locator; - if (!local_wallet_batch.ReadBestBlock(best_block_locator)) { - return util::Error{_("Error: Unable to read wallet's best block locator record")}; - } + (void)local_wallet_batch.ReadBestBlock(best_block_locator); // Update m_txos to match the descriptors remaining in this wallet m_txos.clear(); @@ -4030,7 +4029,7 @@ util::Result 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")}; } @@ -4039,7 +4038,7 @@ util::Result CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch, if (data.solvable_wallet) { solvables_batch = std::make_unique(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")}; } diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index 42b2a47b3b0..5492938be9f 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -1573,6 +1573,36 @@ class WalletMigrationTest(BitcoinTestFramework): self.clear_default_wallet(backup_path) + @staticmethod + def erase_bdb_record(wallet_dat_path, key): + data = bytearray(wallet_dat_path.read_bytes()) + idx = data.find(key) + assert idx != -1, f"{key!r} not found in wallet.dat" + + for i in range(idx, idx + len(key)): + data[i] = 0 + + wallet_dat_path.write_bytes(data) + + def test_missing_bestblock(self): + self.log.info("Test migrating legacy BDB wallet without bestblock record") + wallet_name = "nobestblock" + wallet = self.create_legacy_wallet(wallet_name) + wallet.unloadwallet() + + # Erase block locator records like if this would be a pre-#152 wallet + self.erase_bdb_record(self.old_node.wallets_path / wallet_name / "wallet.dat", b"bestblock_nomerkle") + self.erase_bdb_record(self.old_node.wallets_path / wallet_name / "wallet.dat", b"bestblock") + + shutil.copytree(self.old_node.wallets_path / wallet_name, self.master_node.wallets_path / wallet_name, dirs_exist_ok=True) + # Migrate, checking that rescan occurs + with self.master_node.assert_debug_log(expected_msgs=["Rescanning"], unexpected_msgs=[]): + self.master_node.migratewallet(wallet_name) + + wallet = self.master_node.get_wallet_rpc(wallet_name) + info = wallet.getwalletinfo() + assert_equal(info["descriptors"], True) + assert_equal(info["format"], "sqlite") def run_test(self): self.master_node = self.nodes[0] @@ -1619,6 +1649,7 @@ class WalletMigrationTest(BitcoinTestFramework): self.test_taproot() self.test_solvable_no_privs() self.test_loading_failure_after_migration() + self.test_missing_bestblock() # Note: After this test the first 250 blocks of 'master_node' are pruned self.unsynced_wallet_on_pruned_node_fails()