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