1#!/usr/bin/env python3 2# Copyright (c) 2018-2019 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"""Backwards compatibility functional test 6 7Test various backwards compatibility scenarios. Download the previous node binaries: 8 9contrib/devtools/previous_release.sh -b v0.19.0.1 v0.18.1 v0.17.1 10 11Due to RPC changes introduced in various versions the below tests 12won't work for older versions without some patches or workarounds. 13 14Use only the latest patch version of each release, unless a test specifically 15needs an older patch version. 16""" 17 18import os 19import shutil 20 21from test_framework.test_framework import BitcoinTestFramework, SkipTest 22from test_framework.descriptors import descsum_create 23 24from test_framework.util import ( 25 assert_equal, 26 sync_blocks, 27 sync_mempools 28) 29 30class BackwardsCompatibilityTest(BitcoinTestFramework): 31 def set_test_params(self): 32 self.setup_clean_chain = True 33 self.num_nodes = 5 34 # Add new version after each release: 35 self.extra_args = [ 36 ["-addresstype=bech32"], # Pre-release: use to mine blocks 37 ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # Pre-release: use to receive coins, swap wallets, etc 38 ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.19.0.1 39 ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.18.1 40 ["-nowallet", "-walletrbf=1", "-addresstype=bech32"] # v0.17.1 41 ] 42 43 def skip_test_if_missing_module(self): 44 self.skip_if_no_wallet() 45 46 def setup_nodes(self): 47 if os.getenv("TEST_PREVIOUS_RELEASES") == "false": 48 raise SkipTest("backwards compatibility tests") 49 50 releases_path = os.getenv("PREVIOUS_RELEASES_DIR") or os.getcwd() + "/releases" 51 if not os.path.isdir(releases_path): 52 if os.getenv("TEST_PREVIOUS_RELEASES") == "true": 53 raise AssertionError("TEST_PREVIOUS_RELEASES=1 but releases missing: " + releases_path) 54 raise SkipTest("This test requires binaries for previous releases") 55 56 self.add_nodes(self.num_nodes, extra_args=self.extra_args, versions=[ 57 None, 58 None, 59 190000, 60 180100, 61 170100 62 ], binary=[ 63 self.options.bitcoind, 64 self.options.bitcoind, 65 releases_path + "/v0.19.0.1/bin/bitcoind", 66 releases_path + "/v0.18.1/bin/bitcoind", 67 releases_path + "/v0.17.1/bin/bitcoind" 68 ], binary_cli=[ 69 self.options.bitcoincli, 70 self.options.bitcoincli, 71 releases_path + "/v0.19.0.1/bin/bitcoin-cli", 72 releases_path + "/v0.18.1/bin/bitcoin-cli", 73 releases_path + "/v0.17.1/bin/bitcoin-cli" 74 ]) 75 76 self.start_nodes() 77 78 def run_test(self): 79 self.nodes[0].generatetoaddress(101, self.nodes[0].getnewaddress()) 80 81 sync_blocks(self.nodes) 82 83 # Sanity check the test framework: 84 res = self.nodes[self.num_nodes - 1].getblockchaininfo() 85 assert_equal(res['blocks'], 101) 86 87 node_master = self.nodes[self.num_nodes - 4] 88 node_v19 = self.nodes[self.num_nodes - 3] 89 node_v18 = self.nodes[self.num_nodes - 2] 90 node_v17 = self.nodes[self.num_nodes - 1] 91 92 self.log.info("Test wallet backwards compatibility...") 93 # Create a number of wallets and open them in older versions: 94 95 # w1: regular wallet, created on master: update this test when default 96 # wallets can no longer be opened by older versions. 97 node_master.createwallet(wallet_name="w1") 98 wallet = node_master.get_wallet_rpc("w1") 99 info = wallet.getwalletinfo() 100 assert info['private_keys_enabled'] 101 assert info['keypoolsize'] > 0 102 # Create a confirmed transaction, receiving coins 103 address = wallet.getnewaddress() 104 self.nodes[0].sendtoaddress(address, 10) 105 sync_mempools(self.nodes) 106 self.nodes[0].generate(1) 107 sync_blocks(self.nodes) 108 # Create a conflicting transaction using RBF 109 return_address = self.nodes[0].getnewaddress() 110 tx1_id = self.nodes[1].sendtoaddress(return_address, 1) 111 tx2_id = self.nodes[1].bumpfee(tx1_id)["txid"] 112 # Confirm the transaction 113 sync_mempools(self.nodes) 114 self.nodes[0].generate(1) 115 sync_blocks(self.nodes) 116 # Create another conflicting transaction using RBF 117 tx3_id = self.nodes[1].sendtoaddress(return_address, 1) 118 tx4_id = self.nodes[1].bumpfee(tx3_id)["txid"] 119 # Abandon transaction, but don't confirm 120 self.nodes[1].abandontransaction(tx3_id) 121 122 # w1_v19: regular wallet, created with v0.19 123 node_v19.createwallet(wallet_name="w1_v19") 124 wallet = node_v19.get_wallet_rpc("w1_v19") 125 info = wallet.getwalletinfo() 126 assert info['private_keys_enabled'] 127 assert info['keypoolsize'] > 0 128 # Use addmultisigaddress (see #18075) 129 address_18075 = wallet.addmultisigaddress(1, ["0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52", "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073"], "", "legacy")["address"] 130 assert wallet.getaddressinfo(address_18075)["solvable"] 131 132 # w1_v18: regular wallet, created with v0.18 133 node_v18.createwallet(wallet_name="w1_v18") 134 wallet = node_v18.get_wallet_rpc("w1_v18") 135 info = wallet.getwalletinfo() 136 assert info['private_keys_enabled'] 137 assert info['keypoolsize'] > 0 138 139 # w2: wallet with private keys disabled, created on master: update this 140 # test when default wallets private keys disabled can no longer be 141 # opened by older versions. 142 node_master.createwallet(wallet_name="w2", disable_private_keys=True) 143 wallet = node_master.get_wallet_rpc("w2") 144 info = wallet.getwalletinfo() 145 assert info['private_keys_enabled'] == False 146 assert info['keypoolsize'] == 0 147 148 # w2_v19: wallet with private keys disabled, created with v0.19 149 node_v19.createwallet(wallet_name="w2_v19", disable_private_keys=True) 150 wallet = node_v19.get_wallet_rpc("w2_v19") 151 info = wallet.getwalletinfo() 152 assert info['private_keys_enabled'] == False 153 assert info['keypoolsize'] == 0 154 155 # w2_v18: wallet with private keys disabled, created with v0.18 156 node_v18.createwallet(wallet_name="w2_v18", disable_private_keys=True) 157 wallet = node_v18.get_wallet_rpc("w2_v18") 158 info = wallet.getwalletinfo() 159 assert info['private_keys_enabled'] == False 160 assert info['keypoolsize'] == 0 161 162 # w3: blank wallet, created on master: update this 163 # test when default blank wallets can no longer be opened by older versions. 164 node_master.createwallet(wallet_name="w3", blank=True) 165 wallet = node_master.get_wallet_rpc("w3") 166 info = wallet.getwalletinfo() 167 assert info['private_keys_enabled'] 168 assert info['keypoolsize'] == 0 169 170 # w3_v19: blank wallet, created with v0.19 171 node_v19.createwallet(wallet_name="w3_v19", blank=True) 172 wallet = node_v19.get_wallet_rpc("w3_v19") 173 info = wallet.getwalletinfo() 174 assert info['private_keys_enabled'] 175 assert info['keypoolsize'] == 0 176 177 # w3_v18: blank wallet, created with v0.18 178 node_v18.createwallet(wallet_name="w3_v18", blank=True) 179 wallet = node_v18.get_wallet_rpc("w3_v18") 180 info = wallet.getwalletinfo() 181 assert info['private_keys_enabled'] 182 assert info['keypoolsize'] == 0 183 184 # Copy the wallets to older nodes: 185 node_master_wallets_dir = os.path.join(node_master.datadir, "regtest/wallets") 186 node_v19_wallets_dir = os.path.join(node_v19.datadir, "regtest/wallets") 187 node_v18_wallets_dir = os.path.join(node_v18.datadir, "regtest/wallets") 188 node_v17_wallets_dir = os.path.join(node_v17.datadir, "regtest/wallets") 189 node_master.unloadwallet("w1") 190 node_master.unloadwallet("w2") 191 node_v19.unloadwallet("w1_v19") 192 node_v19.unloadwallet("w2_v19") 193 node_v18.unloadwallet("w1_v18") 194 node_v18.unloadwallet("w2_v18") 195 196 # Copy wallets to v0.17 197 for wallet in os.listdir(node_master_wallets_dir): 198 shutil.copytree( 199 os.path.join(node_master_wallets_dir, wallet), 200 os.path.join(node_v17_wallets_dir, wallet) 201 ) 202 for wallet in os.listdir(node_v18_wallets_dir): 203 shutil.copytree( 204 os.path.join(node_v18_wallets_dir, wallet), 205 os.path.join(node_v17_wallets_dir, wallet) 206 ) 207 208 # Copy wallets to v0.18 209 for wallet in os.listdir(node_master_wallets_dir): 210 shutil.copytree( 211 os.path.join(node_master_wallets_dir, wallet), 212 os.path.join(node_v18_wallets_dir, wallet) 213 ) 214 215 # Copy wallets to v0.19 216 for wallet in os.listdir(node_master_wallets_dir): 217 shutil.copytree( 218 os.path.join(node_master_wallets_dir, wallet), 219 os.path.join(node_v19_wallets_dir, wallet) 220 ) 221 222 # Open the wallets in v0.19 223 node_v19.loadwallet("w1") 224 wallet = node_v19.get_wallet_rpc("w1") 225 info = wallet.getwalletinfo() 226 assert info['private_keys_enabled'] 227 assert info['keypoolsize'] > 0 228 txs = wallet.listtransactions() 229 assert_equal(len(txs), 5) 230 assert_equal(txs[1]["txid"], tx1_id) 231 assert_equal(txs[2]["walletconflicts"], [tx1_id]) 232 assert_equal(txs[1]["replaced_by_txid"], tx2_id) 233 assert not(txs[1]["abandoned"]) 234 assert_equal(txs[1]["confirmations"], -1) 235 assert_equal(txs[2]["blockindex"], 1) 236 assert txs[3]["abandoned"] 237 assert_equal(txs[4]["walletconflicts"], [tx3_id]) 238 assert_equal(txs[3]["replaced_by_txid"], tx4_id) 239 assert not(hasattr(txs[3], "blockindex")) 240 241 node_v19.loadwallet("w2") 242 wallet = node_v19.get_wallet_rpc("w2") 243 info = wallet.getwalletinfo() 244 assert info['private_keys_enabled'] == False 245 assert info['keypoolsize'] == 0 246 247 node_v19.loadwallet("w3") 248 wallet = node_v19.get_wallet_rpc("w3") 249 info = wallet.getwalletinfo() 250 assert info['private_keys_enabled'] 251 assert info['keypoolsize'] == 0 252 253 # Open the wallets in v0.18 254 node_v18.loadwallet("w1") 255 wallet = node_v18.get_wallet_rpc("w1") 256 info = wallet.getwalletinfo() 257 assert info['private_keys_enabled'] 258 assert info['keypoolsize'] > 0 259 txs = wallet.listtransactions() 260 assert_equal(len(txs), 5) 261 assert_equal(txs[1]["txid"], tx1_id) 262 assert_equal(txs[2]["walletconflicts"], [tx1_id]) 263 assert_equal(txs[1]["replaced_by_txid"], tx2_id) 264 assert not(txs[1]["abandoned"]) 265 assert_equal(txs[1]["confirmations"], -1) 266 assert_equal(txs[2]["blockindex"], 1) 267 assert txs[3]["abandoned"] 268 assert_equal(txs[4]["walletconflicts"], [tx3_id]) 269 assert_equal(txs[3]["replaced_by_txid"], tx4_id) 270 assert not(hasattr(txs[3], "blockindex")) 271 272 node_v18.loadwallet("w2") 273 wallet = node_v18.get_wallet_rpc("w2") 274 info = wallet.getwalletinfo() 275 assert info['private_keys_enabled'] == False 276 assert info['keypoolsize'] == 0 277 278 node_v18.loadwallet("w3") 279 wallet = node_v18.get_wallet_rpc("w3") 280 info = wallet.getwalletinfo() 281 assert info['private_keys_enabled'] 282 assert info['keypoolsize'] == 0 283 284 # Open the wallets in v0.17 285 node_v17.loadwallet("w1_v18") 286 wallet = node_v17.get_wallet_rpc("w1_v18") 287 info = wallet.getwalletinfo() 288 assert info['private_keys_enabled'] 289 assert info['keypoolsize'] > 0 290 291 node_v17.loadwallet("w1") 292 wallet = node_v17.get_wallet_rpc("w1") 293 info = wallet.getwalletinfo() 294 assert info['private_keys_enabled'] 295 assert info['keypoolsize'] > 0 296 297 node_v17.loadwallet("w2_v18") 298 wallet = node_v17.get_wallet_rpc("w2_v18") 299 info = wallet.getwalletinfo() 300 assert info['private_keys_enabled'] == False 301 assert info['keypoolsize'] == 0 302 303 node_v17.loadwallet("w2") 304 wallet = node_v17.get_wallet_rpc("w2") 305 info = wallet.getwalletinfo() 306 assert info['private_keys_enabled'] == False 307 assert info['keypoolsize'] == 0 308 309 # RPC loadwallet failure causes bitcoind to exit, in addition to the RPC 310 # call failure, so the following test won't work: 311 # assert_raises_rpc_error(-4, "Wallet loading failed.", node_v17.loadwallet, 'w3_v18') 312 313 # Instead, we stop node and try to launch it with the wallet: 314 self.stop_node(self.num_nodes - 1) 315 node_v17.assert_start_raises_init_error(["-wallet=w3_v18"], "Error: Error loading w3_v18: Wallet requires newer version of Bitcoin Core") 316 node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: Error loading w3: Wallet requires newer version of Bitcoin Core") 317 self.start_node(self.num_nodes - 1) 318 319 self.log.info("Test wallet upgrade path...") 320 # u1: regular wallet, created with v0.17 321 node_v17.createwallet(wallet_name="u1_v17") 322 wallet = node_v17.get_wallet_rpc("u1_v17") 323 address = wallet.getnewaddress("bech32") 324 info = wallet.getaddressinfo(address) 325 hdkeypath = info["hdkeypath"] 326 pubkey = info["pubkey"] 327 328 # Copy the 0.17 wallet to the last Bitcoin Core version and open it: 329 node_v17.unloadwallet("u1_v17") 330 shutil.copytree( 331 os.path.join(node_v17_wallets_dir, "u1_v17"), 332 os.path.join(node_master_wallets_dir, "u1_v17") 333 ) 334 node_master.loadwallet("u1_v17") 335 wallet = node_master.get_wallet_rpc("u1_v17") 336 info = wallet.getaddressinfo(address) 337 descriptor = "wpkh([" + info["hdmasterfingerprint"] + hdkeypath[1:] + "]" + pubkey + ")" 338 assert_equal(info["desc"], descsum_create(descriptor)) 339 340 # Copy the 0.19 wallet to the last Bitcoin Core version and open it: 341 shutil.copytree( 342 os.path.join(node_v19_wallets_dir, "w1_v19"), 343 os.path.join(node_master_wallets_dir, "w1_v19") 344 ) 345 node_master.loadwallet("w1_v19") 346 wallet = node_master.get_wallet_rpc("w1_v19") 347 assert wallet.getaddressinfo(address_18075)["solvable"] 348 349if __name__ == '__main__': 350 BackwardsCompatibilityTest().main() 351