2020-04-29 16:11:49 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# Copyright (c) 2018-2020 The Bitcoin Core developers
|
|
|
|
# Distributed under the MIT software license, see the accompanying
|
|
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
"""upgradewallet RPC functional test
|
|
|
|
|
2020-05-04 13:32:51 +02:00
|
|
|
Test upgradewallet RPC. Download node binaries:
|
2020-04-29 16:11:49 +02:00
|
|
|
|
2020-09-02 13:48:54 +02:00
|
|
|
test/get_previous_releases.py -b v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2
|
2020-05-04 13:32:51 +02:00
|
|
|
|
|
|
|
Only v0.15.2 and v0.16.3 are required by this test. The others are used in feature_backwards_compatibility.py
|
2020-04-29 16:11:49 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
import os
|
|
|
|
import shutil
|
2020-04-30 22:52:52 +02:00
|
|
|
import struct
|
2020-04-29 16:11:49 +02:00
|
|
|
|
2020-04-30 22:52:52 +02:00
|
|
|
from io import BytesIO
|
|
|
|
|
|
|
|
from test_framework.bdb import dump_bdb_kv
|
|
|
|
from test_framework.messages import deser_compact_size, deser_string
|
2020-04-30 15:36:54 +02:00
|
|
|
from test_framework.test_framework import BitcoinTestFramework
|
2020-04-29 16:11:49 +02:00
|
|
|
from test_framework.util import (
|
|
|
|
assert_equal,
|
|
|
|
assert_greater_than,
|
2020-04-30 15:36:54 +02:00
|
|
|
assert_is_hex_string,
|
2020-04-30 22:52:52 +02:00
|
|
|
assert_raises_rpc_error,
|
|
|
|
sha256sum_file,
|
2020-04-29 16:11:49 +02:00
|
|
|
)
|
|
|
|
|
2020-04-30 15:36:54 +02:00
|
|
|
|
2020-04-30 22:52:52 +02:00
|
|
|
UPGRADED_KEYMETA_VERSION = 12
|
|
|
|
|
|
|
|
def deser_keymeta(f):
|
|
|
|
ver, create_time = struct.unpack('<Iq', f.read(12))
|
|
|
|
kp_str = deser_string(f)
|
|
|
|
seed_id = f.read(20)
|
|
|
|
fpr = f.read(4)
|
|
|
|
path_len = 0
|
|
|
|
path = []
|
|
|
|
has_key_orig = False
|
|
|
|
if ver == UPGRADED_KEYMETA_VERSION:
|
|
|
|
path_len = deser_compact_size(f)
|
|
|
|
for i in range(0, path_len):
|
|
|
|
path.append(struct.unpack('<I', f.read(4))[0])
|
|
|
|
has_key_orig = bool(f.read(1))
|
|
|
|
return ver, create_time, kp_str, seed_id, fpr, path_len, path, has_key_orig
|
|
|
|
|
2020-04-29 16:11:49 +02:00
|
|
|
class UpgradeWalletTest(BitcoinTestFramework):
|
|
|
|
def set_test_params(self):
|
|
|
|
self.setup_clean_chain = True
|
|
|
|
self.num_nodes = 3
|
|
|
|
self.extra_args = [
|
2020-04-30 22:52:52 +02:00
|
|
|
["-addresstype=bech32", "-keypool=2"], # current wallet version
|
|
|
|
["-usehd=1", "-keypool=2"], # v0.16.3 wallet
|
|
|
|
["-usehd=0", "-keypool=2"] # v0.15.2 wallet
|
2020-04-29 16:11:49 +02:00
|
|
|
]
|
2020-09-29 02:24:06 +02:00
|
|
|
self.wallet_names = [self.default_wallet_name, None, None]
|
2020-04-29 16:11:49 +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-29 16:11:49 +02:00
|
|
|
|
|
|
|
def setup_network(self):
|
|
|
|
self.setup_nodes()
|
|
|
|
|
|
|
|
def setup_nodes(self):
|
|
|
|
self.add_nodes(self.num_nodes, extra_args=self.extra_args, versions=[
|
|
|
|
None,
|
|
|
|
160300,
|
2020-04-30 15:36:54 +02:00
|
|
|
150200,
|
2020-04-29 16:11:49 +02:00
|
|
|
])
|
|
|
|
self.start_nodes()
|
2020-09-29 02:24:06 +02:00
|
|
|
self.import_deterministic_coinbase_privkeys()
|
2020-04-29 16:11:49 +02:00
|
|
|
|
|
|
|
def dumb_sync_blocks(self):
|
|
|
|
"""
|
|
|
|
Little helper to sync older wallets.
|
|
|
|
Notice that v0.15.2's regtest is hardforked, so there is
|
|
|
|
no sync for it.
|
|
|
|
v0.15.2 is only being used to test for version upgrade
|
|
|
|
and master hash key presence.
|
|
|
|
v0.16.3 is being used to test for version upgrade and balances.
|
|
|
|
Further info: https://github.com/bitcoin/bitcoin/pull/18774#discussion_r416967844
|
|
|
|
"""
|
|
|
|
node_from = self.nodes[0]
|
|
|
|
v16_3_node = self.nodes[1]
|
|
|
|
to_height = node_from.getblockcount()
|
|
|
|
height = self.nodes[1].getblockcount()
|
|
|
|
for i in range(height, to_height+1):
|
|
|
|
b = node_from.getblock(blockhash=node_from.getblockhash(i), verbose=0)
|
|
|
|
v16_3_node.submitblock(b)
|
|
|
|
assert_equal(v16_3_node.getblockcount(), to_height)
|
|
|
|
|
|
|
|
def run_test(self):
|
|
|
|
self.nodes[0].generatetoaddress(101, self.nodes[0].getnewaddress())
|
|
|
|
self.dumb_sync_blocks()
|
|
|
|
# # Sanity check the test framework:
|
|
|
|
res = self.nodes[0].getblockchaininfo()
|
|
|
|
assert_equal(res['blocks'], 101)
|
|
|
|
node_master = self.nodes[0]
|
|
|
|
v16_3_node = self.nodes[1]
|
|
|
|
v15_2_node = self.nodes[2]
|
|
|
|
|
|
|
|
# Send coins to old wallets for later conversion checks.
|
|
|
|
v16_3_wallet = v16_3_node.get_wallet_rpc('wallet.dat')
|
|
|
|
v16_3_address = v16_3_wallet.getnewaddress()
|
|
|
|
node_master.generatetoaddress(101, v16_3_address)
|
|
|
|
self.dumb_sync_blocks()
|
|
|
|
v16_3_balance = v16_3_wallet.getbalance()
|
|
|
|
|
|
|
|
self.log.info("Test upgradewallet RPC...")
|
|
|
|
# Prepare for copying of the older wallet
|
2020-04-30 22:52:52 +02:00
|
|
|
node_master_wallet_dir = os.path.join(node_master.datadir, "regtest/wallets", self.default_wallet_name)
|
|
|
|
node_master_wallet = os.path.join(node_master_wallet_dir, self.default_wallet_name, self.wallet_data_filename)
|
2020-04-29 16:11:49 +02:00
|
|
|
v16_3_wallet = os.path.join(v16_3_node.datadir, "regtest/wallets/wallet.dat")
|
|
|
|
v15_2_wallet = os.path.join(v15_2_node.datadir, "regtest/wallet.dat")
|
2020-04-30 22:52:52 +02:00
|
|
|
split_hd_wallet = os.path.join(v15_2_node.datadir, "regtest/splithd")
|
2020-04-29 16:11:49 +02:00
|
|
|
self.stop_nodes()
|
|
|
|
|
2020-04-30 22:52:52 +02:00
|
|
|
# Make split hd wallet
|
|
|
|
self.start_node(2, ['-usehd=1', '-keypool=2', '-wallet=splithd'])
|
|
|
|
self.stop_node(2)
|
|
|
|
|
|
|
|
def copy_v16():
|
|
|
|
node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
|
|
|
|
# Copy the 0.16.3 wallet to the last Bitcoin Core version and open it:
|
|
|
|
shutil.rmtree(node_master_wallet_dir)
|
|
|
|
os.mkdir(node_master_wallet_dir)
|
|
|
|
shutil.copy(
|
|
|
|
v16_3_wallet,
|
|
|
|
node_master_wallet_dir
|
|
|
|
)
|
|
|
|
node_master.loadwallet(self.default_wallet_name)
|
|
|
|
|
|
|
|
def copy_non_hd():
|
|
|
|
node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
|
|
|
|
# Copy the 0.15.2 non hd wallet to the last Bitcoin Core version and open it:
|
|
|
|
shutil.rmtree(node_master_wallet_dir)
|
|
|
|
os.mkdir(node_master_wallet_dir)
|
|
|
|
shutil.copy(
|
|
|
|
v15_2_wallet,
|
|
|
|
node_master_wallet_dir
|
|
|
|
)
|
|
|
|
node_master.loadwallet(self.default_wallet_name)
|
2020-04-29 16:11:49 +02:00
|
|
|
|
2020-04-30 22:52:52 +02:00
|
|
|
def copy_split_hd():
|
|
|
|
node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
|
|
|
|
# Copy the 0.15.2 split hd wallet to the last Bitcoin Core version and open it:
|
|
|
|
shutil.rmtree(node_master_wallet_dir)
|
|
|
|
os.mkdir(node_master_wallet_dir)
|
|
|
|
shutil.copy(
|
|
|
|
split_hd_wallet,
|
|
|
|
os.path.join(node_master_wallet_dir, 'wallet.dat')
|
|
|
|
)
|
|
|
|
node_master.loadwallet(self.default_wallet_name)
|
|
|
|
|
|
|
|
self.restart_node(0)
|
|
|
|
copy_v16()
|
|
|
|
wallet = node_master.get_wallet_rpc(self.default_wallet_name)
|
2020-04-29 16:11:49 +02:00
|
|
|
old_version = wallet.getwalletinfo()["walletversion"]
|
|
|
|
|
|
|
|
# calling upgradewallet without version arguments
|
|
|
|
# should return nothing if successful
|
2020-10-19 03:01:42 +02:00
|
|
|
assert_equal(wallet.upgradewallet(), {})
|
2020-04-29 16:11:49 +02:00
|
|
|
new_version = wallet.getwalletinfo()["walletversion"]
|
|
|
|
# upgraded wallet version should be greater than older one
|
|
|
|
assert_greater_than(new_version, old_version)
|
|
|
|
# wallet should still contain the same balance
|
|
|
|
assert_equal(wallet.getbalance(), v16_3_balance)
|
|
|
|
|
2020-04-30 22:52:52 +02:00
|
|
|
copy_non_hd()
|
|
|
|
wallet = node_master.get_wallet_rpc(self.default_wallet_name)
|
2020-04-29 16:11:49 +02:00
|
|
|
# should have no master key hash before conversion
|
|
|
|
assert_equal('hdseedid' in wallet.getwalletinfo(), False)
|
|
|
|
# calling upgradewallet with explicit version number
|
|
|
|
# should return nothing if successful
|
2020-10-19 03:01:42 +02:00
|
|
|
assert_equal(wallet.upgradewallet(169900), {})
|
2020-04-29 16:11:49 +02:00
|
|
|
new_version = wallet.getwalletinfo()["walletversion"]
|
|
|
|
# upgraded wallet should have version 169900
|
|
|
|
assert_equal(new_version, 169900)
|
|
|
|
# after conversion master key hash should be present
|
|
|
|
assert_is_hex_string(wallet.getwalletinfo()['hdseedid'])
|
|
|
|
|
2020-04-30 22:52:52 +02:00
|
|
|
self.log.info('Intermediary versions don\'t effect anything')
|
|
|
|
copy_non_hd()
|
|
|
|
# Wallet starts with 60000
|
|
|
|
assert_equal(60000, wallet.getwalletinfo()['walletversion'])
|
|
|
|
wallet.unloadwallet()
|
|
|
|
before_checksum = sha256sum_file(node_master_wallet)
|
|
|
|
node_master.loadwallet('')
|
|
|
|
# Can "upgrade" to 129999 which should have no effect on the wallet
|
|
|
|
wallet.upgradewallet(129999)
|
|
|
|
assert_equal(60000, wallet.getwalletinfo()['walletversion'])
|
|
|
|
wallet.unloadwallet()
|
|
|
|
assert_equal(before_checksum, sha256sum_file(node_master_wallet))
|
|
|
|
node_master.loadwallet('')
|
|
|
|
|
|
|
|
self.log.info('Wallets cannot be downgraded')
|
|
|
|
copy_non_hd()
|
|
|
|
assert_raises_rpc_error(-4, 'Cannot downgrade wallet', wallet.upgradewallet, 40000)
|
|
|
|
wallet.unloadwallet()
|
|
|
|
assert_equal(before_checksum, sha256sum_file(node_master_wallet))
|
|
|
|
node_master.loadwallet('')
|
|
|
|
|
|
|
|
self.log.info('Can upgrade to HD')
|
|
|
|
# Inspect the old wallet and make sure there is no hdchain
|
|
|
|
orig_kvs = dump_bdb_kv(node_master_wallet)
|
|
|
|
assert b'\x07hdchain' not in orig_kvs
|
|
|
|
# Upgrade to HD, no split
|
|
|
|
wallet.upgradewallet(130000)
|
|
|
|
assert_equal(130000, wallet.getwalletinfo()['walletversion'])
|
|
|
|
# Check that there is now a hd chain and it is version 1, no internal chain counter
|
|
|
|
new_kvs = dump_bdb_kv(node_master_wallet)
|
|
|
|
assert b'\x07hdchain' in new_kvs
|
|
|
|
hd_chain = new_kvs[b'\x07hdchain']
|
|
|
|
assert_equal(28, len(hd_chain))
|
|
|
|
hd_chain_version, external_counter, seed_id = struct.unpack('<iI20s', hd_chain)
|
|
|
|
assert_equal(1, hd_chain_version)
|
|
|
|
seed_id = bytearray(seed_id)
|
|
|
|
seed_id.reverse()
|
|
|
|
old_kvs = new_kvs
|
|
|
|
# First 2 keys should still be non-HD
|
|
|
|
for i in range(0, 2):
|
|
|
|
info = wallet.getaddressinfo(wallet.getnewaddress())
|
|
|
|
assert 'hdkeypath' not in info
|
|
|
|
assert 'hdseedid' not in info
|
|
|
|
# Next key should be HD
|
|
|
|
info = wallet.getaddressinfo(wallet.getnewaddress())
|
|
|
|
assert_equal(seed_id.hex(), info['hdseedid'])
|
|
|
|
assert_equal('m/0\'/0\'/0\'', info['hdkeypath'])
|
|
|
|
prev_seed_id = info['hdseedid']
|
|
|
|
# Change key should be the same keypool
|
|
|
|
info = wallet.getaddressinfo(wallet.getrawchangeaddress())
|
|
|
|
assert_equal(prev_seed_id, info['hdseedid'])
|
|
|
|
assert_equal('m/0\'/0\'/1\'', info['hdkeypath'])
|
|
|
|
|
|
|
|
self.log.info('Cannot upgrade to HD Split, needs Pre Split Keypool')
|
|
|
|
assert_raises_rpc_error(-4, 'Cannot upgrade a non HD split wallet without upgrading to support pre split keypool', wallet.upgradewallet, 139900)
|
|
|
|
assert_equal(130000, wallet.getwalletinfo()['walletversion'])
|
|
|
|
assert_raises_rpc_error(-4, 'Cannot upgrade a non HD split wallet without upgrading to support pre split keypool', wallet.upgradewallet, 159900)
|
|
|
|
assert_equal(130000, wallet.getwalletinfo()['walletversion'])
|
|
|
|
assert_raises_rpc_error(-4, 'Cannot upgrade a non HD split wallet without upgrading to support pre split keypool', wallet.upgradewallet, 169899)
|
|
|
|
assert_equal(130000, wallet.getwalletinfo()['walletversion'])
|
|
|
|
|
|
|
|
self.log.info('Upgrade HD to HD chain split')
|
|
|
|
wallet.upgradewallet(169900)
|
|
|
|
assert_equal(169900, wallet.getwalletinfo()['walletversion'])
|
|
|
|
# Check that the hdchain updated correctly
|
|
|
|
new_kvs = dump_bdb_kv(node_master_wallet)
|
|
|
|
hd_chain = new_kvs[b'\x07hdchain']
|
|
|
|
assert_equal(32, len(hd_chain))
|
|
|
|
hd_chain_version, external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain)
|
|
|
|
assert_equal(2, hd_chain_version)
|
|
|
|
assert_equal(0, internal_counter)
|
|
|
|
seed_id = bytearray(seed_id)
|
|
|
|
seed_id.reverse()
|
|
|
|
assert_equal(seed_id.hex(), prev_seed_id)
|
|
|
|
# Next change address is the same keypool
|
|
|
|
info = wallet.getaddressinfo(wallet.getrawchangeaddress())
|
|
|
|
assert_equal(prev_seed_id, info['hdseedid'])
|
|
|
|
assert_equal('m/0\'/0\'/2\'', info['hdkeypath'])
|
|
|
|
# Next change address is the new keypool
|
|
|
|
info = wallet.getaddressinfo(wallet.getrawchangeaddress())
|
|
|
|
assert_equal(prev_seed_id, info['hdseedid'])
|
|
|
|
assert_equal('m/0\'/1\'/0\'', info['hdkeypath'])
|
|
|
|
# External addresses use the same keypool
|
|
|
|
info = wallet.getaddressinfo(wallet.getnewaddress())
|
|
|
|
assert_equal(prev_seed_id, info['hdseedid'])
|
|
|
|
assert_equal('m/0\'/0\'/3\'', info['hdkeypath'])
|
|
|
|
|
|
|
|
self.log.info('Upgrade non-HD to HD chain split')
|
|
|
|
copy_non_hd()
|
|
|
|
wallet.upgradewallet(169900)
|
|
|
|
assert_equal(169900, wallet.getwalletinfo()['walletversion'])
|
|
|
|
# Check that the hdchain updated correctly
|
|
|
|
new_kvs = dump_bdb_kv(node_master_wallet)
|
|
|
|
hd_chain = new_kvs[b'\x07hdchain']
|
|
|
|
assert_equal(32, len(hd_chain))
|
|
|
|
hd_chain_version, external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain)
|
|
|
|
assert_equal(2, hd_chain_version)
|
|
|
|
assert_equal(2, internal_counter)
|
|
|
|
# Drain the keypool by fetching one external key and one change key. Should still be the same keypool
|
|
|
|
info = wallet.getaddressinfo(wallet.getnewaddress())
|
|
|
|
assert 'hdseedid' not in info
|
|
|
|
assert 'hdkeypath' not in info
|
|
|
|
info = wallet.getaddressinfo(wallet.getrawchangeaddress())
|
|
|
|
assert 'hdseedid' not in info
|
|
|
|
assert 'hdkeypath' not in info
|
|
|
|
# The next addresses are HD and should be on different HD chains
|
|
|
|
info = wallet.getaddressinfo(wallet.getnewaddress())
|
|
|
|
ext_id = info['hdseedid']
|
|
|
|
assert_equal('m/0\'/0\'/0\'', info['hdkeypath'])
|
|
|
|
info = wallet.getaddressinfo(wallet.getrawchangeaddress())
|
|
|
|
assert_equal(ext_id, info['hdseedid'])
|
|
|
|
assert_equal('m/0\'/1\'/0\'', info['hdkeypath'])
|
|
|
|
|
|
|
|
self.log.info('KeyMetadata should upgrade when loading into master')
|
|
|
|
copy_v16()
|
|
|
|
old_kvs = dump_bdb_kv(v16_3_wallet)
|
|
|
|
new_kvs = dump_bdb_kv(node_master_wallet)
|
|
|
|
for k, old_v in old_kvs.items():
|
|
|
|
if k.startswith(b'\x07keymeta'):
|
|
|
|
new_ver, new_create_time, new_kp_str, new_seed_id, new_fpr, new_path_len, new_path, new_has_key_orig = deser_keymeta(BytesIO(new_kvs[k]))
|
|
|
|
old_ver, old_create_time, old_kp_str, old_seed_id, old_fpr, old_path_len, old_path, old_has_key_orig = deser_keymeta(BytesIO(old_v))
|
|
|
|
assert_equal(10, old_ver)
|
|
|
|
if old_kp_str == b"": # imported things that don't have keymeta (i.e. imported coinbase privkeys) won't be upgraded
|
|
|
|
assert_equal(new_kvs[k], old_v)
|
|
|
|
continue
|
|
|
|
assert_equal(12, new_ver)
|
|
|
|
assert_equal(new_create_time, old_create_time)
|
|
|
|
assert_equal(new_kp_str, old_kp_str)
|
|
|
|
assert_equal(new_seed_id, old_seed_id)
|
|
|
|
assert_equal(0, old_path_len)
|
|
|
|
assert_equal(new_path_len, len(new_path))
|
|
|
|
assert_equal([], old_path)
|
|
|
|
assert_equal(False, old_has_key_orig)
|
|
|
|
assert_equal(True, new_has_key_orig)
|
|
|
|
|
|
|
|
# Check that the path is right
|
|
|
|
built_path = []
|
|
|
|
for s in new_kp_str.decode().split('/')[1:]:
|
|
|
|
h = 0
|
|
|
|
if s[-1] == '\'':
|
|
|
|
s = s[:-1]
|
|
|
|
h = 0x80000000
|
|
|
|
p = int(s) | h
|
|
|
|
built_path.append(p)
|
|
|
|
assert_equal(new_path, built_path)
|
|
|
|
|
|
|
|
self.log.info('Upgrading to NO_DEFAULT_KEY should not remove the defaultkey')
|
|
|
|
copy_split_hd()
|
|
|
|
# Check the wallet has a default key initially
|
|
|
|
old_kvs = dump_bdb_kv(node_master_wallet)
|
|
|
|
defaultkey = old_kvs[b'\x0adefaultkey']
|
|
|
|
# Upgrade the wallet. Should still have the same default key
|
|
|
|
wallet.upgradewallet(159900)
|
|
|
|
new_kvs = dump_bdb_kv(node_master_wallet)
|
|
|
|
up_defaultkey = new_kvs[b'\x0adefaultkey']
|
|
|
|
assert_equal(defaultkey, up_defaultkey)
|
2020-11-18 19:14:19 +01:00
|
|
|
assert_equal(wallet.getwalletinfo()["walletversion"], 159900)
|
2020-04-30 22:52:52 +02:00
|
|
|
# 0.16.3 doesn't have a default key
|
|
|
|
v16_3_kvs = dump_bdb_kv(v16_3_wallet)
|
|
|
|
assert b'\x0adefaultkey' not in v16_3_kvs
|
|
|
|
|
2020-04-29 16:11:49 +02:00
|
|
|
if __name__ == '__main__':
|
|
|
|
UpgradeWalletTest().main()
|