2019-01-05 16:20:42 -03:00
#!/usr/bin/env python3
2022-12-24 20:49:50 -03:00
# Copyright (c) 2018-2022 The Bitcoin Core developers
2019-01-05 16:20:42 -03: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 17:28:55 -03:00
Test various backwards compatibility scenarios . Requires previous releases binaries ,
see test / README . md .
2020-05-04 07:32:51 -04:00
2019-01-05 16:20:42 -03: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 16:43:35 -03:00
import shutil
2019-01-05 16:20:42 -03:00
2021-05-17 10:38:19 -04:00
from test_framework . blocktools import COINBASE_MATURITY
2020-04-30 09:36:54 -04:00
from test_framework . test_framework import BitcoinTestFramework
2019-02-20 17:40:29 -03:00
from test_framework . descriptors import descsum_create
2019-01-05 16:20:42 -03:00
from test_framework . util import (
assert_equal ,
2020-10-27 16:26:55 -03:00
assert_raises_rpc_error ,
2019-01-05 16:20:42 -03:00
)
2020-04-30 09:36:54 -04:00
2019-01-05 16:20:42 -03:00
class BackwardsCompatibilityTest ( BitcoinTestFramework ) :
2022-11-09 08:53:13 -03:00
def add_options ( self , parser ) :
self . add_wallet_options ( parser )
2019-01-05 16:20:42 -03:00
def set_test_params ( self ) :
self . setup_clean_chain = True
2022-11-27 19:46:15 -03:00
self . num_nodes = 11
2019-01-05 16:20:42 -03:00
# Add new version after each release:
self . extra_args = [
2020-06-19 19:04:05 -04: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 19:46:15 -03:00
[ " -nowallet " , " -walletrbf=1 " , " -addresstype=bech32 " , " -whitelist=noban@127.0.0.1 " ] , # v24.0.1
2022-05-04 10:51:52 -04:00
[ " -nowallet " , " -walletrbf=1 " , " -addresstype=bech32 " , " -whitelist=noban@127.0.0.1 " ] , # v23.0
2020-06-19 19:04:05 -04: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 16:20:42 -03:00
]
2020-09-28 21:24:06 -03:00
self . wallet_names = [ self . default_wallet_name ]
2019-01-05 16:20:42 -03:00
2020-04-05 21:03:17 -04:00
def skip_test_if_missing_module ( self ) :
self . skip_if_no_wallet ( )
2020-04-30 09:36:54 -04:00
self . skip_if_no_previous_releases ( )
2020-04-05 21:03:17 -04:00
2019-01-05 16:20:42 -03:00
def setup_nodes ( self ) :
self . add_nodes ( self . num_nodes , extra_args = self . extra_args , versions = [
None ,
None ,
2022-11-27 19:46:15 -03:00
240001 ,
2022-05-04 10:51:52 -04:00
230000 ,
2021-09-22 08:10:40 -03:00
220000 ,
2021-02-24 13:28:58 -03:00
210000 ,
2021-11-18 11:06:49 -03:00
200100 ,
2020-05-04 07:36:19 -04:00
190100 ,
2019-01-05 16:20:42 -03:00
180100 ,
2020-09-02 07:48:54 -04:00
170200 ,
2020-05-04 07:32:51 -04:00
160300 ,
2019-01-05 16:20:42 -03:00
] )
self . start_nodes ( )
2020-09-28 21:24:06 -03:00
self . import_deterministic_coinbase_privkeys ( )
2019-01-05 16:20:42 -03:00
2023-07-14 16:51:31 -04:00
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
2023-07-03 21:14:43 -04:00
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 " ]
2020-05-19 06:02:49 -04:00
def run_test ( self ) :
node_miner = self . nodes [ 0 ]
node_master = self . nodes [ 1 ]
2023-07-03 21:50:20 -04:00
node_v21 = self . nodes [ self . num_nodes - 6 ]
2020-05-04 07:32:51 -04:00
node_v17 = self . nodes [ self . num_nodes - 2 ]
node_v16 = self . nodes [ self . num_nodes - 1 ]
2019-02-20 16:43:35 -03:00
2020-05-19 06:02:49 -04: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 16:43:35 -03: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 16:26:55 -03:00
node_master . createwallet ( wallet_name = " w1 " )
2019-02-20 16:43:35 -03: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 06:02:49 -04:00
node_miner . sendtoaddress ( address , 10 )
2020-06-09 16:41:08 -04:00
self . sync_mempools ( )
2020-05-19 06:02:49 -04:00
self . generate ( node_miner , 1 )
2019-02-20 16:43:35 -03:00
# Create a conflicting transaction using RBF
2020-05-19 06:02:49 -04: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 16:43:35 -03:00
# Confirm the transaction
2020-06-09 16:41:08 -04:00
self . sync_mempools ( )
2020-05-19 06:02:49 -04:00
self . generate ( node_miner , 1 )
2019-02-20 16:43:35 -03:00
# Create another conflicting transaction using RBF
2020-05-19 06:02:49 -04:00
tx3_id = node_master . sendtoaddress ( return_address , 1 )
tx4_id = node_master . bumpfee ( tx3_id ) [ " txid " ]
2020-03-03 03:58:57 -03:00
# Abandon transaction, but don't confirm
2020-05-19 06:02:49 -04:00
node_master . abandontransaction ( tx3_id )
2019-02-20 16:43:35 -03:00
# 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 16:26:55 -03:00
node_master . createwallet ( wallet_name = " w2 " , disable_private_keys = True )
2019-02-20 16:43:35 -03: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 16:26:55 -03:00
node_master . createwallet ( wallet_name = " w3 " , blank = True )
2019-02-20 16:43:35 -03:00
wallet = node_master . get_wallet_rpc ( " w3 " )
info = wallet . getwalletinfo ( )
assert info [ ' private_keys_enabled ' ]
assert info [ ' keypoolsize ' ] == 0
2020-05-19 06:02:49 -04:00
# Unload wallets and copy to older nodes:
2023-06-14 07:00:11 -04:00
node_master_wallets_dir = node_master . wallets_path
node_v17_wallets_dir = node_v17 . wallets_path
2023-07-03 20:53:16 -04:00
node_v16_wallets_dir = node_v16 . wallets_path
2019-02-20 16:43:35 -03:00
node_master . unloadwallet ( " w1 " )
node_master . unloadwallet ( " w2 " )
2020-05-19 06:02:49 -04:00
node_master . unloadwallet ( " w3 " )
2020-05-04 07:32:51 -04:00
2020-05-19 06:02:49 -04:00
for node in legacy_nodes :
# Copy wallets to previous version
for wallet in os . listdir ( node_master_wallets_dir ) :
2023-07-03 20:53:16 -04:00
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 )
2019-10-09 16:10:37 -03:00
2023-07-03 21:14:43 -04:00
self . test_v19_addmultisigaddress ( )
2020-10-27 16:26:55 -03:00
if not self . options . descriptors :
# Descriptor wallets break compatibility, only run this test for legacy wallet
2020-05-19 06:02:49 -04: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 10:18:42 -03:00
assert not txs [ 1 ] [ " abandoned " ]
2020-05-19 06:02:49 -04: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 10:18:42 -03:00
assert not hasattr ( txs [ 3 ] , " blockindex " )
2020-05-19 06:02:49 -04:00
elif wallet_name == " w2 " :
2022-10-04 10:18:42 -03:00
assert info [ ' private_keys_enabled ' ] == False
2020-05-19 06:02:49 -04:00
assert info [ ' keypoolsize ' ] == 0
else :
2022-10-04 10:18:42 -03:00
assert info [ ' private_keys_enabled ' ] == True
2020-05-19 06:02:49 -04:00
assert info [ ' keypoolsize ' ] == 0
2020-10-27 16:26:55 -03:00
else :
2020-05-19 06:02:49 -04: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 16:43:35 -03: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 06:02:49 -04:00
# assert_raises_rpc_error(-4, "Wallet loading failed.", node_v17.loadwallet, 'w3')
2019-02-20 16:43:35 -03:00
# Instead, we stop node and try to launch it with the wallet:
2020-05-19 06:02:49 -04:00
self . stop_node ( node_v17 . index )
2020-10-27 16:26:55 -03:00
if self . options . descriptors :
2023-07-03 21:23:24 -04:00
self . log . info ( " Test descriptor wallet incompatibility with 0.17 " )
2020-10-27 16:26:55 -03:00
# 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 :
2023-07-03 21:23:24 -04:00
self . log . info ( " Test blank wallet incompatibility with v17 " )
2020-10-27 16:26:55 -03:00
node_v17 . assert_start_raises_init_error ( [ " -wallet=w3 " ] , " Error: Error loading w3: Wallet requires newer version of Bitcoin Core " )
2020-05-19 06:02:49 -04:00
self . start_node ( node_v17 . index )
2020-05-04 07:32:51 -04:00
2023-07-03 20:53:16 -04:00
# 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 " )
2019-02-20 17:40:29 -03:00
2023-07-03 21:50:20 -04:00
# 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 " )
2020-05-22 14:39:52 -04:00
# Create upgrade wallet in v0.16
2020-05-19 06:02:49 -04:00
self . restart_node ( node_v16 . index , extra_args = [ " -wallet=u1_v16 " ] )
2020-05-22 14:39:52 -04: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 06:02:49 -04:00
self . stop_node ( node_v16 . index )
2020-05-22 14:39:52 -04:00
2019-02-20 17:40:29 -03:00
self . log . info ( " Test wallet upgrade path... " )
# u1: regular wallet, created with v0.17
2020-04-02 19:09:15 -03:00
node_v17 . rpc . createwallet ( wallet_name = " u1_v17 " )
2019-02-20 17:40:29 -03:00
wallet = node_v17 . get_wallet_rpc ( " u1_v17 " )
address = wallet . getnewaddress ( " bech32 " )
2020-05-02 23:33:33 -04:00
v17_info = wallet . getaddressinfo ( address )
2023-04-04 12:33:08 -04:00
hdkeypath = v17_info [ " hdkeypath " ] . replace ( " ' " , " h " )
2020-05-02 23:33:33 -04:00
pubkey = v17_info [ " pubkey " ]
2019-02-20 17:40:29 -03:00
2020-10-29 16:50:40 -03: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 (
2023-07-03 20:53:16 -04:00
os . path . join ( node_v16_wallets_dir , " u1_v16 " ) ,
2020-10-29 16:50:40 -03:00
os . path . join ( node_master_wallets_dir , " u1_v16 " )
)
load_res = node_master . loadwallet ( " u1_v16 " )
2023-06-12 17:47:58 -04:00
# Make sure this wallet opens with only the migration warning. See https://github.com/bitcoin/bitcoin/pull/19054
2023-03-19 23:26:14 -03:00
if int ( node_master . getnetworkinfo ( ) [ " version " ] ) > = 249900 :
# loadwallet#warnings (added in v25) -- only present if there is a warning
2023-06-12 17:47:58 -04: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-19 23:26:14 -03:00
else :
# loadwallet#warning (deprecated in v25) -- always present, but empty string if no warning
assert_equal ( load_res [ " warning " ] , ' ' )
2020-10-29 16:50:40 -03:00
wallet = node_master . get_wallet_rpc ( " u1_v16 " )
info = wallet . getaddressinfo ( v16_addr )
2021-06-11 01:40:41 -04:00
descriptor = f " wpkh([ { info [ ' hdmasterfingerprint ' ] } { hdkeypath [ 1 : ] } ] { v16_pubkey } ) "
2020-10-29 16:50:40 -03: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 07:12:28 -03:00
node_master . unloadwallet ( " u1_v16 " )
2023-07-03 20:53:16 -04:00
os . remove ( os . path . join ( node_v16_wallets_dir , " u1_v16 " ) )
2020-10-29 16:50:40 -03:00
shutil . copyfile (
os . path . join ( node_master_wallets_dir , " u1_v16 " ) ,
2023-07-03 20:53:16 -04:00
os . path . join ( node_v16_wallets_dir , " u1_v16 " )
2020-10-29 16:50:40 -03:00
)
2020-05-19 06:02:49 -04:00
self . start_node ( node_v16 . index , extra_args = [ " -wallet=u1_v16 " ] )
2020-10-29 16:50:40 -03:00
wallet = node_v16 . get_wallet_rpc ( " u1_v16 " )
info = wallet . validateaddress ( v16_addr )
assert_equal ( info , v16_info )
2020-02-11 15:59:24 -03:00
2020-10-29 16:50:40 -03: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 01:40:41 -04:00
descriptor = f " wpkh([ { info [ ' hdmasterfingerprint ' ] } { hdkeypath [ 1 : ] } ] { pubkey } ) "
2020-10-29 16:50:40 -03: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 )
2019-01-05 16:20:42 -03:00
if __name__ == ' __main__ ' :
BackwardsCompatibilityTest ( ) . main ( )