|
10 | 10 | import time
|
11 | 11 |
|
12 | 12 | from test_framework.address import (
|
13 |
| - script_to_p2sh, |
14 | 13 | key_to_p2pkh,
|
15 | 14 | key_to_p2wpkh,
|
| 15 | + script_to_p2sh, |
| 16 | + script_to_p2wsh, |
16 | 17 | )
|
17 | 18 | from test_framework.bdb import BTREE_MAGIC
|
18 | 19 | from test_framework.descriptors import descsum_create
|
@@ -1047,6 +1048,159 @@ def test_manual_keys_import(self):
|
1047 | 1048 | assert_equal(expected_descs, migrated_desc)
|
1048 | 1049 | wo_wallet.unloadwallet()
|
1049 | 1050 |
|
| 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 | + |
1050 | 1204 | def run_test(self):
|
1051 | 1205 | self.master_node = self.nodes[0]
|
1052 | 1206 | self.old_node = self.nodes[1]
|
@@ -1074,7 +1228,8 @@ def run_test(self):
|
1074 | 1228 | self.test_blank()
|
1075 | 1229 | self.test_migrate_simple_watch_only()
|
1076 | 1230 | self.test_manual_keys_import()
|
1077 |
| - |
| 1231 | + self.test_p2wsh() |
| 1232 | + self.test_disallowed_p2wsh() |
1078 | 1233 |
|
1079 | 1234 | if __name__ == '__main__':
|
1080 | 1235 | WalletMigrationTest(__file__).main()
|
0 commit comments