10
10
import subprocess
11
11
import textwrap
12
12
13
+ from collections import OrderedDict
14
+
13
15
from test_framework .test_framework import BitcoinTestFramework
14
16
from test_framework .util import assert_equal
15
17
@@ -96,6 +98,89 @@ def get_expected_info_output(self, name="", transactions=0, keypool=2, address=0
96
98
Address Book: %d
97
99
''' % (wallet_name , keypool , transactions , address * output_types ))
98
100
101
+ def read_dump (self , filename ):
102
+ dump = OrderedDict ()
103
+ with open (filename , "r" , encoding = "utf8" ) as f :
104
+ for row in f :
105
+ row = row .strip ()
106
+ key , value = row .split (',' )
107
+ dump [key ] = value
108
+ return dump
109
+
110
+ def assert_is_sqlite (self , filename ):
111
+ with open (filename , 'rb' ) as f :
112
+ file_magic = f .read (16 )
113
+ assert file_magic == b'SQLite format 3\x00 '
114
+
115
+ def assert_is_bdb (self , filename ):
116
+ with open (filename , 'rb' ) as f :
117
+ f .seek (12 , 0 )
118
+ file_magic = f .read (4 )
119
+ assert file_magic == b'\x00 \x05 \x31 \x62 ' or file_magic == b'\x62 \x31 \x05 \x00 '
120
+
121
+ def write_dump (self , dump , filename , magic = None , skip_checksum = False ):
122
+ if magic is None :
123
+ magic = "BITCOIN_CORE_WALLET_DUMP"
124
+ with open (filename , "w" , encoding = "utf8" ) as f :
125
+ row = "," .join ([magic , dump [magic ]]) + "\n "
126
+ f .write (row )
127
+ for k , v in dump .items ():
128
+ if k == magic or k == "checksum" :
129
+ continue
130
+ row = "," .join ([k , v ]) + "\n "
131
+ f .write (row )
132
+ if not skip_checksum :
133
+ row = "," .join (["checksum" , dump ["checksum" ]]) + "\n "
134
+ f .write (row )
135
+
136
+ def assert_dump (self , expected , received ):
137
+ e = expected .copy ()
138
+ r = received .copy ()
139
+
140
+ # BDB will add a "version" record that is not present in sqlite
141
+ # In that case, we should ignore this record in both
142
+ # But because this also effects the checksum, we also need to drop that.
143
+ v_key = "0776657273696f6e" # Version key
144
+ if v_key in e and v_key not in r :
145
+ del e [v_key ]
146
+ del e ["checksum" ]
147
+ del r ["checksum" ]
148
+ if v_key not in e and v_key in r :
149
+ del r [v_key ]
150
+ del e ["checksum" ]
151
+ del r ["checksum" ]
152
+
153
+ assert_equal (len (e ), len (r ))
154
+ for k , v in e .items ():
155
+ assert_equal (v , r [k ])
156
+
157
+ def do_tool_createfromdump (self , wallet_name , dumpfile , file_format = None ):
158
+ dumppath = os .path .join (self .nodes [0 ].datadir , dumpfile )
159
+ rt_dumppath = os .path .join (self .nodes [0 ].datadir , "rt-{}.dump" .format (wallet_name ))
160
+
161
+ dump_data = self .read_dump (dumppath )
162
+
163
+ args = ["-wallet={}" .format (wallet_name ),
164
+ "-dumpfile={}" .format (dumppath )]
165
+ if file_format is not None :
166
+ args .append ("-format={}" .format (file_format ))
167
+ args .append ("createfromdump" )
168
+
169
+ load_output = ""
170
+ if file_format is not None and file_format != dump_data ["format" ]:
171
+ load_output += "Warning: Dumpfile wallet format \" {}\" does not match command line specified format \" {}\" .\n " .format (dump_data ["format" ], file_format )
172
+ self .assert_tool_output (load_output , * args )
173
+ assert os .path .isdir (os .path .join (self .nodes [0 ].datadir , "regtest/wallets" , wallet_name ))
174
+
175
+ self .assert_tool_output ("The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n " , '-wallet={}' .format (wallet_name ), '-dumpfile={}' .format (rt_dumppath ), 'dump' )
176
+
177
+ rt_dump_data = self .read_dump (rt_dumppath )
178
+ wallet_dat = os .path .join (self .nodes [0 ].datadir , "regtest/wallets/" , wallet_name , "wallet.dat" )
179
+ if rt_dump_data ["format" ] == "bdb" :
180
+ self .assert_is_bdb (wallet_dat )
181
+ else :
182
+ self .assert_is_sqlite (wallet_dat )
183
+
99
184
def test_invalid_tool_commands_and_args (self ):
100
185
self .log .info ('Testing that various invalid commands raise with specific error messages' )
101
186
self .assert_raises_tool_error ('Invalid command: foo' , 'foo' )
@@ -228,6 +313,81 @@ def test_salvage(self):
228
313
229
314
self .assert_tool_output ('' , '-wallet=salvage' , 'salvage' )
230
315
316
+ def test_dump_createfromdump (self ):
317
+ self .start_node (0 )
318
+ self .nodes [0 ].createwallet ("todump" )
319
+ file_format = self .nodes [0 ].get_wallet_rpc ("todump" ).getwalletinfo ()["format" ]
320
+ self .nodes [0 ].createwallet ("todump2" )
321
+ self .stop_node (0 )
322
+
323
+ self .log .info ('Checking dump arguments' )
324
+ self .assert_raises_tool_error ('No dump file provided. To use dump, -dumpfile=<filename> must be provided.' , '-wallet=todump' , 'dump' )
325
+
326
+ self .log .info ('Checking basic dump' )
327
+ wallet_dump = os .path .join (self .nodes [0 ].datadir , "wallet.dump" )
328
+ self .assert_tool_output ('The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n ' , '-wallet=todump' , '-dumpfile={}' .format (wallet_dump ), 'dump' )
329
+
330
+ dump_data = self .read_dump (wallet_dump )
331
+ orig_dump = dump_data .copy ()
332
+ # Check the dump magic
333
+ assert_equal (dump_data ['BITCOIN_CORE_WALLET_DUMP' ], '1' )
334
+ # Check the file format
335
+ assert_equal (dump_data ["format" ], file_format )
336
+
337
+ self .log .info ('Checking that a dumpfile cannot be overwritten' )
338
+ self .assert_raises_tool_error ('File {} already exists. If you are sure this is what you want, move it out of the way first.' .format (wallet_dump ), '-wallet=todump2' , '-dumpfile={}' .format (wallet_dump ), 'dump' )
339
+
340
+ self .log .info ('Checking createfromdump arguments' )
341
+ self .assert_raises_tool_error ('No dump file provided. To use createfromdump, -dumpfile=<filename> must be provided.' , '-wallet=todump' , 'createfromdump' )
342
+ non_exist_dump = os .path .join (self .nodes [0 ].datadir , "wallet.nodump" )
343
+ self .assert_raises_tool_error ('Unknown wallet file format "notaformat" provided. Please provide one of "bdb" or "sqlite".' , '-wallet=todump' , '-format=notaformat' , '-dumpfile={}' .format (wallet_dump ), 'createfromdump' )
344
+ self .assert_raises_tool_error ('Dump file {} does not exist.' .format (non_exist_dump ), '-wallet=todump' , '-dumpfile={}' .format (non_exist_dump ), 'createfromdump' )
345
+ wallet_path = os .path .join (self .nodes [0 ].datadir , 'regtest/wallets/todump2' )
346
+ self .assert_raises_tool_error ('Failed to create database path \' {}\' . Database already exists.' .format (wallet_path ), '-wallet=todump2' , '-dumpfile={}' .format (wallet_dump ), 'createfromdump' )
347
+
348
+ self .log .info ('Checking createfromdump' )
349
+ self .do_tool_createfromdump ("load" , "wallet.dump" )
350
+ self .do_tool_createfromdump ("load-bdb" , "wallet.dump" , "bdb" )
351
+ self .do_tool_createfromdump ("load-sqlite" , "wallet.dump" , "sqlite" )
352
+
353
+ self .log .info ('Checking createfromdump handling of magic and versions' )
354
+ bad_ver_wallet_dump = os .path .join (self .nodes [0 ].datadir , "wallet-bad_ver1.dump" )
355
+ dump_data ["BITCOIN_CORE_WALLET_DUMP" ] = "0"
356
+ self .write_dump (dump_data , bad_ver_wallet_dump )
357
+ self .assert_raises_tool_error ('Error: Dumpfile version is not supported. This version of bitcoin-wallet only supports version 1 dumpfiles. Got dumpfile with version 0' , '-wallet=badload' , '-dumpfile={}' .format (bad_ver_wallet_dump ), 'createfromdump' )
358
+ assert not os .path .isdir (os .path .join (self .nodes [0 ].datadir , "regtest/wallets" , "badload" ))
359
+ bad_ver_wallet_dump = os .path .join (self .nodes [0 ].datadir , "wallet-bad_ver2.dump" )
360
+ dump_data ["BITCOIN_CORE_WALLET_DUMP" ] = "2"
361
+ self .write_dump (dump_data , bad_ver_wallet_dump )
362
+ self .assert_raises_tool_error ('Error: Dumpfile version is not supported. This version of bitcoin-wallet only supports version 1 dumpfiles. Got dumpfile with version 2' , '-wallet=badload' , '-dumpfile={}' .format (bad_ver_wallet_dump ), 'createfromdump' )
363
+ assert not os .path .isdir (os .path .join (self .nodes [0 ].datadir , "regtest/wallets" , "badload" ))
364
+ bad_magic_wallet_dump = os .path .join (self .nodes [0 ].datadir , "wallet-bad_magic.dump" )
365
+ del dump_data ["BITCOIN_CORE_WALLET_DUMP" ]
366
+ dump_data ["not_the_right_magic" ] = "1"
367
+ self .write_dump (dump_data , bad_magic_wallet_dump , "not_the_right_magic" )
368
+ self .assert_raises_tool_error ('Error: Dumpfile identifier record is incorrect. Got "not_the_right_magic", expected "BITCOIN_CORE_WALLET_DUMP".' , '-wallet=badload' , '-dumpfile={}' .format (bad_magic_wallet_dump ), 'createfromdump' )
369
+ assert not os .path .isdir (os .path .join (self .nodes [0 ].datadir , "regtest/wallets" , "badload" ))
370
+
371
+ self .log .info ('Checking createfromdump handling of checksums' )
372
+ bad_sum_wallet_dump = os .path .join (self .nodes [0 ].datadir , "wallet-bad_sum1.dump" )
373
+ dump_data = orig_dump .copy ()
374
+ checksum = dump_data ["checksum" ]
375
+ dump_data ["checksum" ] = "1" * 64
376
+ self .write_dump (dump_data , bad_sum_wallet_dump )
377
+ self .assert_raises_tool_error ('Error: Dumpfile checksum does not match. Computed {}, expected {}' .format (checksum , "1" * 64 ), '-wallet=bad' , '-dumpfile={}' .format (bad_sum_wallet_dump ), 'createfromdump' )
378
+ assert not os .path .isdir (os .path .join (self .nodes [0 ].datadir , "regtest/wallets" , "badload" ))
379
+ bad_sum_wallet_dump = os .path .join (self .nodes [0 ].datadir , "wallet-bad_sum2.dump" )
380
+ del dump_data ["checksum" ]
381
+ self .write_dump (dump_data , bad_sum_wallet_dump , skip_checksum = True )
382
+ self .assert_raises_tool_error ('Error: Missing checksum' , '-wallet=badload' , '-dumpfile={}' .format (bad_sum_wallet_dump ), 'createfromdump' )
383
+ assert not os .path .isdir (os .path .join (self .nodes [0 ].datadir , "regtest/wallets" , "badload" ))
384
+ bad_sum_wallet_dump = os .path .join (self .nodes [0 ].datadir , "wallet-bad_sum3.dump" )
385
+ dump_data ["checksum" ] = "2" * 10
386
+ self .write_dump (dump_data , bad_sum_wallet_dump )
387
+ self .assert_raises_tool_error ('Error: Dumpfile checksum does not match. Computed {}, expected {}{}' .format (checksum , "2" * 10 , "0" * 54 ), '-wallet=badload' , '-dumpfile={}' .format (bad_sum_wallet_dump ), 'createfromdump' )
388
+ assert not os .path .isdir (os .path .join (self .nodes [0 ].datadir , "regtest/wallets" , "badload" ))
389
+
390
+
231
391
def run_test (self ):
232
392
self .wallet_path = os .path .join (self .nodes [0 ].datadir , self .chain , 'wallets' , self .default_wallet_name , self .wallet_data_filename )
233
393
self .test_invalid_tool_commands_and_args ()
@@ -239,6 +399,7 @@ def run_test(self):
239
399
if not self .options .descriptors :
240
400
# Salvage is a legacy wallet only thing
241
401
self .test_salvage ()
402
+ self .test_dump_createfromdump ()
242
403
243
404
if __name__ == '__main__' :
244
405
ToolWalletTest ().main ()
0 commit comments