wallet_importdescriptors.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2019-present The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """Test the importdescriptors RPC.
6
7 Test importdescriptors by generating keys on node0, importing the corresponding
8 descriptors on node1 and then testing the address info for the different address
9 variants.
10
11 - `get_generate_key()` is called to generate keys and return the privkeys,
12 pubkeys and all variants of scriptPubKey and address.
13 - `test_importdesc()` is called to send an importdescriptors call to node1, test
14 success, and (if unsuccessful) test the error code and error message returned.
15 - `test_address()` is called to call getaddressinfo for an address on node1
16 and test the values returned."""
17
18 import concurrent.futures
19 import threading
20 import time
21
22 from test_framework.address import key_to_p2sh_p2wpkh, key_to_p2wpkh, script_to_p2wsh
23 from test_framework.blocktools import COINBASE_MATURITY
24 from test_framework.test_framework import BitcoinTestFramework
25 from test_framework.descriptors import descsum_create
26 from test_framework.extendedkey import ExtendedPrivateKey
27 from test_framework.script import SEQUENCE_LOCKTIME_TYPE_FLAG
28 from test_framework.script_util import keys_to_multisig_script
29 from test_framework.util import (
30 assert_equal,
31 assert_raises_rpc_error,
32 JSONRPCException,
33 )
34 from test_framework.wallet_util import (
35 get_generate_key,
36 test_address,
37 )
38
39 class ImportDescriptorsTest(BitcoinTestFramework):
40 def set_test_params(self):
41 self.num_nodes = 2
42 # whitelist peers to speed up tx relay / mempool sync
43 self.noban_tx_relay = True
44 self.extra_args = [["-addresstype=legacy"],
45 ["-addresstype=bech32", "-keypool=5"]
46 ]
47 self.setup_clean_chain = True
48 self.wallet_names = []
49
50 def skip_test_if_missing_module(self):
51 self.skip_if_no_wallet()
52
53 def test_importdesc(self, req, success, error_code=None, error_message=None, warnings=None, wallet=None):
54 """Run importdescriptors and assert success"""
55 if warnings is None:
56 warnings = []
57 wrpc = self.nodes[1].get_wallet_rpc('w1')
58 if wallet is not None:
59 wrpc = wallet
60
61 result = wrpc.importdescriptors([req])
62 observed_warnings = []
63 if 'warnings' in result[0]:
64 observed_warnings = result[0]['warnings']
65 assert_equal("\n".join(sorted(warnings)), "\n".join(sorted(observed_warnings)))
66 self.log.debug(result)
67 assert_equal(result[0]['success'], success)
68 if error_code is not None:
69 assert_equal(result[0]['error']['code'], error_code)
70 assert_equal(result[0]['error']['message'], error_message)
71
72 def test_import_unused_key(self):
73 self.log.info("Test import of unused(KEY)")
74 self.nodes[0].createwallet(wallet_name="import_unused", blank=True)
75 wallet = self.nodes[0].get_wallet_rpc("import_unused")
76
77 assert_equal(len(wallet.gethdkeys()), 0)
78
79 extended_key = ExtendedPrivateKey.generate()
80 xprv = extended_key.to_string()
81 xpub = extended_key.pubkey().to_string()
82 self.test_importdesc({"desc":descsum_create(f"unused({xpub})"),
83 "timestamp": "now"},
84 success=False,
85 error_code=-4,
86 error_message='Cannot import descriptor without private keys to a wallet with private keys enabled',
87 wallet=wallet)
88 self.test_importdesc({"timestamp": "now", "desc": descsum_create(f"unused({xprv})")},
89 success=True,
90 wallet=wallet)
91 hdkeys = wallet.gethdkeys()
92 assert_equal(len(hdkeys), 1)
93 assert_equal(hdkeys[0]["xpub"], xpub)
94 wallet.unloadwallet()
95
96 def test_import_unused_key_existing(self):
97 self.log.info("Test import of unused(KEY) with existing KEY")
98 self.nodes[0].createwallet(wallet_name="import_existing_unused")
99 wallet = self.nodes[0].get_wallet_rpc("import_existing_unused")
100
101 hdkeys = wallet.gethdkeys(private=True)
102 assert_equal(len(hdkeys), 1)
103 xprv = hdkeys[0]["xprv"]
104
105 self.test_importdesc({"timestamp": "now", "desc": descsum_create(f"unused({xprv})")},
106 success=False,
107 error_code=-4,
108 error_message="Cannot import an unused() descriptor when its private key is already in the wallet",
109 wallet=wallet)
110 wallet.unloadwallet()
111
112 def test_import_unused_noprivs(self):
113 self.log.info("Test import of unused(KEY) to wallet without privkeys")
114 self.nodes[0].createwallet(wallet_name="import_unused_noprivs", disable_private_keys=True)
115 wallet = self.nodes[0].get_wallet_rpc("import_unused_noprivs")
116
117 xpub = ExtendedPrivateKey.generate().pubkey().to_string()
118 self.test_importdesc({"timestamp": "now", "desc": descsum_create(f"unused({xpub})")},
119 success=False,
120 error_code=-4,
121 error_message="Cannot import unused() to wallet without private keys enabled",
122 wallet=wallet)
123 wallet.unloadwallet()
124
125 def test_rescan_fails_import(self):
126 xpriv = ExtendedPrivateKey.generate().to_string()
127
128 self.log.info("Test importdescriptors fails when wallet is already rescanning")
129 wallet_name = "rescan_wallet"
130 self.nodes[0].createwallet(wallet_name=wallet_name, blank=True)
131 other_desc = descsum_create("pkh(" + get_generate_key().privkey + ")")
132
133 w_import = self.nodes[0].create_new_rpc_connection(mode="AUTHPROXY") / f"wallet/{wallet_name}"
134
135 with concurrent.futures.ThreadPoolExecutor(max_workers=2) as thread:
136 w_rescan = self.nodes[0].create_new_rpc_connection(mode="AUTHPROXY") / f"wallet/{wallet_name}"
137 w_conflict = self.nodes[0].create_new_rpc_connection(mode="AUTHPROXY") / f"wallet/{wallet_name}"
138 # Use an xprv with timestamp=0 and a large key-range to trigger a slow full rescan that stays in-flight
139 slow_desc = [{"desc": descsum_create("pkh(" + xpriv + "/0h/*h)"),
140 "timestamp": 0, "range": [0, 10000]}]
141 conflicting_desc = [{"desc": descsum_create("pkh(" + xpriv + "/1h/*h)"),
142 "timestamp": 0, "range": [0, 10000]}]
143 num_relevant_blocks = 1000
144 self.generatetoaddress(self.nodes[0], num_relevant_blocks, self.nodes[0].deriveaddresses(slow_desc[0]['desc'], [0, 0])[0])
145 self.generatetoaddress(self.nodes[0], num_relevant_blocks, self.nodes[0].deriveaddresses(conflicting_desc[0]['desc'], [0, 0])[0])
146
147 start = threading.Barrier(3)
148
149 def import_after_barrier(wallet, descriptors):
150 start.wait(timeout=10)
151 return wallet.importdescriptors(descriptors)
152
153 imports = [
154 thread.submit(import_after_barrier, w_rescan, slow_desc),
155 thread.submit(import_after_barrier, w_conflict, conflicting_desc),
156 ]
157 start.wait(timeout=10)
158
159 # One importdescriptor call must hold WalletRescanReserver while the other fails immediately.
160 num_errors = 0
161 num_success = 0
162 for future in concurrent.futures.as_completed(imports, timeout=30 * self.options.timeout_factor):
163 try:
164 assert_equal(future.result(), [{'success': True}])
165 num_success += 1
166 except JSONRPCException as e:
167 assert_equal(e.error["code"], -4)
168 assert_equal(e.error["message"], "Wallet is currently rescanning. Abort existing rescan or wait.")
169 num_errors += 1
170
171 assert_equal(num_success, 1)
172 assert_equal(num_errors, 1)
173
174 # After the rescan finishes, any importdescriptors should succeed.
175 result = w_import.importdescriptors([{"desc": other_desc, "timestamp": "now"}])
176 assert_equal(result[0]['success'], True)
177
178 self.log.info("Aborting an importdescriptors rescan should fail the RPC call")
179 wallet_name = "abort_import_wallet"
180 self.nodes[0].createwallet(wallet_name, blank=True)
181
182 with concurrent.futures.ThreadPoolExecutor(max_workers=1) as thread:
183 w_import = self.nodes[0].create_new_rpc_connection(mode="AUTHPROXY") / f"wallet/{wallet_name}"
184 abort_rpc = self.nodes[0].create_new_rpc_connection(mode="AUTHPROXY") / f"wallet/{wallet_name}"
185 descriptor = [{"desc": descsum_create("pkh(" + xpriv + "/2h/*h)"),
186 "timestamp": 0, "range": [0, 4000]}]
187
188 importing = thread.submit(w_import.importdescriptors, descriptor)
189
190 # Keep trying because an abort before ScanForWalletTransactions starts
191 # is reset when the scan loop begins.
192 abort_succeeded = False
193 abort_deadline = time.time() + 30 * self.options.timeout_factor
194 while not importing.done() and time.time() < abort_deadline:
195 abort_succeeded = abort_rpc.abortrescan() or abort_succeeded
196
197 assert_equal(abort_succeeded, True)
198 try:
199 importing.result(timeout=30 * self.options.timeout_factor)
200 raise AssertionError("importdescriptors unexpectedly succeeded")
201 except JSONRPCException as e:
202 assert_equal(e.error["code"], -1)
203 assert_equal(e.error["message"], "Rescan aborted by user.")
204
205 def run_test(self):
206 self.log.info('Setting up wallets')
207 self.nodes[0].createwallet(wallet_name='w0', disable_private_keys=False)
208 w0 = self.nodes[0].get_wallet_rpc('w0')
209
210 self.nodes[1].createwallet(wallet_name='w1', disable_private_keys=True, blank=True)
211 w1 = self.nodes[1].get_wallet_rpc('w1')
212 assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
213
214 self.nodes[1].createwallet(wallet_name="wpriv", disable_private_keys=False, blank=True)
215 wpriv = self.nodes[1].get_wallet_rpc("wpriv")
216 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 0)
217
218 self.log.info('Mining coins')
219 self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 1, w0.getnewaddress())
220
221 # RPC importdescriptors -----------------------------------------------
222
223 # # Test import fails if no descriptor present
224 self.log.info("Import should fail if a descriptor is not provided")
225 self.test_importdesc({"timestamp": "now"},
226 success=False,
227 error_code=-8,
228 error_message='Descriptor not found.')
229
230 # # Test importing of a P2PKH descriptor
231 key = get_generate_key()
232 self.log.info("Should import a p2pkh descriptor")
233 import_request = {"desc": descsum_create("pkh(" + key.pubkey + ")"),
234 "timestamp": "now",
235 "label": "Descriptor import test"}
236 self.test_importdesc(import_request, success=True)
237 test_address(w1,
238 key.p2pkh_addr,
239 solvable=True,
240 ismine=True,
241 labels=["Descriptor import test"])
242 assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
243
244 self.log.info("Test can import same descriptor with public key twice")
245 self.test_importdesc(import_request, success=True)
246
247 self.log.info("Test can update descriptor label")
248 self.test_importdesc({**import_request, "label": "Updated label"}, success=True)
249 test_address(w1, key.p2pkh_addr, solvable=True, ismine=True, labels=["Updated label"])
250
251 self.log.info("Internal addresses cannot have labels")
252 self.test_importdesc({**import_request, "internal": True},
253 success=False,
254 error_code=-8,
255 error_message="Internal addresses should not have a label")
256
257 self.log.info("External non-ranged addresses can have labels")
258 self.test_importdesc({**import_request, "internal": False}, success=True)
259
260 self.log.info("Internal addresses should be detected as such")
261 key = get_generate_key()
262 self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + ")"),
263 "timestamp": "now",
264 "internal": True},
265 success=True)
266 info = w1.getaddressinfo(key.p2pkh_addr)
267 assert_equal(info["ismine"], True)
268 assert_equal(info["ischange"], True)
269
270 self.log.info("Should not import a descriptor with an invalid public key due to whitespace")
271 self.test_importdesc({"desc": descsum_create("pkh( " + key.pubkey + ")"),
272 "timestamp": "now",
273 "internal": True},
274 error_code=-5,
275 error_message=f"pkh(): Key ' {key.pubkey}' is invalid due to whitespace",
276 success=False)
277 self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + " )"),
278 "timestamp": "now",
279 "internal": True},
280 error_code=-5,
281 error_message=f"pkh(): Key '{key.pubkey} ' is invalid due to whitespace",
282 success=False)
283
284 # # Test importing of a P2SH-P2WPKH descriptor
285 key = get_generate_key()
286 self.log.info("Should not import a p2sh-p2wpkh descriptor without checksum")
287 self.test_importdesc({"desc": "sh(wpkh(" + key.pubkey + "))",
288 "timestamp": "now"
289 },
290 success=False,
291 error_code=-5,
292 error_message="Missing checksum")
293
294 self.log.info("Should not import a p2sh-p2wpkh descriptor that has range specified")
295 self.test_importdesc({"desc": descsum_create("sh(wpkh(" + key.pubkey + "))"),
296 "timestamp": "now",
297 "range": 1,
298 },
299 success=False,
300 error_code=-8,
301 error_message="Range should not be specified for an un-ranged descriptor")
302
303 self.log.info("Should not import a p2sh-p2wpkh descriptor and have it set to active")
304 self.test_importdesc({"desc": descsum_create("sh(wpkh(" + key.pubkey + "))"),
305 "timestamp": "now",
306 "active": True,
307 },
308 success=False,
309 error_code=-8,
310 error_message="Active descriptors must be ranged")
311
312 self.log.info("Should import a (non-active) p2sh-p2wpkh descriptor")
313 self.test_importdesc({"desc": descsum_create("sh(wpkh(" + key.pubkey + "))"),
314 "timestamp": "now",
315 "active": False,
316 },
317 success=True)
318 assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
319
320 test_address(w1,
321 key.p2sh_p2wpkh_addr,
322 ismine=True,
323 solvable=True)
324
325 # Check persistence of data and that loading works correctly
326 w1.unloadwallet()
327 self.nodes[1].loadwallet('w1')
328 test_address(w1,
329 key.p2sh_p2wpkh_addr,
330 ismine=True,
331 solvable=True)
332
333 # # Test importing of a multisig descriptor
334 key1 = get_generate_key()
335 key2 = get_generate_key()
336 self.log.info("Should import a 1-of-2 bare multisig from descriptor")
337 self.test_importdesc({"desc": descsum_create("multi(1," + key1.pubkey + "," + key2.pubkey + ")"),
338 "timestamp": "now"},
339 success=True)
340 self.log.info("Should not treat individual keys from the imported bare multisig as watchonly")
341 test_address(w1,
342 key1.p2pkh_addr,
343 ismine=False)
344
345 # # Test ranged descriptors
346 extended_key = ExtendedPrivateKey.generate()
347 xpriv = extended_key.to_string()
348 xpub = extended_key.pubkey().to_string()
349 pubkeys = [extended_key.derive_path(f"m/0'/0'/{i}'").pubkey().pubkey.get_bytes() for i in range(2)]
350 addresses = [key_to_p2sh_p2wpkh(pubkey) for pubkey in pubkeys] # hdkeypath=m/0'/0'/0' and 1'
351 addresses += [key_to_p2wpkh(pubkey) for pubkey in pubkeys] # wpkh subscripts corresponding to the above addresses
352 desc = "sh(wpkh(" + xpub + "/0/0/*" + "))"
353
354 self.log.info("Ranged descriptors cannot have labels")
355 self.test_importdesc({"desc":descsum_create(desc),
356 "timestamp": "now",
357 "range": [0, 100],
358 "label": "test"},
359 success=False,
360 error_code=-8,
361 error_message='Ranged descriptors should not have a label')
362
363 self.log.info("Ranged descriptors cannot have labels - even if range not provided by user and only implied by asterisk (*)")
364 self.test_importdesc({"desc":descsum_create("wpkh(" + xpub + "/100/0/*)"),
365 "timestamp": "now",
366 "label": "test",
367 "active": True},
368 success=False,
369 warnings=['Range not given, using default keypool range'],
370 error_code=-8,
371 error_message='Ranged descriptors should not have a label')
372
373 self.log.info("Private keys required for private keys enabled wallet")
374 self.test_importdesc({"desc":descsum_create(desc),
375 "timestamp": "now",
376 "range": [0, 100]},
377 success=False,
378 error_code=-4,
379 error_message='Cannot import descriptor without private keys to a wallet with private keys enabled',
380 wallet=wpriv)
381
382 self.log.info("Ranged descriptor import should warn without a specified range")
383 self.test_importdesc({"desc": descsum_create(desc),
384 "timestamp": "now"},
385 success=True,
386 warnings=['Range not given, using default keypool range'])
387 assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
388
389 # # Test importing of a ranged descriptor with xpriv
390 self.log.info("Should not import a ranged descriptor that includes xpriv into a watch-only wallet")
391 desc = "sh(wpkh(" + xpriv + "/0'/0'/*'" + "))"
392 self.test_importdesc({"desc": descsum_create(desc),
393 "timestamp": "now",
394 "range": 1},
395 success=False,
396 error_code=-4,
397 error_message='Cannot import private keys to a wallet with private keys disabled')
398
399 self.log.info("Should not import a descriptor with hardened derivations when private keys are disabled")
400 self.test_importdesc({"desc": descsum_create("wpkh(" + xpub + "/1h/*)"),
401 "timestamp": "now",
402 "range": 1},
403 success=False,
404 error_code=-4,
405 error_message='Cannot expand descriptor. Probably because of hardened derivations without private keys provided')
406
407 for address in addresses:
408 test_address(w1,
409 address,
410 ismine=False,
411 solvable=False)
412
413 self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": -1},
414 success=False, error_code=-8, error_message='End of range is too high')
415
416 self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [-1, 10]},
417 success=False, error_code=-8, error_message='Range should be greater or equal than 0')
418
419 self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [(2 << 31 + 1) - 1000000, (2 << 31 + 1)]},
420 success=False, error_code=-8, error_message='End of range is too high')
421
422 self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [2, 1]},
423 success=False, error_code=-8, error_message='Range specified as [begin,end] must not have begin after end')
424
425 self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]},
426 success=False, error_code=-8, error_message='Range is too large')
427
428 self.log.info("Verify we can only extend descriptor's range")
429 range_request = {"desc": descsum_create(desc), "timestamp": "now", "range": [5, 10], 'active': True}
430 self.test_importdesc(range_request, wallet=wpriv, success=True)
431 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 6)
432 self.test_importdesc({**range_request, "range": [0, 10]}, wallet=wpriv, success=True)
433 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 11)
434 self.test_importdesc({**range_request, "range": [0, 20]}, wallet=wpriv, success=True)
435 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21)
436 # Can keep range the same
437 self.test_importdesc({**range_request, "range": [0, 20]}, wallet=wpriv, success=True)
438 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21)
439
440 self.test_importdesc({**range_request, "range": [5, 10]}, wallet=wpriv, success=False,
441 error_code=-4, error_message=f"Could not add descriptor '{range_request['desc']}': new range must include current range = [0,20]")
442 self.test_importdesc({**range_request, "range": [0, 10]}, wallet=wpriv, success=False,
443 error_code=-4, error_message=f"Could not add descriptor '{range_request['desc']}': new range must include current range = [0,20]")
444 self.test_importdesc({**range_request, "range": [5, 20]}, wallet=wpriv, success=False,
445 error_code=-4, error_message=f"Could not add descriptor '{range_request['desc']}': new range must include current range = [0,20]")
446 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21)
447
448 self.log.info("Check we can change descriptor internal flag")
449 self.test_importdesc({**range_request, "range": [0, 20], "internal": True}, wallet=wpriv, success=True)
450 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 0)
451 assert_raises_rpc_error(-4, 'This wallet has no available keys', wpriv.getnewaddress, '', 'p2sh-segwit')
452 assert_equal(wpriv.getwalletinfo()['keypoolsize_hd_internal'], 21)
453 wpriv.getrawchangeaddress('p2sh-segwit')
454
455 self.test_importdesc({**range_request, "range": [0, 20], "internal": False}, wallet=wpriv, success=True)
456 assert_equal(wpriv.getwalletinfo()['keypoolsize'], 21)
457 wpriv.getnewaddress('', 'p2sh-segwit')
458 assert_equal(wpriv.getwalletinfo()['keypoolsize_hd_internal'], 0)
459 assert_raises_rpc_error(-4, 'This wallet has no available keys', wpriv.getrawchangeaddress, 'p2sh-segwit')
460
461 # Make sure ranged imports import keys in order
462 w1 = self.nodes[1].get_wallet_rpc('w1')
463 self.log.info('Key ranges should be imported in order')
464 xpub = "tpubDAXcJ7s7ZwicqjprRaEWdPoHKrCS215qxGYxpusRLLmJuT69ZSicuGdSfyvyKpvUNYBW1s2U3NSrT6vrCYB9e6nZUEvrqnwXPF8ArTCRXMY"
465 addresses = [
466 'bcrt1qtmp74ayg7p24uslctssvjm06q5phz4yrxucgnv', # m/0'/0'/0
467 'bcrt1q8vprchan07gzagd5e6v9wd7azyucksq2xc76k8', # m/0'/0'/1
468 'bcrt1qtuqdtha7zmqgcrr26n2rqxztv5y8rafjp9lulu', # m/0'/0'/2
469 'bcrt1qau64272ymawq26t90md6an0ps99qkrse58m640', # m/0'/0'/3
470 'bcrt1qsg97266hrh6cpmutqen8s4s962aryy77jp0fg0', # m/0'/0'/4
471 ]
472
473 self.test_importdesc({'desc': descsum_create('wpkh([80002067/0h/0h]' + xpub + '/*)'),
474 'active': True,
475 'range' : [0, 2],
476 'timestamp': 'now'
477 },
478 success=True)
479 self.test_importdesc({'desc': descsum_create('sh(wpkh([abcdef12/0h/0h]' + xpub + '/*))'),
480 'active': True,
481 'range' : [0, 2],
482 'timestamp': 'now'
483 },
484 success=True)
485 self.test_importdesc({'desc': descsum_create('pkh([12345678/0h/0h]' + xpub + '/*)'),
486 'active': True,
487 'range' : [0, 2],
488 'timestamp': 'now'
489 },
490 success=True)
491
492 assert_equal(w1.getwalletinfo()['keypoolsize'], 5 * 3)
493 for i, expected_addr in enumerate(addresses):
494 received_addr = w1.getnewaddress('', 'bech32')
495 assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'bech32')
496 assert_equal(received_addr, expected_addr)
497 bech32_addr_info = w1.getaddressinfo(received_addr)
498 assert_equal(bech32_addr_info['desc'][:23], 'wpkh([80002067/0h/0h/{}]'.format(i))
499
500 shwpkh_addr = w1.getnewaddress('', 'p2sh-segwit')
501 shwpkh_addr_info = w1.getaddressinfo(shwpkh_addr)
502 assert_equal(shwpkh_addr_info['desc'][:26], 'sh(wpkh([abcdef12/0h/0h/{}]'.format(i))
503
504 pkh_addr = w1.getnewaddress('', 'legacy')
505 pkh_addr_info = w1.getaddressinfo(pkh_addr)
506 assert_equal(pkh_addr_info['desc'][:22], 'pkh([12345678/0h/0h/{}]'.format(i))
507
508 assert_equal(w1.getwalletinfo()['keypoolsize'], 4 * 3) # After retrieving a key, we don't refill the keypool again, so it's one less for each address type
509 w1.keypoolrefill()
510 assert_equal(w1.getwalletinfo()['keypoolsize'], 5 * 3)
511
512 self.log.info("Check we can change next_index")
513 # go back and forth with next_index
514 for i in [4, 0, 2, 1, 3]:
515 self.test_importdesc({'desc': descsum_create('wpkh([80002067/0h/0h]' + xpub + '/*)'),
516 'active': True,
517 'range': [0, 9],
518 'next_index': i,
519 'timestamp': 'now'
520 },
521 success=True)
522 assert_equal(w1.getnewaddress('', 'bech32'), addresses[i])
523
524 # Check active=False default
525 self.log.info('Check imported descriptors are not active by default')
526 self.test_importdesc({'desc': descsum_create('pkh([12345678/1h]' + xpub + '/*)'),
527 'range' : [0, 2],
528 'timestamp': 'now',
529 'internal': True
530 },
531 success=True)
532 assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'legacy')
533
534 self.log.info('Check can activate inactive descriptor')
535 self.test_importdesc({'desc': descsum_create('pkh([12345678]' + xpub + '/*)'),
536 'range': [0, 5],
537 'active': True,
538 'timestamp': 'now',
539 'internal': True
540 },
541 success=True)
542 address = w1.getrawchangeaddress('legacy')
543 assert_equal(address, "mpA2Wh9dvZT7yfELq1UnrUmAoc5qCkMetg")
544
545 self.log.info('Check can deactivate active descriptor')
546 self.test_importdesc({'desc': descsum_create('pkh([12345678]' + xpub + '/*)'),
547 'range': [0, 5],
548 'active': False,
549 'timestamp': 'now',
550 'internal': True
551 },
552 success=True)
553 assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'legacy')
554
555 self.log.info('Verify activation state is persistent')
556 w1.unloadwallet()
557 self.nodes[1].loadwallet('w1')
558 assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'legacy')
559
560 # # Test importing a descriptor containing a WIF private key
561 wif_priv = "cTe1f5rdT8A8DFgVWTjyPwACsDPJM9ff4QngFxUixCSvvbg1x6sh"
562 address = "2MuhcG52uHPknxDgmGPsV18jSHFBnnRgjPg"
563 desc = "sh(wpkh(" + wif_priv + "))"
564 self.log.info("Should import a descriptor with a WIF private key as spendable")
565 self.test_importdesc({"desc": descsum_create(desc),
566 "timestamp": "now"},
567 success=True,
568 wallet=wpriv)
569
570 self.log.info('Test can import same descriptor with private key twice')
571 self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now"}, success=True, wallet=wpriv)
572
573 test_address(wpriv,
574 address,
575 solvable=True,
576 ismine=True)
577 txid = w0.sendtoaddress(address, 49.99995540)
578 self.generatetoaddress(self.nodes[0], 6, w0.getnewaddress())
579 tx = wpriv.createrawtransaction([{"txid": txid, "vout": 0}], {w0.getnewaddress(): 49.999})
580 signed_tx = wpriv.signrawtransactionwithwallet(tx)
581 w1.sendrawtransaction(signed_tx['hex'])
582
583 # Make sure that we can use import and use multisig as addresses
584 self.log.info('Test that multisigs can be imported, signed for, and getnewaddress\'d')
585 self.nodes[1].createwallet(wallet_name="wmulti_priv", disable_private_keys=False, blank=True)
586 wmulti_priv = self.nodes[1].get_wallet_rpc("wmulti_priv")
587 assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 0)
588
589 derivation_path = "84h/0h/0h"
590 change_derivation_path = "84h/1h/0h"
591 extended_key_1 = ExtendedPrivateKey.generate()
592 xprv1 = extended_key_1.to_string()
593 xprv1_fingerprint = extended_key_1._fingerprint().hex()
594 acc_xpub1_key = extended_key_1.derive_path(derivation_path).pubkey()
595 acc_xpub1 = acc_xpub1_key.to_string()
596 chg_xpub1_key = extended_key_1.derive_path(change_derivation_path).pubkey()
597 chg_xpub1 = chg_xpub1_key.to_string()
598
599 extended_key_2 = ExtendedPrivateKey.generate()
600 xprv2 = extended_key_2.to_string()
601 xprv2_fingerprint = extended_key_2._fingerprint().hex()
602 acc_xprv2 = extended_key_2.derive_path(derivation_path).to_string()
603 acc_xpub2_key = extended_key_2.derive_path(derivation_path).pubkey()
604 acc_xpub2 = acc_xpub2_key.to_string()
605 chg_xpub2_key = extended_key_2.derive_path(change_derivation_path).pubkey()
606 chg_xpub2 = chg_xpub2_key.to_string()
607
608 extended_key_3 = ExtendedPrivateKey.generate()
609 xprv3 = extended_key_3.to_string()
610 xprv3_fingerprint = extended_key_3._fingerprint().hex()
611 acc_xpub3_key = extended_key_3.derive_path(derivation_path).pubkey()
612 acc_xpub3 = acc_xpub3_key.to_string()
613 chg_xpub3_key = extended_key_3.derive_path(change_derivation_path).pubkey()
614 chg_xpub3 = chg_xpub3_key.to_string()
615
616 self.test_importdesc({"desc": descsum_create(f"wsh(multi(2,{xprv1}/{derivation_path}/*,{xprv2}/{derivation_path}/*,{xprv3}/{derivation_path}/*))"),
617 "active": True,
618 "range": 1000,
619 "next_index": 0,
620 "timestamp": "now"},
621 success=True,
622 wallet=wmulti_priv)
623 self.test_importdesc({"desc": descsum_create(f"wsh(multi(2,{xprv1}/{change_derivation_path}/*,{xprv2}/{change_derivation_path}/*,{xprv3}/{change_derivation_path}/*))"),
624 "active": True,
625 "internal" : True,
626 "range": 1000,
627 "next_index": 0,
628 "timestamp": "now"},
629 success=True,
630 wallet=wmulti_priv)
631
632 assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1001) # Range end (1000) is inclusive, so 1001 addresses generated
633
634 addr = wmulti_priv.getnewaddress('', 'bech32') # uses receive 0
635 expected_addr = script_to_p2wsh(keys_to_multisig_script([k.derive_path("m/0").pubkey.get_bytes() for k in [acc_xpub1_key, acc_xpub2_key, acc_xpub3_key]], k=2))
636 assert_equal(addr, expected_addr) # Derived at m/84'/0'/0'/0
637
638 change_addr = wmulti_priv.getrawchangeaddress('bech32') # uses change 0
639 expected_change_addr = script_to_p2wsh(keys_to_multisig_script([k.derive_path("m/0").pubkey.get_bytes() for k in [chg_xpub1_key, chg_xpub2_key, chg_xpub3_key]], k=2))
640 assert_equal(change_addr, expected_change_addr) # Derived at m/84'/1'/0'/0
641 assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1000)
642
643 txid = w0.sendtoaddress(addr, 10)
644 self.generate(self.nodes[0], 6)
645 send_txid = wmulti_priv.sendtoaddress(w0.getnewaddress(), 8) # uses change 1
646 decoded = wmulti_priv.gettransaction(txid=send_txid, verbose=True)['decoded']
647 assert_equal(len(decoded['vin'][0]['txinwitness']), 4)
648 self.sync_all()
649
650 self.nodes[1].createwallet(wallet_name="wmulti_pub", disable_private_keys=True, blank=True)
651 wmulti_pub = self.nodes[1].get_wallet_rpc("wmulti_pub")
652 assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 0)
653
654 self.test_importdesc({"desc": descsum_create(f"wsh(multi(2,[{xprv1_fingerprint}/{derivation_path}]{acc_xpub1}/*,[{xprv2_fingerprint}/{derivation_path}]{acc_xpub2}/*,[{xprv3_fingerprint}/{derivation_path}]{acc_xpub3}/*))"),
655 "active": True,
656 "range": 1000,
657 "next_index": 0,
658 "timestamp": "now"},
659 success=True,
660 wallet=wmulti_pub)
661 self.test_importdesc({"desc": descsum_create(f"wsh(multi(2,[{xprv1_fingerprint}/{change_derivation_path}]{chg_xpub1}/*,[{xprv2_fingerprint}/{change_derivation_path}]{chg_xpub2}/*,[{xprv3_fingerprint}/{change_derivation_path}]{chg_xpub3}/*))"),
662 "active": True,
663 "internal" : True,
664 "range": 1000,
665 "next_index": 0,
666 "timestamp": "now"},
667 success=True,
668 wallet=wmulti_pub)
669
670 assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 1000) # The first one was already consumed by previous import and is detected as used
671 addr = wmulti_pub.getnewaddress('', 'bech32') # uses receive 1
672 expected_addr = script_to_p2wsh(keys_to_multisig_script([k.derive_path("m/1").pubkey.get_bytes() for k in [acc_xpub1_key, acc_xpub2_key, acc_xpub3_key]], k=2))
673 assert_equal(addr, expected_addr) # Derived at m/84'/0'/0'/1
674 assert_equal(wmulti_pub.getaddressinfo(addr)["desc"].count(f"{derivation_path}/1"), 3) # Derived at m/84h/0h/0h/1 for all three keys
675 change_addr = wmulti_pub.getrawchangeaddress('bech32') # uses change 2
676 expected_change_addr = script_to_p2wsh(keys_to_multisig_script([k.derive_path("m/2").pubkey.get_bytes() for k in [chg_xpub1_key, chg_xpub2_key, chg_xpub3_key]], k=2))
677 assert_equal(change_addr, expected_change_addr) # Derived at m/84'/1'/0'/2
678 assert_equal(wmulti_pub.getaddressinfo(change_addr)["desc"].count(f"{change_derivation_path}/2"), 3) # Derived at m/84h/1h/0h/1 for all three keys
679 assert send_txid in self.nodes[0].getrawmempool(True)
680 assert send_txid in (x['txid'] for x in wmulti_pub.listunspent(0))
681 assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999)
682
683 # generate some utxos for next tests
684 utxo = self.create_outpoints(w0, outputs=[{addr: 10}])[0]
685
686 addr2 = wmulti_pub.getnewaddress('', 'bech32')
687 utxo2 = self.create_outpoints(w0, outputs=[{addr2: 10}])[0]
688
689 self.generate(self.nodes[0], 6)
690 assert_equal(wmulti_pub.getbalance(), wmulti_priv.getbalance())
691
692 # Make sure that descriptor wallets containing multiple xpubs in a single descriptor load correctly
693 wmulti_pub.unloadwallet()
694 self.nodes[1].loadwallet('wmulti_pub')
695
696 self.log.info("Multisig with distributed keys")
697 self.nodes[1].createwallet(wallet_name="wmulti_priv1")
698 wmulti_priv1 = self.nodes[1].get_wallet_rpc("wmulti_priv1")
699 res = wmulti_priv1.importdescriptors([
700 {
701 "desc": descsum_create(f"wsh(multi(2,{xprv1}/{derivation_path}/*,[{xprv2_fingerprint}/{derivation_path}]{acc_xpub2}/*,[{xprv3_fingerprint}/{derivation_path}]{acc_xpub3}/*))"),
702 "active": True,
703 "range": 1000,
704 "next_index": 0,
705 "timestamp": "now"
706 },
707 {
708 "desc": descsum_create(f"wsh(multi(2,{xprv1}/{change_derivation_path}/*,[{xprv2_fingerprint}/{change_derivation_path}]{chg_xpub2}/*,[{xprv3_fingerprint}/{change_derivation_path}]{chg_xpub3}/*))"),
709 "active": True,
710 "internal" : True,
711 "range": 1000,
712 "next_index": 0,
713 "timestamp": "now"
714 }])
715 assert_equal(res[0]['success'], True)
716 assert_equal(res[0]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors')
717 assert_equal(res[1]['success'], True)
718 assert_equal(res[1]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors')
719
720 self.nodes[1].createwallet(wallet_name='wmulti_priv2', blank=True)
721 wmulti_priv2 = self.nodes[1].get_wallet_rpc('wmulti_priv2')
722 res = wmulti_priv2.importdescriptors([
723 {
724 "desc": descsum_create(f"wsh(multi(2,[{xprv1_fingerprint}/{derivation_path}]{acc_xpub1}/*,{xprv2}/{derivation_path}/*,[{xprv3_fingerprint}/{derivation_path}]{acc_xpub3}/*))"),
725 "active": True,
726 "range": 1000,
727 "next_index": 0,
728 "timestamp": "now"
729 },
730 {
731 "desc": descsum_create(f"wsh(multi(2,[{xprv1_fingerprint}/{change_derivation_path}]{chg_xpub1}/*,{xprv2}/{change_derivation_path}/*,[{xprv3_fingerprint}/{change_derivation_path}]{chg_xpub3}/*))"),
732 "active": True,
733 "internal" : True,
734 "range": 1000,
735 "next_index": 0,
736 "timestamp": "now"
737 }])
738 assert_equal(res[0]['success'], True)
739 assert_equal(res[0]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors')
740 assert_equal(res[1]['success'], True)
741 assert_equal(res[1]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors')
742
743 rawtx = self.nodes[1].createrawtransaction([utxo], {w0.getnewaddress(): 9.999})
744 tx_signed_1 = wmulti_priv1.signrawtransactionwithwallet(rawtx)
745 assert_equal(tx_signed_1['complete'], False)
746 tx_signed_2 = wmulti_priv2.signrawtransactionwithwallet(tx_signed_1['hex'])
747 assert_equal(tx_signed_2['complete'], True)
748 self.nodes[1].sendrawtransaction(tx_signed_2['hex'])
749
750 self.log.info("We can create and use a huge multisig under P2WSH")
751 self.nodes[1].createwallet(wallet_name='wmulti_priv_big', blank=True)
752 wmulti_priv_big = self.nodes[1].get_wallet_rpc('wmulti_priv_big')
753 xprv = ExtendedPrivateKey.generate().to_string()
754 xkey = f"{xprv}/*"
755 xkey_int = f"{xprv}/1/*"
756 res = wmulti_priv_big.importdescriptors([
757 {
758 "desc": descsum_create(f"wsh(sortedmulti(20,{(xkey + ',') * 19}{xkey}))"),
759 "active": True,
760 "range": 1000,
761 "next_index": 0,
762 "timestamp": "now"
763 },
764 {
765 "desc": descsum_create(f"wsh(sortedmulti(20,{(xkey_int + ',') * 19}{xkey_int}))"),
766 "active": True,
767 "internal": True,
768 "range": 1000,
769 "next_index": 0,
770 "timestamp": "now"
771 }])
772 assert_equal(res[0]['success'], True)
773 assert_equal(res[1]['success'], True)
774
775 addr = wmulti_priv_big.getnewaddress()
776 w0.sendtoaddress(addr, 10)
777 self.generate(self.nodes[0], 1)
778 # It is standard and would relay.
779 txid = wmulti_priv_big.sendtoaddress(w0.getnewaddress(), 9.999)
780 decoded = wmulti_priv_big.gettransaction(txid=txid, verbose=True)['decoded']
781 # 20 sigs + dummy + witness script
782 assert_equal(len(decoded['vin'][0]['txinwitness']), 22)
783
784
785 self.log.info("Under P2SH, multisig are standard with up to 15 "
786 "compressed keys")
787 self.nodes[1].createwallet(wallet_name='multi_priv_big_legacy',
788 blank=True)
789 multi_priv_big = self.nodes[1].get_wallet_rpc('multi_priv_big_legacy')
790 res = multi_priv_big.importdescriptors([
791 {
792 "desc": descsum_create(f"sh(multi(15,{(xkey + ',') * 14}{xkey}))"),
793 "active": True,
794 "range": 1000,
795 "next_index": 0,
796 "timestamp": "now"
797 },
798 {
799 "desc": descsum_create(f"sh(multi(15,{(xkey_int + ',') * 14}{xkey_int}))"),
800 "active": True,
801 "internal": True,
802 "range": 1000,
803 "next_index": 0,
804 "timestamp": "now"
805 }])
806 assert_equal(res[0]['success'], True)
807 assert_equal(res[1]['success'], True)
808
809 addr = multi_priv_big.getnewaddress("", "legacy")
810 w0.sendtoaddress(addr, 10)
811 self.generate(self.nodes[0], 6)
812 # It is standard and would relay.
813 txid = multi_priv_big.sendtoaddress(w0.getnewaddress(), 10, "", "", True)
814 decoded = multi_priv_big.gettransaction(txid=txid, verbose=True)['decoded']
815
816 self.log.info("Amending multisig with new private keys")
817 self.nodes[1].createwallet(wallet_name="wmulti_priv3")
818 wmulti_priv3 = self.nodes[1].get_wallet_rpc("wmulti_priv3")
819 res = wmulti_priv3.importdescriptors([
820 {
821 "desc": descsum_create(f"wsh(multi(2,{xprv1}/{derivation_path}/*,[{xprv2_fingerprint}/{derivation_path}]{acc_xpub2}/*,[{xprv3_fingerprint}/{derivation_path}]{acc_xpub3}/*))"),
822 "active": True,
823 "range": 1000,
824 "next_index": 0,
825 "timestamp": "now"
826 }])
827 assert_equal(res[0]['success'], True)
828 res = wmulti_priv3.importdescriptors([
829 {
830 "desc": descsum_create(f"wsh(multi(2,{xprv1}/{derivation_path}/*,[{xprv2_fingerprint}/{derivation_path}]{acc_xprv2}/*,[{xprv3_fingerprint}/{derivation_path}]{acc_xpub3}/*))"),
831 "active": True,
832 "range": 1000,
833 "next_index": 0,
834 "timestamp": "now"
835 }])
836 assert_equal(res[0]['success'], True)
837
838 rawtx = self.nodes[1].createrawtransaction([utxo2], {w0.getnewaddress(): 9.999})
839 tx = wmulti_priv3.signrawtransactionwithwallet(rawtx)
840 assert_equal(tx['complete'], True)
841 self.nodes[1].sendrawtransaction(tx['hex'])
842
843 self.log.info("Combo descriptors cannot be active")
844 self.test_importdesc({"desc": descsum_create(f"combo({ExtendedPrivateKey.generate().pubkey().to_string()}/*)"),
845 "active": True,
846 "range": 1,
847 "timestamp": "now"},
848 success=False,
849 error_code=-4,
850 error_message="Combo descriptors cannot be set to active")
851
852 self.log.info("Descriptors with no type cannot be active")
853 self.test_importdesc({"desc": descsum_create(f"pk({ExtendedPrivateKey.generate().pubkey().to_string()}/*)"),
854 "active": True,
855 "range": 1,
856 "timestamp": "now"},
857 success=True,
858 warnings=["Unknown output type, cannot set descriptor to active."])
859
860 self.log.info("Test importing a descriptor to an encrypted wallet")
861
862 descriptor = {"desc": descsum_create("pkh(" + xpriv + "/1h/*h)"),
863 "timestamp": "now",
864 "active": True,
865 "range": [0,4000],
866 "next_index": 4000}
867
868 self.nodes[0].createwallet("temp_wallet", blank=True)
869 temp_wallet = self.nodes[0].get_wallet_rpc("temp_wallet")
870 temp_wallet.importdescriptors([descriptor])
871 self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 1, temp_wallet.getnewaddress())
872 self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 1, temp_wallet.getnewaddress())
873
874 self.nodes[0].createwallet("encrypted_wallet", blank=True, passphrase="passphrase")
875 encrypted_wallet = self.nodes[0].get_wallet_rpc("encrypted_wallet")
876
877 self.log.info("Wallet must be unlocked to import a descriptor")
878 assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.",
879 encrypted_wallet.importdescriptors, [descriptor])
880
881 descriptor["timestamp"] = 0
882 descriptor["next_index"] = 0
883
884 encrypted_wallet.walletpassphrase("passphrase", 99999)
885 with concurrent.futures.ThreadPoolExecutor(max_workers=1) as thread:
886 with self.nodes[0].assert_debug_log(expected_msgs=["Rescan started from block 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206... (slow variant inspecting all blocks)"], timeout=10):
887 importing = thread.submit(encrypted_wallet.importdescriptors, requests=[descriptor])
888
889 # Set the passphrase timeout to 1 to test that the wallet remains unlocked during the rescan
890 self.nodes[0].cli("-rpcwallet=encrypted_wallet").walletpassphrase("passphrase", 1)
891
892 try:
893 self.nodes[0].cli("-rpcwallet=encrypted_wallet").walletlock()
894 except JSONRPCException as e:
895 assert_equal(e.error["code"], -4)
896 assert "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before locking the wallet." in e.error["message"]
897
898 try:
899 self.nodes[0].cli("-rpcwallet=encrypted_wallet").walletpassphrasechange("passphrase", "newpassphrase")
900 except JSONRPCException as e:
901 assert_equal(e.error["code"], -4)
902 assert "Error: the wallet is currently being used to rescan the blockchain for related transactions. Please call `abortrescan` before changing the passphrase." in e.error["message"]
903
904 assert_equal(importing.result(), [{"success": True}])
905
906 assert_equal(temp_wallet.getbalance(), encrypted_wallet.getbalance())
907
908 self.log.info("Multipath descriptors")
909 self.nodes[1].createwallet(wallet_name="multipath", blank=True)
910 w_multipath = self.nodes[1].get_wallet_rpc("multipath")
911 self.nodes[1].createwallet(wallet_name="multipath_split", blank=True)
912 w_multisplit = self.nodes[1].get_wallet_rpc("multipath_split")
913 timestamp = int(time.time())
914
915 self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"),
916 "active": True,
917 "range": 10,
918 "timestamp": "now",
919 "label": "some label"},
920 success=False,
921 error_code=-8,
922 error_message="Multipath descriptors should not have a label",
923 wallet=w_multipath)
924 self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"),
925 "active": True,
926 "range": 10,
927 "timestamp": timestamp,
928 "internal": True},
929 success=False,
930 error_code=-5,
931 error_message="Cannot have multipath descriptor while also specifying \'internal\'",
932 wallet=w_multipath)
933
934 self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/<10;20>/0/*)"),
935 "active": True,
936 "range": 10,
937 "timestamp": timestamp},
938 success=True,
939 wallet=w_multipath)
940
941 self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/10/0/*)"),
942 "active": True,
943 "range": 10,
944 "timestamp": timestamp},
945 success=True,
946 wallet=w_multisplit)
947 self.test_importdesc({"desc": descsum_create(f"wpkh({xpriv}/20/0/*)"),
948 "active": True,
949 "range": 10,
950 "internal": True,
951 "timestamp": timestamp},
952 success=True,
953 wallet=w_multisplit)
954 for _ in range(0, 10):
955 assert_equal(w_multipath.getnewaddress(address_type="bech32"), w_multisplit.getnewaddress(address_type="bech32"))
956 assert_equal(w_multipath.getrawchangeaddress(address_type="bech32"), w_multisplit.getrawchangeaddress(address_type="bech32"))
957 assert_equal(sorted(w_multipath.listdescriptors()["descriptors"], key=lambda x: x["desc"]), sorted(w_multisplit.listdescriptors()["descriptors"], key=lambda x: x["desc"]))
958
959 self.log.info("Test older() safety")
960
961 for flag in [0, SEQUENCE_LOCKTIME_TYPE_FLAG]:
962 self.log.debug("Importing a safe value always works")
963 safe_value = (65535 | flag)
964 self.test_importdesc(
965 {
966 'desc': descsum_create(f"wsh(and_v(v:pk([12345678/0h/0h]{xpub}/*),older({safe_value})))"),
967 'active': True,
968 'range': [0, 2],
969 'timestamp': 'now'
970 },
971 success=True
972 )
973
974 self.log.debug("Importing an unsafe value results in a warning")
975 unsafe_value = safe_value + 1
976 desc = descsum_create(f"wsh(and_v(v:pk([12345678/0h/0h]{xpub}/*),older({unsafe_value})))")
977 expected_warning = (
978 f"time-based relative locktime: older({unsafe_value}) > (65535 * 512) seconds is unsafe"
979 if flag == SEQUENCE_LOCKTIME_TYPE_FLAG
980 else f"height-based relative locktime: older({unsafe_value}) > 65535 blocks is unsafe"
981 )
982 self.test_importdesc(
983 {
984 'desc': desc,
985 'active': True,
986 'range': [0, 2],
987 'timestamp': 'now'
988 },
989 success=True,
990 warnings=[expected_warning],
991 )
992
993
994 self.test_import_unused_key()
995 self.test_import_unused_key_existing()
996 self.test_import_unused_noprivs()
997 self.test_rescan_fails_import()
998
999 if __name__ == '__main__':
1000 ImportDescriptorsTest(__file__).main()
1001