Skip to content

Commit b1ab927

Browse files
committed
tests: Test migration of additional P2WSH scripts
1 parent c39b3cf commit b1ab927

File tree

1 file changed

+157
-2
lines changed

1 file changed

+157
-2
lines changed

test/functional/wallet_migration.py

Lines changed: 157 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
import time
1111

1212
from test_framework.address import (
13-
script_to_p2sh,
1413
key_to_p2pkh,
1514
key_to_p2wpkh,
15+
script_to_p2sh,
16+
script_to_p2wsh,
1617
)
1718
from test_framework.bdb import BTREE_MAGIC
1819
from test_framework.descriptors import descsum_create
@@ -1047,6 +1048,159 @@ def test_manual_keys_import(self):
10471048
assert_equal(expected_descs, migrated_desc)
10481049
wo_wallet.unloadwallet()
10491050

1051+
def test_p2wsh(self):
1052+
self.log.info("Test that non-multisig P2WSH output scripts are migrated")
1053+
def_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
1054+
1055+
wallet = self.create_legacy_wallet("p2wsh")
1056+
1057+
# Craft wsh(pkh(key))
1058+
pubkey = wallet.getaddressinfo(wallet.getnewaddress())["pubkey"]
1059+
pkh_script = key_to_p2pkh_script(pubkey).hex()
1060+
wsh_pkh_script = script_to_p2wsh_script(pkh_script).hex()
1061+
wsh_pkh_addr = script_to_p2wsh(pkh_script)
1062+
1063+
# Legacy single key scripts (i.e. pkh(key) and pk(key)) are not inserted into mapScripts
1064+
# automatically, they need to be imported directly if we want to receive to P2WSH (or P2SH)
1065+
# wrappings of such scripts.
1066+
wallet.importaddress(address=pkh_script, p2sh=False)
1067+
wallet.importaddress(address=wsh_pkh_script, p2sh=False)
1068+
1069+
def_wallet.sendtoaddress(wsh_pkh_addr, 5)
1070+
self.generate(self.nodes[0], 6)
1071+
assert_equal(wallet.getbalances()['mine']['trusted'], 5)
1072+
1073+
_, wallet = self.migrate_and_get_rpc("p2wsh")
1074+
1075+
assert_equal(wallet.getbalances()['mine']['trusted'], 5)
1076+
addr_info = wallet.getaddressinfo(wsh_pkh_addr)
1077+
assert_equal(addr_info["ismine"], True)
1078+
assert_equal(addr_info["iswatchonly"], False)
1079+
assert_equal(addr_info["solvable"], True)
1080+
1081+
wallet.unloadwallet()
1082+
1083+
def test_disallowed_p2wsh(self):
1084+
self.log.info("Test that P2WSH output scripts with invalid witnessScripts are not migrated and do not cause migration failure")
1085+
def_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
1086+
1087+
wallet = self.create_legacy_wallet("invalid_p2wsh")
1088+
1089+
invalid_addrs = []
1090+
1091+
# For a P2WSH output script stored in the legacy wallet's mapScripts, both the native P2WSH
1092+
# and the P2SH-P2WSH are detected by IsMine. We need to verify that descriptors for both
1093+
# output scripts are added to the resulting descriptor wallet.
1094+
# However, this cannot be done using a multisig as wallet migration treats multisigs specially.
1095+
# Instead, this is tested by importing a wsh(pkh()) script. But importing this directly will
1096+
# insert the wsh() into setWatchOnly which means that the setWatchOnly migration ends up handling
1097+
# this case, which we do not want.
1098+
# In order to get the wsh(pkh()) into only mapScripts and not setWatchOnly, we need to utilize
1099+
# importmulti and wrap the wsh(pkh()) inside of a sh(). This will insert the sh(wsh(pkh())) into
1100+
# setWatchOnly but not the wsh(pkh()).
1101+
# Furthermore, migration should not migrate the wsh(pkh()) if the key is uncompressed.
1102+
comp_wif, comp_pubkey = generate_keypair(compressed=True, wif=True)
1103+
comp_pkh_script = key_to_p2pkh_script(comp_pubkey).hex()
1104+
comp_wsh_pkh_script = script_to_p2wsh_script(comp_pkh_script).hex()
1105+
comp_sh_wsh_pkh_script = script_to_p2sh_script(comp_wsh_pkh_script).hex()
1106+
comp_wsh_pkh_addr = script_to_p2wsh(comp_pkh_script)
1107+
1108+
uncomp_wif, uncomp_pubkey = generate_keypair(compressed=False, wif=True)
1109+
uncomp_pkh_script = key_to_p2pkh_script(uncomp_pubkey).hex()
1110+
uncomp_wsh_pkh_script = script_to_p2wsh_script(uncomp_pkh_script).hex()
1111+
uncomp_sh_wsh_pkh_script = script_to_p2sh_script(uncomp_wsh_pkh_script).hex()
1112+
uncomp_wsh_pkh_addr = script_to_p2wsh(uncomp_pkh_script)
1113+
invalid_addrs.append(uncomp_wsh_pkh_addr)
1114+
1115+
import_res = wallet.importmulti([
1116+
{
1117+
"scriptPubKey": comp_sh_wsh_pkh_script,
1118+
"timestamp": "now",
1119+
"redeemscript": comp_wsh_pkh_script,
1120+
"witnessscript": comp_pkh_script,
1121+
"keys": [
1122+
comp_wif,
1123+
],
1124+
},
1125+
{
1126+
"scriptPubKey": uncomp_sh_wsh_pkh_script,
1127+
"timestamp": "now",
1128+
"redeemscript": uncomp_wsh_pkh_script,
1129+
"witnessscript": uncomp_pkh_script,
1130+
"keys": [
1131+
uncomp_wif,
1132+
],
1133+
},
1134+
])
1135+
assert_equal(import_res[0]["success"], True)
1136+
assert_equal(import_res[1]["success"], True)
1137+
1138+
# Create a wsh(sh(pkh())) - P2SH inside of P2WSH is invalid
1139+
comp_sh_pkh_script = script_to_p2sh_script(comp_pkh_script).hex()
1140+
wsh_sh_pkh_script = script_to_p2wsh_script(comp_sh_pkh_script).hex()
1141+
wsh_sh_pkh_addr = script_to_p2wsh(comp_sh_pkh_script)
1142+
invalid_addrs.append(wsh_sh_pkh_addr)
1143+
1144+
# Import wsh(sh(pkh()))
1145+
wallet.importaddress(address=comp_sh_pkh_script, p2sh=False)
1146+
wallet.importaddress(address=wsh_sh_pkh_script, p2sh=False)
1147+
1148+
# Create a wsh(wsh(pkh())) - P2WSH inside of P2WSH is invalid
1149+
wsh_wsh_pkh_script = script_to_p2wsh_script(comp_wsh_pkh_script).hex()
1150+
wsh_wsh_pkh_addr = script_to_p2wsh(comp_wsh_pkh_script)
1151+
invalid_addrs.append(wsh_wsh_pkh_addr)
1152+
1153+
# Import wsh(wsh(pkh()))
1154+
wallet.importaddress(address=wsh_wsh_pkh_script, p2sh=False)
1155+
1156+
# The wsh(pkh()) with a compressed key is always valid, so we should see that the wallet detects it as ismine, not
1157+
# watchonly, and can provide us information about the witnessScript via "embedded"
1158+
comp_wsh_pkh_addr_info = wallet.getaddressinfo(comp_wsh_pkh_addr)
1159+
assert_equal(comp_wsh_pkh_addr_info["ismine"], True)
1160+
assert_equal(comp_wsh_pkh_addr_info["iswatchonly"], False)
1161+
assert "embedded" in comp_wsh_pkh_addr_info
1162+
1163+
# The invalid addresses are invalid, so the legacy wallet should not detect them as ismine,
1164+
# nor consider them watchonly. However, because the legacy wallet has the witnessScripts/redeemScripts,
1165+
# we should see information about those in "embedded"
1166+
for addr in invalid_addrs:
1167+
addr_info = wallet.getaddressinfo(addr)
1168+
assert_equal(addr_info["ismine"], False)
1169+
assert_equal(addr_info["iswatchonly"], False)
1170+
assert "embedded" in addr_info
1171+
1172+
# Fund those output scripts, although the invalid addresses will not have any balance.
1173+
# This behavior follows as the addresses are not ismine.
1174+
def_wallet.send([{comp_wsh_pkh_addr: 1}] + [{k: i + 1} for i, k in enumerate(invalid_addrs)])
1175+
self.generate(self.nodes[0], 6)
1176+
bal = wallet.getbalances()
1177+
assert_equal(bal["mine"]["trusted"], 1)
1178+
assert_equal(bal["watchonly"]["trusted"], 0)
1179+
1180+
res, wallet = self.migrate_and_get_rpc("invalid_p2wsh")
1181+
assert "watchonly_name" not in res
1182+
assert "solvables_name" not in res
1183+
1184+
assert_equal(wallet.getbalances()["mine"]["trusted"], 1)
1185+
1186+
# After migration, the wsh(pkh()) with a compressed key is still valid and the descriptor wallet will have
1187+
# information about the witnessScript
1188+
comp_wsh_pkh_addr_info = wallet.getaddressinfo(comp_wsh_pkh_addr)
1189+
assert_equal(comp_wsh_pkh_addr_info["ismine"], True)
1190+
assert_equal(comp_wsh_pkh_addr_info["iswatchonly"], False)
1191+
assert "embedded" in comp_wsh_pkh_addr_info
1192+
1193+
# After migration, the invalid addresses should still not be detected as ismine and not watchonly.
1194+
# The descriptor wallet should not have migrated these at all, so there should additionally be no
1195+
# information in "embedded" about the witnessScripts/redeemScripts.
1196+
for addr in invalid_addrs:
1197+
addr_info = wallet.getaddressinfo(addr)
1198+
assert_equal(addr_info["ismine"], False)
1199+
assert_equal(addr_info["iswatchonly"], False)
1200+
assert "embedded" not in addr_info
1201+
1202+
wallet.unloadwallet()
1203+
10501204
def run_test(self):
10511205
self.master_node = self.nodes[0]
10521206
self.old_node = self.nodes[1]
@@ -1074,7 +1228,8 @@ def run_test(self):
10741228
self.test_blank()
10751229
self.test_migrate_simple_watch_only()
10761230
self.test_manual_keys_import()
1077-
1231+
self.test_p2wsh()
1232+
self.test_disallowed_p2wsh()
10781233

10791234
if __name__ == '__main__':
10801235
WalletMigrationTest(__file__).main()

0 commit comments

Comments
 (0)