Skip to content

Commit bf76359

Browse files
committed
tests: Test specific upgradewallet scenarios and that upgrades work
1 parent 4b418a9 commit bf76359

File tree

1 file changed

+231
-26
lines changed

1 file changed

+231
-26
lines changed

test/functional/wallet_upgradewallet.py

Lines changed: 231 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,47 @@
1313

1414
import os
1515
import shutil
16+
import struct
1617

18+
from io import BytesIO
19+
20+
from test_framework.bdb import dump_bdb_kv
21+
from test_framework.messages import deser_compact_size, deser_string
1722
from test_framework.test_framework import BitcoinTestFramework
1823
from test_framework.util import (
1924
assert_equal,
2025
assert_greater_than,
2126
assert_is_hex_string,
27+
assert_raises_rpc_error,
28+
sha256sum_file,
2229
)
2330

2431

32+
UPGRADED_KEYMETA_VERSION = 12
33+
34+
def deser_keymeta(f):
35+
ver, create_time = struct.unpack('<Iq', f.read(12))
36+
kp_str = deser_string(f)
37+
seed_id = f.read(20)
38+
fpr = f.read(4)
39+
path_len = 0
40+
path = []
41+
has_key_orig = False
42+
if ver == UPGRADED_KEYMETA_VERSION:
43+
path_len = deser_compact_size(f)
44+
for i in range(0, path_len):
45+
path.append(struct.unpack('<I', f.read(4))[0])
46+
has_key_orig = bool(f.read(1))
47+
return ver, create_time, kp_str, seed_id, fpr, path_len, path, has_key_orig
48+
2549
class UpgradeWalletTest(BitcoinTestFramework):
2650
def set_test_params(self):
2751
self.setup_clean_chain = True
2852
self.num_nodes = 3
2953
self.extra_args = [
30-
["-addresstype=bech32"], # current wallet version
31-
["-usehd=1"], # v0.16.3 wallet
32-
["-usehd=0"] # v0.15.2 wallet
54+
["-addresstype=bech32", "-keypool=2"], # current wallet version
55+
["-usehd=1", "-keypool=2"], # v0.16.3 wallet
56+
["-usehd=0", "-keypool=2"] # v0.15.2 wallet
3357
]
3458
self.wallet_names = [self.default_wallet_name, None, None]
3559

@@ -87,22 +111,53 @@ def run_test(self):
87111

88112
self.log.info("Test upgradewallet RPC...")
89113
# Prepare for copying of the older wallet
90-
node_master_wallet_dir = os.path.join(node_master.datadir, "regtest/wallets")
114+
node_master_wallet_dir = os.path.join(node_master.datadir, "regtest/wallets", self.default_wallet_name)
115+
node_master_wallet = os.path.join(node_master_wallet_dir, self.default_wallet_name, self.wallet_data_filename)
91116
v16_3_wallet = os.path.join(v16_3_node.datadir, "regtest/wallets/wallet.dat")
92117
v15_2_wallet = os.path.join(v15_2_node.datadir, "regtest/wallet.dat")
118+
split_hd_wallet = os.path.join(v15_2_node.datadir, "regtest/splithd")
93119
self.stop_nodes()
94120

95-
# Copy the 0.16.3 wallet to the last Bitcoin Core version and open it:
96-
shutil.rmtree(node_master_wallet_dir)
97-
os.mkdir(node_master_wallet_dir)
98-
shutil.copy(
99-
v16_3_wallet,
100-
node_master_wallet_dir
101-
)
102-
self.restart_node(0, ['-nowallet'])
103-
node_master.loadwallet('')
121+
# Make split hd wallet
122+
self.start_node(2, ['-usehd=1', '-keypool=2', '-wallet=splithd'])
123+
self.stop_node(2)
124+
125+
def copy_v16():
126+
node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
127+
# Copy the 0.16.3 wallet to the last Bitcoin Core version and open it:
128+
shutil.rmtree(node_master_wallet_dir)
129+
os.mkdir(node_master_wallet_dir)
130+
shutil.copy(
131+
v16_3_wallet,
132+
node_master_wallet_dir
133+
)
134+
node_master.loadwallet(self.default_wallet_name)
135+
136+
def copy_non_hd():
137+
node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
138+
# Copy the 0.15.2 non hd wallet to the last Bitcoin Core version and open it:
139+
shutil.rmtree(node_master_wallet_dir)
140+
os.mkdir(node_master_wallet_dir)
141+
shutil.copy(
142+
v15_2_wallet,
143+
node_master_wallet_dir
144+
)
145+
node_master.loadwallet(self.default_wallet_name)
104146

105-
wallet = node_master.get_wallet_rpc('')
147+
def copy_split_hd():
148+
node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
149+
# Copy the 0.15.2 split hd wallet to the last Bitcoin Core version and open it:
150+
shutil.rmtree(node_master_wallet_dir)
151+
os.mkdir(node_master_wallet_dir)
152+
shutil.copy(
153+
split_hd_wallet,
154+
os.path.join(node_master_wallet_dir, 'wallet.dat')
155+
)
156+
node_master.loadwallet(self.default_wallet_name)
157+
158+
self.restart_node(0)
159+
copy_v16()
160+
wallet = node_master.get_wallet_rpc(self.default_wallet_name)
106161
old_version = wallet.getwalletinfo()["walletversion"]
107162

108163
# calling upgradewallet without version arguments
@@ -114,18 +169,8 @@ def run_test(self):
114169
# wallet should still contain the same balance
115170
assert_equal(wallet.getbalance(), v16_3_balance)
116171

117-
self.stop_node(0)
118-
# Copy the 0.15.2 wallet to the last Bitcoin Core version and open it:
119-
shutil.rmtree(node_master_wallet_dir)
120-
os.mkdir(node_master_wallet_dir)
121-
shutil.copy(
122-
v15_2_wallet,
123-
node_master_wallet_dir
124-
)
125-
self.restart_node(0, ['-nowallet'])
126-
node_master.loadwallet('')
127-
128-
wallet = node_master.get_wallet_rpc('')
172+
copy_non_hd()
173+
wallet = node_master.get_wallet_rpc(self.default_wallet_name)
129174
# should have no master key hash before conversion
130175
assert_equal('hdseedid' in wallet.getwalletinfo(), False)
131176
# calling upgradewallet with explicit version number
@@ -137,5 +182,165 @@ def run_test(self):
137182
# after conversion master key hash should be present
138183
assert_is_hex_string(wallet.getwalletinfo()['hdseedid'])
139184

185+
self.log.info('Intermediary versions don\'t effect anything')
186+
copy_non_hd()
187+
# Wallet starts with 60000
188+
assert_equal(60000, wallet.getwalletinfo()['walletversion'])
189+
wallet.unloadwallet()
190+
before_checksum = sha256sum_file(node_master_wallet)
191+
node_master.loadwallet('')
192+
# Can "upgrade" to 129999 which should have no effect on the wallet
193+
wallet.upgradewallet(129999)
194+
assert_equal(60000, wallet.getwalletinfo()['walletversion'])
195+
wallet.unloadwallet()
196+
assert_equal(before_checksum, sha256sum_file(node_master_wallet))
197+
node_master.loadwallet('')
198+
199+
self.log.info('Wallets cannot be downgraded')
200+
copy_non_hd()
201+
assert_raises_rpc_error(-4, 'Cannot downgrade wallet', wallet.upgradewallet, 40000)
202+
wallet.unloadwallet()
203+
assert_equal(before_checksum, sha256sum_file(node_master_wallet))
204+
node_master.loadwallet('')
205+
206+
self.log.info('Can upgrade to HD')
207+
# Inspect the old wallet and make sure there is no hdchain
208+
orig_kvs = dump_bdb_kv(node_master_wallet)
209+
assert b'\x07hdchain' not in orig_kvs
210+
# Upgrade to HD, no split
211+
wallet.upgradewallet(130000)
212+
assert_equal(130000, wallet.getwalletinfo()['walletversion'])
213+
# Check that there is now a hd chain and it is version 1, no internal chain counter
214+
new_kvs = dump_bdb_kv(node_master_wallet)
215+
assert b'\x07hdchain' in new_kvs
216+
hd_chain = new_kvs[b'\x07hdchain']
217+
assert_equal(28, len(hd_chain))
218+
hd_chain_version, external_counter, seed_id = struct.unpack('<iI20s', hd_chain)
219+
assert_equal(1, hd_chain_version)
220+
seed_id = bytearray(seed_id)
221+
seed_id.reverse()
222+
old_kvs = new_kvs
223+
# First 2 keys should still be non-HD
224+
for i in range(0, 2):
225+
info = wallet.getaddressinfo(wallet.getnewaddress())
226+
assert 'hdkeypath' not in info
227+
assert 'hdseedid' not in info
228+
# Next key should be HD
229+
info = wallet.getaddressinfo(wallet.getnewaddress())
230+
assert_equal(seed_id.hex(), info['hdseedid'])
231+
assert_equal('m/0\'/0\'/0\'', info['hdkeypath'])
232+
prev_seed_id = info['hdseedid']
233+
# Change key should be the same keypool
234+
info = wallet.getaddressinfo(wallet.getrawchangeaddress())
235+
assert_equal(prev_seed_id, info['hdseedid'])
236+
assert_equal('m/0\'/0\'/1\'', info['hdkeypath'])
237+
238+
self.log.info('Cannot upgrade to HD Split, needs Pre Split Keypool')
239+
assert_raises_rpc_error(-4, 'Cannot upgrade a non HD split wallet without upgrading to support pre split keypool', wallet.upgradewallet, 139900)
240+
assert_equal(130000, wallet.getwalletinfo()['walletversion'])
241+
assert_raises_rpc_error(-4, 'Cannot upgrade a non HD split wallet without upgrading to support pre split keypool', wallet.upgradewallet, 159900)
242+
assert_equal(130000, wallet.getwalletinfo()['walletversion'])
243+
assert_raises_rpc_error(-4, 'Cannot upgrade a non HD split wallet without upgrading to support pre split keypool', wallet.upgradewallet, 169899)
244+
assert_equal(130000, wallet.getwalletinfo()['walletversion'])
245+
246+
self.log.info('Upgrade HD to HD chain split')
247+
wallet.upgradewallet(169900)
248+
assert_equal(169900, wallet.getwalletinfo()['walletversion'])
249+
# Check that the hdchain updated correctly
250+
new_kvs = dump_bdb_kv(node_master_wallet)
251+
hd_chain = new_kvs[b'\x07hdchain']
252+
assert_equal(32, len(hd_chain))
253+
hd_chain_version, external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain)
254+
assert_equal(2, hd_chain_version)
255+
assert_equal(0, internal_counter)
256+
seed_id = bytearray(seed_id)
257+
seed_id.reverse()
258+
assert_equal(seed_id.hex(), prev_seed_id)
259+
# Next change address is the same keypool
260+
info = wallet.getaddressinfo(wallet.getrawchangeaddress())
261+
assert_equal(prev_seed_id, info['hdseedid'])
262+
assert_equal('m/0\'/0\'/2\'', info['hdkeypath'])
263+
# Next change address is the new keypool
264+
info = wallet.getaddressinfo(wallet.getrawchangeaddress())
265+
assert_equal(prev_seed_id, info['hdseedid'])
266+
assert_equal('m/0\'/1\'/0\'', info['hdkeypath'])
267+
# External addresses use the same keypool
268+
info = wallet.getaddressinfo(wallet.getnewaddress())
269+
assert_equal(prev_seed_id, info['hdseedid'])
270+
assert_equal('m/0\'/0\'/3\'', info['hdkeypath'])
271+
272+
self.log.info('Upgrade non-HD to HD chain split')
273+
copy_non_hd()
274+
wallet.upgradewallet(169900)
275+
assert_equal(169900, wallet.getwalletinfo()['walletversion'])
276+
# Check that the hdchain updated correctly
277+
new_kvs = dump_bdb_kv(node_master_wallet)
278+
hd_chain = new_kvs[b'\x07hdchain']
279+
assert_equal(32, len(hd_chain))
280+
hd_chain_version, external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain)
281+
assert_equal(2, hd_chain_version)
282+
assert_equal(2, internal_counter)
283+
# Drain the keypool by fetching one external key and one change key. Should still be the same keypool
284+
info = wallet.getaddressinfo(wallet.getnewaddress())
285+
assert 'hdseedid' not in info
286+
assert 'hdkeypath' not in info
287+
info = wallet.getaddressinfo(wallet.getrawchangeaddress())
288+
assert 'hdseedid' not in info
289+
assert 'hdkeypath' not in info
290+
# The next addresses are HD and should be on different HD chains
291+
info = wallet.getaddressinfo(wallet.getnewaddress())
292+
ext_id = info['hdseedid']
293+
assert_equal('m/0\'/0\'/0\'', info['hdkeypath'])
294+
info = wallet.getaddressinfo(wallet.getrawchangeaddress())
295+
assert_equal(ext_id, info['hdseedid'])
296+
assert_equal('m/0\'/1\'/0\'', info['hdkeypath'])
297+
298+
self.log.info('KeyMetadata should upgrade when loading into master')
299+
copy_v16()
300+
old_kvs = dump_bdb_kv(v16_3_wallet)
301+
new_kvs = dump_bdb_kv(node_master_wallet)
302+
for k, old_v in old_kvs.items():
303+
if k.startswith(b'\x07keymeta'):
304+
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]))
305+
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))
306+
assert_equal(10, old_ver)
307+
if old_kp_str == b"": # imported things that don't have keymeta (i.e. imported coinbase privkeys) won't be upgraded
308+
assert_equal(new_kvs[k], old_v)
309+
continue
310+
assert_equal(12, new_ver)
311+
assert_equal(new_create_time, old_create_time)
312+
assert_equal(new_kp_str, old_kp_str)
313+
assert_equal(new_seed_id, old_seed_id)
314+
assert_equal(0, old_path_len)
315+
assert_equal(new_path_len, len(new_path))
316+
assert_equal([], old_path)
317+
assert_equal(False, old_has_key_orig)
318+
assert_equal(True, new_has_key_orig)
319+
320+
# Check that the path is right
321+
built_path = []
322+
for s in new_kp_str.decode().split('/')[1:]:
323+
h = 0
324+
if s[-1] == '\'':
325+
s = s[:-1]
326+
h = 0x80000000
327+
p = int(s) | h
328+
built_path.append(p)
329+
assert_equal(new_path, built_path)
330+
331+
self.log.info('Upgrading to NO_DEFAULT_KEY should not remove the defaultkey')
332+
copy_split_hd()
333+
# Check the wallet has a default key initially
334+
old_kvs = dump_bdb_kv(node_master_wallet)
335+
defaultkey = old_kvs[b'\x0adefaultkey']
336+
# Upgrade the wallet. Should still have the same default key
337+
wallet.upgradewallet(159900)
338+
new_kvs = dump_bdb_kv(node_master_wallet)
339+
up_defaultkey = new_kvs[b'\x0adefaultkey']
340+
assert_equal(defaultkey, up_defaultkey)
341+
# 0.16.3 doesn't have a default key
342+
v16_3_kvs = dump_bdb_kv(v16_3_wallet)
343+
assert b'\x0adefaultkey' not in v16_3_kvs
344+
140345
if __name__ == '__main__':
141346
UpgradeWalletTest().main()

0 commit comments

Comments
 (0)