Skip to content

Commit c2d04f1

Browse files
committed
Merge bitcoin/bitcoin#28610: wallet: Migrate entire address book entries to watchonly and solvables too
406b71a wallet: Migrate entire address book entries (Andrew Chow) Pull request description: Not all of the data in an address book entry was being copied to the watchonly and solvables wallets. This includes information such as whether the address was previously spent, and any receive requests that may exist. A test has been added to check that the previously spent information is copied, although it passes without the changes in this PR since this information is also regenerated when a transaction is loaded/added into a wallet. ACKs for top commit: ryanofsky: Code review ACK 406b71a. Just suggested change since last review furszy: Code review ACK 406b71a Tree-SHA512: 13de42b16a1d8524fe0555764744139566b2e7d29741ceffc1158a905dd537136b762330568b3b5cac28cbee1bfd363a20de97d0a6c5296738cb3aa99133945b
2 parents 04b9df0 + 406b71a commit c2d04f1

File tree

2 files changed

+69
-27
lines changed

2 files changed

+69
-27
lines changed

src/wallet/wallet.cpp

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3989,25 +3989,17 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
39893989
if (data.watchonly_wallet) {
39903990
LOCK(data.watchonly_wallet->cs_wallet);
39913991
if (data.watchonly_wallet->IsMine(addr_pair.first)) {
3992-
// Add to the watchonly. Preserve the labels, purpose, and change-ness
3993-
std::string label = addr_pair.second.GetLabel();
3994-
data.watchonly_wallet->m_address_book[addr_pair.first].purpose = addr_pair.second.purpose;
3995-
if (!addr_pair.second.IsChange()) {
3996-
data.watchonly_wallet->m_address_book[addr_pair.first].SetLabel(label);
3997-
}
3992+
// Add to the watchonly. Copy the entire address book entry
3993+
data.watchonly_wallet->m_address_book[addr_pair.first] = addr_pair.second;
39983994
dests_to_delete.push_back(addr_pair.first);
39993995
continue;
40003996
}
40013997
}
40023998
if (data.solvable_wallet) {
40033999
LOCK(data.solvable_wallet->cs_wallet);
40044000
if (data.solvable_wallet->IsMine(addr_pair.first)) {
4005-
// Add to the solvable. Preserve the labels, purpose, and change-ness
4006-
std::string label = addr_pair.second.GetLabel();
4007-
data.solvable_wallet->m_address_book[addr_pair.first].purpose = addr_pair.second.purpose;
4008-
if (!addr_pair.second.IsChange()) {
4009-
data.solvable_wallet->m_address_book[addr_pair.first].SetLabel(label);
4010-
}
4001+
// Add to the solvable. Copy the entire address book entry
4002+
data.solvable_wallet->m_address_book[addr_pair.first] = addr_pair.second;
40114003
dests_to_delete.push_back(addr_pair.first);
40124004
continue;
40134005
}
@@ -4027,21 +4019,13 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
40274019
// Labels for everything else ("send") should be cloned to all
40284020
if (data.watchonly_wallet) {
40294021
LOCK(data.watchonly_wallet->cs_wallet);
4030-
// Add to the watchonly. Preserve the labels, purpose, and change-ness
4031-
std::string label = addr_pair.second.GetLabel();
4032-
data.watchonly_wallet->m_address_book[addr_pair.first].purpose = addr_pair.second.purpose;
4033-
if (!addr_pair.second.IsChange()) {
4034-
data.watchonly_wallet->m_address_book[addr_pair.first].SetLabel(label);
4035-
}
4022+
// Add to the watchonly. Copy the entire address book entry
4023+
data.watchonly_wallet->m_address_book[addr_pair.first] = addr_pair.second;
40364024
}
40374025
if (data.solvable_wallet) {
40384026
LOCK(data.solvable_wallet->cs_wallet);
4039-
// Add to the solvable. Preserve the labels, purpose, and change-ness
4040-
std::string label = addr_pair.second.GetLabel();
4041-
data.solvable_wallet->m_address_book[addr_pair.first].purpose = addr_pair.second.purpose;
4042-
if (!addr_pair.second.IsChange()) {
4043-
data.solvable_wallet->m_address_book[addr_pair.first].SetLabel(label);
4044-
}
4027+
// Add to the solvable. Copy the entire address book entry
4028+
data.solvable_wallet->m_address_book[addr_pair.first] = addr_pair.second;
40454029
}
40464030
}
40474031
}
@@ -4052,10 +4036,12 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
40524036
WalletBatch batch{wallet.GetDatabase()};
40534037
for (const auto& [destination, addr_book_data] : wallet.m_address_book) {
40544038
auto address{EncodeDestination(destination)};
4055-
std::optional<std::string> label = addr_book_data.IsChange() ? std::nullopt : std::make_optional(addr_book_data.GetLabel());
4056-
// don't bother writing default values (unknown purpose)
40574039
if (addr_book_data.purpose) batch.WritePurpose(address, PurposeToString(*addr_book_data.purpose));
4058-
if (label) batch.WriteName(address, *label);
4040+
if (addr_book_data.label) batch.WriteName(address, *addr_book_data.label);
4041+
for (const auto& [id, request] : addr_book_data.receive_requests) {
4042+
batch.WriteAddressReceiveRequest(destination, id, request);
4043+
}
4044+
if (addr_book_data.previously_spent) batch.WriteAddressPreviouslySpent(destination, true);
40594045
}
40604046
};
40614047
if (data.watchonly_wallet) persist_address_book(*data.watchonly_wallet);

test/functional/wallet_migration.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,61 @@ def test_failed_migration_cleanup(self):
877877
assert_equal(magic, BTREE_MAGIC)
878878

879879

880+
def test_avoidreuse(self):
881+
self.log.info("Test that avoidreuse persists after migration")
882+
def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
883+
884+
wallet = self.create_legacy_wallet("avoidreuse")
885+
wallet.setwalletflag("avoid_reuse", True)
886+
887+
# Import a pubkey to the test wallet and send some funds to it
888+
reused_imported_addr = def_wallet.getnewaddress()
889+
wallet.importpubkey(def_wallet.getaddressinfo(reused_imported_addr)["pubkey"])
890+
imported_utxos = self.create_outpoints(def_wallet, outputs=[{reused_imported_addr: 2}])
891+
def_wallet.lockunspent(False, imported_utxos)
892+
893+
# Send funds to the test wallet
894+
reused_addr = wallet.getnewaddress()
895+
def_wallet.sendtoaddress(reused_addr, 2)
896+
897+
self.generate(self.nodes[0], 1)
898+
899+
# Send funds from the test wallet with both its own and the imported
900+
wallet.sendall([def_wallet.getnewaddress()])
901+
def_wallet.sendall(recipients=[def_wallet.getnewaddress()], inputs=imported_utxos)
902+
self.generate(self.nodes[0], 1)
903+
balances = wallet.getbalances()
904+
assert_equal(balances["mine"]["trusted"], 0)
905+
assert_equal(balances["watchonly"]["trusted"], 0)
906+
907+
# Reuse the addresses
908+
def_wallet.sendtoaddress(reused_addr, 1)
909+
def_wallet.sendtoaddress(reused_imported_addr, 1)
910+
self.generate(self.nodes[0], 1)
911+
balances = wallet.getbalances()
912+
assert_equal(balances["mine"]["used"], 1)
913+
# Reused watchonly will not show up in balances
914+
assert_equal(balances["watchonly"]["trusted"], 0)
915+
assert_equal(balances["watchonly"]["untrusted_pending"], 0)
916+
assert_equal(balances["watchonly"]["immature"], 0)
917+
918+
utxos = wallet.listunspent()
919+
assert_equal(len(utxos), 2)
920+
for utxo in utxos:
921+
assert_equal(utxo["reused"], True)
922+
923+
# Migrate
924+
migrate_res = wallet.migratewallet()
925+
watchonly_wallet = self.nodes[0].get_wallet_rpc(migrate_res["watchonly_name"])
926+
927+
# One utxo in each wallet, marked used
928+
utxos = wallet.listunspent()
929+
assert_equal(len(utxos), 1)
930+
assert_equal(utxos[0]["reused"], True)
931+
watchonly_utxos = watchonly_wallet.listunspent()
932+
assert_equal(len(watchonly_utxos), 1)
933+
assert_equal(watchonly_utxos[0]["reused"], True)
934+
880935
def run_test(self):
881936
self.generate(self.nodes[0], 101)
882937

@@ -896,6 +951,7 @@ def run_test(self):
896951
self.test_conflict_txs()
897952
self.test_hybrid_pubkey()
898953
self.test_failed_migration_cleanup()
954+
self.test_avoidreuse()
899955

900956
if __name__ == '__main__':
901957
WalletMigrationTest().main()

0 commit comments

Comments
 (0)