9
9
except ImportError :
10
10
pass
11
11
12
+ import concurrent .futures
13
+
12
14
from test_framework .blocktools import COINBASE_MATURITY
15
+ from test_framework .descriptors import descsum_create
13
16
from test_framework .test_framework import BitcoinTestFramework
14
17
from test_framework .util import (
15
18
assert_equal ,
@@ -33,6 +36,41 @@ def skip_test_if_missing_module(self):
33
36
self .skip_if_no_sqlite ()
34
37
self .skip_if_no_py_sqlite3 ()
35
38
39
+ def test_concurrent_writes (self ):
40
+ self .log .info ("Test sqlite concurrent writes are in the correct order" )
41
+ self .restart_node (0 , extra_args = ["-unsafesqlitesync=0" ])
42
+ self .nodes [0 ].createwallet (wallet_name = "concurrency" , blank = True )
43
+ wallet = self .nodes [0 ].get_wallet_rpc ("concurrency" )
44
+ # First import a descriptor that uses hardened dervation so that topping up
45
+ # Will require writing a ton to db
46
+ wallet .importdescriptors ([{"desc" :descsum_create ("wpkh(tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg/0h/0h/*h)" ), "timestamp" : "now" , "active" : True }])
47
+ with concurrent .futures .ThreadPoolExecutor (max_workers = 1 ) as thread :
48
+ topup = thread .submit (wallet .keypoolrefill , newsize = 1000 )
49
+
50
+ # Then while the topup is running, we need to do something that will call
51
+ # ChainStateFlushed which will trigger a write to the db, hopefully at the
52
+ # same time that the topup still has an open db transaction.
53
+ self .nodes [0 ].cli .gettxoutsetinfo ()
54
+ assert_equal (topup .result (), None )
55
+
56
+ wallet .unloadwallet ()
57
+
58
+ # Check that everything was written
59
+ wallet_db = self .nodes [0 ].wallets_path / "concurrency" / self .wallet_data_filename
60
+ conn = sqlite3 .connect (wallet_db )
61
+ with conn :
62
+ # Retrieve the bestblock_nomerkle record
63
+ bestblock_rec = conn .execute ("SELECT value FROM main WHERE hex(key) = '1262657374626C6F636B5F6E6F6D65726B6C65'" ).fetchone ()[0 ]
64
+ # Retrieve the number of descriptor cache records
65
+ # Since we store binary data, sqlite's comparison operators don't work everywhere
66
+ # so just retrieve all records and process them ourselves.
67
+ db_keys = conn .execute ("SELECT key FROM main" ).fetchall ()
68
+ cache_records = len ([k [0 ] for k in db_keys if b"walletdescriptorcache" in k [0 ]])
69
+ conn .close ()
70
+
71
+ assert_equal (bestblock_rec [5 :37 ][::- 1 ].hex (), self .nodes [0 ].getbestblockhash ())
72
+ assert_equal (cache_records , 1000 )
73
+
36
74
def run_test (self ):
37
75
if self .is_bdb_compiled ():
38
76
# Make a legacy wallet and check it is BDB
@@ -240,6 +278,8 @@ def run_test(self):
240
278
conn .close ()
241
279
assert_raises_rpc_error (- 4 , "Unexpected legacy entry in descriptor wallet found." , self .nodes [0 ].loadwallet , "crashme" )
242
280
281
+ self .test_concurrent_writes ()
282
+
243
283
244
284
if __name__ == '__main__' :
245
285
WalletDescriptorTest ().main ()
0 commit comments