diff options
-rwxr-xr-x | test/functional/wallet_backwards_compatibility.py | 341 | ||||
-rwxr-xr-x | test/get_previous_releases.py | 9 |
2 files changed, 203 insertions, 147 deletions
diff --git a/test/functional/wallet_backwards_compatibility.py b/test/functional/wallet_backwards_compatibility.py index 49e36b21c5..4d6e6024c5 100755 --- a/test/functional/wallet_backwards_compatibility.py +++ b/test/functional/wallet_backwards_compatibility.py @@ -33,11 +33,12 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 11 + self.num_nodes = 12 # Add new version after each release: self.extra_args = [ ["-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to mine blocks. noban for immediate tx relay ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to receive coins, swap wallets, etc + ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v25.0 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v24.0.1 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v23.0 ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v22.0 @@ -58,6 +59,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): self.add_nodes(self.num_nodes, extra_args=self.extra_args, versions=[ None, None, + 250000, 240001, 230000, 220000, @@ -72,20 +74,72 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): self.start_nodes() self.import_deterministic_coinbase_privkeys() - def nodes_wallet_dir(self, node): - if node.version < 170000: - return node.chain_path - return node.wallets_path + def split_version(self, node): + major = node.version // 10000 + minor = (node.version % 10000) // 100 + patch = (node.version % 100) + return (major, minor, patch) + + def major_version_equals(self, node, major): + node_major, _, _ = self.split_version(node) + return node_major == major + + def major_version_less_than(self, node, major): + node_major, _, _ = self.split_version(node) + return node_major < major + + def major_version_at_least(self, node, major): + node_major, _, _ = self.split_version(node) + return node_major >= major + + def test_v19_addmultisigaddress(self): + if not self.is_bdb_compiled(): + return + # Specific test for addmultisigaddress using v19 + # See #18075 + self.log.info("Testing 0.19 addmultisigaddress case (#18075)") + node_master = self.nodes[1] + node_v19 = self.nodes[self.num_nodes - 4] + node_v19.rpc.createwallet(wallet_name="w1_v19") + wallet = node_v19.get_wallet_rpc("w1_v19") + info = wallet.getwalletinfo() + assert info['private_keys_enabled'] + assert info['keypoolsize'] > 0 + # Use addmultisigaddress (see #18075) + address_18075 = wallet.rpc.addmultisigaddress(1, ["0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52", "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073"], "", "legacy")["address"] + assert wallet.getaddressinfo(address_18075)["solvable"] + node_v19.unloadwallet("w1_v19") + + # Copy the 0.19 wallet to the last Bitcoin Core version and open it: + shutil.copytree( + os.path.join(node_v19.wallets_path, "w1_v19"), + os.path.join(node_master.wallets_path, "w1_v19") + ) + node_master.loadwallet("w1_v19") + wallet = node_master.get_wallet_rpc("w1_v19") + assert wallet.getaddressinfo(address_18075)["solvable"] + + # Now copy that same wallet back to 0.19 to make sure no automatic upgrade breaks it + node_master.unloadwallet("w1_v19") + shutil.rmtree(os.path.join(node_v19.wallets_path, "w1_v19")) + shutil.copytree( + os.path.join(node_master.wallets_path, "w1_v19"), + os.path.join(node_v19.wallets_path, "w1_v19") + ) + node_v19.loadwallet("w1_v19") + wallet = node_v19.get_wallet_rpc("w1_v19") + assert wallet.getaddressinfo(address_18075)["solvable"] def run_test(self): node_miner = self.nodes[0] node_master = self.nodes[1] - node_v19 = self.nodes[self.num_nodes - 4] - node_v18 = self.nodes[self.num_nodes - 3] + node_v21 = self.nodes[self.num_nodes - 6] node_v17 = self.nodes[self.num_nodes - 2] node_v16 = self.nodes[self.num_nodes - 1] - legacy_nodes = self.nodes[2:] + legacy_nodes = self.nodes[2:] # Nodes that support legacy wallets + legacy_only_nodes = self.nodes[-5:] # Nodes that only support legacy wallets + descriptors_nodes = self.nodes[2:-5] # Nodes that support descriptor wallets self.generatetoaddress(node_miner, COINBASE_MATURITY + 1, node_miner.getnewaddress()) @@ -121,24 +175,6 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): # Abandon transaction, but don't confirm node_master.abandontransaction(tx3_id) - # w1_v19: regular wallet, created with v0.19 - node_v19.rpc.createwallet(wallet_name="w1_v19") - wallet = node_v19.get_wallet_rpc("w1_v19") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] > 0 - # Use addmultisigaddress (see #18075) - address_18075 = wallet.rpc.addmultisigaddress(1, ["0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52", "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073"], "", "legacy")["address"] - assert wallet.getaddressinfo(address_18075)["solvable"] - node_v19.unloadwallet("w1_v19") - - # w1_v18: regular wallet, created with v0.18 - node_v18.rpc.createwallet(wallet_name="w1_v18") - wallet = node_v18.get_wallet_rpc("w1_v18") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] > 0 - # w2: wallet with private keys disabled, created on master: update this # test when default wallets private keys disabled can no longer be # opened by older versions. @@ -158,9 +194,6 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): # Unload wallets and copy to older nodes: node_master_wallets_dir = node_master.wallets_path - node_v19_wallets_dir = node_v19.wallets_path - node_v17_wallets_dir = node_v17.wallets_path - node_v16_wallets_dir = node_v16.chain_path node_master.unloadwallet("w1") node_master.unloadwallet("w2") node_master.unloadwallet("w3") @@ -168,24 +201,35 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): for node in legacy_nodes: # Copy wallets to previous version for wallet in os.listdir(node_master_wallets_dir): - shutil.copytree( - os.path.join(node_master_wallets_dir, wallet), - os.path.join(self.nodes_wallet_dir(node), wallet) - ) - - if not self.options.descriptors: - # Descriptor wallets break compatibility, only run this test for legacy wallet - # Load modern wallet with older nodes - for node in legacy_nodes: - for wallet_name in ["w1", "w2", "w3"]: - if node.version < 170000: - # loadwallet was introduced in v0.17.0 - continue - if node.version < 180000 and wallet_name == "w3": - # Blank wallets were introduced in v0.18.0. We test the loading error below. - continue - node.loadwallet(wallet_name) - wallet = node.get_wallet_rpc(wallet_name) + dest = node.wallets_path / wallet + source = node_master_wallets_dir / wallet + if self.major_version_equals(node, 16): + # 0.16 node expect the wallet to be in the wallet dir but as a plain file rather than in directories + shutil.copyfile(source / "wallet.dat", dest) + else: + shutil.copytree(source, dest) + + self.test_v19_addmultisigaddress() + + self.log.info("Test that a wallet made on master can be opened on:") + # In descriptors wallet mode, run this test on the nodes that support descriptor wallets + # In legacy wallets mode, run this test on the nodes that support legacy wallets + for node in descriptors_nodes if self.options.descriptors else legacy_nodes: + if self.major_version_less_than(node, 17): + # loadwallet was introduced in v0.17.0 + continue + self.log.info(f"- {node.version}") + for wallet_name in ["w1", "w2", "w3"]: + if self.major_version_less_than(node, 18) and wallet_name == "w3": + # Blank wallets were introduced in v0.18.0. We test the loading error below. + continue + if self.major_version_less_than(node, 22) and wallet_name == "w1" and self.options.descriptors: + # Descriptor wallets created after 0.21 have taproot descriptors which 0.21 does not support, tested below + continue + # Also try to reopen on master after opening on old + for n in [node, node_master]: + n.loadwallet(wallet_name) + wallet = n.get_wallet_rpc(wallet_name) info = wallet.getwalletinfo() if wallet_name == "w1": assert info['private_keys_enabled'] == True @@ -208,130 +252,133 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): else: assert info['private_keys_enabled'] == True assert info['keypoolsize'] == 0 - else: - for node in legacy_nodes: - # Descriptor wallets appear to be corrupted wallets to old software - # and loadwallet is introduced in v0.17.0 - if node.version >= 170000 and node.version < 210000: - for wallet_name in ["w1", "w2", "w3"]: - assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node.loadwallet, wallet_name) - # RPC loadwallet failure causes bitcoind to exit, in addition to the RPC - # call failure, so the following test won't work: - # assert_raises_rpc_error(-4, "Wallet loading failed.", node_v17.loadwallet, 'w3') + # Copy back to master + wallet.unloadwallet() + if n == node: + shutil.rmtree(node_master.wallets_path / wallet_name) + shutil.copytree( + n.wallets_path / wallet_name, + node_master.wallets_path / wallet_name, + ) + + # Check that descriptor wallets don't work on legacy only nodes + if self.options.descriptors: + self.log.info("Test descriptor wallet incompatibility on:") + for node in legacy_only_nodes: + # RPC loadwallet failure causes bitcoind to exit in <= 0.17, in addition to the RPC + # call failure, so the following test won't work: + # assert_raises_rpc_error(-4, "Wallet loading failed.", node_v17.loadwallet, 'w3') + if self.major_version_less_than(node, 18): + continue + self.log.info(f"- {node.version}") + # Descriptor wallets appear to be corrupted wallets to old software + assert self.major_version_at_least(node, 18) and self.major_version_less_than(node, 21) + for wallet_name in ["w1", "w2", "w3"]: + assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node.loadwallet, wallet_name) # Instead, we stop node and try to launch it with the wallet: self.stop_node(node_v17.index) if self.options.descriptors: + self.log.info("Test descriptor wallet incompatibility with 0.17") # Descriptor wallets appear to be corrupted wallets to old software node_v17.assert_start_raises_init_error(["-wallet=w1"], "Error: wallet.dat corrupt, salvage failed") node_v17.assert_start_raises_init_error(["-wallet=w2"], "Error: wallet.dat corrupt, salvage failed") node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: wallet.dat corrupt, salvage failed") else: + self.log.info("Test blank wallet incompatibility with v17") node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: Error loading w3: Wallet requires newer version of Bitcoin Core") self.start_node(node_v17.index) - if not self.options.descriptors: - # Descriptor wallets break compatibility, only run this test for legacy wallets - # Open most recent wallet in v0.16 (no loadwallet RPC) - self.restart_node(node_v16.index, extra_args=["-wallet=w2"]) - wallet = node_v16.get_wallet_rpc("w2") - info = wallet.getwalletinfo() - assert info['keypoolsize'] == 1 - - # Create upgrade wallet in v0.16 - self.restart_node(node_v16.index, extra_args=["-wallet=u1_v16"]) - wallet = node_v16.get_wallet_rpc("u1_v16") - v16_addr = wallet.getnewaddress('', "bech32") - v16_info = wallet.validateaddress(v16_addr) - v16_pubkey = v16_info['pubkey'] + # No wallet created in master can be opened in 0.16 + self.log.info("Test that wallets created in master are too new for 0.16") self.stop_node(node_v16.index) + for wallet_name in ["w1", "w2", "w3"]: + if self.options.descriptors: + node_v16.assert_start_raises_init_error([f"-wallet={wallet_name}"], f"Error: {wallet_name} corrupt, salvage failed") + else: + node_v16.assert_start_raises_init_error([f"-wallet={wallet_name}"], f"Error: Error loading {wallet_name}: Wallet requires newer version of Bitcoin Core") + + # When descriptors are enabled, w1 cannot be opened by 0.21 since it contains a taproot descriptor + if self.options.descriptors: + self.log.info("Test that 0.21 cannot open wallet containing tr() descriptors") + assert_raises_rpc_error(-1, "map::at", node_v21.loadwallet, "w1") + + self.log.info("Test that a wallet can upgrade to and downgrade from master, from:") + for node in descriptors_nodes if self.options.descriptors else legacy_nodes: + self.log.info(f"- {node.version}") + wallet_name = f"up_{node.version}" + if self.major_version_less_than(node, 17): + # createwallet is only available in 0.17+ + self.restart_node(node.index, extra_args=[f"-wallet={wallet_name}"]) + wallet_prev = node.get_wallet_rpc(wallet_name) + address = wallet_prev.getnewaddress('', "bech32") + addr_info = wallet_prev.validateaddress(address) + else: + if self.major_version_at_least(node, 21): + node.rpc.createwallet(wallet_name=wallet_name, descriptors=self.options.descriptors) + else: + node.rpc.createwallet(wallet_name=wallet_name) + wallet_prev = node.get_wallet_rpc(wallet_name) + address = wallet_prev.getnewaddress('', "bech32") + addr_info = wallet_prev.getaddressinfo(address) + + hdkeypath = addr_info["hdkeypath"].replace("'", "h") + pubkey = addr_info["pubkey"] + + # 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 + if self.major_version_at_least(node, 17): + wallet_prev.unloadwallet() + else: + self.stop_node(node.index) + + # Restore the wallet to master + load_res = node_master.restorewallet(wallet_name, backup_path) - self.log.info("Test wallet upgrade path...") - # u1: regular wallet, created with v0.17 - node_v17.rpc.createwallet(wallet_name="u1_v17") - wallet = node_v17.get_wallet_rpc("u1_v17") - address = wallet.getnewaddress("bech32") - v17_info = wallet.getaddressinfo(address) - hdkeypath = v17_info["hdkeypath"].replace("'", "h") - pubkey = v17_info["pubkey"] - - if self.is_bdb_compiled(): - # Old wallets are BDB and will only work if BDB is compiled - # Copy the 0.16 wallet to the last Bitcoin Core version and open it: - shutil.copyfile( - os.path.join(node_v16_wallets_dir, "wallets/u1_v16"), - os.path.join(node_master_wallets_dir, "u1_v16") - ) - load_res = node_master.loadwallet("u1_v16") # Make sure this wallet opens with only the migration warning. See https://github.com/bitcoin/bitcoin/pull/19054 - if int(node_master.getnetworkinfo()["version"]) >= 249900: - # loadwallet#warnings (added in v25) -- only present if there is a warning + if not self.options.descriptors: # Legacy wallets will have only a deprecation warning assert_equal(load_res["warnings"], ["Wallet loaded successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future. Legacy wallets can be migrated to a descriptor wallet with migratewallet."]) else: - # loadwallet#warning (deprecated in v25) -- always present, but empty string if no warning - assert_equal(load_res["warning"], '') - wallet = node_master.get_wallet_rpc("u1_v16") - info = wallet.getaddressinfo(v16_addr) - descriptor = f"wpkh([{info['hdmasterfingerprint']}{hdkeypath[1:]}]{v16_pubkey})" - assert_equal(info["desc"], descsum_create(descriptor)) + assert "warnings" not in load_res - # Now copy that same wallet back to 0.16 to make sure no automatic upgrade breaks it - node_master.unloadwallet("u1_v16") - os.remove(os.path.join(node_v16_wallets_dir, "wallets/u1_v16")) - shutil.copyfile( - os.path.join(node_master_wallets_dir, "u1_v16"), - os.path.join(node_v16_wallets_dir, "wallets/u1_v16") - ) - self.start_node(node_v16.index, extra_args=["-wallet=u1_v16"]) - wallet = node_v16.get_wallet_rpc("u1_v16") - info = wallet.validateaddress(v16_addr) - assert_equal(info, v16_info) - - # Copy the 0.17 wallet to the last Bitcoin Core version and open it: - node_v17.unloadwallet("u1_v17") - shutil.copytree( - os.path.join(node_v17_wallets_dir, "u1_v17"), - os.path.join(node_master_wallets_dir, "u1_v17") - ) - node_master.loadwallet("u1_v17") - wallet = node_master.get_wallet_rpc("u1_v17") + wallet = node_master.get_wallet_rpc(wallet_name) info = wallet.getaddressinfo(address) descriptor = f"wpkh([{info['hdmasterfingerprint']}{hdkeypath[1:]}]{pubkey})" assert_equal(info["desc"], descsum_create(descriptor)) - # Now copy that same wallet back to 0.17 to make sure no automatic upgrade breaks it - node_master.unloadwallet("u1_v17") - shutil.rmtree(os.path.join(node_v17_wallets_dir, "u1_v17")) - shutil.copytree( - os.path.join(node_master_wallets_dir, "u1_v17"), - os.path.join(node_v17_wallets_dir, "u1_v17") - ) - node_v17.loadwallet("u1_v17") - wallet = node_v17.get_wallet_rpc("u1_v17") - info = wallet.getaddressinfo(address) - assert_equal(info, v17_info) - - # Copy the 0.19 wallet to the last Bitcoin Core version and open it: - shutil.copytree( - os.path.join(node_v19_wallets_dir, "w1_v19"), - os.path.join(node_master_wallets_dir, "w1_v19") - ) - node_master.loadwallet("w1_v19") - wallet = node_master.get_wallet_rpc("w1_v19") - assert wallet.getaddressinfo(address_18075)["solvable"] - - # Now copy that same wallet back to 0.19 to make sure no automatic upgrade breaks it - node_master.unloadwallet("w1_v19") - shutil.rmtree(os.path.join(node_v19_wallets_dir, "w1_v19")) - shutil.copytree( - os.path.join(node_master_wallets_dir, "w1_v19"), - os.path.join(node_v19_wallets_dir, "w1_v19") - ) - node_v19.loadwallet("w1_v19") - wallet = node_v19.get_wallet_rpc("w1_v19") - assert wallet.getaddressinfo(address_18075)["solvable"] + # Make backup so the wallet can be copied back to old node + down_wallet_name = f"re_down_{node.version}" + down_backup_path = os.path.join(self.options.tmpdir, f"{down_wallet_name}.dat") + wallet.backupwallet(down_backup_path) + wallet.unloadwallet() + + # Check that no automatic upgrade broke the downgrading the wallet + if self.major_version_less_than(node, 17): + # loadwallet is only available in 0.17+ + shutil.copyfile( + down_backup_path, + node.wallets_path / down_wallet_name + ) + self.start_node(node.index, extra_args=[f"-wallet={down_wallet_name}"]) + wallet_res = node.get_wallet_rpc(down_wallet_name) + info = wallet_res.validateaddress(address) + assert_equal(info, addr_info) + else: + 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) if __name__ == '__main__': BackwardsCompatibilityTest().main() diff --git a/test/get_previous_releases.py b/test/get_previous_releases.py index 07a5ee8691..459693102b 100755 --- a/test/get_previous_releases.py +++ b/test/get_previous_releases.py @@ -89,6 +89,15 @@ SHA256_SUMS = { "6b163cef7de4beb07b8cb3347095e0d76a584019b1891135cd1268a1f05b9d88": {"tag": "v24.0.1", "tarball": "bitcoin-24.0.1-riscv64-linux-gnu.tar.gz"}, "e2f751512f3c0f00eb68ba946d9c829e6cf99422a61e8f5e0a7c109c318674d0": {"tag": "v24.0.1", "tarball": "bitcoin-24.0.1-x86_64-apple-darwin.tar.gz"}, "49df6e444515d457ea0b885d66f521f2a26ca92ccf73d5296082e633544253bf": {"tag": "v24.0.1", "tarball": "bitcoin-24.0.1-x86_64-linux-gnu.tar.gz"}, + + "3a7bdd959a0b426624f63f394f25e5b7769a5a2f96f8126dcc2ea53f3fa5212b": {"tag": "v25.0", "tarball": "bitcoin-25.0-aarch64-linux-gnu.tar.gz"}, + "e537c8630b05e63242d979c3004f851fd73c2a10b5b4fdbb161788427c7b3c0f": {"tag": "v25.0", "tarball": "bitcoin-25.0-arm-linux-gnueabihf.tar.gz"}, + "3b35075d6c1209743611c705a13575be2668bc069bc6301ce78a2e1e53ebe7cc": {"tag": "v25.0", "tarball": "bitcoin-25.0-arm64-apple-darwin.tar.gz"}, + "0c8e135a6fd297270d3b65196042d761453493a022b5ff7fb847fc911e938214": {"tag": "v25.0", "tarball": "bitcoin-25.0-powerpc64-linux-gnu.tar.gz"}, + "fa8af160782f5adfcea570f72b947073c1663b3e9c3cd0f82b216b609fe47573": {"tag": "v25.0", "tarball": "bitcoin-25.0-powerpc64le-linux-gnu.tar.gz"}, + "fe6e347a66043946920c72c9c4afca301968101e6b82fb90a63d7885ebcceb32": {"tag": "v25.0", "tarball": "bitcoin-25.0-riscv64-linux-gnu.tar.gz"}, + "5708fc639cdfc27347cccfd50db9b73b53647b36fb5f3a4a93537cbe8828c27f": {"tag": "v25.0", "tarball": "bitcoin-25.0-x86_64-apple-darwin.tar.gz"}, + "33930d432593e49d58a9bff4c30078823e9af5d98594d2935862788ce8a20aec": {"tag": "v25.0", "tarball": "bitcoin-25.0-x86_64-linux-gnu.tar.gz"}, } |