Skip to content

Commit 9039d8f

Browse files
committed
Merge bitcoin/bitcoin#31374: wallet: fix crash during watch-only wallet migration
cdd207c test: add coverage for migrating standalone imported keys (furszy) 297a876 test: add coverage for migrating watch-only script (furszy) 932cd1e wallet: fix crash during watch-only wallet migration (furszy) Pull request description: The crash occurs because we assume the cached scripts structure will not be empty, but it can be empty for watch-only wallets that start blank. This also adds test coverage for standalone imported keys, which were also crashing because pubkey imports are treated the same way as hex script imports through `importaddress()`. Testing Notes: This can be verified by cherry-picking and running any of the test commits on master. It will crash there but pass on this branch. ACKs for top commit: theStack: re-ACK cdd207c brunoerg: reACK cdd207c achow101: ACK cdd207c Tree-SHA512: e05c77cf3e9f35f10f122a73680b3f131f683c56685c1e26b5ffc857f95195b64c8c9d4535960ed3d6f931935aa79b0b1242537462006126bdb68251f0452954
2 parents 35000e3 + cdd207c commit 9039d8f

File tree

2 files changed

+66
-2
lines changed

2 files changed

+66
-2
lines changed

src/wallet/wallet.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4085,7 +4085,11 @@ util::Result<void> CWallet::ApplyMigrationData(WalletBatch& local_wallet_batch,
40854085
if (ExtractDestination(script, dest)) not_migrated_dests.emplace(dest);
40864086
}
40874087

4088-
Assume(!m_cached_spks.empty());
4088+
// When the legacy wallet has no spendable scripts, the main wallet will be empty, leaving its script cache empty as well.
4089+
// The watch-only and/or solvable wallet(s) will contain the scripts in their respective caches.
4090+
if (!data.desc_spkms.empty()) Assume(!m_cached_spks.empty());
4091+
if (!data.watch_descs.empty()) Assume(!data.watchonly_wallet->m_cached_spks.empty());
4092+
if (!data.solvable_descs.empty()) Assume(!data.solvable_wallet->m_cached_spks.empty());
40894093

40904094
for (auto& desc_spkm : data.desc_spkms) {
40914095
if (m_spk_managers.count(desc_spkm->GetID()) > 0) {

test/functional/wallet_migration.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@
1919
from test_framework.key import ECPubKey
2020
from test_framework.test_framework import BitcoinTestFramework
2121
from test_framework.messages import COIN, CTransaction, CTxOut
22-
from test_framework.script_util import key_to_p2pkh_script, script_to_p2sh_script, script_to_p2wsh_script
22+
from test_framework.script import hash160
23+
from test_framework.script_util import key_to_p2pkh_script, key_to_p2pk_script, script_to_p2sh_script, script_to_p2wsh_script
2324
from test_framework.util import (
2425
assert_equal,
2526
assert_raises_rpc_error,
2627
sha256sum_file,
2728
)
2829
from test_framework.wallet_util import (
2930
get_generate_key,
31+
generate_keypair,
3032
)
3133

3234

@@ -1006,6 +1008,61 @@ def check_comments():
10061008

10071009
wallet.unloadwallet()
10081010

1011+
def test_migrate_simple_watch_only(self):
1012+
self.log.info("Test migrating a watch-only p2pk script")
1013+
wallet = self.create_legacy_wallet("bare_p2pk", blank=True)
1014+
_, pubkey = generate_keypair()
1015+
p2pk_script = key_to_p2pk_script(pubkey)
1016+
wallet.importaddress(address=p2pk_script.hex())
1017+
# Migrate wallet in the latest node
1018+
res, _ = self.migrate_and_get_rpc("bare_p2pk")
1019+
wo_wallet = self.master_node.get_wallet_rpc(res['watchonly_name'])
1020+
assert_equal(wo_wallet.listdescriptors()['descriptors'][0]['desc'], descsum_create(f'pk({pubkey.hex()})'))
1021+
wo_wallet.unloadwallet()
1022+
1023+
def test_manual_keys_import(self):
1024+
self.log.info("Test migrating standalone private keys")
1025+
wallet = self.create_legacy_wallet("import_privkeys", blank=True)
1026+
privkey, pubkey = generate_keypair(wif=True)
1027+
wallet.importprivkey(privkey=privkey, label="hi", rescan=False)
1028+
1029+
# Migrate and verify
1030+
res, wallet = self.migrate_and_get_rpc("import_privkeys")
1031+
1032+
# There should be descriptors containing the imported key for: pk(), pkh(), sh(wpkh()), wpkh()
1033+
key_origin = hash160(pubkey)[:4].hex()
1034+
pubkey_hex = pubkey.hex()
1035+
pk_desc = descsum_create(f'pk([{key_origin}]{pubkey_hex})')
1036+
pkh_desc = descsum_create(f'pkh([{key_origin}]{pubkey_hex})')
1037+
sh_wpkh_desc = descsum_create(f'sh(wpkh([{key_origin}]{pubkey_hex}))')
1038+
wpkh_desc = descsum_create(f'wpkh([{key_origin}]{pubkey_hex})')
1039+
expected_descs = [pk_desc, pkh_desc, sh_wpkh_desc, wpkh_desc]
1040+
1041+
# Verify all expected descriptors were migrated
1042+
migrated_desc = [item['desc'] for item in wallet.listdescriptors()['descriptors'] if pubkey.hex() in item['desc']]
1043+
assert_equal(expected_descs, migrated_desc)
1044+
wallet.unloadwallet()
1045+
1046+
######################################################
1047+
self.log.info("Test migrating standalone public keys")
1048+
wallet = self.create_legacy_wallet("import_pubkeys", blank=True)
1049+
wallet.importpubkey(pubkey=pubkey_hex, rescan=False)
1050+
1051+
res, _ = self.migrate_and_get_rpc("import_pubkeys")
1052+
1053+
# Same as before, there should be descriptors in the watch-only wallet for the imported pubkey
1054+
wo_wallet = self.nodes[0].get_wallet_rpc(res['watchonly_name'])
1055+
# As we imported the pubkey only, there will be no key origin in the following descriptors
1056+
pk_desc = descsum_create(f'pk({pubkey_hex})')
1057+
pkh_desc = descsum_create(f'pkh({pubkey_hex})')
1058+
sh_wpkh_desc = descsum_create(f'sh(wpkh({pubkey_hex}))')
1059+
wpkh_desc = descsum_create(f'wpkh({pubkey_hex})')
1060+
expected_descs = [pk_desc, pkh_desc, sh_wpkh_desc, wpkh_desc]
1061+
1062+
# Verify all expected descriptors were migrated
1063+
migrated_desc = [item['desc'] for item in wo_wallet.listdescriptors()['descriptors']]
1064+
assert_equal(expected_descs, migrated_desc)
1065+
wo_wallet.unloadwallet()
10091066

10101067
def run_test(self):
10111068
self.master_node = self.nodes[0]
@@ -1032,6 +1089,9 @@ def run_test(self):
10321089
self.test_avoidreuse()
10331090
self.test_preserve_tx_extra_info()
10341091
self.test_blank()
1092+
self.test_migrate_simple_watch_only()
1093+
self.test_manual_keys_import()
1094+
10351095

10361096
if __name__ == '__main__':
10371097
WalletMigrationTest(__file__).main()

0 commit comments

Comments
 (0)