@ -9,7 +9,10 @@ try:
except ImportError :
pass
import concurrent . futures
from test_framework . blocktools import COINBASE_MATURITY
from test_framework . descriptors import descsum_create
from test_framework . test_framework import BitcoinTestFramework
from test_framework . util import (
assert_equal ,
@ -33,6 +36,41 @@ class WalletDescriptorTest(BitcoinTestFramework):
self . skip_if_no_sqlite ( )
self . skip_if_no_py_sqlite3 ( )
def test_concurrent_writes ( self ) :
self . log . info ( " Test sqlite concurrent writes are in the correct order " )
self . restart_node ( 0 , extra_args = [ " -unsafesqlitesync=0 " ] )
self . nodes [ 0 ] . createwallet ( wallet_name = " concurrency " , blank = True )
wallet = self . nodes [ 0 ] . get_wallet_rpc ( " concurrency " )
# First import a descriptor that uses hardened dervation so that topping up
# Will require writing a ton to db
wallet . importdescriptors ( [ { " desc " : descsum_create ( " wpkh(tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg/0h/0h/*h) " ) , " timestamp " : " now " , " active " : True } ] )
with concurrent . futures . ThreadPoolExecutor ( max_workers = 1 ) as thread :
topup = thread . submit ( wallet . keypoolrefill , newsize = 1000 )
# Then while the topup is running, we need to do something that will call
# ChainStateFlushed which will trigger a write to the db, hopefully at the
# same time that the topup still has an open db transaction.
self . nodes [ 0 ] . cli . gettxoutsetinfo ( )
assert_equal ( topup . result ( ) , None )
wallet . unloadwallet ( )
# Check that everything was written
wallet_db = self . nodes [ 0 ] . wallets_path / " concurrency " / self . wallet_data_filename
conn = sqlite3 . connect ( wallet_db )
with conn :
# Retrieve the bestblock_nomerkle record
bestblock_rec = conn . execute ( " SELECT value FROM main WHERE hex(key) = ' 1262657374626C6F636B5F6E6F6D65726B6C65 ' " ) . fetchone ( ) [ 0 ]
# Retrieve the number of descriptor cache records
# Since we store binary data, sqlite's comparison operators don't work everywhere
# so just retrieve all records and process them ourselves.
db_keys = conn . execute ( " SELECT key FROM main " ) . fetchall ( )
cache_records = len ( [ k [ 0 ] for k in db_keys if b " walletdescriptorcache " in k [ 0 ] ] )
conn . close ( )
assert_equal ( bestblock_rec [ 5 : 37 ] [ : : - 1 ] . hex ( ) , self . nodes [ 0 ] . getbestblockhash ( ) )
assert_equal ( cache_records , 1000 )
def run_test ( self ) :
if self . is_bdb_compiled ( ) :
# Make a legacy wallet and check it is BDB
@ -240,6 +278,8 @@ class WalletDescriptorTest(BitcoinTestFramework):
conn . close ( )
assert_raises_rpc_error ( - 4 , " Unexpected legacy entry in descriptor wallet found. " , self . nodes [ 0 ] . loadwallet , " crashme " )
self . test_concurrent_writes ( )
if __name__ == ' __main__ ' :
WalletDescriptorTest ( ) . main ( )