diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c240e885312200..915bcb6436dbfe 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3957,12 +3957,8 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) if (data.watchonly_wallet) { LOCK(data.watchonly_wallet->cs_wallet); if (data.watchonly_wallet->IsMine(addr_pair.first)) { - // Add to the watchonly. Preserve the labels, purpose, and change-ness - std::string label = addr_pair.second.GetLabel(); - data.watchonly_wallet->m_address_book[addr_pair.first].purpose = addr_pair.second.purpose; - if (!addr_pair.second.IsChange()) { - data.watchonly_wallet->m_address_book[addr_pair.first].SetLabel(label); - } + // Add to the watchonly. Copy the entire address book entry + data.watchonly_wallet->m_address_book[addr_pair.first] = addr_pair.second; dests_to_delete.push_back(addr_pair.first); continue; } @@ -3970,12 +3966,8 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) if (data.solvable_wallet) { LOCK(data.solvable_wallet->cs_wallet); if (data.solvable_wallet->IsMine(addr_pair.first)) { - // Add to the solvable. Preserve the labels, purpose, and change-ness - std::string label = addr_pair.second.GetLabel(); - data.solvable_wallet->m_address_book[addr_pair.first].purpose = addr_pair.second.purpose; - if (!addr_pair.second.IsChange()) { - data.solvable_wallet->m_address_book[addr_pair.first].SetLabel(label); - } + // Add to the solvable. Copy the entire address book entry + data.solvable_wallet->m_address_book[addr_pair.first] = addr_pair.second; dests_to_delete.push_back(addr_pair.first); continue; } @@ -3995,21 +3987,13 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) // Labels for everything else ("send") should be cloned to all if (data.watchonly_wallet) { LOCK(data.watchonly_wallet->cs_wallet); - // Add to the watchonly. Preserve the labels, purpose, and change-ness - std::string label = addr_pair.second.GetLabel(); - data.watchonly_wallet->m_address_book[addr_pair.first].purpose = addr_pair.second.purpose; - if (!addr_pair.second.IsChange()) { - data.watchonly_wallet->m_address_book[addr_pair.first].SetLabel(label); - } + // Add to the watchonly. Copy the entire address book entry + data.watchonly_wallet->m_address_book[addr_pair.first] = addr_pair.second; } if (data.solvable_wallet) { LOCK(data.solvable_wallet->cs_wallet); - // Add to the solvable. Preserve the labels, purpose, and change-ness - std::string label = addr_pair.second.GetLabel(); - data.solvable_wallet->m_address_book[addr_pair.first].purpose = addr_pair.second.purpose; - if (!addr_pair.second.IsChange()) { - data.solvable_wallet->m_address_book[addr_pair.first].SetLabel(label); - } + // Add to the solvable. Copy the entire address book entry + data.solvable_wallet->m_address_book[addr_pair.first] = addr_pair.second; } } } @@ -4024,6 +4008,10 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error) // don't bother writing default values (unknown purpose) if (addr_book_data.purpose) batch.WritePurpose(address, PurposeToString(*addr_book_data.purpose)); if (label) batch.WriteName(address, *label); + for (const auto& [id, request] : addr_book_data.receive_requests) { + batch.WriteAddressReceiveRequest(destination, id, request); + } + if (addr_book_data.previously_spent) batch.WriteAddressPreviouslySpent(destination, true); } }; if (data.watchonly_wallet) persist_address_book(*data.watchonly_wallet); diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index 286dcb5fda30e1..d986576dc1b7e0 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -827,6 +827,63 @@ def test_hybrid_pubkey(self): wallet.unloadwallet() + def test_avoidreuse(self): + self.log.info("Test that avoidreuse persists after migration") + def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + wallet = self.create_legacy_wallet("avoidreuse") + wallet.setwalletflag("avoid_reuse", True) + + # Import a pubkey to the test wallet and send some funds to it + reused_imported_addr = def_wallet.getnewaddress() + wallet.importpubkey(def_wallet.getaddressinfo(reused_imported_addr)["pubkey"]) + txid = def_wallet.sendtoaddress(reused_imported_addr, 2) + vout = find_vout_for_address(def_wallet, txid, reused_imported_addr) + imported_utxo = {"txid": txid, "vout": vout} + def_wallet.lockunspent(False, [imported_utxo]) + + # Send funds to the test wallet + reused_addr = wallet.getnewaddress() + def_wallet.sendtoaddress(reused_addr, 2) + + self.generate(self.nodes[0], 1) + + # Send funds from the test wallet with both its own and the imported + wallet.sendall([def_wallet.getnewaddress()]) + def_wallet.sendall(recipients=[def_wallet.getnewaddress()], inputs=[imported_utxo]) + self.generate(self.nodes[0], 1) + balances = wallet.getbalances() + assert_equal(balances["mine"]["trusted"], 0) + assert_equal(balances["watchonly"]["trusted"], 0) + + # Reuse the addresses + def_wallet.sendtoaddress(reused_addr, 1) + def_wallet.sendtoaddress(reused_imported_addr, 1) + self.generate(self.nodes[0], 1) + balances = wallet.getbalances() + assert_equal(balances["mine"]["used"], 1) + # Reused watchonly will not show up in balances + assert_equal(balances["watchonly"]["trusted"], 0) + assert_equal(balances["watchonly"]["untrusted_pending"], 0) + assert_equal(balances["watchonly"]["immature"], 0) + + utxos = wallet.listunspent() + assert_equal(len(utxos), 2) + for utxo in utxos: + assert_equal(utxo["reused"], True) + + # Migrate + migrate_res = wallet.migratewallet() + watchonly_wallet = self.nodes[0].get_wallet_rpc(migrate_res["watchonly_name"]) + + # One utxo in each wallet, marked used + utxos = wallet.listunspent() + assert_equal(len(utxos), 1) + assert_equal(utxos[0]["reused"], True) + watchonly_utxos = watchonly_wallet.listunspent() + assert_equal(len(watchonly_utxos), 1) + assert_equal(watchonly_utxos[0]["reused"], True) + def run_test(self): self.generate(self.nodes[0], 101) @@ -845,6 +902,7 @@ def run_test(self): self.test_migrate_raw_p2sh() self.test_conflict_txs() self.test_hybrid_pubkey() + self.test_avoidreuse() if __name__ == '__main__': WalletMigrationTest().main()