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.
252# Downgrade: load on the old release, then force it to rewrite the tx253# record by disconnecting the block that confirmed the canonical tx.254old_dir = node_old.wallets_path / wallet_name255shutil.copytree(node_master.wallets_path / wallet_name, old_dir)256node_old.loadwallet(wallet_name)257old_wallet = node_old.get_wallet_rpc(wallet_name)258assert_equal(old_wallet.gettransaction(txid)["hex"], key_path_tx)259old_wallet.invalidateblock(block_hash)260old_wallet.syncwithvalidationinterfacequeue()261old_wallet.unloadwallet()263# Re-open on master: the alternate must still be there264self.cleanup_folder(node_master.wallets_path / wallet_name)265shutil.copytree(old_dir, node_master.wallets_path / wallet_name)266node_master.loadwallet(wallet_name)267wallet = node_master.get_wallet_rpc(wallet_name)268assert_equal(wallet.gettransaction(txid)["alternate_wtxids"], [script_path_wtxid])269wallet.unloadwallet()272def run_test(self):273node_miner = self.nodes[0]274node_master = self.nodes[1]275node_v21 = self.nodes[self.num_nodes - 2]276node_v20 = self.nodes[self.num_nodes - 1] # bdb only278previous_nodes = self.nodes[2:] # All previous version nodes279legacy_nodes = self.nodes[-6:] # Nodes that support legacy wallets280descriptors_nodes = self.nodes[2:-1] # Nodes that support descriptor wallets282self.generatetoaddress(node_miner, COINBASE_MATURITY + 1, node_miner.getnewaddress())284# Sanity check the test framework:285assert_equal(node_v20.getblockchaininfo()["blocks"], COINBASE_MATURITY + 1)287self.log.info("Test wallet backwards compatibility...")288# Create a number of wallets and open them in older versions:290# w1: regular wallet, created on master: update this test when default291# wallets can no longer be opened by older versions.292node_master.createwallet(wallet_name="w1")293wallet = node_master.get_wallet_rpc("w1")294info = wallet.getwalletinfo()295assert info['private_keys_enabled']296assert info['keypoolsize'] > 0297# Create a confirmed transaction, receiving coins298address = wallet.getnewaddress()299node_miner.sendtoaddress(address, 10)300self.sync_mempools()301self.generate(node_miner, 1)302# Create a conflicting transaction using RBF303return_address = node_miner.getnewaddress()304tx1_id = node_master.sendtoaddress(return_address, 1)305tx2_id = node_master.bumpfee(tx1_id)["txid"]306# Confirm the transaction307self.sync_mempools()308self.generate(node_miner, 1)309# Create another conflicting transaction using RBF310tx3_id = node_master.sendtoaddress(return_address, 1)311tx4_id = node_master.bumpfee(tx3_id)["txid"]312self.sync_mempools()313# Abandon transaction, but don't confirm314node_master.abandontransaction(tx3_id)316# w2: wallet with private keys disabled, created on master: update this317# test when default wallets private keys disabled can no longer be318# opened by older versions.319node_master.createwallet(wallet_name="w2", disable_private_keys=True)320wallet = node_master.get_wallet_rpc("w2")321info = wallet.getwalletinfo()322assert_equal(info['private_keys_enabled'], False)323assert_equal(info['keypoolsize'], 0)325# w3: blank wallet, created on master: update this326# test when default blank wallets can no longer be opened by older versions.327node_master.createwallet(wallet_name="w3", blank=True)328wallet = node_master.get_wallet_rpc("w3")329info = wallet.getwalletinfo()330assert info['private_keys_enabled']331assert_equal(info['keypoolsize'], 0)333node_master.createwallet(wallet_name="miniscript")334wallet = node_master.get_wallet_rpc("miniscript")335miniscript_desc = "wsh(or_b(pk([deadbeef/0h/1h/2h]tprv8ZgxMBicQKsPerQj6m35no46amfKQdjY7AhLnmatHYXs8S4MTgeZYkWAn4edSGwwL3vkSiiGqSZQrmy5D3P5gBoqgvYP2fCUpBwbKTMTAkL/3h/*),s:pk([beefdead/4h/5h]tpubD6NzVbkrYhZ4YU9vM1s53UhD75UyJatx8EMzMZ3VUjR2FciNfLLkAw6a4pWACChzobTseNqdWk4G7ZdBqRDLtLSACKykTScmqibb1ZrCvJu/6/7/*)))"336miniscript_apos = miniscript_desc.replace("[beefdead/4h/5h]", "[beefdead/4'/5']")337assert miniscript_apos != miniscript_desc338for desc in [miniscript_desc, miniscript_apos]:339res = wallet.importdescriptors([{"desc": descsum_create(desc), "timestamp":"now"}])340assert_equal(res[0]["success"], True)342# Unload wallets and copy to older nodes:343node_master_wallets_dir = node_master.wallets_path344node_master.unloadwallet("w1")345node_master.unloadwallet("w2")346node_master.unloadwallet("w3")347node_master.unloadwallet("miniscript")349for node in previous_nodes:350# Copy wallets to previous version351for wallet in os.listdir(node_master_wallets_dir):352dest = node.wallets_path / wallet353source = node_master_wallets_dir / wallet354shutil.copytree(source, dest)356self.log.info("Test that a wallet made on master can be opened on:")357# This test only works on the nodes that support descriptor wallets358# since we can no longer create legacy wallets.359for node in descriptors_nodes:360self.log.info(f"- {node.version}")361for wallet_name in ["w1", "w2", "w3", "miniscript"]:362if self.major_version_less_than(node, 22) and wallet_name == "w1":363# Descriptor wallets created after 0.21 have taproot descriptors which 0.21 does not support, tested below364continue365if self.major_version_less_than(node, 24) and wallet_name == "miniscript":366# Miniscript was introduced in 24.0367continue368# Also try to reopen on master after opening on old369for n in [node, node_master]:370# 31.0 and 31.1 have a descriptor id calculation incompatibility.371# Miniscript descriptors imported into node versions other than 31.0 will372# result in wallets that cannot be loaded into 31.0 and 31.1.373# These wallets will emit a "Wallet corrupted" error.374if wallet_name == "miniscript" and n.version in (310000, 310100):375assert_raises_rpc_error(-4, "Wallet corrupted", n.loadwallet, wallet_name)376continue378n.loadwallet(wallet_name)379wallet = n.get_wallet_rpc(wallet_name)380info = wallet.getwalletinfo()381if wallet_name == "w1":382assert_equal(info['private_keys_enabled'], True)383assert info['keypoolsize'] > 0384txs = wallet.listtransactions()385assert_equal(len(txs), 5)386assert_equal(txs[1]["txid"], tx1_id)387assert_equal(txs[2]["walletconflicts"], [tx1_id])388assert_equal(txs[1]["replaced_by_txid"], tx2_id)389assert not txs[1]["abandoned"]390assert_equal(txs[1]["confirmations"], -1)391assert_equal(txs[2]["blockindex"], 1)392assert txs[3]["abandoned"]393assert_equal(txs[4]["walletconflicts"], [tx3_id])394assert_equal(txs[3]["replaced_by_txid"], tx4_id)395assert not hasattr(txs[3], "blockindex")396elif wallet_name == "w2":397assert_equal(info['private_keys_enabled'], False)398assert_equal(info['keypoolsize'], 0)399elif wallet_name == "w3":400assert_equal(info['private_keys_enabled'], True)401assert_equal(info['keypoolsize'], 0)402elif wallet_name == "miniscript":403descs = wallet.listdescriptors()["descriptors"]404assert any(desc["desc"].startswith("wsh(or_b(pk") for desc in descs), "Miniscript descriptor missing"406# Copy back to master407wallet.unloadwallet()408if n == node:409(node_master.wallets_path / wallet_name / "wallet.dat").unlink()410(node_master.wallets_path / wallet_name).rmdir()411shutil.copytree(412n.wallets_path / wallet_name,413node_master.wallets_path / wallet_name,414)416# Check that descriptor wallets don't work on legacy only nodes417self.log.info("Test descriptor wallet incompatibility on v0.20")418# Descriptor wallets appear to be corrupted wallets to old software419assert self.major_version_equals(node_v20, 20)420for wallet_name in ["w1", "w2", "w3"]:421assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v20.loadwallet, wallet_name)423# w1 cannot be opened by 0.21 since it contains a taproot descriptor424self.log.info("Test that 0.21 cannot open wallet containing tr() descriptors")425assert_raises_rpc_error(-1, "map::at", node_v21.loadwallet, "w1")427self.log.info("Test that a wallet can upgrade to and downgrade from master, from:")428for node in descriptors_nodes:429self.log.info(f"- {node.version}")430wallet_name = f"up_{node.version}"431node.createwallet(wallet_name=wallet_name, descriptors=True)432wallet_prev = node.get_wallet_rpc(wallet_name)433address = wallet_prev.getnewaddress('', "bech32")434addr_info = wallet_prev.getaddressinfo(address)436hdkeypath = addr_info["hdkeypath"].replace("'", "h")437pubkey = addr_info["pubkey"]439if self.major_version_at_least(node, 24):440res = wallet_prev.importdescriptors([{"desc": descsum_create(miniscript_desc), "timestamp":"now"}])441assert_equal(res[0]["success"], True)443# Make a backup of the wallet file444backup_path = os.path.join(self.options.tmpdir, f"{wallet_name}.dat")445wallet_prev.backupwallet(backup_path)447# Remove the wallet from old node448wallet_prev.unloadwallet()450# Open backup with sqlite and get flags451def get_flags(conn):452flags_rec = conn.execute(f"SELECT value FROM main WHERE key = x'{ser_string(b'flags').hex()}'").fetchone()453return int.from_bytes(flags_rec[0], byteorder="little")455old_flags = self.inspect_sqlite_db(backup_path, get_flags)457# Restore the wallet to master458load_res = node_master.restorewallet(wallet_name, backup_path)459# There should be no warnings460assert "warnings" not in load_res462wallet = node_master.get_wallet_rpc(wallet_name)463info = wallet.getaddressinfo(address)464descriptor = f"wpkh([{info['hdmasterfingerprint']}{hdkeypath[1:]}]{pubkey})"465assert_equal(info["desc"], descsum_create(descriptor))467if self.major_version_at_least(node, 24):468descs = wallet.listdescriptors()["descriptors"]469assert any(desc["desc"].startswith("wsh(or_b(pk") for desc in descs), "Miniscript descriptor missing"471# Make backup so the wallet can be copied back to old node472down_wallet_name = f"re_down_{node.version}"473down_backup_path = os.path.join(self.options.tmpdir, f"{down_wallet_name}.dat")474wallet.backupwallet(down_backup_path)476# Check that taproot descriptors can be added to 0.21 wallets477# This must be done after the backup is created so that 0.21 can still load478# the backup479if self.major_version_equals(node, 21):480assert_raises_rpc_error(-12, "No bech32m addresses available", wallet.getnewaddress, address_type="bech32m")481xpubs = wallet.gethdkeys(active_only=True)482assert_equal(len(xpubs), 1)483assert_equal(len(xpubs[0]["descriptors"]), 6)484wallet.createwalletdescriptor("bech32m")485xpubs = wallet.gethdkeys(active_only=True)486assert_equal(len(xpubs), 1)487assert_equal(len(xpubs[0]["descriptors"]), 8)488tr_descs = [desc["desc"] for desc in xpubs[0]["descriptors"] if desc["desc"].startswith("tr(")]489assert_equal(len(tr_descs), 2)490for desc in tr_descs:491assert info["hdmasterfingerprint"] in desc492wallet.getnewaddress(address_type="bech32m")494wallet.unloadwallet()496# Open the wallet with sqlite and inspect the flags and records497def check_upgraded_records(conn, old_flags):498flags_rec = conn.execute(f"SELECT value FROM main WHERE key = x'{ser_string(b'flags').hex()}'").fetchone()499new_flags = int.from_bytes(flags_rec[0], byteorder="little")500diff_flags = new_flags & ~old_flags
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.