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.
751migrate_res = self.master_node.migratewallet("plainfile")752assert_equal(f"plainfile_{mocked_time}.legacy.bak", os.path.basename(migrate_res["backup_path"]))753wallet = self.master_node.get_wallet_rpc("plainfile")754info = wallet.getwalletinfo()755assert_equal(info["descriptors"], True)756assert_equal(info["format"], "sqlite")758assert (self.master_node.wallets_path / "plainfile").is_dir()759assert (self.master_node.wallets_path / "plainfile" / "wallet.dat").is_file()761def test_addressbook(self):762df_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)764self.log.info("Test migration of address book data")765wallet = self.create_legacy_wallet("legacy_addrbook")766df_wallet.sendtoaddress(wallet.getnewaddress(), 3)768# Import watch-only script to create a watch-only wallet after migration769watch_addr = df_wallet.getnewaddress()770wallet.importaddress(watch_addr)771df_wallet.sendtoaddress(watch_addr, 2)773# Import solvable script774multi_addr1 = wallet.getnewaddress()775multi_addr2 = wallet.getnewaddress()776multi_addr3 = df_wallet.getnewaddress()777wallet.importpubkey(df_wallet.getaddressinfo(multi_addr3)["pubkey"])778ms_addr_info = wallet.addmultisigaddress(2, [multi_addr1, multi_addr2, multi_addr3])780self.generate(self.master_node, 1)782# Test vectors783addr_external = {784"addr": df_wallet.getnewaddress(),785"is_mine": False,786"is_change": False,787"label": ""788}789addr_external_with_label = {790"addr": df_wallet.getnewaddress(),791"is_mine": False,792"is_change": False,793"label": "external"794}795addr_internal = {796"addr": wallet.getnewaddress(),797"is_mine": True,798"is_change": False,799"label": ""800}801addr_internal_with_label = {802"addr": wallet.getnewaddress(),803"is_mine": True,804"is_change": False,805"label": "internal"806}807change_address = {808"addr": wallet.getrawchangeaddress(),809"is_mine": True,810"is_change": True,811"label": None812}813watch_only_addr = {814"addr": watch_addr,815"is_mine": False,816"is_change": False,817"label": "imported"818}819ms_addr = {820"addr": ms_addr_info['address'],821"is_mine": False,822"is_change": False,823"label": "multisig"824}826# To store the change address in the addressbook need to send coins to it827wallet.send(outputs=[{wallet.getnewaddress(): 2}], options={"change_address": change_address['addr']})828self.generate(self.master_node, 1)830# Util wrapper func for 'addr_info'831def check(info, node):832self.check_address(node, info['addr'], info['is_mine'], info['is_change'], info["label"])834# Pre-migration: set label and perform initial checks835for addr_info in [addr_external, addr_external_with_label, addr_internal, addr_internal_with_label, change_address, watch_only_addr, ms_addr]:836if not addr_info['is_change']:837wallet.setlabel(addr_info['addr'], addr_info["label"])838check(addr_info, wallet)840# Migrate wallet841info_migration, wallet = self.migrate_and_get_rpc("legacy_addrbook")842wallet_wo = self.master_node.get_wallet_rpc(info_migration["watchonly_name"])843wallet_solvables = self.master_node.get_wallet_rpc(info_migration["solvables_name"])845#########################846# Post migration checks #847#########################849# First check the main wallet850for addr_info in [addr_external, addr_external_with_label, addr_internal, addr_internal_with_label, change_address, ms_addr]:851check(addr_info, wallet)853# Watch-only wallet will contain the watch-only entry (with 'is_mine=True') and all external addresses ('send')854self.check_address(wallet_wo, watch_only_addr['addr'], is_mine=True, is_change=watch_only_addr['is_change'], label=watch_only_addr["label"])855for addr_info in [addr_external, addr_external_with_label, ms_addr]:856check(addr_info, wallet_wo)858# Solvables wallet will contain the multisig entry (with 'is_mine=True') and all external addresses ('send')859self.check_address(wallet_solvables, ms_addr['addr'], is_mine=True, is_change=ms_addr['is_change'], label=ms_addr["label"])860for addr_info in [addr_external, addr_external_with_label]:861check(addr_info, wallet_solvables)863########################################################################################864# Now restart migrated wallets and verify that the addressbook entries are still there #865########################################################################################867# First the main wallet868self.master_node.unloadwallet("legacy_addrbook")869self.master_node.loadwallet("legacy_addrbook")870for addr_info in [addr_external, addr_external_with_label, addr_internal, addr_internal_with_label, change_address, ms_addr]:871check(addr_info, wallet)873# Watch-only wallet874self.master_node.unloadwallet(info_migration["watchonly_name"])875self.master_node.loadwallet(info_migration["watchonly_name"])876self.check_address(wallet_wo, watch_only_addr['addr'], is_mine=True, is_change=watch_only_addr['is_change'], label=watch_only_addr["label"])877for addr_info in [addr_external, addr_external_with_label, ms_addr]:878check(addr_info, wallet_wo)880# Solvables wallet881self.master_node.unloadwallet(info_migration["solvables_name"])882self.master_node.loadwallet(info_migration["solvables_name"])883self.check_address(wallet_solvables, ms_addr['addr'], is_mine=True, is_change=ms_addr['is_change'], label=ms_addr["label"])884for addr_info in [addr_external, addr_external_with_label]:885check(addr_info, wallet_solvables)887def test_migrate_raw_p2sh(self):888self.log.info("Test migration of watch-only raw p2sh script")889df_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)890wallet = self.create_legacy_wallet("raw_p2sh")892def send_to_script(script, amount):893tx = CTransaction()894tx.vout.append(CTxOut(nValue=amount*COIN, scriptPubKey=script))896hex_tx = df_wallet.fundrawtransaction(tx.serialize().hex())['hex']897signed_tx = df_wallet.signrawtransactionwithwallet(hex_tx)898df_wallet.sendrawtransaction(signed_tx['hex'])899self.generate(self.master_node, 1)901# Craft sh(pkh(key)) script and send coins to it902pubkey = df_wallet.getaddressinfo(df_wallet.getnewaddress())["pubkey"]903script_pkh = key_to_p2pkh_script(pubkey)904script_sh_pkh = script_to_p2sh_script(script_pkh)905send_to_script(script=script_sh_pkh, amount=2)907# Import script and check balance908wallet.importaddress(address=script_pkh.hex(), label="raw_spk", rescan=True, p2sh=True)909assert_equal(wallet.getbalances()['watchonly']['trusted'], 2)911# Craft wsh(pkh(key)) and send coins to it912pubkey = df_wallet.getaddressinfo(df_wallet.getnewaddress())["pubkey"]913script_wsh_pkh = script_to_p2wsh_script(key_to_p2pkh_script(pubkey))914send_to_script(script=script_wsh_pkh, amount=3)916# Import script and check balance917wallet.importaddress(address=script_wsh_pkh.hex(), label="raw_spk2", rescan=True, p2sh=False)918assert_equal(wallet.getbalances()['watchonly']['trusted'], 5)920# Import sh(pkh()) script, by using importaddress(), with the p2sh flag enabled.921# This will wrap the script under another sh level, which is invalid!, and store it inside the wallet.922# The migration process must skip the invalid scripts and the addressbook records linked to them.923# They are not being watched by the current wallet, nor should be watched by the migrated one.924label_sh_pkh = "raw_sh_pkh"925script_pkh = key_to_p2pkh_script(df_wallet.getaddressinfo(df_wallet.getnewaddress())["pubkey"])926script_sh_pkh = script_to_p2sh_script(script_pkh)927addy_script_sh_pkh = script_to_p2sh(script_pkh) # valid script address928addy_script_double_sh_pkh = script_to_p2sh(script_sh_pkh) # invalid script address930# Note: 'importaddress()' will add two scripts, a valid one sh(pkh()) and an invalid one 'sh(sh(pkh()))'.931# Both of them will be stored with the same addressbook label. And only the latter one should932# be discarded during migration. The first one must be migrated.933wallet.importaddress(address=script_sh_pkh.hex(), label=label_sh_pkh, rescan=False, p2sh=True)935# Migrate wallet and re-check balance936info_migration, wallet = self.migrate_and_get_rpc("raw_p2sh")937wallet_wo = self.master_node.get_wallet_rpc(info_migration["watchonly_name"])939# Watch-only balance is under "mine".940assert_equal(wallet_wo.getbalances()['mine']['trusted'], 5)941# The watch-only scripts are no longer part of the main wallet942assert_equal(wallet.getbalances()['mine']['trusted'], 0)944# The invalid sh(sh(pk())) script label must not be part of the main wallet anymore945assert label_sh_pkh not in wallet.listlabels()946# But, the standard sh(pkh()) script should be part of the watch-only wallet.947addrs_by_label = wallet_wo.getaddressesbylabel(label_sh_pkh)948assert addy_script_sh_pkh in addrs_by_label949assert addy_script_double_sh_pkh not in addrs_by_label951# Also, the watch-only wallet should have the descriptor for the standard sh(pkh())952desc = descsum_create(f"addr({addy_script_sh_pkh})")953assert desc in [it['desc'] for it in wallet_wo.listdescriptors()['descriptors']]954# And doesn't have a descriptor for the invalid one955desc_invalid = descsum_create(f"addr({addy_script_double_sh_pkh})")956assert_equal(next((it['desc'] for it in wallet_wo.listdescriptors()['descriptors'] if it['desc'] == desc_invalid), None), None)958# Just in case, also verify wallet restart959self.master_node.unloadwallet(info_migration["watchonly_name"])960self.master_node.loadwallet(info_migration["watchonly_name"])961assert_equal(wallet_wo.getbalances()['mine']['trusted'], 5)963def test_conflict_txs(self):964self.log.info("Test migration when wallet contains conflicting transactions")965def_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)967wallet = self.create_legacy_wallet("conflicts")968def_wallet.sendtoaddress(wallet.getnewaddress(), 10)969self.generate(self.master_node, 1)971# parent tx972parent_txid = wallet.sendtoaddress(wallet.getnewaddress(), 9)973parent_txid_bytes = bytes.fromhex(parent_txid)[::-1]974conflict_utxo = wallet.gettransaction(txid=parent_txid, verbose=True)["decoded"]["vin"][0]976# The specific assertion in MarkConflicted being tested requires that the parent tx is already loaded977# by the time the child tx is loaded. Since transactions end up being loaded in txid order due to how978# sqlite stores things, we can just grind the child tx until it has a txid that is greater than the parent's.979locktime = 500000000 # Use locktime as nonce, starting at unix timestamp minimum980addr = wallet.getnewaddress()981while True:982child_send_res = wallet.send(outputs=[{addr: 8}], options={"add_to_wallet": False, "locktime": locktime})983child_txid = child_send_res["txid"]984child_txid_bytes = bytes.fromhex(child_txid)[::-1]985if (child_txid_bytes > parent_txid_bytes):986wallet.sendrawtransaction(child_send_res["hex"])987break988locktime += 1990# conflict with parent991conflict_unsigned = self.master_node.createrawtransaction(inputs=[conflict_utxo], outputs=[{wallet.getnewaddress(): 9.9999}])992conflict_signed = wallet.signrawtransactionwithwallet(conflict_unsigned)["hex"]993conflict_txid = self.master_node.sendrawtransaction(conflict_signed)994self.generate(self.master_node, 1)995assert_equal(wallet.gettransaction(txid=parent_txid)["confirmations"], -1)996assert_equal(wallet.gettransaction(txid=child_txid)["confirmations"], -1)997assert_equal(wallet.gettransaction(txid=conflict_txid)["confirmations"], 1)999_, wallet = self.migrate_and_get_rpc("conflicts")1000assert_equal(wallet.gettransaction(txid=parent_txid)["confirmations"], -1)
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.