1313
1414import os
1515import 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
1722from test_framework .test_framework import BitcoinTestFramework
1823from 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+
2549class 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'\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+
140345if __name__ == '__main__' :
141346 UpgradeWalletTest ().main ()
0 commit comments