wallet_migration.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2020-present The Bitcoin Core developers
   3  # Distributed under the MIT software license, see the accompanying
   4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  """Test Migrating a wallet from legacy to descriptor."""
   6  from contextlib import suppress
   7  from pathlib import Path
   8  import json
   9  import os.path
  10  import random
  11  import shutil
  12  import struct
  13  import time
  14  from decimal import Decimal
  15  
  16  from test_framework.address import (
  17      key_to_p2pkh,
  18      key_to_p2wpkh,
  19      script_to_p2sh,
  20      script_to_p2wsh,
  21  )
  22  from test_framework.descriptors import descsum_create
  23  from test_framework.key import ECPubKey
  24  from test_framework.test_framework import BitcoinTestFramework
  25  from test_framework.messages import COIN, CTransaction, CTxOut, ser_string
  26  from test_framework.script import hash160
  27  from test_framework.script_util import key_to_p2pkh_script, key_to_p2pk_script, script_to_p2sh_script, script_to_p2wsh_script
  28  from test_framework.util import (
  29      assert_equal,
  30      assert_greater_than,
  31      assert_raises_rpc_error,
  32      find_vout_for_address,
  33      sha256sum_file,
  34  )
  35  from test_framework.wallet_util import (
  36      get_generate_key,
  37      generate_keypair,
  38  )
  39  
  40  BTREE_MAGIC = 0x053162
  41  
  42  
  43  class WalletMigrationTest(BitcoinTestFramework):
  44      def set_test_params(self):
  45          self.setup_clean_chain = True
  46          self.num_nodes = 2
  47          self.supports_cli = False
  48          self.extra_args = [[], ["-deprecatedrpc=create_bdb"]]
  49  
  50      def skip_test_if_missing_module(self):
  51          self.skip_if_no_wallet()
  52          self.skip_if_no_previous_releases()
  53  
  54      def setup_nodes(self):
  55          self.add_nodes(
  56              self.num_nodes,
  57              extra_args=self.extra_args,
  58              versions=[
  59                  None,
  60                  280200,
  61              ],
  62          )
  63          self.start_nodes()
  64          self.init_wallet(node=0)
  65  
  66      def assert_is_sqlite(self, wallet_name, wallet_loaded = True):
  67          wallet_file_path = self.master_node.wallets_path / wallet_name / self.wallet_data_filename
  68          with open(wallet_file_path, 'rb') as f:
  69              file_magic = f.read(16)
  70              assert_equal(file_magic, b'SQLite format 3\x00')
  71          # if the wallet is not loaded we can't ask the node about it
  72          if wallet_loaded:
  73              assert_equal(self.master_node.get_wallet_rpc(wallet_name).getwalletinfo()["format"], "sqlite")
  74  
  75      def assert_is_bdb(self, wallet_name):
  76          with open(self.master_node.wallets_path / wallet_name / self.wallet_data_filename, "rb") as f:
  77              data = f.read(16)
  78              _, _, magic = struct.unpack("QII", data)
  79              assert_equal(magic, BTREE_MAGIC)
  80  
  81      def create_legacy_wallet(self, wallet_name, **kwargs):
  82          self.old_node.createwallet(wallet_name=wallet_name, descriptors=False, **kwargs)
  83          wallet = self.old_node.get_wallet_rpc(wallet_name)
  84          info = wallet.getwalletinfo()
  85          assert_equal(info["descriptors"], False)
  86          assert_equal(info["format"], "bdb")
  87          return wallet
  88  
  89      def assert_addr_info_equal(self, addr_info, addr_info_old):
  90          assert_equal(addr_info["address"], addr_info_old["address"])
  91          assert_equal(addr_info["scriptPubKey"], addr_info_old["scriptPubKey"])
  92          assert_equal(addr_info["ismine"], addr_info_old["ismine"])
  93          assert_equal(addr_info["hdkeypath"], addr_info_old["hdkeypath"].replace("'","h"))
  94          assert_equal(addr_info["solvable"], addr_info_old["solvable"])
  95          assert_equal(addr_info["ischange"], addr_info_old["ischange"])
  96          assert_equal(addr_info["hdmasterfingerprint"], addr_info_old["hdmasterfingerprint"])
  97  
  98      def assert_list_txs_equal(self, received_list_txs, expected_list_txs):
  99          for d in received_list_txs:
 100              if "parent_descs" in d:
 101                  del d["parent_descs"]
 102          for d in expected_list_txs:
 103              if "parent_descs" in d:
 104                  del d["parent_descs"]
 105          assert_equal(received_list_txs, expected_list_txs)
 106  
 107      def check_address(self, wallet, addr, is_mine, is_change, label):
 108          addr_info = wallet.getaddressinfo(addr)
 109          assert_equal(addr_info['ismine'], is_mine)
 110          assert_equal(addr_info['ischange'], is_change)
 111          if label is not None:
 112              assert_equal(addr_info['labels'], [label]),
 113          else:
 114              assert_equal(addr_info['labels'], []),
 115  
 116      def migrate_and_get_rpc(self, wallet_name, **kwargs):
 117          # Since we may rescan on loading of a wallet, make sure that the best block
 118          # is written before beginning migration
 119          # Reload to force write that record
 120          self.old_node.unloadwallet(wallet_name)
 121          self.old_node.loadwallet(wallet_name)
 122          assert_equal(self.old_node.get_wallet_rpc(wallet_name).getwalletinfo()["descriptors"], False)
 123          # Now unload so we can copy it to the master node for the migration test
 124          self.old_node.unloadwallet(wallet_name)
 125          if wallet_name == "":
 126              shutil.copyfile(self.old_node.wallets_path / "wallet.dat", self.master_node.wallets_path / "wallet.dat")
 127          else:
 128              src = os.path.abspath(self.old_node.wallets_path / wallet_name)
 129              dst = os.path.abspath(self.master_node.wallets_path / wallet_name)
 130              if src != dst :
 131                  shutil.copytree(self.old_node.wallets_path / wallet_name, self.master_node.wallets_path / wallet_name, dirs_exist_ok=True)
 132          # Check that the wallet shows up in listwalletdir with a warning about migration
 133          wallets = self.master_node.listwalletdir()
 134          for w in wallets["wallets"]:
 135              if w["name"] == wallet_name:
 136                  assert_equal(w["warnings"], ["This wallet is a legacy wallet and will need to be migrated with migratewallet before it can be loaded"])
 137  
 138          # migratewallet uses current time in naming the backup file, set a mock time
 139          # to check that this works correctly.
 140          mocked_time = int(time.time())
 141          self.master_node.setmocktime(mocked_time)
 142          # Migrate, checking that rescan does not occur
 143          with self.master_node.assert_debug_log(expected_msgs=[], unexpected_msgs=["Rescanning"]):
 144              migrate_info = self.master_node.migratewallet(wallet_name=wallet_name, **kwargs)
 145          self.master_node.setmocktime(0)
 146          # Update wallet name in case the initial wallet was completely migrated to a watch-only wallet
 147          # (in which case the wallet name would be suffixed by the 'watchonly' term)
 148          migrated_wallet_name = migrate_info['wallet_name']
 149          wallet = self.master_node.get_wallet_rpc(migrated_wallet_name)
 150          wallet_info = wallet.getwalletinfo()
 151          assert_equal(wallet_info["descriptors"], True)
 152          self.assert_is_sqlite(migrated_wallet_name)
 153          # Always verify the backup path exist after migration
 154          assert os.path.exists(migrate_info['backup_path'])
 155          if wallet_name == "":
 156              backup_prefix = "default_wallet"
 157          else:
 158              backup_prefix = os.path.basename(os.path.realpath(self.old_node.wallets_path / wallet_name))
 159  
 160          backup_filename = f"{backup_prefix}_{mocked_time}.legacy.bak"
 161          expected_backup_path = self.master_node.wallets_path / backup_filename
 162          assert_equal(str(expected_backup_path), migrate_info['backup_path'])
 163          assert {"name": backup_filename} not in self.master_node.listwalletdir()["wallets"]
 164  
 165          # Open the wallet with sqlite and verify that the wallet has the last hardened cache flag
 166          # set and the last hardened cache entries
 167          def check_last_hardened(conn):
 168              flags_rec = conn.execute(f"SELECT value FROM main WHERE key = x'{ser_string(b'flags').hex()}'").fetchone()
 169              flags = int.from_bytes(flags_rec[0], byteorder="little")
 170  
 171              # All wallets should have the upgrade flag set
 172              assert_equal(bool(flags & (1 << 2)), True)
 173  
 174              # Fetch all records with the walletdescriptorlhcache prefix
 175              # if the wallet has private keys and is not blank
 176              if wallet_info["private_keys_enabled"] and not wallet_info["blank"]:
 177                  lh_cache_recs = conn.execute(f"SELECT value FROM main where key >= x'{ser_string(b'walletdescriptorlhcache').hex()}' AND key < x'{ser_string(b'walletdescriptorlhcachf').hex()}'").fetchall()
 178                  assert_greater_than(len(lh_cache_recs), 0)
 179  
 180          inspect_path = os.path.join(self.options.tmpdir, os.path.basename(f"{migrated_wallet_name}_inspect.dat"))
 181          wallet.backupwallet(inspect_path)
 182          self.inspect_sqlite_db(inspect_path, check_last_hardened)
 183  
 184          return migrate_info, wallet
 185  
 186      def test_basic(self):
 187          # Remove the deprecated response fields that'd be present in the RPC responses
 188          # sent by the old node(s).
 189          def remove_deprecated_keys(list):
 190              deprecated_keys = {"bip125-replaceable"}
 191              for obj in list:
 192                  for key in deprecated_keys:
 193                      obj.pop(key)
 194              return list
 195  
 196          default = self.master_node.get_wallet_rpc(self.default_wallet_name)
 197  
 198          self.log.info("Test migration of a basic keys only wallet without balance")
 199          basic0 = self.create_legacy_wallet("basic0")
 200  
 201          addr = basic0.getnewaddress()
 202          change = basic0.getrawchangeaddress()
 203  
 204          old_addr_info = basic0.getaddressinfo(addr)
 205          old_change_addr_info = basic0.getaddressinfo(change)
 206          assert_equal(old_addr_info["ismine"], True)
 207          assert_equal(old_addr_info["hdkeypath"], "m/0'/0'/0'")
 208          assert_equal(old_change_addr_info["ismine"], True)
 209          assert_equal(old_change_addr_info["hdkeypath"], "m/0'/1'/0'")
 210  
 211          # Note: migration could take a while.
 212          _, basic0 = self.migrate_and_get_rpc("basic0")
 213  
 214          # The wallet should create the following descriptors:
 215          # * BIP32 descriptors in the form of "0h/0h/*" and "0h/1h/*" (2 descriptors)
 216          # * BIP44 descriptors in the form of "44h/1h/0h/0/*" and "44h/1h/0h/1/*" (2 descriptors)
 217          # * BIP49 descriptors, P2SH(P2WPKH), in the form of "86h/1h/0h/0/*" and "86h/1h/0h/1/*" (2 descriptors)
 218          # * BIP84 descriptors, P2WPKH, in the form of "84h/1h/0h/1/*" and "84h/1h/0h/1/*" (2 descriptors)
 219          # * BIP86 descriptors, P2TR, in the form of "86h/1h/0h/0/*" and "86h/1h/0h/1/*" (2 descriptors)
 220          # * A combo(PK) descriptor for the wallet master key.
 221          # So, should have a total of 11 descriptors on it.
 222          assert_equal(len(basic0.listdescriptors()["descriptors"]), 11)
 223  
 224          # Compare addresses info
 225          addr_info = basic0.getaddressinfo(addr)
 226          change_addr_info = basic0.getaddressinfo(change)
 227          self.assert_addr_info_equal(addr_info, old_addr_info)
 228          self.assert_addr_info_equal(change_addr_info, old_change_addr_info)
 229  
 230          addr_info = basic0.getaddressinfo(basic0.getnewaddress("", "bech32"))
 231          assert_equal(addr_info["hdkeypath"], "m/84h/1h/0h/0/0")
 232  
 233          self.log.info("Test migration of a basic keys only wallet with a balance")
 234          basic1 = self.create_legacy_wallet("basic1")
 235  
 236          for _ in range(0, 10):
 237              default.sendtoaddress(basic1.getnewaddress(), 1)
 238  
 239          self.generate(self.master_node, 1)
 240  
 241          for _ in range(0, 5):
 242              basic1.sendtoaddress(default.getnewaddress(), 0.5)
 243  
 244          self.generate(self.master_node, 1)
 245          bal = basic1.getbalance()
 246          txs = basic1.listtransactions()
 247          addr_gps = basic1.listaddressgroupings()
 248  
 249          basic1_migrate, basic1 = self.migrate_and_get_rpc("basic1")
 250          assert_equal(basic1.getbalance(), bal)
 251          self.assert_list_txs_equal(basic1.listtransactions(), remove_deprecated_keys(txs))
 252  
 253          self.log.info("Test backup file can be successfully restored")
 254          self.old_node.restorewallet("basic1_restored", basic1_migrate['backup_path'])
 255          basic1_restored = self.old_node.get_wallet_rpc("basic1_restored")
 256          basic1_restored_wi = basic1_restored.getwalletinfo()
 257          assert_equal(basic1_restored_wi['balance'], bal)
 258          assert_equal(basic1_restored.listaddressgroupings(), addr_gps)
 259          self.assert_list_txs_equal(remove_deprecated_keys(basic1_restored.listtransactions()), txs)
 260  
 261          # restart master node and verify that everything is still there
 262          self.restart_node(0)
 263          self.connect_nodes(0, 1)
 264          default = self.master_node.get_wallet_rpc(self.default_wallet_name)
 265          self.master_node.loadwallet("basic1")
 266          basic1 = self.master_node.get_wallet_rpc("basic1")
 267          assert_equal(basic1.getbalance(), bal)
 268          self.assert_list_txs_equal(basic1.listtransactions(), txs)
 269  
 270          self.log.info("Test migration of a wallet with balance received on the seed")
 271          basic2 = self.create_legacy_wallet("basic2")
 272          basic2_seed = get_generate_key()
 273          basic2.sethdseed(True, basic2_seed.privkey)
 274          assert_equal(basic2.getbalance(), 0)
 275  
 276          # Receive coins on different output types for the same seed
 277          basic2_balance = 0
 278          for addr in [basic2_seed.p2pkh_addr, basic2_seed.p2wpkh_addr, basic2_seed.p2sh_p2wpkh_addr]:
 279              send_value = random.randint(1, 4)
 280              default.sendtoaddress(addr, send_value)
 281              basic2_balance += send_value
 282              self.generate(self.master_node, 1)
 283              assert_equal(basic2.getbalance(), basic2_balance)
 284          basic2_txs = basic2.listtransactions()
 285  
 286          # Now migrate and test that we still have the same balance/transactions
 287          _, basic2 = self.migrate_and_get_rpc("basic2")
 288          assert_equal(basic2.getbalance(), basic2_balance)
 289          self.assert_list_txs_equal(basic2.listtransactions(), remove_deprecated_keys(basic2_txs))
 290  
 291          # Now test migration on a descriptor wallet
 292          self.log.info("Test \"nothing to migrate\" when the user tries to migrate a loaded wallet with no legacy data")
 293          assert_raises_rpc_error(-4, "Error: This wallet is already a descriptor wallet", basic2.migratewallet)
 294  
 295          self.log.info("Test \"nothing to migrate\" when the user tries to migrate an unloaded wallet with no legacy data")
 296          basic2.unloadwallet()
 297          assert_raises_rpc_error(-4, "Error: This wallet is already a descriptor wallet", self.master_node.migratewallet, "basic2")
 298  
 299      def test_multisig(self):
 300          default = self.master_node.get_wallet_rpc(self.default_wallet_name)
 301  
 302          # Contrived case where all the multisig keys are in a single wallet
 303          self.log.info("Test migration of a wallet with all keys for a multisig")
 304          multisig0 = self.create_legacy_wallet("multisig0")
 305          addr1 = multisig0.getnewaddress()
 306          addr2 = multisig0.getnewaddress()
 307          addr3 = multisig0.getnewaddress()
 308  
 309          ms_info = multisig0.addmultisigaddress(2, [addr1, addr2, addr3])
 310  
 311          _, multisig0 = self.migrate_and_get_rpc("multisig0")
 312          ms_addr_info = multisig0.getaddressinfo(ms_info["address"])
 313          assert_equal(ms_addr_info["ismine"], True)
 314          assert_equal(ms_addr_info["desc"], ms_info["descriptor"])
 315          assert_equal("multisig0_watchonly" in self.master_node.listwallets(), False)
 316          assert_equal("multisig0_solvables" in self.master_node.listwallets(), False)
 317  
 318          pub1 = multisig0.getaddressinfo(addr1)["pubkey"]
 319          pub2 = multisig0.getaddressinfo(addr2)["pubkey"]
 320  
 321          # Some keys in multisig do not belong to this wallet
 322          self.log.info("Test migration of a wallet that has some keys in a multisig")
 323          multisig1 = self.create_legacy_wallet("multisig1")
 324          ms_info = multisig1.addmultisigaddress(2, [multisig1.getnewaddress(), pub1, pub2])
 325          ms_info2 = multisig1.addmultisigaddress(2, [multisig1.getnewaddress(), pub1, pub2])
 326  
 327          addr1 = ms_info["address"]
 328          addr2 = ms_info2["address"]
 329          txid = default.sendtoaddress(addr1, 10)
 330          multisig1.importaddress(addr1)
 331          assert_equal(multisig1.getaddressinfo(addr1)["ismine"], False)
 332          assert_equal(multisig1.getaddressinfo(addr1)["iswatchonly"], True)
 333          assert_equal(multisig1.getaddressinfo(addr1)["solvable"], True)
 334          self.generate(self.master_node, 1)
 335          multisig1.gettransaction(txid)
 336          assert_equal(multisig1.getbalances()["watchonly"]["trusted"], 10)
 337          assert_equal(multisig1.getaddressinfo(addr2)["ismine"], False)
 338          assert_equal(multisig1.getaddressinfo(addr2)["iswatchonly"], False)
 339          assert_equal(multisig1.getaddressinfo(addr2)["solvable"], True)
 340  
 341          # Migrating multisig1 should see the multisig is no longer part of multisig1
 342          # A new wallet multisig1_watchonly is created which has the multisig address
 343          # Transaction to multisig is in multisig1_watchonly and not multisig1
 344          _, multisig1 = self.migrate_and_get_rpc("multisig1")
 345          assert_equal(multisig1.getaddressinfo(addr1)["ismine"], False)
 346          assert_equal(multisig1.getaddressinfo(addr1)["solvable"], False)
 347          assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", multisig1.gettransaction, txid)
 348          assert_equal(multisig1.getbalance(), 0)
 349          assert_equal(multisig1.listtransactions(), [])
 350  
 351          assert_equal("multisig1_watchonly" in self.master_node.listwallets(), True)
 352          ms1_watchonly = self.master_node.get_wallet_rpc("multisig1_watchonly")
 353          ms1_wallet_info = ms1_watchonly.getwalletinfo()
 354          assert_equal(ms1_wallet_info['descriptors'], True)
 355          assert_equal(ms1_wallet_info['private_keys_enabled'], False)
 356          self.assert_is_sqlite("multisig1_watchonly")
 357          assert_equal(ms1_watchonly.getaddressinfo(addr1)["ismine"], True)
 358          assert_equal(ms1_watchonly.getaddressinfo(addr1)["solvable"], True)
 359          # Because addr2 was not being watched, it isn't in multisig1_watchonly but rather multisig1_solvables
 360          assert_equal(ms1_watchonly.getaddressinfo(addr2)["ismine"], False)
 361          assert_equal(ms1_watchonly.getaddressinfo(addr2)["solvable"], False)
 362          ms1_watchonly.gettransaction(txid)
 363          assert_equal(ms1_watchonly.getbalance(), 10)
 364  
 365          # Migrating multisig1 should see the second multisig is no longer part of multisig1
 366          # A new wallet multisig1_solvables is created which has the second address
 367          # This should have no transactions
 368          assert_equal("multisig1_solvables" in self.master_node.listwallets(), True)
 369          ms1_solvable = self.master_node.get_wallet_rpc("multisig1_solvables")
 370          ms1_wallet_info = ms1_solvable.getwalletinfo()
 371          assert_equal(ms1_wallet_info['descriptors'], True)
 372          assert_equal(ms1_wallet_info['private_keys_enabled'], False)
 373          self.assert_is_sqlite("multisig1_solvables")
 374          assert_equal(ms1_solvable.getaddressinfo(addr1)["ismine"], False)
 375          assert_equal(ms1_solvable.getaddressinfo(addr1)["solvable"], False)
 376          assert_equal(ms1_solvable.getaddressinfo(addr2)["ismine"], True)
 377          assert_equal(ms1_solvable.getaddressinfo(addr2)["solvable"], True)
 378          assert_equal(ms1_solvable.getbalance(), 0)
 379          assert_equal(ms1_solvable.listtransactions(), [])
 380  
 381  
 382      def test_other_watchonly(self):
 383          default = self.master_node.get_wallet_rpc(self.default_wallet_name)
 384  
 385          # Wallet with an imported address. Should be the same thing as the multisig test
 386          self.log.info("Test migration of a wallet with watchonly imports")
 387          imports0 = self.create_legacy_wallet("imports0")
 388  
 389          # External address label
 390          imports0.setlabel(default.getnewaddress(), "external")
 391  
 392          # Normal non-watchonly tx
 393          received_addr = imports0.getnewaddress()
 394          imports0.setlabel(received_addr, "Receiving")
 395          received_txid = default.sendtoaddress(received_addr, 10)
 396  
 397          # Watchonly tx
 398          import_addr = default.getnewaddress()
 399          imports0.importaddress(import_addr)
 400          imports0.setlabel(import_addr, "imported")
 401          received_watchonly_txid = default.sendtoaddress(import_addr, 10)
 402  
 403          # Received watchonly tx that is then spent
 404          import_sent_addr = default.getnewaddress()
 405          imports0.importaddress(import_sent_addr)
 406          received_sent_watchonly_utxo = self.create_outpoints(node=default, outputs=[{import_sent_addr: 10}])[0]
 407  
 408          send = default.sendall(recipients=[default.getnewaddress()], inputs=[received_sent_watchonly_utxo])
 409          sent_watchonly_txid = send["txid"]
 410  
 411          # Tx that has both a watchonly and spendable output
 412          watchonly_spendable_txid = default.send(outputs=[{received_addr: 1}, {import_addr:1}])["txid"]
 413  
 414          self.generate(self.master_node, 2)
 415          received_watchonly_tx_info = imports0.gettransaction(received_watchonly_txid, True)
 416          received_sent_watchonly_tx_info = imports0.gettransaction(received_sent_watchonly_utxo["txid"], True)
 417  
 418          balances = imports0.getbalances()
 419          spendable_bal = balances["mine"]["trusted"]
 420          watchonly_bal = balances["watchonly"]["trusted"]
 421          assert_equal(len(imports0.listtransactions(include_watchonly=True)), 6)
 422  
 423          # Mock time forward a bit so we can check that tx metadata is preserved
 424          self.master_node.setmocktime(int(time.time()) + 100)
 425  
 426          # Migrate
 427          _, imports0 = self.migrate_and_get_rpc("imports0")
 428          assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, received_watchonly_txid)
 429          assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, received_sent_watchonly_utxo['txid'])
 430          assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", imports0.gettransaction, sent_watchonly_txid)
 431          assert_equal(len(imports0.listtransactions()), 2)
 432          imports0.gettransaction(received_txid)
 433          imports0.gettransaction(watchonly_spendable_txid)
 434          assert_equal(imports0.getbalance(), spendable_bal)
 435  
 436          assert_equal("imports0_watchonly" in self.master_node.listwallets(), True)
 437          watchonly = self.master_node.get_wallet_rpc("imports0_watchonly")
 438          watchonly_info = watchonly.getwalletinfo()
 439          assert_equal(watchonly_info["descriptors"], True)
 440          self.assert_is_sqlite("imports0_watchonly")
 441          assert_equal(watchonly_info["private_keys_enabled"], False)
 442          received_migrated_watchonly_tx_info = watchonly.gettransaction(received_watchonly_txid)
 443          assert_equal(received_watchonly_tx_info["time"], received_migrated_watchonly_tx_info["time"])
 444          assert_equal(received_watchonly_tx_info["timereceived"], received_migrated_watchonly_tx_info["timereceived"])
 445          received_sent_migrated_watchonly_tx_info = watchonly.gettransaction(received_sent_watchonly_utxo["txid"])
 446          assert_equal(received_sent_watchonly_tx_info["time"], received_sent_migrated_watchonly_tx_info["time"])
 447          assert_equal(received_sent_watchonly_tx_info["timereceived"], received_sent_migrated_watchonly_tx_info["timereceived"])
 448          watchonly.gettransaction(sent_watchonly_txid)
 449          watchonly.gettransaction(watchonly_spendable_txid)
 450          assert_equal(watchonly.getbalance(), watchonly_bal)
 451          assert_raises_rpc_error(-5, "Invalid or non-wallet transaction id", watchonly.gettransaction, received_txid)
 452          assert_equal(len(watchonly.listtransactions(include_watchonly=True)), 4)
 453  
 454          # Check that labels were migrated and persisted to watchonly wallet
 455          self.master_node.unloadwallet("imports0_watchonly")
 456          self.master_node.loadwallet("imports0_watchonly")
 457          labels = watchonly.listlabels()
 458          assert "external" in labels
 459          assert "imported" in labels
 460  
 461      def test_no_privkeys(self):
 462          default = self.master_node.get_wallet_rpc(self.default_wallet_name)
 463  
 464          # Migrating an actual watchonly wallet should not create a new watchonly wallet
 465          self.log.info("Test migration of a pure watchonly wallet")
 466          watchonly0 = self.create_legacy_wallet("watchonly0", disable_private_keys=True)
 467  
 468          addr = default.getnewaddress()
 469          desc = default.getaddressinfo(addr)["desc"]
 470          res = watchonly0.importmulti([
 471              {
 472                  "desc": desc,
 473                  "watchonly": True,
 474                  "timestamp": "now",
 475              }])
 476          assert_equal(res[0]['success'], True)
 477          default.sendtoaddress(addr, 10)
 478          self.generate(self.master_node, 1)
 479  
 480          _, watchonly0 = self.migrate_and_get_rpc("watchonly0")
 481          assert_equal("watchonly0_watchonly" in self.master_node.listwallets(), False)
 482          info = watchonly0.getwalletinfo()
 483          assert_equal(info["descriptors"], True)
 484          assert_equal(info["private_keys_enabled"], False)
 485          self.assert_is_sqlite("watchonly0")
 486  
 487          # Migrating a wallet with pubkeys added to the keypool
 488          self.log.info("Test migration of a pure watchonly wallet with pubkeys in keypool")
 489          watchonly1 = self.create_legacy_wallet("watchonly1", disable_private_keys=True)
 490  
 491          addr1 = default.getnewaddress(address_type="bech32")
 492          addr2 = default.getnewaddress(address_type="bech32")
 493          desc1 = default.getaddressinfo(addr1)["desc"]
 494          desc2 = default.getaddressinfo(addr2)["desc"]
 495          res = watchonly1.importmulti([
 496              {
 497                  "desc": desc1,
 498                  "keypool": True,
 499                  "timestamp": "now",
 500              },
 501              {
 502                  "desc": desc2,
 503                  "keypool": True,
 504                  "timestamp": "now",
 505              }
 506          ])
 507          assert_equal(res[0]["success"], True)
 508          assert_equal(res[1]["success"], True)
 509          # Before migrating, we can fetch addr1 from the keypool
 510          assert_equal(watchonly1.getnewaddress(address_type="bech32"), addr1)
 511  
 512          _, watchonly1 = self.migrate_and_get_rpc("watchonly1")
 513          info = watchonly1.getwalletinfo()
 514          assert_equal(info["descriptors"], True)
 515          assert_equal(info["private_keys_enabled"], False)
 516          self.assert_is_sqlite("watchonly1")
 517          # After migrating, the "keypool" is empty
 518          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", watchonly1.getnewaddress)
 519  
 520          self.log.info("Test migration of a watch-only empty wallet")
 521          for idx, is_blank in enumerate([True, False], start=1):
 522              wallet_name = f"watchonly_empty{idx}"
 523              self.create_legacy_wallet(wallet_name, disable_private_keys=True, blank=is_blank)
 524              _, watchonly_empty = self.migrate_and_get_rpc(wallet_name)
 525              info = watchonly_empty.getwalletinfo()
 526              assert_equal(info["private_keys_enabled"], False)
 527              assert_equal(info["blank"], is_blank)
 528  
 529      def test_pk_coinbases(self):
 530          self.log.info("Test migration of a wallet using old pk() coinbases")
 531          wallet = self.create_legacy_wallet("pkcb")
 532  
 533          addr = wallet.getnewaddress()
 534          addr_info = wallet.getaddressinfo(addr)
 535          desc = descsum_create("pk(" + addr_info["pubkey"] + ")")
 536  
 537          self.generatetodescriptor(self.master_node, 1, desc)
 538  
 539          bals = wallet.getbalances()
 540          bals["mine"]["nonmempool"] = Decimal('0.0')
 541  
 542          _, wallet = self.migrate_and_get_rpc("pkcb")
 543  
 544          assert_equal(bals, wallet.getbalances())
 545  
 546      def test_encrypted(self):
 547          self.log.info("Test migration of an encrypted wallet")
 548          wallet = self.create_legacy_wallet("encrypted")
 549          default = self.master_node.get_wallet_rpc(self.default_wallet_name)
 550  
 551          wallet.encryptwallet("pass")
 552          addr = wallet.getnewaddress()
 553          txid = default.sendtoaddress(addr, 1)
 554          self.generate(self.master_node, 1)
 555          bals = wallet.getbalances()
 556          bals["mine"]["nonmempool"] = Decimal('0.0')
 557  
 558          # Use self.migrate_and_get_rpc to test this error to get everything copied over to the master node
 559          assert_raises_rpc_error(-4, "Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect", self.migrate_and_get_rpc, "encrypted")
 560  
 561          # Use the RPC directly on the master node for the rest of these checks
 562          self.master_node.bumpmocktime(1) # Prevents filename duplication on wallet backups which is a problem on Windows
 563          assert_raises_rpc_error(-4, "Error: Wallet decryption failed, the wallet passphrase was not provided or was incorrect", self.master_node.migratewallet, "encrypted", "badpass")
 564  
 565          self.master_node.bumpmocktime(1) # Prevents filename duplication on wallet backups which is a problem on Windows
 566          assert_raises_rpc_error(-4, "The passphrase contains a null character", self.master_node.migratewallet, "encrypted", "pass\0with\0null")
 567  
 568          # Verify we can properly migrate the encrypted wallet
 569          self.master_node.bumpmocktime(1) # Prevents filename duplication on wallet backups which is a problem on Windows
 570          self.master_node.migratewallet("encrypted", passphrase="pass")
 571          wallet = self.master_node.get_wallet_rpc("encrypted")
 572  
 573          info = wallet.getwalletinfo()
 574          assert_equal(info["descriptors"], True)
 575          assert_equal(info["format"], "sqlite")
 576          assert_equal(info["unlocked_until"], 0)
 577          wallet.gettransaction(txid)
 578  
 579          assert_equal(bals, wallet.getbalances())
 580  
 581      def test_nonexistent(self):
 582          self.log.info("Check migratewallet errors for nonexistent wallets")
 583          default = self.master_node.get_wallet_rpc(self.default_wallet_name)
 584          assert_raises_rpc_error(-8, "The RPC endpoint wallet and the wallet name parameter specify different wallets", default.migratewallet, "someotherwallet")
 585          assert_raises_rpc_error(-8, "Either the RPC endpoint wallet or the wallet name parameter must be provided", self.master_node.migratewallet)
 586          assert_raises_rpc_error(-4, "Error: Wallet does not exist", self.master_node.migratewallet, "notawallet")
 587  
 588      def test_unloaded_by_path(self):
 589          self.log.info("Test migration of a wallet that isn't loaded, specified by path")
 590          wallet = self.create_legacy_wallet("notloaded2")
 591          default = self.master_node.get_wallet_rpc(self.default_wallet_name)
 592  
 593          addr = wallet.getnewaddress()
 594          txid = default.sendtoaddress(addr, 1)
 595          self.generate(self.master_node, 1)
 596          bals = wallet.getbalances()
 597          bals["mine"]["nonmempool"] = Decimal('0.0')
 598  
 599          wallet.unloadwallet()
 600  
 601          wallet_file_path = self.old_node.wallets_path / "notloaded2"
 602          self.master_node.migratewallet(wallet_file_path)
 603  
 604          # Because we gave the name by full path, the loaded wallet's name is that path too.
 605          wallet = self.master_node.get_wallet_rpc(str(wallet_file_path))
 606  
 607          info = wallet.getwalletinfo()
 608          assert_equal(info["descriptors"], True)
 609          assert_equal(info["format"], "sqlite")
 610          wallet.gettransaction(txid)
 611  
 612          assert_equal(bals, wallet.getbalances())
 613  
 614      def test_wallet_with_path(self, wallet_path):
 615          self.log.info("Test migrating a wallet with the following path/name: %s", wallet_path)
 616          # the wallet data is actually inside of path/that/ends/
 617          wallet = self.create_legacy_wallet(wallet_path)
 618          default = self.master_node.get_wallet_rpc(self.default_wallet_name)
 619  
 620          addr = wallet.getnewaddress()
 621          txid = default.sendtoaddress(addr, 1)
 622          self.generate(self.master_node, 1)
 623          bals = wallet.getbalances()
 624          bals["mine"]["nonmempool"] = Decimal('0.0')
 625  
 626          _, wallet = self.migrate_and_get_rpc(wallet_path)
 627  
 628          assert wallet.gettransaction(txid)
 629  
 630          assert_equal(bals, wallet.getbalances())
 631  
 632      def clear_default_wallet(self, backup_file):
 633          # Test cleanup: Clear unnamed default wallet for subsequent tests
 634          (self.old_node.wallets_path / "wallet.dat").unlink()
 635          (self.master_node.wallets_path / "wallet.dat").unlink(missing_ok=True)
 636          with suppress(FileNotFoundError):
 637              (self.master_node.wallets_path / "default_wallet_watchonly" / "wallet.dat").unlink()
 638              (self.master_node.wallets_path / "default_wallet_watchonly").rmdir()
 639  
 640              (self.master_node.wallets_path / "default_wallet_solvables" / "wallet.dat").unlink()
 641              (self.master_node.wallets_path / "default_wallet_solvables").rmdir()
 642          backup_file.unlink()
 643  
 644      def test_default_wallet(self):
 645          self.log.info("Test migration of the wallet named as the empty string")
 646          wallet = self.create_legacy_wallet("")
 647  
 648          res, wallet = self.migrate_and_get_rpc("")
 649          info = wallet.getwalletinfo()
 650          assert_equal(info["descriptors"], True)
 651          assert_equal(info["format"], "sqlite")
 652  
 653          walletdir_list = wallet.listwalletdir()
 654          assert {"name": info["walletname"]} in [{"name": w["name"]} for w in walletdir_list["wallets"]]
 655  
 656          # Make sure the backup uses a non-empty filename
 657          # migrate_and_get_rpc already checks for backup file existence
 658          assert os.path.basename(res["backup_path"]).startswith("default_wallet")
 659  
 660          wallet.unloadwallet()
 661          self.clear_default_wallet(backup_file=Path(res["backup_path"]))
 662  
 663      def test_default_wallet_watch_only(self):
 664          self.log.info("Test unnamed (default) watch-only wallet migration")
 665          master_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
 666          wallet = self.create_legacy_wallet("", blank=True)
 667          wallet.importaddress(master_wallet.getnewaddress(address_type="legacy"))
 668  
 669          res, wallet = self.migrate_and_get_rpc("")
 670  
 671          info = wallet.getwalletinfo()
 672          assert_equal(info["descriptors"], True)
 673          assert_equal(info["format"], "sqlite")
 674          assert_equal(info["private_keys_enabled"], False)
 675          assert_equal(info["walletname"], "default_wallet_watchonly")
 676          # Check the default wallet is not available anymore
 677          assert not (self.master_node.wallets_path / "wallet.dat").exists()
 678  
 679          wallet.unloadwallet()
 680          self.clear_default_wallet(backup_file=Path(res["backup_path"]))
 681  
 682      def test_migration_failure(self, wallet_name):
 683          is_default = wallet_name == ""
 684          wallet_pretty_name = "unnamed (default)" if is_default else f'"{wallet_name}"'
 685          self.log.info(f"Test failure during migration of wallet named: {wallet_pretty_name}")
 686          # Preface, set up legacy wallet and unload it
 687          master_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
 688          wallet = self.create_legacy_wallet(wallet_name, blank=True)
 689          wallet.importaddress(master_wallet.getnewaddress(address_type="legacy"))
 690          wallet.unloadwallet()
 691  
 692          if os.path.isabs(wallet_name):
 693              old_path = master_path = Path(wallet_name)
 694          else:
 695              old_path = self.old_node.wallets_path / wallet_name
 696              master_path = self.master_node.wallets_path / wallet_name
 697              os.makedirs(master_path, exist_ok=True)
 698              shutil.copyfile(old_path / "wallet.dat", master_path / "wallet.dat")
 699  
 700          # This will be the watch-only directory the migration tries to create,
 701          # we make migration fail by placing a wallet.dat file there.
 702          wo_prefix = wallet_name or "default_wallet"
 703          # wo_prefix might have path characters in it, this corresponds with
 704          # DoMigration().
 705          wo_dirname = f"{wo_prefix}_watchonly"
 706          watch_only_dir = self.master_node.wallets_path / wo_dirname
 707          os.mkdir(watch_only_dir)
 708          shutil.copyfile(old_path / "wallet.dat", watch_only_dir / "wallet.dat")
 709  
 710          mocked_time = int(time.time())
 711          self.master_node.setmocktime(mocked_time)
 712          assert_raises_rpc_error(-4, "Failed to create database", self.master_node.migratewallet, wallet_name)
 713          self.master_node.setmocktime(0)
 714  
 715          # Verify the /wallets/ path exists.
 716          assert self.master_node.wallets_path.exists()
 717  
 718          # Verify both wallet paths exist.
 719          assert Path(old_path / "wallet.dat").exists()
 720          assert Path(master_path / "wallet.dat").exists()
 721  
 722          backup_prefix = "default_wallet" if is_default else os.path.basename(os.path.abspath(master_path))
 723          backup_path = self.master_node.wallets_path / f"{backup_prefix}_{mocked_time}.legacy.bak"
 724          assert backup_path.exists()
 725  
 726          self.assert_is_bdb(wallet_name)
 727  
 728          # Cleanup
 729          if is_default:
 730              self.clear_default_wallet(backup_path)
 731          else:
 732              backup_path.unlink()
 733              Path(watch_only_dir / "wallet.dat").unlink()
 734              Path(watch_only_dir).rmdir()
 735              Path(master_path / "wallet.dat").unlink()
 736              Path(old_path / "wallet.dat").unlink(missing_ok=True)
 737  
 738      def test_direct_file(self):
 739          self.log.info("Test migration of a wallet that is not in a wallet directory")
 740          wallet = self.create_legacy_wallet("plainfile")
 741          wallet.unloadwallet()
 742  
 743          shutil.copyfile(
 744              self.old_node.wallets_path / "plainfile" / "wallet.dat" ,
 745              self.master_node.wallets_path / "plainfile"
 746          )
 747          assert (self.master_node.wallets_path / "plainfile").is_file()
 748  
 749          mocked_time = int(time.time())
 750          self.master_node.setmocktime(mocked_time)
 751          migrate_res = self.master_node.migratewallet("plainfile")
 752          assert_equal(f"plainfile_{mocked_time}.legacy.bak", os.path.basename(migrate_res["backup_path"]))
 753          wallet = self.master_node.get_wallet_rpc("plainfile")
 754          info = wallet.getwalletinfo()
 755          assert_equal(info["descriptors"], True)
 756          assert_equal(info["format"], "sqlite")
 757  
 758          assert (self.master_node.wallets_path / "plainfile").is_dir()
 759          assert (self.master_node.wallets_path / "plainfile" / "wallet.dat").is_file()
 760  
 761      def test_addressbook(self):
 762          df_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
 763  
 764          self.log.info("Test migration of address book data")
 765          wallet = self.create_legacy_wallet("legacy_addrbook")
 766          df_wallet.sendtoaddress(wallet.getnewaddress(), 3)
 767  
 768          # Import watch-only script to create a watch-only wallet after migration
 769          watch_addr = df_wallet.getnewaddress()
 770          wallet.importaddress(watch_addr)
 771          df_wallet.sendtoaddress(watch_addr, 2)
 772  
 773          # Import solvable script
 774          multi_addr1 = wallet.getnewaddress()
 775          multi_addr2 = wallet.getnewaddress()
 776          multi_addr3 = df_wallet.getnewaddress()
 777          wallet.importpubkey(df_wallet.getaddressinfo(multi_addr3)["pubkey"])
 778          ms_addr_info = wallet.addmultisigaddress(2, [multi_addr1, multi_addr2, multi_addr3])
 779  
 780          self.generate(self.master_node, 1)
 781  
 782          # Test vectors
 783          addr_external = {
 784              "addr": df_wallet.getnewaddress(),
 785              "is_mine": False,
 786              "is_change": False,
 787              "label": ""
 788          }
 789          addr_external_with_label = {
 790              "addr": df_wallet.getnewaddress(),
 791              "is_mine": False,
 792              "is_change": False,
 793              "label": "external"
 794          }
 795          addr_internal = {
 796              "addr": wallet.getnewaddress(),
 797              "is_mine": True,
 798              "is_change": False,
 799              "label": ""
 800          }
 801          addr_internal_with_label = {
 802              "addr": wallet.getnewaddress(),
 803              "is_mine": True,
 804              "is_change": False,
 805              "label": "internal"
 806          }
 807          change_address = {
 808              "addr": wallet.getrawchangeaddress(),
 809              "is_mine": True,
 810              "is_change": True,
 811              "label": None
 812          }
 813          watch_only_addr = {
 814              "addr": watch_addr,
 815              "is_mine": False,
 816              "is_change": False,
 817              "label": "imported"
 818          }
 819          ms_addr = {
 820              "addr": ms_addr_info['address'],
 821              "is_mine": False,
 822              "is_change": False,
 823              "label": "multisig"
 824          }
 825  
 826          # To store the change address in the addressbook need to send coins to it
 827          wallet.send(outputs=[{wallet.getnewaddress(): 2}], options={"change_address": change_address['addr']})
 828          self.generate(self.master_node, 1)
 829  
 830          # Util wrapper func for 'addr_info'
 831          def check(info, node):
 832              self.check_address(node, info['addr'], info['is_mine'], info['is_change'], info["label"])
 833  
 834          # Pre-migration: set label and perform initial checks
 835          for addr_info in [addr_external, addr_external_with_label, addr_internal, addr_internal_with_label, change_address, watch_only_addr, ms_addr]:
 836              if not addr_info['is_change']:
 837                  wallet.setlabel(addr_info['addr'], addr_info["label"])
 838              check(addr_info, wallet)
 839  
 840          # Migrate wallet
 841          info_migration, wallet = self.migrate_and_get_rpc("legacy_addrbook")
 842          wallet_wo = self.master_node.get_wallet_rpc(info_migration["watchonly_name"])
 843          wallet_solvables = self.master_node.get_wallet_rpc(info_migration["solvables_name"])
 844  
 845          #########################
 846          # Post migration checks #
 847          #########################
 848  
 849          # First check the main wallet
 850          for addr_info in [addr_external, addr_external_with_label, addr_internal, addr_internal_with_label, change_address, ms_addr]:
 851              check(addr_info, wallet)
 852  
 853          # Watch-only wallet will contain the watch-only entry (with 'is_mine=True') and all external addresses ('send')
 854          self.check_address(wallet_wo, watch_only_addr['addr'], is_mine=True, is_change=watch_only_addr['is_change'], label=watch_only_addr["label"])
 855          for addr_info in [addr_external, addr_external_with_label, ms_addr]:
 856              check(addr_info, wallet_wo)
 857  
 858          # Solvables wallet will contain the multisig entry (with 'is_mine=True') and all external addresses ('send')
 859          self.check_address(wallet_solvables, ms_addr['addr'], is_mine=True, is_change=ms_addr['is_change'], label=ms_addr["label"])
 860          for addr_info in [addr_external, addr_external_with_label]:
 861              check(addr_info, wallet_solvables)
 862  
 863          ########################################################################################
 864          # Now restart migrated wallets and verify that the addressbook entries are still there #
 865          ########################################################################################
 866  
 867          # First the main wallet
 868          self.master_node.unloadwallet("legacy_addrbook")
 869          self.master_node.loadwallet("legacy_addrbook")
 870          for addr_info in [addr_external, addr_external_with_label, addr_internal, addr_internal_with_label, change_address, ms_addr]:
 871              check(addr_info, wallet)
 872  
 873          # Watch-only wallet
 874          self.master_node.unloadwallet(info_migration["watchonly_name"])
 875          self.master_node.loadwallet(info_migration["watchonly_name"])
 876          self.check_address(wallet_wo, watch_only_addr['addr'], is_mine=True, is_change=watch_only_addr['is_change'], label=watch_only_addr["label"])
 877          for addr_info in [addr_external, addr_external_with_label, ms_addr]:
 878              check(addr_info, wallet_wo)
 879  
 880          # Solvables wallet
 881          self.master_node.unloadwallet(info_migration["solvables_name"])
 882          self.master_node.loadwallet(info_migration["solvables_name"])
 883          self.check_address(wallet_solvables, ms_addr['addr'], is_mine=True, is_change=ms_addr['is_change'], label=ms_addr["label"])
 884          for addr_info in [addr_external, addr_external_with_label]:
 885              check(addr_info, wallet_solvables)
 886  
 887      def test_migrate_raw_p2sh(self):
 888          self.log.info("Test migration of watch-only raw p2sh script")
 889          df_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
 890          wallet = self.create_legacy_wallet("raw_p2sh")
 891  
 892          def send_to_script(script, amount):
 893              tx = CTransaction()
 894              tx.vout.append(CTxOut(nValue=amount*COIN, scriptPubKey=script))
 895  
 896              hex_tx = df_wallet.fundrawtransaction(tx.serialize().hex())['hex']
 897              signed_tx = df_wallet.signrawtransactionwithwallet(hex_tx)
 898              df_wallet.sendrawtransaction(signed_tx['hex'])
 899              self.generate(self.master_node, 1)
 900  
 901          # Craft sh(pkh(key)) script and send coins to it
 902          pubkey = df_wallet.getaddressinfo(df_wallet.getnewaddress())["pubkey"]
 903          script_pkh = key_to_p2pkh_script(pubkey)
 904          script_sh_pkh = script_to_p2sh_script(script_pkh)
 905          send_to_script(script=script_sh_pkh, amount=2)
 906  
 907          # Import script and check balance
 908          wallet.importaddress(address=script_pkh.hex(), label="raw_spk", rescan=True, p2sh=True)
 909          assert_equal(wallet.getbalances()['watchonly']['trusted'], 2)
 910  
 911          # Craft wsh(pkh(key)) and send coins to it
 912          pubkey = df_wallet.getaddressinfo(df_wallet.getnewaddress())["pubkey"]
 913          script_wsh_pkh = script_to_p2wsh_script(key_to_p2pkh_script(pubkey))
 914          send_to_script(script=script_wsh_pkh, amount=3)
 915  
 916          # Import script and check balance
 917          wallet.importaddress(address=script_wsh_pkh.hex(), label="raw_spk2", rescan=True, p2sh=False)
 918          assert_equal(wallet.getbalances()['watchonly']['trusted'], 5)
 919  
 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.
 924          label_sh_pkh = "raw_sh_pkh"
 925          script_pkh = key_to_p2pkh_script(df_wallet.getaddressinfo(df_wallet.getnewaddress())["pubkey"])
 926          script_sh_pkh = script_to_p2sh_script(script_pkh)
 927          addy_script_sh_pkh = script_to_p2sh(script_pkh)  # valid script address
 928          addy_script_double_sh_pkh = script_to_p2sh(script_sh_pkh)  # invalid script address
 929  
 930          # 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 should
 932          #       be discarded during migration. The first one must be migrated.
 933          wallet.importaddress(address=script_sh_pkh.hex(), label=label_sh_pkh, rescan=False, p2sh=True)
 934  
 935          # Migrate wallet and re-check balance
 936          info_migration, wallet = self.migrate_and_get_rpc("raw_p2sh")
 937          wallet_wo = self.master_node.get_wallet_rpc(info_migration["watchonly_name"])
 938  
 939          # Watch-only balance is under "mine".
 940          assert_equal(wallet_wo.getbalances()['mine']['trusted'], 5)
 941          # The watch-only scripts are no longer part of the main wallet
 942          assert_equal(wallet.getbalances()['mine']['trusted'], 0)
 943  
 944          # The invalid sh(sh(pk())) script label must not be part of the main wallet anymore
 945          assert label_sh_pkh not in wallet.listlabels()
 946          # But, the standard sh(pkh()) script should be part of the watch-only wallet.
 947          addrs_by_label = wallet_wo.getaddressesbylabel(label_sh_pkh)
 948          assert addy_script_sh_pkh in addrs_by_label
 949          assert addy_script_double_sh_pkh not in addrs_by_label
 950  
 951          # Also, the watch-only wallet should have the descriptor for the standard sh(pkh())
 952          desc = descsum_create(f"addr({addy_script_sh_pkh})")
 953          assert desc in [it['desc'] for it in wallet_wo.listdescriptors()['descriptors']]
 954          # And doesn't have a descriptor for the invalid one
 955          desc_invalid = descsum_create(f"addr({addy_script_double_sh_pkh})")
 956          assert_equal(next((it['desc'] for it in wallet_wo.listdescriptors()['descriptors'] if it['desc'] == desc_invalid), None), None)
 957  
 958          # Just in case, also verify wallet restart
 959          self.master_node.unloadwallet(info_migration["watchonly_name"])
 960          self.master_node.loadwallet(info_migration["watchonly_name"])
 961          assert_equal(wallet_wo.getbalances()['mine']['trusted'], 5)
 962  
 963      def test_conflict_txs(self):
 964          self.log.info("Test migration when wallet contains conflicting transactions")
 965          def_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
 966  
 967          wallet = self.create_legacy_wallet("conflicts")
 968          def_wallet.sendtoaddress(wallet.getnewaddress(), 10)
 969          self.generate(self.master_node, 1)
 970  
 971          # parent tx
 972          parent_txid = wallet.sendtoaddress(wallet.getnewaddress(), 9)
 973          parent_txid_bytes = bytes.fromhex(parent_txid)[::-1]
 974          conflict_utxo = wallet.gettransaction(txid=parent_txid, verbose=True)["decoded"]["vin"][0]
 975  
 976          # The specific assertion in MarkConflicted being tested requires that the parent tx is already loaded
 977          # by the time the child tx is loaded. Since transactions end up being loaded in txid order due to how
 978          # sqlite stores things, we can just grind the child tx until it has a txid that is greater than the parent's.
 979          locktime = 500000000 # Use locktime as nonce, starting at unix timestamp minimum
 980          addr = wallet.getnewaddress()
 981          while True:
 982              child_send_res = wallet.send(outputs=[{addr: 8}], options={"add_to_wallet": False, "locktime": locktime})
 983              child_txid = child_send_res["txid"]
 984              child_txid_bytes = bytes.fromhex(child_txid)[::-1]
 985              if (child_txid_bytes > parent_txid_bytes):
 986                  wallet.sendrawtransaction(child_send_res["hex"])
 987                  break
 988              locktime += 1
 989  
 990          # conflict with parent
 991          conflict_unsigned = self.master_node.createrawtransaction(inputs=[conflict_utxo], outputs=[{wallet.getnewaddress(): 9.9999}])
 992          conflict_signed = wallet.signrawtransactionwithwallet(conflict_unsigned)["hex"]
 993          conflict_txid = self.master_node.sendrawtransaction(conflict_signed)
 994          self.generate(self.master_node, 1)
 995          assert_equal(wallet.gettransaction(txid=parent_txid)["confirmations"], -1)
 996          assert_equal(wallet.gettransaction(txid=child_txid)["confirmations"], -1)
 997          assert_equal(wallet.gettransaction(txid=conflict_txid)["confirmations"], 1)
 998  
 999          _, wallet = self.migrate_and_get_rpc("conflicts")
1000          assert_equal(wallet.gettransaction(txid=parent_txid)["confirmations"], -1)
1001          assert_equal(wallet.gettransaction(txid=child_txid)["confirmations"], -1)
1002          assert_equal(wallet.gettransaction(txid=conflict_txid)["confirmations"], 1)
1003  
1004          wallet.unloadwallet()
1005  
1006      def test_hybrid_pubkey(self):
1007          self.log.info("Test migration when wallet contains a hybrid pubkey")
1008  
1009          wallet = self.create_legacy_wallet("hybrid_keys")
1010  
1011          # Get the hybrid pubkey for one of the keys in the wallet
1012          normal_pubkey = wallet.getaddressinfo(wallet.getnewaddress())["pubkey"]
1013          first_byte = bytes.fromhex(normal_pubkey)[0] + 4 # Get the hybrid pubkey first byte
1014          parsed_pubkey = ECPubKey()
1015          parsed_pubkey.set(bytes.fromhex(normal_pubkey))
1016          parsed_pubkey.compressed = False
1017          hybrid_pubkey_bytes = bytearray(parsed_pubkey.get_bytes())
1018          hybrid_pubkey_bytes[0] = first_byte # Make it hybrid
1019          hybrid_pubkey = hybrid_pubkey_bytes.hex()
1020  
1021          # Import the hybrid pubkey
1022          wallet.importpubkey(hybrid_pubkey)
1023          p2pkh_addr = key_to_p2pkh(hybrid_pubkey)
1024          p2pkh_addr_info = wallet.getaddressinfo(p2pkh_addr)
1025          assert_equal(p2pkh_addr_info["iswatchonly"], True)
1026          assert_equal(p2pkh_addr_info["ismine"], False) # Things involving hybrid pubkeys are not spendable
1027  
1028          # Also import the p2wpkh for the pubkey to make sure we don't migrate it
1029          p2wpkh_addr = key_to_p2wpkh(hybrid_pubkey)
1030          wallet.importaddress(p2wpkh_addr)
1031  
1032          migrate_info, wallet = self.migrate_and_get_rpc("hybrid_keys")
1033  
1034          # Both addresses should only appear in the watchonly wallet
1035          p2pkh_addr_info = wallet.getaddressinfo(p2pkh_addr)
1036          assert_equal(p2pkh_addr_info["ismine"], False)
1037          p2wpkh_addr_info = wallet.getaddressinfo(p2wpkh_addr)
1038          assert_equal(p2wpkh_addr_info["ismine"], False)
1039  
1040          watchonly_wallet = self.master_node.get_wallet_rpc(migrate_info["watchonly_name"])
1041          watchonly_p2pkh_addr_info = watchonly_wallet.getaddressinfo(p2pkh_addr)
1042          assert_equal(watchonly_p2pkh_addr_info["ismine"], True)
1043          watchonly_p2wpkh_addr_info = watchonly_wallet.getaddressinfo(p2wpkh_addr)
1044          assert_equal(watchonly_p2wpkh_addr_info["ismine"], True)
1045  
1046          # There should only be raw or addr descriptors
1047          for desc in watchonly_wallet.listdescriptors()["descriptors"]:
1048              if desc["desc"].startswith("raw(") or desc["desc"].startswith("addr("):
1049                  continue
1050              assert False, "Hybrid pubkey watchonly wallet has more than just raw() and addr()"
1051  
1052          wallet.unloadwallet()
1053  
1054      def test_failed_migration_cleanup(self):
1055          self.log.info("Test that a failed migration is cleaned up")
1056          wallet = self.create_legacy_wallet("failed")
1057  
1058          # Make a copy of the wallet with the solvables wallet name so that we are unable
1059          # to create the solvables wallet when migrating, thus failing to migrate
1060          wallet.unloadwallet()
1061          solvables_path = self.master_node.wallets_path / "failed_solvables"
1062          shutil.copytree(self.old_node.wallets_path / "failed", solvables_path)
1063          original_shasum = sha256sum_file(solvables_path / "wallet.dat")
1064  
1065          self.old_node.loadwallet("failed")
1066  
1067          # Add a multisig so that a solvables wallet is created
1068          wallet.addmultisigaddress(2, [wallet.getnewaddress(), get_generate_key().pubkey])
1069          wallet.importaddress(get_generate_key().p2pkh_addr)
1070  
1071          self.old_node.unloadwallet("failed")
1072          shutil.copytree(self.old_node.wallets_path / "failed", self.master_node.wallets_path / "failed")
1073          assert_raises_rpc_error(-4, "Failed to create database", self.master_node.migratewallet, "failed")
1074  
1075          assert all(wallet not in self.master_node.listwallets() for wallet in ["failed", "failed_watchonly", "failed_solvables"])
1076  
1077          assert not (self.master_node.wallets_path / "failed_watchonly").exists()
1078          # Since the file in failed_solvables is one that we put there, migration shouldn't touch it
1079          assert solvables_path.exists()
1080          new_shasum = sha256sum_file(solvables_path / "wallet.dat")
1081          assert_equal(original_shasum, new_shasum)
1082  
1083          # Check the wallet we tried to migrate is still BDB
1084          self.assert_is_bdb("failed")
1085  
1086      def test_blank(self):
1087          self.log.info("Test that a blank wallet is migrated")
1088          wallet = self.create_legacy_wallet("blank", blank=True)
1089          assert_equal(wallet.getwalletinfo()["blank"], True)
1090          _, wallet = self.migrate_and_get_rpc("blank")
1091          assert_equal(wallet.getwalletinfo()["blank"], True)
1092  
1093      def test_avoidreuse(self):
1094          self.log.info("Test that avoidreuse persists after migration")
1095          def_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
1096  
1097          wallet = self.create_legacy_wallet("avoidreuse")
1098          wallet.setwalletflag("avoid_reuse", True)
1099  
1100          # Import a pubkey to the test wallet and send some funds to it
1101          reused_imported_addr = def_wallet.getnewaddress()
1102          wallet.importpubkey(def_wallet.getaddressinfo(reused_imported_addr)["pubkey"])
1103          imported_utxos = self.create_outpoints(def_wallet, outputs=[{reused_imported_addr: 2}])
1104          def_wallet.lockunspent(False, imported_utxos)
1105  
1106          # Send funds to the test wallet
1107          reused_addr = wallet.getnewaddress()
1108          def_wallet.sendtoaddress(reused_addr, 2)
1109  
1110          self.generate(self.master_node, 1)
1111  
1112          # Send funds from the test wallet with both its own and the imported
1113          wallet.sendall([def_wallet.getnewaddress()])
1114          def_wallet.sendall(recipients=[def_wallet.getnewaddress()], inputs=imported_utxos)
1115          self.generate(self.master_node, 1)
1116          balances = wallet.getbalances()
1117          assert_equal(balances["mine"]["trusted"], 0)
1118          assert_equal(balances["watchonly"]["trusted"], 0)
1119  
1120          # Reuse the addresses
1121          def_wallet.sendtoaddress(reused_addr, 1)
1122          def_wallet.sendtoaddress(reused_imported_addr, 1)
1123          self.generate(self.master_node, 1)
1124          balances = wallet.getbalances()
1125          assert_equal(balances["mine"]["used"], 1)
1126          # Reused watchonly will not show up in balances
1127          assert_equal(balances["watchonly"]["trusted"], 0)
1128          assert_equal(balances["watchonly"]["untrusted_pending"], 0)
1129          assert_equal(balances["watchonly"]["immature"], 0)
1130  
1131          utxos = wallet.listunspent()
1132          assert_equal(len(utxos), 2)
1133          for utxo in utxos:
1134              assert_equal(utxo["reused"], True)
1135  
1136          # Migrate
1137          _, wallet = self.migrate_and_get_rpc("avoidreuse")
1138          watchonly_wallet = self.master_node.get_wallet_rpc("avoidreuse_watchonly")
1139  
1140          # One utxo in each wallet, marked used
1141          utxos = wallet.listunspent()
1142          assert_equal(len(utxos), 1)
1143          assert_equal(utxos[0]["reused"], True)
1144          watchonly_utxos = watchonly_wallet.listunspent()
1145          assert_equal(len(watchonly_utxos), 1)
1146          assert_equal(watchonly_utxos[0]["reused"], True)
1147  
1148      def test_preserve_tx_extra_info(self):
1149          self.log.info("Test that tx extra data is preserved after migration")
1150          def_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
1151  
1152          # Create and fund wallet
1153          wallet = self.create_legacy_wallet("persist_comments")
1154          def_wallet.sendtoaddress(wallet.getnewaddress(), 2)
1155  
1156          self.generate(self.master_node, 6)
1157  
1158          # Create tx and bump it to store 'replaced_by_txid' and 'replaces_txid' data within the transactions.
1159          # Additionally, store an extra comment within the original tx.
1160          extra_comment = "don't discard me"
1161          original_tx_id = wallet.sendtoaddress(address=wallet.getnewaddress(), amount=1, comment=extra_comment)
1162          bumped_tx = wallet.bumpfee(txid=original_tx_id)
1163  
1164          def check_comments():
1165              for record in wallet.listtransactions():
1166                  if record["txid"] == original_tx_id:
1167                      assert_equal(record["replaced_by_txid"], bumped_tx["txid"])
1168                      assert_equal(record['comment'], extra_comment)
1169                  elif record["txid"] == bumped_tx["txid"]:
1170                      assert_equal(record["replaces_txid"], original_tx_id)
1171  
1172          # Pre-migration verification
1173          check_comments()
1174          # Migrate
1175          _, wallet = self.migrate_and_get_rpc("persist_comments")
1176          # Post-migration verification
1177          check_comments()
1178  
1179          wallet.unloadwallet()
1180  
1181      def test_migrate_simple_watch_only(self):
1182          self.log.info("Test migrating a watch-only p2pk script")
1183          wallet = self.create_legacy_wallet("bare_p2pk", blank=True)
1184          _, pubkey = generate_keypair()
1185          p2pk_script = key_to_p2pk_script(pubkey)
1186          wallet.importaddress(address=p2pk_script.hex())
1187          # Migrate wallet in the latest node
1188          res, _ = self.migrate_and_get_rpc("bare_p2pk")
1189          wo_wallet = self.master_node.get_wallet_rpc(res['wallet_name'])
1190          assert_equal(wo_wallet.listdescriptors()['descriptors'][0]['desc'], descsum_create(f'pk({pubkey.hex()})'))
1191          assert_equal(wo_wallet.getwalletinfo()["private_keys_enabled"], False)
1192  
1193          # Ensure that migrating a wallet with watch-only scripts does not create a spendable wallet.
1194          assert_equal('bare_p2pk_watchonly', res['wallet_name'])
1195          assert "bare_p2pk" not in self.master_node.listwallets()
1196          assert "bare_p2pk" not in [w["name"] for w in self.master_node.listwalletdir()["wallets"]]
1197  
1198          wo_wallet.unloadwallet()
1199  
1200      def test_manual_keys_import(self):
1201          self.log.info("Test migrating standalone private keys")
1202          wallet = self.create_legacy_wallet("import_privkeys", blank=True)
1203          privkey, pubkey = generate_keypair(wif=True)
1204          wallet.importprivkey(privkey=privkey, label="hi", rescan=False)
1205  
1206          # Migrate and verify
1207          res, wallet = self.migrate_and_get_rpc("import_privkeys")
1208  
1209          # There should be descriptors containing the imported key for: pk(), pkh(), sh(wpkh()), wpkh()
1210          key_origin = hash160(pubkey)[:4].hex()
1211          pubkey_hex = pubkey.hex()
1212          combo_desc = descsum_create(f"combo([{key_origin}]{pubkey_hex})")
1213  
1214          # Verify all expected descriptors were migrated
1215          migrated_desc = [item['desc'] for item in wallet.listdescriptors()['descriptors'] if pubkey.hex() in item['desc']]
1216          assert_equal([combo_desc], migrated_desc)
1217          wallet.unloadwallet()
1218  
1219          ######################################################
1220          self.log.info("Test migrating standalone public keys")
1221          wallet = self.create_legacy_wallet("import_pubkeys", blank=True)
1222          wallet.importpubkey(pubkey=pubkey_hex, rescan=False)
1223  
1224          res, _ = self.migrate_and_get_rpc("import_pubkeys")
1225  
1226          # Same as before, there should be descriptors in the watch-only wallet for the imported pubkey
1227          wo_wallet = self.nodes[0].get_wallet_rpc(res['wallet_name'])
1228          # Assert this is a watch-only wallet
1229          assert_equal(wo_wallet.getwalletinfo()["private_keys_enabled"], False)
1230          # As we imported the pubkey only, there will be no key origin in the following descriptors
1231          pk_desc = descsum_create(f'pk({pubkey_hex})')
1232          pkh_desc = descsum_create(f'pkh({pubkey_hex})')
1233          sh_wpkh_desc = descsum_create(f'sh(wpkh({pubkey_hex}))')
1234          wpkh_desc = descsum_create(f'wpkh({pubkey_hex})')
1235          expected_descs = [pk_desc, pkh_desc, sh_wpkh_desc, wpkh_desc]
1236  
1237          # Verify all expected descriptors were migrated
1238          migrated_desc = [item['desc'] for item in wo_wallet.listdescriptors()['descriptors']]
1239          assert_equal(expected_descs, migrated_desc)
1240          # Ensure that migrating a wallet with watch-only scripts does not create a spendable wallet.
1241          assert_equal('import_pubkeys_watchonly', res['wallet_name'])
1242          assert "import_pubkeys" not in self.master_node.listwallets()
1243          assert "import_pubkeys" not in [w["name"] for w in self.master_node.listwalletdir()["wallets"]]
1244          wo_wallet.unloadwallet()
1245  
1246      def test_p2wsh(self):
1247          self.log.info("Test that non-multisig P2WSH output scripts are migrated")
1248          def_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
1249  
1250          wallet = self.create_legacy_wallet("p2wsh")
1251  
1252          # Craft wsh(pkh(key))
1253          pubkey = wallet.getaddressinfo(wallet.getnewaddress())["pubkey"]
1254          pkh_script = key_to_p2pkh_script(pubkey).hex()
1255          wsh_pkh_script = script_to_p2wsh_script(pkh_script).hex()
1256          wsh_pkh_addr = script_to_p2wsh(pkh_script)
1257  
1258          # Legacy single key scripts (i.e. pkh(key) and pk(key)) are not inserted into mapScripts
1259          # automatically, they need to be imported directly if we want to receive to P2WSH (or P2SH)
1260          # wrappings of such scripts.
1261          wallet.importaddress(address=pkh_script, p2sh=False)
1262          wallet.importaddress(address=wsh_pkh_script, p2sh=False)
1263  
1264          def_wallet.sendtoaddress(wsh_pkh_addr, 5)
1265          self.generate(self.nodes[0], 6)
1266          assert_equal(wallet.getbalances()['mine']['trusted'], 5)
1267  
1268          _, wallet = self.migrate_and_get_rpc("p2wsh")
1269  
1270          assert_equal(wallet.getbalances()['mine']['trusted'], 5)
1271          addr_info = wallet.getaddressinfo(wsh_pkh_addr)
1272          assert_equal(addr_info["ismine"], True)
1273          assert_equal(addr_info["solvable"], True)
1274  
1275          wallet.unloadwallet()
1276  
1277      def test_disallowed_p2wsh(self):
1278          self.log.info("Test that P2WSH output scripts with invalid witnessScripts are not migrated and do not cause migration failure")
1279          def_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
1280  
1281          wallet = self.create_legacy_wallet("invalid_p2wsh")
1282  
1283          invalid_addrs = []
1284  
1285          # For a P2WSH output script stored in the legacy wallet's mapScripts, both the native P2WSH
1286          # and the P2SH-P2WSH are detected by IsMine. We need to verify that descriptors for both
1287          # output scripts are added to the resulting descriptor wallet.
1288          # However, this cannot be done using a multisig as wallet migration treats multisigs specially.
1289          # Instead, this is tested by importing a wsh(pkh()) script. But importing this directly will
1290          # insert the wsh() into setWatchOnly which means that the setWatchOnly migration ends up handling
1291          # this case, which we do not want.
1292          # In order to get the wsh(pkh()) into only mapScripts and not setWatchOnly, we need to utilize
1293          # importmulti and wrap the wsh(pkh()) inside of a sh(). This will insert the sh(wsh(pkh())) into
1294          # setWatchOnly but not the wsh(pkh()).
1295          # Furthermore, migration should not migrate the wsh(pkh()) if the key is uncompressed.
1296          comp_wif, comp_pubkey = generate_keypair(compressed=True, wif=True)
1297          comp_pkh_script = key_to_p2pkh_script(comp_pubkey).hex()
1298          comp_wsh_pkh_script = script_to_p2wsh_script(comp_pkh_script).hex()
1299          comp_sh_wsh_pkh_script = script_to_p2sh_script(comp_wsh_pkh_script).hex()
1300          comp_wsh_pkh_addr = script_to_p2wsh(comp_pkh_script)
1301  
1302          uncomp_wif, uncomp_pubkey = generate_keypair(compressed=False, wif=True)
1303          uncomp_pkh_script = key_to_p2pkh_script(uncomp_pubkey).hex()
1304          uncomp_wsh_pkh_script = script_to_p2wsh_script(uncomp_pkh_script).hex()
1305          uncomp_sh_wsh_pkh_script = script_to_p2sh_script(uncomp_wsh_pkh_script).hex()
1306          uncomp_wsh_pkh_addr = script_to_p2wsh(uncomp_pkh_script)
1307          invalid_addrs.append(uncomp_wsh_pkh_addr)
1308  
1309          import_res = wallet.importmulti([
1310              {
1311                  "scriptPubKey": comp_sh_wsh_pkh_script,
1312                  "timestamp": "now",
1313                  "redeemscript": comp_wsh_pkh_script,
1314                  "witnessscript": comp_pkh_script,
1315                  "keys": [
1316                      comp_wif,
1317                  ],
1318              },
1319              {
1320                  "scriptPubKey": uncomp_sh_wsh_pkh_script,
1321                  "timestamp": "now",
1322                  "redeemscript": uncomp_wsh_pkh_script,
1323                  "witnessscript": uncomp_pkh_script,
1324                  "keys": [
1325                      uncomp_wif,
1326                  ],
1327              },
1328          ])
1329          assert_equal(import_res[0]["success"], True)
1330          assert_equal(import_res[1]["success"], True)
1331  
1332          # Create a wsh(sh(pkh())) - P2SH inside of P2WSH is invalid
1333          comp_sh_pkh_script = script_to_p2sh_script(comp_pkh_script).hex()
1334          wsh_sh_pkh_script = script_to_p2wsh_script(comp_sh_pkh_script).hex()
1335          wsh_sh_pkh_addr = script_to_p2wsh(comp_sh_pkh_script)
1336          invalid_addrs.append(wsh_sh_pkh_addr)
1337  
1338          # Import wsh(sh(pkh()))
1339          wallet.importaddress(address=comp_sh_pkh_script, p2sh=False)
1340          wallet.importaddress(address=wsh_sh_pkh_script, p2sh=False)
1341  
1342          # Create a wsh(wsh(pkh())) - P2WSH inside of P2WSH is invalid
1343          wsh_wsh_pkh_script = script_to_p2wsh_script(comp_wsh_pkh_script).hex()
1344          wsh_wsh_pkh_addr = script_to_p2wsh(comp_wsh_pkh_script)
1345          invalid_addrs.append(wsh_wsh_pkh_addr)
1346  
1347          # Import wsh(wsh(pkh()))
1348          wallet.importaddress(address=wsh_wsh_pkh_script, p2sh=False)
1349  
1350          # The wsh(pkh()) with a compressed key is always valid, so we should see that the wallet detects it as ismine, not
1351          # watchonly, and can provide us information about the witnessScript via "embedded"
1352          comp_wsh_pkh_addr_info = wallet.getaddressinfo(comp_wsh_pkh_addr)
1353          assert_equal(comp_wsh_pkh_addr_info["ismine"], True)
1354          assert_equal(comp_wsh_pkh_addr_info["iswatchonly"], False)
1355          assert "embedded" in comp_wsh_pkh_addr_info
1356  
1357          # The invalid addresses are invalid, so the legacy wallet should not detect them as ismine,
1358          # nor consider them watchonly. However, because the legacy wallet has the witnessScripts/redeemScripts,
1359          # we should see information about those in "embedded"
1360          for addr in invalid_addrs:
1361              addr_info = wallet.getaddressinfo(addr)
1362              assert_equal(addr_info["ismine"], False)
1363              assert_equal(addr_info["iswatchonly"], False)
1364              assert "embedded" in addr_info
1365  
1366          # Fund those output scripts, although the invalid addresses will not have any balance.
1367          # This behavior follows as the addresses are not ismine.
1368          def_wallet.send([{comp_wsh_pkh_addr: 1}] + [{k: i + 1} for i, k in enumerate(invalid_addrs)])
1369          self.generate(self.nodes[0], 6)
1370          bal = wallet.getbalances()
1371          assert_equal(bal["mine"]["trusted"], 1)
1372          assert_equal(bal["watchonly"]["trusted"], 0)
1373  
1374          res, wallet = self.migrate_and_get_rpc("invalid_p2wsh")
1375          assert "watchonly_name" not in res
1376          assert "solvables_name" not in res
1377  
1378          assert_equal(wallet.getbalances()["mine"]["trusted"], 1)
1379  
1380          # After migration, the wsh(pkh()) with a compressed key is still valid and the descriptor wallet will have
1381          # information about the witnessScript
1382          comp_wsh_pkh_addr_info = wallet.getaddressinfo(comp_wsh_pkh_addr)
1383          assert_equal(comp_wsh_pkh_addr_info["ismine"], True)
1384          assert "embedded" in comp_wsh_pkh_addr_info
1385  
1386          # After migration, the invalid addresses should still not be detected as ismine and not watchonly.
1387          # The descriptor wallet should not have migrated these at all, so there should additionally be no
1388          # information in "embedded" about the witnessScripts/redeemScripts.
1389          for addr in invalid_addrs:
1390              addr_info = wallet.getaddressinfo(addr)
1391              assert_equal(addr_info["ismine"], False)
1392              assert "embedded" not in addr_info
1393  
1394          wallet.unloadwallet()
1395  
1396      def test_miniscript(self):
1397          # It turns out that due to how signing logic works, legacy wallets that have valid miniscript witnessScripts
1398          # and the private keys for them can still sign and spend them, even though output scripts involving them
1399          # as a witnessScript would not be detected as ISMINE_SPENDABLE.
1400          self.log.info("Test migration of a legacy wallet containing miniscript")
1401          def_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
1402          wallet = self.create_legacy_wallet("miniscript")
1403  
1404          privkey, _ = generate_keypair(compressed=True, wif=True)
1405  
1406          # Make a descriptor where we only have some of the keys. This will be migrated to the watchonly wallet.
1407          some_keys_priv_desc = descsum_create(f"wsh(or_b(pk({privkey}),s:pk(029ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0)))")
1408          some_keys_addr = self.master_node.deriveaddresses(some_keys_priv_desc)[0]
1409  
1410          # Make a descriptor where we have all of the keys. This will stay in the migrated wallet
1411          all_keys_priv_desc = descsum_create(f"wsh(and_v(v:pk({privkey}),1))")
1412          all_keys_addr = self.master_node.deriveaddresses(all_keys_priv_desc)[0]
1413  
1414          imp = wallet.importmulti([
1415              {
1416                  "desc": some_keys_priv_desc,
1417                  "timestamp": "now",
1418              },
1419              {
1420                  "desc": all_keys_priv_desc,
1421                  "timestamp": "now",
1422              }
1423          ])
1424          assert_equal(imp[0]["success"], True)
1425          assert_equal(imp[1]["success"], True)
1426  
1427          def_wallet.sendtoaddress(some_keys_addr, 1)
1428          def_wallet.sendtoaddress(all_keys_addr, 1)
1429          self.generate(self.master_node, 6)
1430          # Check that the miniscript can be spent by the legacy wallet
1431          send_res = wallet.send(outputs=[{some_keys_addr: 1},{all_keys_addr: 0.75}], include_watching=True, change_address=def_wallet.getnewaddress())
1432          assert_equal(send_res["complete"], True)
1433          self.generate(self.old_node, 6)
1434          assert_equal(wallet.getbalances()["watchonly"]["trusted"], 1.75)
1435  
1436          _, wallet = self.migrate_and_get_rpc("miniscript")
1437  
1438          # The miniscript with all keys should be in the migrated wallet
1439          assert_equal(wallet.getbalances()["mine"], {"trusted": 0.75, "untrusted_pending": 0, "immature": 0, "nonmempool": 0})
1440          assert_equal(wallet.getaddressinfo(all_keys_addr)["ismine"], True)
1441          assert_equal(wallet.getaddressinfo(some_keys_addr)["ismine"], False)
1442  
1443          # The miniscript with some keys should be in the watchonly wallet
1444          assert "miniscript_watchonly" in self.master_node.listwallets()
1445          watchonly = self.master_node.get_wallet_rpc("miniscript_watchonly")
1446          assert_equal(watchonly.getbalances()["mine"], {"trusted": 1, "untrusted_pending": 0, "immature": 0, "nonmempool": 0})
1447          assert_equal(watchonly.getaddressinfo(some_keys_addr)["ismine"], True)
1448          assert_equal(watchonly.getaddressinfo(all_keys_addr)["ismine"], False)
1449  
1450      def test_taproot(self):
1451          # It turns out that due to how signing logic works, legacy wallets that have the private key for a Taproot
1452          # output key will be able to sign and spend those scripts, even though they would not be detected as ISMINE_SPENDABLE.
1453          self.log.info("Test migration of Taproot scripts")
1454          def_wallet = self.master_node.get_wallet_rpc(self.default_wallet_name)
1455          wallet = self.create_legacy_wallet("taproot")
1456  
1457          privkey, _ = generate_keypair(compressed=True, wif=True)
1458  
1459          rawtr_desc = descsum_create(f"rawtr({privkey})")
1460          rawtr_addr = self.master_node.deriveaddresses(rawtr_desc)[0]
1461          rawtr_spk = self.master_node.validateaddress(rawtr_addr)["scriptPubKey"]
1462          tr_desc = descsum_create(f"tr({privkey})")
1463          tr_addr = self.master_node.deriveaddresses(tr_desc)[0]
1464          tr_spk = self.master_node.validateaddress(tr_addr)["scriptPubKey"]
1465          tr_script_desc = descsum_create(f"tr(9ffbe722b147f3035c87cb1c60b9a5947dd49c774cc31e94773478711a929ac0,pk({privkey}))")
1466          tr_script_addr = self.master_node.deriveaddresses(tr_script_desc)[0]
1467          tr_script_spk = self.master_node.validateaddress(tr_script_addr)["scriptPubKey"]
1468  
1469          wallet.importaddress(rawtr_spk)
1470          wallet.importaddress(tr_spk)
1471          wallet.importaddress(tr_script_spk)
1472          wallet.importprivkey(privkey)
1473  
1474          txid = def_wallet.send([{rawtr_addr: 1},{tr_addr: 2}, {tr_script_addr: 3}])["txid"]
1475          rawtr_vout = find_vout_for_address(self.master_node, txid, rawtr_addr)
1476          tr_vout = find_vout_for_address(self.master_node, txid, tr_addr)
1477          tr_script_vout = find_vout_for_address(self.master_node, txid, tr_script_addr)
1478          self.generate(self.master_node, 6)
1479          assert_equal(wallet.getbalances()["watchonly"]["trusted"], 6)
1480  
1481          # Check that the rawtr can be spent by the legacy wallet
1482          send_res = wallet.send(outputs=[{rawtr_addr: 0.5}], include_watching=True, change_address=def_wallet.getnewaddress(), inputs=[{"txid": txid, "vout": rawtr_vout}])
1483          assert_equal(send_res["complete"], True)
1484          self.generate(self.old_node, 6)
1485          assert_equal(wallet.getbalances()["watchonly"]["trusted"], 5.5)
1486          assert_equal(wallet.getbalances()["mine"]["trusted"], 0)
1487  
1488          # Check that the tr() cannot be spent by the legacy wallet
1489          send_res = wallet.send(outputs=[{def_wallet.getnewaddress(): 4}], include_watching=True, inputs=[{"txid": txid, "vout": tr_vout}, {"txid": txid, "vout": tr_script_vout}])
1490          assert_equal(send_res["complete"], False)
1491  
1492          res, wallet = self.migrate_and_get_rpc("taproot")
1493  
1494          # The rawtr should be migrated
1495          assert_equal(wallet.getbalances()["mine"], {"trusted": 0.5, "untrusted_pending": 0, "immature": 0, "nonmempool": 0})
1496          assert_equal(wallet.getaddressinfo(rawtr_addr)["ismine"], True)
1497          assert_equal(wallet.getaddressinfo(tr_addr)["ismine"], False)
1498          assert_equal(wallet.getaddressinfo(tr_script_addr)["ismine"], False)
1499  
1500          # The tr() with some keys should be in the watchonly wallet
1501          assert "taproot_watchonly" in self.master_node.listwallets()
1502          watchonly = self.master_node.get_wallet_rpc("taproot_watchonly")
1503          assert_equal(watchonly.getbalances()["mine"], {"trusted": 5, "untrusted_pending": 0, "immature": 0, "nonmempool": 0})
1504          assert_equal(watchonly.getaddressinfo(rawtr_addr)["ismine"], False)
1505          assert_equal(watchonly.getaddressinfo(tr_addr)["ismine"], True)
1506          assert_equal(watchonly.getaddressinfo(tr_script_addr)["ismine"], True)
1507  
1508      def test_no_load_after_migration(self):
1509          self.log.info("Test migration with load_wallet disabled")
1510          default = self.master_node.get_wallet_rpc(self.default_wallet_name)
1511  
1512          wallet_name = "no_load_after_migration"
1513          wallet = self.create_legacy_wallet(wallet_name)
1514          addr = wallet.getnewaddress()
1515          txid = default.sendtoaddress(addr, 1)
1516          self.generate(self.master_node, 1)
1517          bals = wallet.getbalances()
1518  
1519          # Copy wallet from old node to master node
1520          self.old_node.unloadwallet(wallet_name)
1521          shutil.copytree(
1522              self.old_node.wallets_path / wallet_name,
1523              self.master_node.wallets_path / wallet_name,
1524              dirs_exist_ok=True,
1525          )
1526  
1527          migrate_info = self.master_node.migratewallet(wallet_name=wallet_name, load_wallet=False)
1528  
1529          # The wallet should NOT be loaded after migration
1530          assert wallet_name not in self.master_node.listwallets()
1531  
1532          # The returned wallet_name should be populated correctly
1533          assert_equal(migrate_info["wallet_name"], wallet_name)
1534  
1535          # The backup path should be reported and exist on disk
1536          assert os.path.exists(migrate_info["backup_path"])
1537  
1538          # The migrated wallet files should be on disk in SQLite format
1539          self.assert_is_sqlite(wallet_name, wallet_loaded = False)
1540  
1541          # Load the wallet and verify its state is correct after migration
1542          self.master_node.loadwallet(wallet_name)
1543          loaded_wallet = self.master_node.get_wallet_rpc(wallet_name)
1544          info = loaded_wallet.getwalletinfo()
1545          assert_equal(info["descriptors"], True)
1546          assert_equal(info["format"], "sqlite")
1547          loaded_wallet.gettransaction(txid)
1548          assert_equal(loaded_wallet.getbalance(), bals["mine"]["trusted"])
1549          loaded_wallet.unloadwallet()
1550  
1551      def test_no_load_unnamed_wallet_does_not_autoload(self):
1552          self.log.info("Test no-load migration of unnamed wallet suppresses default autoload")
1553          self.master_node = self.nodes[0]
1554          self.old_node = self.nodes[1]
1555  
1556          wallet = self.create_legacy_wallet("", load_on_startup=False)
1557          wallet.unloadwallet()
1558  
1559          self.stop_node(0)
1560  
1561          def remove_wallet_setting(node):
1562              settings_path = node.chain_path / "settings.json"
1563              if not settings_path.exists():
1564                  return
1565              with settings_path.open(encoding="utf8") as settings_file:
1566                  settings = json.load(settings_file)
1567              settings.pop("wallet", None)
1568              with settings_path.open("w", encoding="utf8") as settings_file:
1569                  json.dump(settings, settings_file, indent=4)
1570                  settings_file.write("\n")
1571  
1572          remove_wallet_setting(self.master_node)
1573          (self.master_node.wallets_path / "wallet.dat").unlink(missing_ok=True)
1574          shutil.copyfile(self.old_node.wallets_path / "wallet.dat", self.master_node.wallets_path / "wallet.dat")
1575          self.start_node(0)
1576  
1577          assert_equal(self.master_node.listwallets(), [])
1578  
1579          migrate_info = self.master_node.migratewallet(wallet_name="", load_wallet=False)
1580          assert_equal(migrate_info["wallet_name"], "")
1581          backup_path = Path(migrate_info["backup_path"])
1582          assert "" not in self.master_node.listwallets()
1583  
1584          with (self.master_node.chain_path / "settings.json").open(encoding="utf8") as settings_file:
1585              wallet_setting_after_migration = json.load(settings_file).get("wallet")
1586  
1587          self.restart_node(0)
1588          wallets_after_restart = self.master_node.listwallets()
1589  
1590          assert "" not in wallets_after_restart
1591  
1592          self.clear_default_wallet(backup_path)
1593          self.master_node.loadwallet(self.default_wallet_name, load_on_startup=True)
1594  
1595          assert_equal(wallet_setting_after_migration, [])
1596  
1597      def test_no_load_reports_auxiliary_wallet_names(self):
1598          self.log.info("Test no-load migration reports auxiliary wallet names")
1599          wallet_name = "no_load_auxiliary_names"
1600          wallet = self.create_legacy_wallet(wallet_name)
1601  
1602          wallet.importaddress(address=self.master_node.get_wallet_rpc(self.default_wallet_name).getnewaddress(), rescan=False)
1603          _, pubkey = generate_keypair(compressed=True, wif=True)
1604          wallet.addmultisigaddress(nrequired=1, keys=[pubkey.hex()])
1605  
1606          # Simulate that the wallet was created by the master node with an old version
1607          # so it should already know the wallet prior migration. Set load_on_startup true,
1608          # to simulate that the wallet was automatically being loaded on each restart.
1609          self.master_node.createwallet(wallet_name=wallet_name, load_on_startup=True)
1610          self.master_node.unloadwallet(wallet_name)
1611          self.cleanup_folder(self.master_node.wallets_path / wallet_name)
1612          with (self.master_node.chain_path / "settings.json").open(encoding="utf8") as settings_file:
1613              assert wallet_name in json.load(settings_file).get("wallet", [])
1614  
1615          self.old_node.unloadwallet(wallet_name)
1616          shutil.copytree(self.old_node.wallets_path / wallet_name, self.master_node.wallets_path / wallet_name)
1617  
1618          migrate_info = self.master_node.migratewallet(wallet_name=wallet_name, load_wallet=False)
1619  
1620          assert_equal(migrate_info["wallet_name"], wallet_name)
1621          assert_equal(migrate_info["watchonly_name"], f"{wallet_name}_watchonly")
1622          assert_equal(migrate_info["solvables_name"], f"{wallet_name}_solvables")
1623          assert wallet_name not in self.master_node.listwallets()
1624          assert f"{wallet_name}_watchonly" not in self.master_node.listwallets()
1625          assert f"{wallet_name}_solvables" not in self.master_node.listwallets()
1626  
1627          # Migrate wallet with load_wallet=False should have overwritten the wallet settings
1628          # and the load_on_startup setting must removed.
1629          with (self.master_node.chain_path / "settings.json").open(encoding="utf8") as settings_file:
1630              startup_wallets = json.load(settings_file).get("wallet", [])
1631          assert wallet_name not in startup_wallets
1632          assert f"{wallet_name}_watchonly" not in startup_wallets
1633          assert f"{wallet_name}_solvables" not in startup_wallets
1634  
1635  
1636      def test_solvable_no_privs(self):
1637          self.log.info("Test migrating a multisig that we do not have any private keys for")
1638          wallet = self.create_legacy_wallet("multisig_noprivs")
1639  
1640          _, pubkey = generate_keypair(compressed=True, wif=True)
1641  
1642          add_ms_res = wallet.addmultisigaddress(nrequired=1, keys=[pubkey.hex()])
1643          addr = add_ms_res["address"]
1644  
1645          # The multisig address should be ISMINE_NO but we should have the script info
1646          addr_info = wallet.getaddressinfo(addr)
1647          assert_equal(addr_info["ismine"], False)
1648          assert "hex" in addr_info
1649  
1650          migrate_res, wallet = self.migrate_and_get_rpc("multisig_noprivs")
1651          assert_equal(migrate_res["solvables_name"], "multisig_noprivs_solvables")
1652          solvables = self.master_node.get_wallet_rpc(migrate_res["solvables_name"])
1653  
1654          # The multisig should not be in the spendable wallet
1655          addr_info = wallet.getaddressinfo(addr)
1656          assert_equal(addr_info["ismine"], False)
1657          assert "hex" not in addr_info
1658  
1659          # The multisig address should be in the solvables wallet
1660          addr_info = solvables.getaddressinfo(addr)
1661          assert_equal(addr_info["ismine"], True)
1662          assert_equal(addr_info["solvable"], True)
1663          assert "hex" in addr_info
1664  
1665      def test_loading_failure_after_migration(self):
1666          self.log.info("Test that a failed loading of the wallet at the end of migration restores the backup")
1667          self.stop_node(self.old_node.index)
1668          self.old_node.chain = "signet"
1669          self.old_node.replace_in_config([("regtest=", "signet="), ("[regtest]", "[signet]")])
1670          # Disable network sync and prevent disk space warning on small (tmp)fs
1671          self.start_node(self.old_node.index, extra_args=self.old_node.extra_args + ["-maxconnections=0", "-prune=550"])
1672  
1673          wallet_name = "failed_load_after_migrate"
1674          self.create_legacy_wallet(wallet_name)
1675          assert_raises_rpc_error(-4, "Wallet loading failed. Wallet files should not be reused across chains.", lambda: self.migrate_and_get_rpc(wallet_name))
1676  
1677          # Check the wallet we tried to migrate is still BDB
1678          self.assert_is_bdb(wallet_name)
1679  
1680          self.stop_node(self.old_node.index)
1681          self.old_node.chain = "regtest"
1682          self.old_node.replace_in_config([("signet=", "regtest="), ("[signet]", "[regtest]")])
1683          self.start_node(self.old_node.index)
1684          self.connect_nodes(1, 0)
1685  
1686      def unsynced_wallet_on_pruned_node_fails(self):
1687          self.log.info("Test migration of an unsynced wallet on a pruned node fails gracefully if loadwallet is set")
1688          wallet = self.create_legacy_wallet("", load_on_startup=False)
1689          last_wallet_synced_block = wallet.getwalletinfo()['lastprocessedblock']['height']
1690          wallet.unloadwallet()
1691  
1692          shutil.copyfile(self.old_node.wallets_path / "wallet.dat", self.master_node.wallets_path / "wallet.dat")
1693  
1694          # Generate blocks just so the wallet best block is pruned
1695          self.restart_node(0, ["-fastprune", "-prune=1", "-nowallet"])
1696          self.connect_nodes(0, 1)
1697          self.generate(self.master_node, 450, sync_fun=self.no_op)
1698          self.master_node.pruneblockchain(250)
1699          # Ensure next block to sync is unavailable
1700          assert_raises_rpc_error(-1, "Block not available (pruned data)", self.master_node.getblock, self.master_node.getblockhash(last_wallet_synced_block + 1))
1701  
1702          # Check migration failure
1703          mocked_time = int(time.time())
1704          self.master_node.setmocktime(mocked_time)
1705          assert_raises_rpc_error(-4, "last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of a pruned node)", self.master_node.migratewallet, wallet_name="")
1706          self.master_node.setmocktime(0)
1707  
1708          # Verify the /wallets/ path exists, the wallet is still BDB and the backup file is there.
1709          assert self.master_node.wallets_path.exists()
1710          self.assert_is_bdb("")
1711          backup_path = self.master_node.wallets_path / f"default_wallet_{mocked_time}.legacy.bak"
1712          assert backup_path.exists()
1713  
1714          self.log.info("Test migration of an unsynced wallet on a pruned node does not fails if loadwallet is not set")
1715          self.master_node.migratewallet("", load_wallet=False)
1716          # The wallet should NOT be loaded after migration
1717          assert "" not in self.master_node.listwallets()
1718          # Load the wallet should fail
1719          assert_raises_rpc_error(-4, "last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of a pruned node)", self.master_node.loadwallet, filename="")
1720  
1721          self.clear_default_wallet(backup_path)
1722  
1723      @staticmethod
1724      def erase_bdb_record(wallet_dat_path, key):
1725          data = bytearray(wallet_dat_path.read_bytes())
1726          idx = data.find(key)
1727          assert idx != -1, f"{key!r} not found in wallet.dat"
1728  
1729          for i in range(idx, idx + len(key)):
1730              data[i] = 0
1731  
1732          wallet_dat_path.write_bytes(data)
1733  
1734      def test_missing_bestblock(self):
1735          self.log.info("Test migrating legacy BDB wallet without bestblock record")
1736          wallet_name = "nobestblock"
1737          wallet = self.create_legacy_wallet(wallet_name)
1738          wallet.unloadwallet()
1739  
1740          # Erase block locator records like if this would be a pre-#152 wallet
1741          self.erase_bdb_record(self.old_node.wallets_path / wallet_name / "wallet.dat", b"bestblock_nomerkle")
1742          self.erase_bdb_record(self.old_node.wallets_path / wallet_name / "wallet.dat", b"bestblock")
1743  
1744          shutil.copytree(self.old_node.wallets_path / wallet_name, self.master_node.wallets_path / wallet_name, dirs_exist_ok=True)
1745          # Migrate, checking that rescan occurs
1746          with self.master_node.assert_debug_log(expected_msgs=["Rescanning"], unexpected_msgs=[]):
1747              self.master_node.migratewallet(wallet_name)
1748  
1749          wallet = self.master_node.get_wallet_rpc(wallet_name)
1750          info = wallet.getwalletinfo()
1751          assert_equal(info["descriptors"], True)
1752          assert_equal(info["format"], "sqlite")
1753  
1754      def run_test(self):
1755          self.master_node = self.nodes[0]
1756          self.old_node = self.nodes[1]
1757  
1758          self.generate(self.master_node, 101)
1759  
1760          # TODO: Test the actual records in the wallet for these tests too. The behavior may be correct, but the data written may not be what we actually want
1761          self.test_basic()
1762          self.test_multisig()
1763          self.test_other_watchonly()
1764          self.test_no_privkeys()
1765          self.test_pk_coinbases()
1766          self.test_encrypted()
1767          self.test_nonexistent()
1768          self.test_unloaded_by_path()
1769          self.test_wallet_with_path("path/to/trailing/")
1770          self.test_wallet_with_path("path/to/mywallet")
1771  
1772          migration_failure_cases = [
1773              "",
1774              os.path.abspath(self.master_node.datadir_path / "absolute_path"),
1775              "normallynamedwallet"
1776          ]
1777          for wallet_name in migration_failure_cases:
1778              self.test_migration_failure(wallet_name=wallet_name)
1779  
1780          self.test_default_wallet()
1781          self.test_default_wallet_watch_only()
1782          self.test_direct_file()
1783          self.test_addressbook()
1784          self.test_migrate_raw_p2sh()
1785          self.test_conflict_txs()
1786          self.test_hybrid_pubkey()
1787          self.test_failed_migration_cleanup()
1788          self.test_avoidreuse()
1789          self.test_preserve_tx_extra_info()
1790          self.test_blank()
1791          self.test_migrate_simple_watch_only()
1792          self.test_manual_keys_import()
1793          self.test_p2wsh()
1794          self.test_disallowed_p2wsh()
1795          self.test_miniscript()
1796          self.test_taproot()
1797          self.test_no_load_after_migration()
1798          self.test_no_load_unnamed_wallet_does_not_autoload()
1799          self.test_no_load_reports_auxiliary_wallet_names()
1800          self.test_solvable_no_privs()
1801          self.test_loading_failure_after_migration()
1802          self.test_missing_bestblock()
1803  
1804          # Note: After this test the first 250 blocks of 'master_node' are pruned
1805          self.unsynced_wallet_on_pruned_node_fails()
1806  
1807  if __name__ == '__main__':
1808      WalletMigrationTest(__file__).main()
1809