Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2019-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Test the importdescriptors RPC.7Test importdescriptors by generating keys on node0, importing the corresponding8descriptors on node1 and then testing the address info for the different address9variants.11- `get_generate_key()` is called to generate keys and return the privkeys,12pubkeys and all variants of scriptPubKey and address.13- `test_importdesc()` is called to send an importdescriptors call to node1, test14success, and (if unsuccessful) test the error code and error message returned.15- `test_address()` is called to call getaddressinfo for an address on node116and test the values returned."""18import concurrent.futures19import threading20import time22from test_framework.address import key_to_p2sh_p2wpkh, key_to_p2wpkh, script_to_p2wsh23from test_framework.blocktools import COINBASE_MATURITY24from test_framework.test_framework import BitcoinTestFramework25from test_framework.descriptors import descsum_create26from test_framework.extendedkey import ExtendedPrivateKey27from test_framework.script import SEQUENCE_LOCKTIME_TYPE_FLAG28from test_framework.script_util import keys_to_multisig_script29from test_framework.util import (30assert_equal,31assert_raises_rpc_error,32JSONRPCException,33)34from test_framework.wallet_util import (35get_generate_key,36test_address,37)39MISSING_KEYS_WARNING = "Not all private keys provided. Some wallet functionality may return unexpected errors"41class ImportDescriptorsTest(BitcoinTestFramework):42def set_test_params(self):43self.num_nodes = 244# whitelist peers to speed up tx relay / mempool sync45self.noban_tx_relay = True46self.extra_args = [["-addresstype=legacy"],47["-addresstype=bech32", "-keypool=5"]48]49self.setup_clean_chain = True50self.wallet_names = []52def skip_test_if_missing_module(self):53self.skip_if_no_wallet()55def test_importdesc(self, req, success, global_error=False, error_code=None, error_message=None, warnings=None, wallet=None):56"""Run importdescriptors and assert success"""57if warnings is None:58warnings = []59wrpc = self.nodes[1].get_wallet_rpc('w1')60if wallet is not None:61wrpc = wallet63if global_error and not success:64try:65result = wrpc.importdescriptors([req])66except JSONRPCException as e:67assert_equal(e.error["code"], error_code)68assert_equal(e.error["message"], error_message)69return71result = wrpc.importdescriptors([req])72observed_warnings = []73if 'warnings' in result[0]:74observed_warnings = result[0]['warnings']75assert_equal("\n".join(sorted(warnings)), "\n".join(sorted(observed_warnings)))76self.log.debug(result)77assert_equal(result[0]['success'], success)78if error_code is not None:79assert_equal(result[0]['error']['code'], error_code)80assert_equal(result[0]['error']['message'], error_message)82def test_import_unused_key(self):83self.log.info("Test import of unused(KEY)")84self.nodes[0].createwallet(wallet_name="import_unused", blank=True)85wallet = self.nodes[0].get_wallet_rpc("import_unused")87assert_equal(len(wallet.gethdkeys()), 0)89extended_key = ExtendedPrivateKey.generate()90xprv = extended_key.to_string()91xpub = extended_key.pubkey().to_string()92self.test_importdesc({"desc":descsum_create(f"unused({xpub})"),93"timestamp": "now"},94success=False,95error_code=-4,96error_message='Cannot import descriptor without private keys to a wallet with private keys enabled',97wallet=wallet)98self.test_importdesc({"timestamp": "now", "desc": descsum_create(f"unused({xprv})")},99success=True,100wallet=wallet)101hdkeys = wallet.gethdkeys()102assert_equal(len(hdkeys), 1)103assert_equal(hdkeys[0]["xpub"], xpub)104wallet.unloadwallet()106def test_import_unused_key_existing(self):107self.log.info("Test import of unused(KEY) with existing KEY")108self.nodes[0].createwallet(wallet_name="import_existing_unused")109wallet = self.nodes[0].get_wallet_rpc("import_existing_unused")111hdkeys = wallet.gethdkeys(private=True)112assert_equal(len(hdkeys), 1)113xprv = hdkeys[0]["xprv"]115self.test_importdesc({"timestamp": "now", "desc": descsum_create(f"unused({xprv})")},116success=False,117error_code=-4,118error_message="Cannot import an unused() descriptor when its private key is already in the wallet",119wallet=wallet)120wallet.unloadwallet()122def test_import_unused_noprivs(self):123self.log.info("Test import of unused(KEY) to wallet without privkeys")124self.nodes[0].createwallet(wallet_name="import_unused_noprivs", disable_private_keys=True)125wallet = self.nodes[0].get_wallet_rpc("import_unused_noprivs")127xpub = ExtendedPrivateKey.generate().pubkey().to_string()128self.test_importdesc({"timestamp": "now", "desc": descsum_create(f"unused({xpub})")},129success=False,130error_code=-4,131error_message="Cannot import unused() to wallet without private keys enabled",132wallet=wallet)133wallet.unloadwallet()135def test_per_item_errors_are_reported_in_order(self):136self.log.info("Test that import results are in the same order as the original request")137self.nodes[0].createwallet(wallet_name="test_order_import", blank=True)138wallet = self.nodes[0].get_wallet_rpc('test_order_import')139whitespace_pubkey = f" {get_generate_key().pubkey}"140cases = [141({142"timestamp": "now"143}, [False, "Descriptor not found."]),144({145"desc": descsum_create(f"pkh({get_generate_key().privkey})"),146"timestamp": 1,147"label": "Valid descriptor 1",148}, [True]),149({150"desc": descsum_create(f"pkh({get_generate_key().privkey})"),151"timestamp": "now",152"internal": True,153}, [True]),154({155"desc": descsum_create(f"pkh({get_generate_key().pubkey})"),156"timestamp": "now",157"label": "Invalid descriptor 2",158"internal": True,159}, [False, "Internal addresses should not have a label"]),160({161"desc": descsum_create(f"pkh({whitespace_pubkey})"),162"timestamp": "now",163"internal": True,164}, [False, f"pkh(): Key '{whitespace_pubkey}' is invalid due to whitespace"]),165]167descriptors, expected = map(list, zip(*cases))168results = wallet.importdescriptors(descriptors)169for i, result in enumerate(results):170assert_equal(result["success"], expected[i][0])171if not result["success"]:172assert_equal(result["error"]["message"], expected[i][1])174def test_rescan_fails_import(self):175xpriv = ExtendedPrivateKey.generate().to_string()177self.log.info("Test importdescriptors fails when wallet is already rescanning")178wallet_name = "rescan_wallet"179self.nodes[0].createwallet(wallet_name=wallet_name, blank=True)180other_desc = descsum_create("pkh(" + get_generate_key().privkey + ")")182w_import = self.nodes[0].create_new_rpc_connection(mode="AUTHPROXY") / f"wallet/{wallet_name}"184with concurrent.futures.ThreadPoolExecutor(max_workers=2) as thread:185w_rescan = self.nodes[0].create_new_rpc_connection(mode="AUTHPROXY") / f"wallet/{wallet_name}"186w_conflict = self.nodes[0].create_new_rpc_connection(mode="AUTHPROXY") / f"wallet/{wallet_name}"187# Use an xprv with timestamp=0 and a large key-range to trigger a slow full rescan that stays in-flight188slow_desc = [{"desc": descsum_create("pkh(" + xpriv + "/0h/*h)"),189"timestamp": 0, "range": [0, 10000]}]190conflicting_desc = [{"desc": descsum_create("pkh(" + xpriv + "/1h/*h)"),191"timestamp": 0, "range": [0, 10000]}]192num_relevant_blocks = 1000193self.generatetoaddress(self.nodes[0], num_relevant_blocks, self.nodes[0].deriveaddresses(slow_desc[0]['desc'], [0, 0])[0])194self.generatetoaddress(self.nodes[0], num_relevant_blocks, self.nodes[0].deriveaddresses(conflicting_desc[0]['desc'], [0, 0])[0])196start = threading.Barrier(3)198def import_after_barrier(wallet, descriptors):199start.wait(timeout=10)200return wallet.importdescriptors(descriptors)202imports = [203thread.submit(import_after_barrier, w_rescan, slow_desc),204thread.submit(import_after_barrier, w_conflict, conflicting_desc),205]206start.wait(timeout=10)208# One importdescriptor call must hold WalletRescanReserver while the other fails immediately.209num_errors = 0210num_success = 0211for future in concurrent.futures.as_completed(imports, timeout=30 * self.options.timeout_factor):212try:213assert_equal(future.result(), [{'success': True}])214num_success += 1215except JSONRPCException as e:216assert_equal(e.error["code"], -4)217assert_equal(e.error["message"], "Wallet is currently rescanning. Abort existing rescan or wait.")218num_errors += 1220assert_equal(num_success, 1)221assert_equal(num_errors, 1)223# After the rescan finishes, any importdescriptors should succeed.224result = w_import.importdescriptors([{"desc": other_desc, "timestamp": "now"}])225assert_equal(result[0]['success'], True)227self.log.info("Aborting an importdescriptors rescan should fail the RPC call")228wallet_name = "abort_import_wallet"229self.nodes[0].createwallet(wallet_name, blank=True)231with concurrent.futures.ThreadPoolExecutor(max_workers=1) as thread:232w_import = self.nodes[0].create_new_rpc_connection(mode="AUTHPROXY") / f"wallet/{wallet_name}"233abort_rpc = self.nodes[0].create_new_rpc_connection(mode="AUTHPROXY") / f"wallet/{wallet_name}"234descriptor = [{"desc": descsum_create("pkh(" + xpriv + "/2h/*h)"),235"timestamp": 0, "range": [0, 4000]}]237importing = thread.submit(w_import.importdescriptors, descriptor)239# Keep trying because an abort before ScanForWalletTransactions starts240# is reset when the scan loop begins.241abort_succeeded = False242abort_deadline = time.time() + 30 * self.options.timeout_factor243while not importing.done() and time.time() < abort_deadline:244abort_succeeded = abort_rpc.abortrescan() or abort_succeeded246assert_equal(abort_succeeded, True)247try:248importing.result(timeout=30 * self.options.timeout_factor)249raise AssertionError("importdescriptors unexpectedly succeeded")250except JSONRPCException as e:
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.