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.
251self.assert_list_txs_equal(basic1.listtransactions(), listtransactions_compatibility(txs))253self.log.info("Test backup file can be successfully restored")254self.old_node.restorewallet("basic1_restored", basic1_migrate['backup_path'])255basic1_restored = self.old_node.get_wallet_rpc("basic1_restored")256basic1_restored_wi = basic1_restored.getwalletinfo()257assert_equal(basic1_restored_wi['balance'], bal)258assert_equal(basic1_restored.listaddressgroupings(), addr_gps)259self.assert_list_txs_equal(listtransactions_compatibility(basic1_restored.listtransactions()), txs)261# restart master node and verify that everything is still there262self.restart_node(0)263self.connect_nodes(0, 1)264default = self.master_node.get_wallet_rpc(self.default_wallet_name)265self.master_node.loadwallet("basic1")266basic1 = self.master_node.get_wallet_rpc("basic1")267assert_equal(basic1.getbalance(), bal)268self.assert_list_txs_equal(basic1.listtransactions(), txs)270self.log.info("Test migration of a wallet with balance received on the seed")271basic2 = self.create_legacy_wallet("basic2")272basic2_seed = get_generate_key()273basic2.sethdseed(True, basic2_seed.privkey)274assert_equal(basic2.getbalance(), 0)276# Receive coins on different output types for the same seed277basic2_balance = 0278for addr in [basic2_seed.p2pkh_addr, basic2_seed.p2wpkh_addr, basic2_seed.p2sh_p2wpkh_addr]:279send_value = random.randint(1, 4)280default.sendtoaddress(addr, send_value)281basic2_balance += send_value282self.generate(self.master_node, 1)283assert_equal(basic2.getbalance(), basic2_balance)284basic2_txs = basic2.listtransactions()286# Now migrate and test that we still have the same balance/transactions287_, basic2 = self.migrate_and_get_rpc("basic2")288assert_equal(basic2.getbalance(), basic2_balance)289self.assert_list_txs_equal(basic2.listtransactions(), listtransactions_compatibility(basic2_txs))291# Now test migration on a descriptor wallet292self.log.info("Test \"nothing to migrate\" when the user tries to migrate a loaded wallet with no legacy data")293assert_raises_rpc_error(-4, "Error: This wallet is already a descriptor wallet", basic2.migratewallet)295self.log.info("Test \"nothing to migrate\" when the user tries to migrate an unloaded wallet with no legacy data")296basic2.unloadwallet()297assert_raises_rpc_error(-4, "Error: This wallet is already a descriptor wallet", self.master_node.migratewallet, "basic2")299def test_multisig(self):300default = self.master_node.get_wallet_rpc(self.default_wallet_name)302# Contrived case where all the multisig keys are in a single wallet303self.log.info("Test migration of a wallet with all keys for a multisig")304multisig0 = self.create_legacy_wallet("multisig0")305addr1 = multisig0.getnewaddress()306addr2 = multisig0.getnewaddress()307addr3 = multisig0.getnewaddress()309ms_info = multisig0.addmultisigaddress(2, [addr1, addr2, addr3])311_, multisig0 = self.migrate_and_get_rpc("multisig0")312ms_addr_info = multisig0.getaddressinfo(ms_info["address"])313assert_equal(ms_addr_info["ismine"], True)314assert_equal(ms_addr_info["desc"], ms_info["descriptor"])315assert_equal("multisig0_watchonly" in self.master_node.listwallets(), False)316assert_equal("multisig0_solvables" in self.master_node.listwallets(), False)318pub1 = multisig0.getaddressinfo(addr1)["pubkey"]319pub2 = multisig0.getaddressinfo(addr2)["pubkey"]321# Some keys in multisig do not belong to this wallet322self.log.info("Test migration of a wallet that has some keys in a multisig")323multisig1 = self.create_legacy_wallet("multisig1")324ms_info = multisig1.addmultisigaddress(2, [multisig1.getnewaddress(), pub1, pub2])325ms_info2 = multisig1.addmultisigaddress(2, [multisig1.getnewaddress(), pub1, pub2])327addr1 = ms_info["address"]328addr2 = ms_info2["address"]329txid = default.sendtoaddress(addr1, 10)330multisig1.importaddress(addr1)331assert_equal(multisig1.getaddressinfo(addr1)["ismine"], False)332assert_equal(multisig1.getaddressinfo(addr1)["iswatchonly"], True)333assert_equal(multisig1.getaddressinfo(addr1)["solvable"], True)334self.generate(self.master_node, 1)335multisig1.gettransaction(txid)336assert_equal(multisig1.getbalances()["watchonly"]["trusted"], 10)337assert_equal(multisig1.getaddressinfo(addr2)["ismine"], False)338assert_equal(multisig1.getaddressinfo(addr2)["iswatchonly"], False)339assert_equal(multisig1.getaddressinfo(addr2)["solvable"], True)341# Migrating multisig1 should see the multisig is no longer part of multisig1342# A new wallet multisig1_watchonly is created which has the multisig address343# Transaction to multisig is in multisig1_watchonly and not multisig1344_, multisig1 = self.migrate_and_get_rpc("multisig1")345assert_equal(multisig1.getaddressinfo(addr1)["ismine"], False)346assert_equal(multisig1.getaddressinfo(addr1)["solvable"], False)347assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", multisig1.gettransaction, txid)348assert_equal(multisig1.getbalance(), 0)349assert_equal(multisig1.listtransactions(), [])351assert_equal("multisig1_watchonly" in self.master_node.listwallets(), True)352ms1_watchonly = self.master_node.get_wallet_rpc("multisig1_watchonly")353ms1_wallet_info = ms1_watchonly.getwalletinfo()354assert_equal(ms1_wallet_info['descriptors'], True)355assert_equal(ms1_wallet_info['private_keys_enabled'], False)356self.assert_is_sqlite("multisig1_watchonly")357assert_equal(ms1_watchonly.getaddressinfo(addr1)["ismine"], True)358assert_equal(ms1_watchonly.getaddressinfo(addr1)["solvable"], True)359# Because addr2 was not being watched, it isn't in multisig1_watchonly but rather multisig1_solvables360assert_equal(ms1_watchonly.getaddressinfo(addr2)["ismine"], False)361assert_equal(ms1_watchonly.getaddressinfo(addr2)["solvable"], False)362ms1_watchonly.gettransaction(txid)363assert_equal(ms1_watchonly.getbalance(), 10)365# Migrating multisig1 should see the second multisig is no longer part of multisig1366# A new wallet multisig1_solvables is created which has the second address367# This should have no transactions368assert_equal("multisig1_solvables" in self.master_node.listwallets(), True)369ms1_solvable = self.master_node.get_wallet_rpc("multisig1_solvables")370ms1_wallet_info = ms1_solvable.getwalletinfo()371assert_equal(ms1_wallet_info['descriptors'], True)372assert_equal(ms1_wallet_info['private_keys_enabled'], False)373self.assert_is_sqlite("multisig1_solvables")374assert_equal(ms1_solvable.getaddressinfo(addr1)["ismine"], False)375assert_equal(ms1_solvable.getaddressinfo(addr1)["solvable"], False)376assert_equal(ms1_solvable.getaddressinfo(addr2)["ismine"], True)377assert_equal(ms1_solvable.getaddressinfo(addr2)["solvable"], True)378assert_equal(ms1_solvable.getbalance(), 0)379assert_equal(ms1_solvable.listtransactions(), [])382def test_other_watchonly(self):383default = self.master_node.get_wallet_rpc(self.default_wallet_name)385# Wallet with an imported address. Should be the same thing as the multisig test386self.log.info("Test migration of a wallet with watchonly imports")387imports0 = self.create_legacy_wallet("imports0")389# External address label390imports0.setlabel(default.getnewaddress(), "external")392# Normal non-watchonly tx393received_addr = imports0.getnewaddress()394imports0.setlabel(received_addr, "Receiving")395received_txid = default.sendtoaddress(received_addr, 10)397# Watchonly tx398import_addr = default.getnewaddress()399imports0.importaddress(import_addr)400imports0.setlabel(import_addr, "imported")401received_watchonly_txid = default.sendtoaddress(import_addr, 10)403# Received watchonly tx that is then spent404import_sent_addr = default.getnewaddress()405imports0.importaddress(import_sent_addr)406received_sent_watchonly_utxo = self.create_outpoints(node=default, outputs=[{import_sent_addr: 10}])[0]408send = default.sendall(recipients=[default.getnewaddress()], inputs=[received_sent_watchonly_utxo])409sent_watchonly_txid = send["txid"]411# Tx that has both a watchonly and spendable output412watchonly_spendable_txid = default.send(outputs=[{received_addr: 1}, {import_addr:1}])["txid"]414self.generate(self.master_node, 2)415received_watchonly_tx_info = imports0.gettransaction(received_watchonly_txid, True)416received_sent_watchonly_tx_info = imports0.gettransaction(received_sent_watchonly_utxo["txid"], True)418balances = imports0.getbalances()419spendable_bal = balances["mine"]["trusted"]420watchonly_bal = balances["watchonly"]["trusted"]421assert_equal(len(imports0.listtransactions(include_watchonly=True)), 6)423# Mock time forward a bit so we can check that tx metadata is preserved424self.master_node.setmocktime(int(time.time()) + 100)426# Migrate427_, imports0 = self.migrate_and_get_rpc("imports0")428assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, received_watchonly_txid)429assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, received_sent_watchonly_utxo['txid'])430assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, sent_watchonly_txid)431assert_equal(len(imports0.listtransactions()), 2)432imports0.gettransaction(received_txid)433imports0.gettransaction(watchonly_spendable_txid)434assert_equal(imports0.getbalance(), spendable_bal)436assert_equal("imports0_watchonly" in self.master_node.listwallets(), True)437watchonly = self.master_node.get_wallet_rpc("imports0_watchonly")438watchonly_info = watchonly.getwalletinfo()439assert_equal(watchonly_info["descriptors"], True)440self.assert_is_sqlite("imports0_watchonly")441assert_equal(watchonly_info["private_keys_enabled"], False)442received_migrated_watchonly_tx_info = watchonly.gettransaction(received_watchonly_txid)443assert_equal(received_watchonly_tx_info["time"], received_migrated_watchonly_tx_info["time"])444assert_equal(received_watchonly_tx_info["timereceived"], received_migrated_watchonly_tx_info["timereceived"])445received_sent_migrated_watchonly_tx_info = watchonly.gettransaction(received_sent_watchonly_utxo["txid"])446assert_equal(received_sent_watchonly_tx_info["time"], received_sent_migrated_watchonly_tx_info["time"])447assert_equal(received_sent_watchonly_tx_info["timereceived"], received_sent_migrated_watchonly_tx_info["timereceived"])448watchonly.gettransaction(sent_watchonly_txid)449watchonly.gettransaction(watchonly_spendable_txid)450assert_equal(watchonly.getbalance(), watchonly_bal)451assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", watchonly.gettransaction, received_txid)452assert_equal(len(watchonly.listtransactions(include_watchonly=True)), 4)454# Check that labels were migrated and persisted to watchonly wallet455self.master_node.unloadwallet("imports0_watchonly")456self.master_node.loadwallet("imports0_watchonly")457labels = watchonly.listlabels()458assert "external" in labels459assert "imported" in labels461def test_no_privkeys(self):462default = self.master_node.get_wallet_rpc(self.default_wallet_name)464# Migrating an actual watchonly wallet should not create a new watchonly wallet465self.log.info("Test migration of a pure watchonly wallet")466watchonly0 = self.create_legacy_wallet("watchonly0", disable_private_keys=True)468addr = default.getnewaddress()469desc = default.getaddressinfo(addr)["desc"]470res = watchonly0.importmulti([471{472"desc": desc,473"watchonly": True,474"timestamp": "now",475}])476assert_equal(res[0]['success'], True)477default.sendtoaddress(addr, 10)478self.generate(self.master_node, 1)480_, watchonly0 = self.migrate_and_get_rpc("watchonly0")481assert_equal("watchonly0_watchonly" in self.master_node.listwallets(), False)482info = watchonly0.getwalletinfo()483assert_equal(info["descriptors"], True)484assert_equal(info["private_keys_enabled"], False)485self.assert_is_sqlite("watchonly0")487# Migrating a wallet with pubkeys added to the keypool488self.log.info("Test migration of a pure watchonly wallet with pubkeys in keypool")489watchonly1 = self.create_legacy_wallet("watchonly1", disable_private_keys=True)491addr1 = default.getnewaddress(address_type="bech32")492addr2 = default.getnewaddress(address_type="bech32")493desc1 = default.getaddressinfo(addr1)["desc"]494desc2 = default.getaddressinfo(addr2)["desc"]495res = watchonly1.importmulti([496{497"desc": desc1,498"keypool": True,499"timestamp": "now",500},
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.