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 descriptor wallet function."""7try:8import sqlite39except ImportError:10pass12import re14from test_framework.blocktools import COINBASE_MATURITY15from test_framework.descriptors import descsum_create16from test_framework.extendedkey import ExtendedPrivateKey17from test_framework.messages import ser_string18from test_framework.test_framework import BitcoinTestFramework19from test_framework.util import (20assert_not_equal,21assert_equal,22assert_raises_rpc_error23)24from test_framework.wallet_util import WalletUnlock27class WalletDescriptorTest(BitcoinTestFramework):28def set_test_params(self):29self.setup_clean_chain = True30self.num_nodes = 131self.extra_args = [['-keypool=100']]33def skip_test_if_missing_module(self):34self.skip_if_no_wallet()35self.skip_if_no_py_sqlite3()37def test_parent_descriptors(self):38self.log.info("Check that parent_descs is the same for all RPCs and is normalized")39self.nodes[0].createwallet(wallet_name="parent_descs")40wallet = self.nodes[0].get_wallet_rpc("parent_descs")41default_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)43addr = wallet.getnewaddress()44parent_desc = wallet.getaddressinfo(addr)["parent_desc"]46# Verify that the parent descriptor is normalized47# First remove the checksum48desc_verify = parent_desc.split("#")[0]49# Next extract the xpub50desc_verify = re.sub(r"tpub\w+?(?=/)", "", desc_verify)51# Extract origin info52origin_match = re.search(r'\[([\da-fh/]+)\]', desc_verify)53origin_part = origin_match.group(1) if origin_match else ""54# Split on "]" for everything after the origin info55after_origin = desc_verify.split("]", maxsplit=1)[-1]56# Look for the hardened markers “h” inside each piece57# We don't need to check for aspostrophe as normalization will not output aspostrophe58found_hardened_in_origin = "h" in origin_part59found_hardened_after_origin = "h" in after_origin60assert_equal(found_hardened_in_origin, True)61assert_equal(found_hardened_after_origin, False)63# Send some coins so we can check listunspent, listtransactions, listunspent, and gettransaction64since_block = self.nodes[0].getbestblockhash()65txid = default_wallet.sendtoaddress(addr, 1)66self.generate(self.nodes[0], 1)68unspent = wallet.listunspent()69assert_equal(len(unspent), 1)70assert_equal(unspent[0]["parent_descs"], [parent_desc])72txs = wallet.listtransactions()73assert_equal(len(txs), 1)74assert_equal(txs[0]["parent_descs"], [parent_desc])76txs = wallet.listsinceblock(since_block)["transactions"]77assert_equal(len(txs), 1)78assert_equal(txs[0]["parent_descs"], [parent_desc])80tx = wallet.gettransaction(txid=txid, verbose=True)81assert_equal(tx["details"][0]["parent_descs"], [parent_desc])83wallet.unloadwallet()85def run_test(self):86self.generate(self.nodes[0], COINBASE_MATURITY + 1)88# Make a descriptor wallet89self.log.info("Making a descriptor wallet")90self.nodes[0].createwallet(wallet_name="desc1")91wallet = self.nodes[0].get_wallet_rpc("desc1")93# A descriptor wallet should have 100 addresses * 4 types = 400 keys94self.log.info("Checking wallet info")95wallet_info = wallet.getwalletinfo()96assert_equal(wallet_info['format'], 'sqlite')97assert_equal(wallet_info['keypoolsize'], 400)98assert_equal(wallet_info['keypoolsize_hd_internal'], 400)99assert 'keypoololdest' not in wallet_info101# Check that getnewaddress works102self.log.info("Test that getnewaddress and getrawchangeaddress work")103addr = wallet.getnewaddress("", "legacy")104addr_info = wallet.getaddressinfo(addr)105assert addr_info['desc'].startswith('pkh(')106assert_equal(addr_info['hdkeypath'], 'm/44h/1h/0h/0/0')108addr = wallet.getnewaddress("", "p2sh-segwit")109addr_info = wallet.getaddressinfo(addr)110assert addr_info['desc'].startswith('sh(wpkh(')111assert_equal(addr_info['hdkeypath'], 'm/49h/1h/0h/0/0')113addr = wallet.getnewaddress("", "bech32")114addr_info = wallet.getaddressinfo(addr)115assert addr_info['desc'].startswith('wpkh(')116assert_equal(addr_info['hdkeypath'], 'm/84h/1h/0h/0/0')118addr = wallet.getnewaddress("", "bech32m")119addr_info = wallet.getaddressinfo(addr)120assert addr_info['desc'].startswith('tr(')121assert_equal(addr_info['hdkeypath'], 'm/86h/1h/0h/0/0')123# Check that getrawchangeaddress works124addr = wallet.getrawchangeaddress("legacy")125addr_info = wallet.getaddressinfo(addr)126assert addr_info['desc'].startswith('pkh(')127assert_equal(addr_info['hdkeypath'], 'm/44h/1h/0h/1/0')129addr = wallet.getrawchangeaddress("p2sh-segwit")130addr_info = wallet.getaddressinfo(addr)131assert addr_info['desc'].startswith('sh(wpkh(')132assert_equal(addr_info['hdkeypath'], 'm/49h/1h/0h/1/0')134addr = wallet.getrawchangeaddress("bech32")135addr_info = wallet.getaddressinfo(addr)136assert addr_info['desc'].startswith('wpkh(')137assert_equal(addr_info['hdkeypath'], 'm/84h/1h/0h/1/0')139addr = wallet.getrawchangeaddress("bech32m")140addr_info = wallet.getaddressinfo(addr)141assert addr_info['desc'].startswith('tr(')142assert_equal(addr_info['hdkeypath'], 'm/86h/1h/0h/1/0')144# Make a wallet to receive coins at145self.nodes[0].createwallet(wallet_name="desc2")146recv_wrpc = self.nodes[0].get_wallet_rpc("desc2")147send_wrpc = self.nodes[0].get_wallet_rpc("desc1")149# Generate some coins150self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 1, send_wrpc.getnewaddress())152# Make transactions153self.log.info("Test sending and receiving")154addr = recv_wrpc.getnewaddress()155send_wrpc.sendtoaddress(addr, 10)157self.log.info("Test encryption")158# Get the master fingerprint before encrypt159info1 = send_wrpc.getaddressinfo(send_wrpc.getnewaddress())161# Encrypt wallet 0162send_wrpc.encryptwallet('pass')163with WalletUnlock(send_wrpc, "pass"):164addr = send_wrpc.getnewaddress()165info2 = send_wrpc.getaddressinfo(addr)166assert_not_equal(info1['hdmasterfingerprint'], info2['hdmasterfingerprint'])167assert 'hdmasterfingerprint' in send_wrpc.getaddressinfo(send_wrpc.getnewaddress())168info3 = send_wrpc.getaddressinfo(addr)169assert_equal(info2['desc'], info3['desc'])171self.log.info("Test that getnewaddress still works after keypool is exhausted in an encrypted wallet")172for _ in range(500):173send_wrpc.getnewaddress()175self.log.info("Test that unlock is needed when deriving only hardened keys in an encrypted wallet")176with WalletUnlock(send_wrpc, "pass"):177send_wrpc.importdescriptors([{178"desc": descsum_create(f"wpkh({ExtendedPrivateKey.generate().to_string()}/0h/*h)"),179"timestamp": "now",180"range": [0,10],181"active": True182}])183# Exhaust keypool of 100184for _ in range(100):185send_wrpc.getnewaddress(address_type='bech32')186# This should now error187assert_raises_rpc_error(-12, "Keypool ran out, please call keypoolrefill first", send_wrpc.getnewaddress, '', 'bech32')189self.log.info("Test born encrypted wallets")190self.nodes[0].createwallet('desc_enc', False, False, 'pass', False, True)191enc_rpc = self.nodes[0].get_wallet_rpc('desc_enc')192enc_rpc.getnewaddress() # Makes sure that we can get a new address from a born encrypted wallet194self.log.info("Test blank descriptor wallets")195self.nodes[0].createwallet(wallet_name='desc_blank', blank=True)196blank_rpc = self.nodes[0].get_wallet_rpc('desc_blank')197assert_raises_rpc_error(-4, 'This wallet has no available keys', blank_rpc.getnewaddress)199self.log.info("Test descriptor wallet with disabled private keys")200self.nodes[0].createwallet(wallet_name='desc_no_priv', disable_private_keys=True)201nopriv_rpc = self.nodes[0].get_wallet_rpc('desc_no_priv')202assert_raises_rpc_error(-4, 'This wallet has no available keys', nopriv_rpc.getnewaddress)204self.log.info("Test descriptor exports")205self.nodes[0].createwallet(wallet_name='desc_export')206exp_rpc = self.nodes[0].get_wallet_rpc('desc_export')207self.nodes[0].createwallet(wallet_name='desc_import', disable_private_keys=True)208imp_rpc = self.nodes[0].get_wallet_rpc('desc_import')210addr_types = [('legacy', False, 'pkh(', '44h/1h/0h', -13),211('p2sh-segwit', False, 'sh(wpkh(', '49h/1h/0h', -14),212('bech32', False, 'wpkh(', '84h/1h/0h', -13),213('bech32m', False, 'tr(', '86h/1h/0h', -13),214('legacy', True, 'pkh(', '44h/1h/0h', -13),215('p2sh-segwit', True, 'sh(wpkh(', '49h/1h/0h', -14),216('bech32', True, 'wpkh(', '84h/1h/0h', -13),217('bech32m', True, 'tr(', '86h/1h/0h', -13)]219for addr_type, internal, desc_prefix, deriv_path, int_idx in addr_types:220int_str = 'internal' if internal else 'external'222self.log.info("Testing descriptor address type for {} {}".format(addr_type, int_str))223if internal:224addr = exp_rpc.getrawchangeaddress(address_type=addr_type)225else:226addr = exp_rpc.getnewaddress(address_type=addr_type)227desc = exp_rpc.getaddressinfo(addr)['parent_desc']228assert_equal(desc_prefix, desc[0:len(desc_prefix)])229idx = desc.index('/') + 1230assert_equal(deriv_path, desc[idx:idx + 9])231if internal:232assert_equal('1', desc[int_idx])233else:234assert_equal('0', desc[int_idx])236self.log.info("Testing the same descriptor is returned for address type {} {}".format(addr_type, int_str))237for i in range(0, 10):238if internal:239addr = exp_rpc.getrawchangeaddress(address_type=addr_type)240else:241addr = exp_rpc.getnewaddress(address_type=addr_type)242test_desc = exp_rpc.getaddressinfo(addr)['parent_desc']243assert_equal(desc, test_desc)245self.log.info("Testing import of exported {} descriptor".format(addr_type))246imp_rpc.importdescriptors([{247'desc': desc,248'active': True,249'next_index': 11,250'timestamp': 'now',
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.