Repository: bitcoin/bitcoin
File: test/functional/wallet_backwards_compatibility.py (Python)

Code before:
            shutil.copyfile(
                down_backup_path,
                target_dir / "wallet.dat"
            )
            node.loadwallet(down_wallet_name)
            wallet_res = node.get_wallet_rpc(down_wallet_name)
            info = wallet_res.getaddressinfo(address)
            assert_equal(info, addr_info)

        self.log.info("Test that a wallet from a legacy only node must be migrated, from:")
        for node in legacy_nodes:
            self.log.info(f"- {node.version}")
            wallet_name = f"legacy_up_{node.version}"
            if self.major_version_at_least(node, 21):
                node.createwallet(wallet_name=wallet_name, descriptors=False)
            else:
                node.createwallet(wallet_name=wallet_name)
            wallet_prev = node.get_wallet_rpc(wallet_name)
            address = wallet_prev.getnewaddress('', "bech32")
            addr_info = wallet_prev.getaddressinfo(address)

            # Make a backup of the wallet file
            backup_path = os.path.join(self.options.tmpdir, f"{wallet_name}.dat")
            wallet_prev.backupwallet(backup_path)

            # Remove the wallet from old node
            wallet_prev.unloadwallet()

            # Restore the wallet to master
            # Legacy wallets are no longer supported. Trying to load these should result in an error
            assert_raises_rpc_error(-18, "The wallet appears to be a Legacy wallet, please use the wallet migration tool (migratewallet RPC or the GUI option)", node_master.restorewallet, wallet_name, backup_path)

        self.test_v22_inactivehdchain_path()
        self.test_ignore_legacy_during_startup(legacy_nodes, node_master)

if __name__ == '__main__':
    BackwardsCompatibilityTest(__file__).main()


Review comment (performance):
In 3854d6b56c50b26f7b61dfb37f5230726d561cf9 "tests: Check that the last hardened cache upgrade occurs"

At the stage where there are multiple such instances of hardcoding the hex representation of the database keys in the tests, I would introduce a `get_db_key_hex` utility. Mentioned in overall review comment.

Code after:

            # Open the wallet with sqlite and inspect the flags and records
            def check_upgraded_records(conn, old_flags):
                flags_rec = conn.execute(f"SELECT value FROM main WHERE key = x'{ser_string(b'flags').hex()}'").fetchone()
                new_flags = int.from_bytes(flags_rec[0], byteorder="little")
                diff_flags = new_flags & ~old_flags

                # Check for last hardened xpubs if the flag is newly set
                if diff_flags & (1 << 2):
                    self.log.debug("Checking descriptor cache was upgraded")
                    # Fetch all records with the walletdescriptorlhcache prefix
                    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()
                    assert_greater_than(len(lh_cache_recs), 0)

            self.inspect_sqlite_db(down_backup_path, check_upgraded_records, old_flags)

            # Check that no automatic upgrade broke downgrading the wallet
            target_dir = node.wallets_path / down_wallet_name
            os.makedirs(target_dir, exist_ok=True)
            shutil.copyfile(
                down_backup_path,
                target_dir / "wallet.dat"
            )
            node.loadwallet(down_wallet_name)
            wallet_res = node.get_wallet_rpc(down_wallet_name)
            info = wallet_res.getaddressinfo(address)
            assert_equal(info, addr_info)

        self.log.info("Test that a wallet from a legacy only node must be migrated, from:")
        for node in legacy_nodes:
            self.log.info(f"- {node.version}")
            wallet_name = f"legacy_up_{node.version}"
            if self.major_version_at_least(node, 21):
                node.createwallet(wallet_name=wallet_name, descriptors=False)
            else:
                node.createwallet(wallet_name=wallet_name)
            wallet_prev = node.get_wallet_rpc(wallet_name)
            address = wallet_prev.getnewaddress('', "bech32")
            addr_info = wallet_prev.getaddressinfo(address)

            # Make a backup of the wallet file
            backup_path = os.path.join(self.options.tmpdir, f"{wallet_name}.dat")
            wallet_prev.backupwallet(backup_path)

            # Remove the wallet from old node
            wallet_prev.unloadwallet()

            # Restore the wallet to master
            # Legacy wallets are no longer supported. Trying to load these should result in an error
            assert_raises_rpc_error(-18, "The wallet appears to be a Legacy wallet, please use the wallet migration tool (migratewallet RPC or the GUI option)", node_master.restorewallet, wallet_name, backup_path)