2019-01-05 20:20:42 +01:00
#!/usr/bin/env python3
2022-12-25 00:49:50 +01:00
# Copyright (c) 2018-2022 The Bitcoin Core developers
2019-01-05 20:20:42 +01:00
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
""" Backwards compatibility functional test
2020-11-03 21:28:55 +01:00
Test various backwards compatibility scenarios . Requires previous releases binaries ,
see test / README . md .
2020-05-04 13:32:51 +02:00
2019-01-05 20:20:42 +01:00
Due to RPC changes introduced in various versions the below tests
won ' t work for older versions without some patches or workarounds.
Use only the latest patch version of each release , unless a test specifically
needs an older patch version .
"""
import os
2019-02-20 20:43:35 +01:00
import shutil
2019-01-05 20:20:42 +01:00
2021-05-17 16:38:19 +02:00
from test_framework . blocktools import COINBASE_MATURITY
2020-04-30 15:36:54 +02:00
from test_framework . test_framework import BitcoinTestFramework
2019-02-20 21:40:29 +01:00
from test_framework . descriptors import descsum_create
2019-01-05 20:20:42 +01:00
from test_framework . util import (
assert_equal ,
2020-10-27 20:26:55 +01:00
assert_raises_rpc_error ,
2019-01-05 20:20:42 +01:00
)
2020-04-30 15:36:54 +02:00
2019-01-05 20:20:42 +01:00
class BackwardsCompatibilityTest ( BitcoinTestFramework ) :
2022-11-09 12:53:13 +01:00
def add_options ( self , parser ) :
self . add_wallet_options ( parser )
2019-01-05 20:20:42 +01:00
def set_test_params ( self ) :
self . setup_clean_chain = True
2022-11-27 23:46:15 +01:00
self . num_nodes = 11
2019-01-05 20:20:42 +01:00
# Add new version after each release:
self . extra_args = [
2020-06-20 01:04:05 +02:00
[ " -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
2022-11-27 23:46:15 +01:00
[ " -nowallet " , " -walletrbf=1 " , " -addresstype=bech32 " , " -whitelist=noban@127.0.0.1 " ] , # v24.0.1
2022-05-04 16:51:52 +02:00
[ " -nowallet " , " -walletrbf=1 " , " -addresstype=bech32 " , " -whitelist=noban@127.0.0.1 " ] , # v23.0
2020-06-20 01:04:05 +02:00
[ " -nowallet " , " -walletrbf=1 " , " -addresstype=bech32 " , " -whitelist=noban@127.0.0.1 " ] , # v22.0
[ " -nowallet " , " -walletrbf=1 " , " -addresstype=bech32 " , " -whitelist=noban@127.0.0.1 " ] , # v0.21.0
[ " -nowallet " , " -walletrbf=1 " , " -addresstype=bech32 " , " -whitelist=noban@127.0.0.1 " ] , # v0.20.1
[ " -nowallet " , " -walletrbf=1 " , " -addresstype=bech32 " , " -whitelist=noban@127.0.0.1 " ] , # v0.19.1
[ " -nowallet " , " -walletrbf=1 " , " -addresstype=bech32 " , " -whitelist=127.0.0.1 " ] , # v0.18.1
[ " -nowallet " , " -walletrbf=1 " , " -addresstype=bech32 " , " -whitelist=127.0.0.1 " ] , # v0.17.2
[ " -nowallet " , " -walletrbf=1 " , " -addresstype=bech32 " , " -whitelist=127.0.0.1 " , " -wallet=wallet.dat " ] , # v0.16.3
2019-01-05 20:20:42 +01:00
]
2020-09-29 02:24:06 +02:00
self . wallet_names = [ self . default_wallet_name ]
2019-01-05 20:20:42 +01:00
2020-04-06 03:03:17 +02:00
def skip_test_if_missing_module ( self ) :
self . skip_if_no_wallet ( )
2020-04-30 15:36:54 +02:00
self . skip_if_no_previous_releases ( )
2020-04-06 03:03:17 +02:00
2019-01-05 20:20:42 +01:00
def setup_nodes ( self ) :
self . add_nodes ( self . num_nodes , extra_args = self . extra_args , versions = [
None ,
None ,
2022-11-27 23:46:15 +01:00
240001 ,
2022-05-04 16:51:52 +02:00
230000 ,
2021-09-22 13:10:40 +02:00
220000 ,
2021-02-24 17:28:58 +01:00
210000 ,
2021-11-18 15:06:49 +01:00
200100 ,
2020-05-04 13:36:19 +02:00
190100 ,
2019-01-05 20:20:42 +01:00
180100 ,
2020-09-02 13:48:54 +02:00
170200 ,
2020-05-04 13:32:51 +02:00
160300 ,
2019-01-05 20:20:42 +01:00
] )
self . start_nodes ( )
2020-09-29 02:24:06 +02:00
self . import_deterministic_coinbase_privkeys ( )
2019-01-05 20:20:42 +01:00
2020-05-19 12:02:49 +02:00
def nodes_wallet_dir ( self , node ) :
if node . version < 170000 :
2023-06-14 13:00:11 +02:00
return node . chain_path
return node . wallets_path
2019-01-05 20:20:42 +01:00
2020-05-19 12:02:49 +02:00
def run_test ( self ) :
node_miner = self . nodes [ 0 ]
node_master = self . nodes [ 1 ]
2020-05-04 13:32:51 +02:00
node_v19 = self . nodes [ self . num_nodes - 4 ]
node_v18 = self . nodes [ self . num_nodes - 3 ]
node_v17 = self . nodes [ self . num_nodes - 2 ]
node_v16 = self . nodes [ self . num_nodes - 1 ]
2019-02-20 20:43:35 +01:00
2020-05-19 12:02:49 +02:00
legacy_nodes = self . nodes [ 2 : ]
self . generatetoaddress ( node_miner , COINBASE_MATURITY + 1 , node_miner . getnewaddress ( ) )
# Sanity check the test framework:
res = node_v16 . getblockchaininfo ( )
assert_equal ( res [ ' blocks ' ] , COINBASE_MATURITY + 1 )
2019-02-20 20:43:35 +01:00
self . log . info ( " Test wallet backwards compatibility... " )
# Create a number of wallets and open them in older versions:
# w1: regular wallet, created on master: update this test when default
# wallets can no longer be opened by older versions.
2020-10-27 20:26:55 +01:00
node_master . createwallet ( wallet_name = " w1 " )
2019-02-20 20:43:35 +01:00
wallet = node_master . get_wallet_rpc ( " w1 " )
info = wallet . getwalletinfo ( )
assert info [ ' private_keys_enabled ' ]
assert info [ ' keypoolsize ' ] > 0
# Create a confirmed transaction, receiving coins
address = wallet . getnewaddress ( )
2020-05-19 12:02:49 +02:00
node_miner . sendtoaddress ( address , 10 )
2020-06-09 22:41:08 +02:00
self . sync_mempools ( )
2020-05-19 12:02:49 +02:00
self . generate ( node_miner , 1 )
2019-02-20 20:43:35 +01:00
# Create a conflicting transaction using RBF
2020-05-19 12:02:49 +02:00
return_address = node_miner . getnewaddress ( )
tx1_id = node_master . sendtoaddress ( return_address , 1 )
tx2_id = node_master . bumpfee ( tx1_id ) [ " txid " ]
2019-02-20 20:43:35 +01:00
# Confirm the transaction
2020-06-09 22:41:08 +02:00
self . sync_mempools ( )
2020-05-19 12:02:49 +02:00
self . generate ( node_miner , 1 )
2019-02-20 20:43:35 +01:00
# Create another conflicting transaction using RBF
2020-05-19 12:02:49 +02:00
tx3_id = node_master . sendtoaddress ( return_address , 1 )
tx4_id = node_master . bumpfee ( tx3_id ) [ " txid " ]
2020-03-03 07:58:57 +01:00
# Abandon transaction, but don't confirm
2020-05-19 12:02:49 +02:00
node_master . abandontransaction ( tx3_id )
2019-02-20 20:43:35 +01:00
2019-10-09 21:10:37 +02:00
# w1_v19: regular wallet, created with v0.19
2020-04-03 00:09:15 +02:00
node_v19 . rpc . createwallet ( wallet_name = " w1_v19 " )
2019-10-09 21:10:37 +02:00
wallet = node_v19 . get_wallet_rpc ( " w1_v19 " )
info = wallet . getwalletinfo ( )
assert info [ ' private_keys_enabled ' ]
assert info [ ' keypoolsize ' ] > 0
2020-02-11 19:59:24 +01:00
# Use addmultisigaddress (see #18075)
2020-04-03 00:09:15 +02:00
address_18075 = wallet . rpc . addmultisigaddress ( 1 , [ " 0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52 " , " 037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073 " ] , " " , " legacy " ) [ " address " ]
2020-02-11 19:59:24 +01:00
assert wallet . getaddressinfo ( address_18075 ) [ " solvable " ]
2020-05-19 12:02:49 +02:00
node_v19 . unloadwallet ( " w1_v19 " )
2019-10-09 21:10:37 +02:00
2019-02-20 20:43:35 +01:00
# w1_v18: regular wallet, created with v0.18
2020-04-03 00:09:15 +02:00
node_v18 . rpc . createwallet ( wallet_name = " w1_v18 " )
2019-02-20 20:43:35 +01:00
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.
2020-10-27 20:26:55 +01:00
node_master . createwallet ( wallet_name = " w2 " , disable_private_keys = True )
2019-02-20 20:43:35 +01:00
wallet = node_master . get_wallet_rpc ( " w2 " )
info = wallet . getwalletinfo ( )
assert info [ ' private_keys_enabled ' ] == False
assert info [ ' keypoolsize ' ] == 0
# w3: blank wallet, created on master: update this
# test when default blank wallets can no longer be opened by older versions.
2020-10-27 20:26:55 +01:00
node_master . createwallet ( wallet_name = " w3 " , blank = True )
2019-02-20 20:43:35 +01:00
wallet = node_master . get_wallet_rpc ( " w3 " )
info = wallet . getwalletinfo ( )
assert info [ ' private_keys_enabled ' ]
assert info [ ' keypoolsize ' ] == 0
2020-05-19 12:02:49 +02:00
# Unload wallets and copy to older nodes:
2023-06-14 13:00:11 +02:00
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
2019-02-20 20:43:35 +01:00
node_master . unloadwallet ( " w1 " )
node_master . unloadwallet ( " w2 " )
2020-05-19 12:02:49 +02:00
node_master . unloadwallet ( " w3 " )
2020-05-04 13:32:51 +02:00
2020-05-19 12:02:49 +02:00
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 )
)
2019-10-09 21:10:37 +02:00
2020-10-27 20:26:55 +01:00
if not self . options . descriptors :
# Descriptor wallets break compatibility, only run this test for legacy wallet
2020-05-19 12:02:49 +02:00
# 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 )
info = wallet . getwalletinfo ( )
if wallet_name == " w1 " :
assert info [ ' private_keys_enabled ' ] == True
assert info [ ' keypoolsize ' ] > 0
txs = wallet . listtransactions ( )
assert_equal ( len ( txs ) , 5 )
assert_equal ( txs [ 1 ] [ " txid " ] , tx1_id )
assert_equal ( txs [ 2 ] [ " walletconflicts " ] , [ tx1_id ] )
assert_equal ( txs [ 1 ] [ " replaced_by_txid " ] , tx2_id )
2022-10-04 15:18:42 +02:00
assert not txs [ 1 ] [ " abandoned " ]
2020-05-19 12:02:49 +02:00
assert_equal ( txs [ 1 ] [ " confirmations " ] , - 1 )
assert_equal ( txs [ 2 ] [ " blockindex " ] , 1 )
assert txs [ 3 ] [ " abandoned " ]
assert_equal ( txs [ 4 ] [ " walletconflicts " ] , [ tx3_id ] )
assert_equal ( txs [ 3 ] [ " replaced_by_txid " ] , tx4_id )
2022-10-04 15:18:42 +02:00
assert not hasattr ( txs [ 3 ] , " blockindex " )
2020-05-19 12:02:49 +02:00
elif wallet_name == " w2 " :
2022-10-04 15:18:42 +02:00
assert info [ ' private_keys_enabled ' ] == False
2020-05-19 12:02:49 +02:00
assert info [ ' keypoolsize ' ] == 0
else :
2022-10-04 15:18:42 +02:00
assert info [ ' private_keys_enabled ' ] == True
2020-05-19 12:02:49 +02:00
assert info [ ' keypoolsize ' ] == 0
2020-10-27 20:26:55 +01:00
else :
2020-05-19 12:02:49 +02:00
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 )
2019-02-20 20:43:35 +01:00
# RPC loadwallet failure causes bitcoind to exit, in addition to the RPC
# call failure, so the following test won't work:
2020-05-19 12:02:49 +02:00
# assert_raises_rpc_error(-4, "Wallet loading failed.", node_v17.loadwallet, 'w3')
2019-02-20 20:43:35 +01:00
# Instead, we stop node and try to launch it with the wallet:
2020-05-19 12:02:49 +02:00
self . stop_node ( node_v17 . index )
2020-10-27 20:26:55 +01:00
if self . options . descriptors :
# 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 :
node_v17 . assert_start_raises_init_error ( [ " -wallet=w3 " ] , " Error: Error loading w3: Wallet requires newer version of Bitcoin Core " )
2020-05-19 12:02:49 +02:00
self . start_node ( node_v17 . index )
2020-05-04 13:32:51 +02:00
2020-10-27 20:26:55 +01:00
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)
2020-05-19 12:02:49 +02:00
self . restart_node ( node_v16 . index , extra_args = [ " -wallet=w2 " ] )
2020-10-27 20:26:55 +01:00
wallet = node_v16 . get_wallet_rpc ( " w2 " )
info = wallet . getwalletinfo ( )
assert info [ ' keypoolsize ' ] == 1
2019-02-20 21:40:29 +01:00
2020-05-22 20:39:52 +02:00
# Create upgrade wallet in v0.16
2020-05-19 12:02:49 +02:00
self . restart_node ( node_v16 . index , extra_args = [ " -wallet=u1_v16 " ] )
2020-05-22 20:39:52 +02:00
wallet = node_v16 . get_wallet_rpc ( " u1_v16 " )
v16_addr = wallet . getnewaddress ( ' ' , " bech32 " )
v16_info = wallet . validateaddress ( v16_addr )
v16_pubkey = v16_info [ ' pubkey ' ]
2020-05-19 12:02:49 +02:00
self . stop_node ( node_v16 . index )
2020-05-22 20:39:52 +02:00
2019-02-20 21:40:29 +01:00
self . log . info ( " Test wallet upgrade path... " )
# u1: regular wallet, created with v0.17
2020-04-03 00:09:15 +02:00
node_v17 . rpc . createwallet ( wallet_name = " u1_v17 " )
2019-02-20 21:40:29 +01:00
wallet = node_v17 . get_wallet_rpc ( " u1_v17 " )
address = wallet . getnewaddress ( " bech32 " )
2020-05-03 05:33:33 +02:00
v17_info = wallet . getaddressinfo ( address )
2023-04-04 18:33:08 +02:00
hdkeypath = v17_info [ " hdkeypath " ] . replace ( " ' " , " h " )
2020-05-03 05:33:33 +02:00
pubkey = v17_info [ " pubkey " ]
2019-02-20 21:40:29 +01:00
2020-10-29 20:50:40 +01:00
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 " )
2023-06-12 23:47:58 +02:00
# Make sure this wallet opens with only the migration warning. See https://github.com/bitcoin/bitcoin/pull/19054
2023-03-20 03:26:14 +01:00
if int ( node_master . getnetworkinfo ( ) [ " version " ] ) > = 249900 :
# loadwallet#warnings (added in v25) -- only present if there is a warning
2023-06-12 23:47:58 +02:00
# 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. " ] )
2023-03-20 03:26:14 +01:00
else :
# loadwallet#warning (deprecated in v25) -- always present, but empty string if no warning
assert_equal ( load_res [ " warning " ] , ' ' )
2020-10-29 20:50:40 +01:00
wallet = node_master . get_wallet_rpc ( " u1_v16 " )
info = wallet . getaddressinfo ( v16_addr )
2021-06-11 07:40:41 +02:00
descriptor = f " wpkh([ { info [ ' hdmasterfingerprint ' ] } { hdkeypath [ 1 : ] } ] { v16_pubkey } ) "
2020-10-29 20:50:40 +01:00
assert_equal ( info [ " desc " ] , descsum_create ( descriptor ) )
# Now copy that same wallet back to 0.16 to make sure no automatic upgrade breaks it
2022-12-08 11:12:28 +01:00
node_master . unloadwallet ( " u1_v16 " )
2020-10-29 20:50:40 +01:00
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 " )
)
2020-05-19 12:02:49 +02:00
self . start_node ( node_v16 . index , extra_args = [ " -wallet=u1_v16 " ] )
2020-10-29 20:50:40 +01:00
wallet = node_v16 . get_wallet_rpc ( " u1_v16 " )
info = wallet . validateaddress ( v16_addr )
assert_equal ( info , v16_info )
2020-02-11 19:59:24 +01:00
2020-10-29 20:50:40 +01:00
# 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 " )
info = wallet . getaddressinfo ( address )
2021-06-11 07:40:41 +02:00
descriptor = f " wpkh([ { info [ ' hdmasterfingerprint ' ] } { hdkeypath [ 1 : ] } ] { pubkey } ) "
2020-10-29 20:50:40 +01:00
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 " ]
2020-05-03 05:33:33 +02:00
2019-01-05 20:20:42 +01:00
if __name__ == ' __main__ ' :
BackwardsCompatibilityTest ( ) . main ( )