|
61 | 61 | import os |
62 | 62 |
|
63 | 63 |
|
| 64 | +KEYPOOL_SIZE = 10 # smaller than default size to speed-up test |
| 65 | + |
64 | 66 | class PSBTTest(BitcoinTestFramework): |
65 | 67 | def set_test_params(self): |
66 | 68 | self.num_nodes = 3 |
67 | 69 | self.extra_args = [ |
68 | 70 | ["-walletrbf=1", "-addresstype=bech32", "-changetype=bech32"], #TODO: Remove address type restrictions once taproot has psbt extensions |
69 | | - ["-walletrbf=0", "-changetype=legacy"], |
| 71 | + ["-walletrbf=0", "-changetype=legacy", "-keypool={}".format(KEYPOOL_SIZE)], |
70 | 72 | [] |
71 | 73 | ] |
72 | 74 | # whitelist peers to speed up tx relay / mempool sync |
@@ -149,6 +151,71 @@ def test_utxo_conversion(self): |
149 | 151 | self.connect_nodes(1, 0) |
150 | 152 | self.connect_nodes(0, 2) |
151 | 153 |
|
| 154 | + def test_offline_gap_limit(self): |
| 155 | + self.log.info("Test offline signing with addresses beyond initial keypool") |
| 156 | + offline_node = self.nodes[1] |
| 157 | + online_node = self.nodes[2] |
| 158 | + |
| 159 | + # Create offline wallet with small keypool |
| 160 | + offline_node.createwallet(wallet_name='offline_small_keypool', descriptors=True) |
| 161 | + offline_signer = offline_node.get_wallet_rpc('offline_small_keypool') |
| 162 | + |
| 163 | + # Get the descriptor from the offline wallet |
| 164 | + descs = offline_signer.listdescriptors()["descriptors"] |
| 165 | + |
| 166 | + # Create watch-only wallet on online node with the same descriptors |
| 167 | + online_node.createwallet(wallet_name='watch_only', disable_private_keys=True, descriptors=True, blank=True) |
| 168 | + watch_only = online_node.get_wallet_rpc('watch_only') |
| 169 | + |
| 170 | + # Disconnect offline node from others |
| 171 | + self.disconnect_nodes(0, 1) |
| 172 | + self.disconnect_nodes(1, 2) |
| 173 | + |
| 174 | + import_res = watch_only.importdescriptors(descs) |
| 175 | + assert_equal(import_res[0]["success"], True) |
| 176 | + |
| 177 | + # Generate several addresses on the watch-only wallet to gap limit of offline wallet |
| 178 | + for _ in range(KEYPOOL_SIZE): |
| 179 | + watch_only.getnewaddress() |
| 180 | + |
| 181 | + # Fund one of the later addresses (e.g., the 20th address) |
| 182 | + target_addr = watch_only.getnewaddress() |
| 183 | + default_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) |
| 184 | + default_wallet.sendtoaddress(address=target_addr, amount=1.0) |
| 185 | + self.generate(self.nodes[0], nblocks=1, sync_fun=lambda: self.sync_all([online_node, self.nodes[0]])) |
| 186 | + |
| 187 | + # Verify the watch-only wallet can see the transaction |
| 188 | + utxos = watch_only.listunspent(addresses=[target_addr]) |
| 189 | + assert_equal(len(utxos), 1) |
| 190 | + assert_equal(utxos[0]["address"], target_addr) |
| 191 | + |
| 192 | + # Create a PSBT on the watch-only (online) wallet to spend this UTXO |
| 193 | + dest_addr = default_wallet.getnewaddress() |
| 194 | + psbt = watch_only.walletcreatefundedpsbt( |
| 195 | + inputs=[{"txid": utxos[0]["txid"], "vout": utxos[0]["vout"]}], |
| 196 | + outputs=[{dest_addr: 0.999}], |
| 197 | + options={"fee_rate": 1} |
| 198 | + )["psbt"] |
| 199 | + |
| 200 | + # Verify the offline wallet can sign the PSBT |
| 201 | + signed_psbt = offline_signer.walletprocesspsbt(psbt) |
| 202 | + assert_equal(signed_psbt["complete"], True) |
| 203 | + |
| 204 | + # Broadcast the transaction from the online node |
| 205 | + txid = online_node.sendrawtransaction(signed_psbt["hex"]) |
| 206 | + self.generate(online_node, nblocks=1, sync_fun=lambda: self.sync_all([online_node, self.nodes[0]])) |
| 207 | + |
| 208 | + # Verify transaction was confirmed |
| 209 | + assert_equal(online_node.gettxout(txid, 0)["confirmations"], 1) |
| 210 | + |
| 211 | + # Cleanup |
| 212 | + watch_only.unloadwallet() |
| 213 | + offline_signer.unloadwallet() |
| 214 | + |
| 215 | + # Reconnect |
| 216 | + self.connect_nodes(1, 0) |
| 217 | + self.connect_nodes(0, 2) |
| 218 | + |
152 | 219 | def test_input_confs_control(self): |
153 | 220 | self.nodes[0].createwallet("minconf") |
154 | 221 | wallet = self.nodes[0].get_wallet_rpc("minconf") |
@@ -859,6 +926,7 @@ def run_test(self): |
859 | 926 |
|
860 | 927 | self.test_utxo_conversion() |
861 | 928 | self.test_psbt_incomplete_after_invalid_modification() |
| 929 | + self.test_offline_gap_limit() |
862 | 930 |
|
863 | 931 | self.test_input_confs_control() |
864 | 932 |
|
|
0 commit comments