13
13
14
14
import os
15
15
import shutil
16
+ import struct
16
17
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
17
22
from test_framework .test_framework import BitcoinTestFramework
18
23
from test_framework .util import (
19
24
assert_equal ,
20
25
assert_greater_than ,
21
26
assert_is_hex_string ,
27
+ assert_raises_rpc_error ,
28
+ sha256sum_file ,
22
29
)
23
30
24
31
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
+
25
49
class UpgradeWalletTest (BitcoinTestFramework ):
26
50
def set_test_params (self ):
27
51
self .setup_clean_chain = True
28
52
self .num_nodes = 3
29
53
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
33
57
]
34
58
self .wallet_names = [self .default_wallet_name , None , None ]
35
59
@@ -87,22 +111,53 @@ def run_test(self):
87
111
88
112
self .log .info ("Test upgradewallet RPC..." )
89
113
# 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 )
91
116
v16_3_wallet = os .path .join (v16_3_node .datadir , "regtest/wallets/wallet.dat" )
92
117
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" )
93
119
self .stop_nodes ()
94
120
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 )
104
146
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 )
106
161
old_version = wallet .getwalletinfo ()["walletversion" ]
107
162
108
163
# calling upgradewallet without version arguments
@@ -114,18 +169,8 @@ def run_test(self):
114
169
# wallet should still contain the same balance
115
170
assert_equal (wallet .getbalance (), v16_3_balance )
116
171
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 )
129
174
# should have no master key hash before conversion
130
175
assert_equal ('hdseedid' in wallet .getwalletinfo (), False )
131
176
# calling upgradewallet with explicit version number
@@ -137,5 +182,165 @@ def run_test(self):
137
182
# after conversion master key hash should be present
138
183
assert_is_hex_string (wallet .getwalletinfo ()['hdseedid' ])
139
184
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'\x07 hdchain' 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'\x07 hdchain' in new_kvs
216
+ hd_chain = new_kvs [b'\x07 hdchain' ]
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'\x07 hdchain' ]
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'\x07 hdchain' ]
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'\x07 keymeta' ):
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'\x0a defaultkey' ]
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'\x0a defaultkey' ]
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'\x0a defaultkey' not in v16_3_kvs
344
+
140
345
if __name__ == '__main__' :
141
346
UpgradeWalletTest ().main ()
0 commit comments