wallet_fast_rescan.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2022-present The Bitcoin Core developers 3 # Distributed under the MIT software license, see the accompanying 4 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 """Test that fast rescan using block filters for descriptor wallets detects 6 top-ups correctly and finds the same transactions than the slow variant.""" 7 from test_framework.address import address_to_scriptpubkey 8 from test_framework.descriptors import descsum_create 9 from test_framework.test_framework import BitcoinTestFramework 10 from test_framework.test_node import TestNode 11 from test_framework.util import assert_equal 12 from test_framework.wallet import MiniWallet 13 from test_framework.wallet_util import get_generate_key 14 15 16 KEYPOOL_SIZE = 100 # smaller than default size to speed-up test 17 NUM_BLOCKS = 6 # number of blocks to mine 18 19 20 class WalletFastRescanTest(BitcoinTestFramework): 21 def set_test_params(self): 22 self.num_nodes = 1 23 self.extra_args = [[f'-keypool={KEYPOOL_SIZE}', '-blockfilterindex=1']] 24 25 def skip_test_if_missing_module(self): 26 self.skip_if_no_wallet() 27 28 def get_wallet_txids(self, node: TestNode, wallet_name: str) -> list[str]: 29 w = node.get_wallet_rpc(wallet_name) 30 txs = w.listtransactions('*', 1000000) 31 return [tx['txid'] for tx in txs] 32 33 def run_test(self): 34 node = self.nodes[0] 35 funding_wallet = MiniWallet(node) 36 37 self.log.info("Create descriptor wallet with backup") 38 WALLET_BACKUP_FILENAME = node.datadir_path / 'wallet.bak' 39 node.createwallet(wallet_name='topup_test') 40 w = node.get_wallet_rpc('topup_test') 41 fixed_key = get_generate_key() 42 w.importdescriptors([{"desc": descsum_create(f"wpkh({fixed_key.privkey})"), "timestamp": "now"}]) 43 descriptors = w.listdescriptors()['descriptors'] 44 w.backupwallet(WALLET_BACKUP_FILENAME) 45 46 num_txs = 0 47 48 fast_rescan_messages = [] 49 def append_fast_rescan_message(): 50 chain_info = self.nodes[0].getblockchaininfo() 51 fast_rescan_messages.append(f"Fast rescan: inspect block {chain_info['blocks']} [{chain_info['bestblockhash']}] (filter matched)") 52 53 self.log.info("Create tx sending to non-ranged descriptors") 54 self.log.debug(f"Block 1/{NUM_BLOCKS}") 55 spk = bytes.fromhex(fixed_key.p2wpkh_script) 56 self.log.debug(f"-> fixed non-range descriptor address {fixed_key.p2wpkh_addr}") 57 funding_wallet.send_to(from_node=node, scriptPubKey=spk, amount=10000) 58 num_txs += 1 59 self.generate(node, 1) 60 append_fast_rescan_message() 61 62 self.log.info("Create txs sending to end range address of each descriptor, triggering top-ups") 63 for i in range(1, NUM_BLOCKS): 64 self.log.debug(f"Block {i+1}/{NUM_BLOCKS}") 65 # Get descriptors with updated ranges 66 ranged_descs = [desc for desc in w.listdescriptors()['descriptors'] if 'range' in desc] 67 for desc_info in ranged_descs: 68 start_range, end_range = desc_info['range'] 69 addr = w.deriveaddresses(desc_info['desc'], [end_range, end_range])[0] 70 spk = address_to_scriptpubkey(addr) 71 self.log.debug(f"-> range [{start_range},{end_range}], last address {addr}") 72 funding_wallet.send_to(from_node=node, scriptPubKey=spk, amount=10000) 73 num_txs += 1 74 self.generate(node, 1) 75 append_fast_rescan_message() 76 77 self.log.info("Import wallet backup with block filter index") 78 with node.assert_debug_log(['fast variant using block filters', *fast_rescan_messages]): 79 node.restorewallet('rescan_fast', WALLET_BACKUP_FILENAME) 80 txids_fast = self.get_wallet_txids(node, 'rescan_fast') 81 82 self.log.info("Import non-active descriptors with block filter index") 83 node.createwallet(wallet_name='rescan_fast_nonactive', disable_private_keys=True, blank=True) 84 with node.assert_debug_log(['fast variant using block filters', *fast_rescan_messages]): 85 w = node.get_wallet_rpc('rescan_fast_nonactive') 86 w.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors]) 87 txids_fast_nonactive = self.get_wallet_txids(node, 'rescan_fast_nonactive') 88 89 self.restart_node(0, [f'-keypool={KEYPOOL_SIZE}', '-blockfilterindex=0']) 90 self.log.info("Import wallet backup w/o block filter index") 91 with node.assert_debug_log(['slow variant inspecting all blocks']): 92 node.restorewallet("rescan_slow", WALLET_BACKUP_FILENAME) 93 txids_slow = self.get_wallet_txids(node, 'rescan_slow') 94 95 self.log.info("Import non-active descriptors w/o block filter index") 96 node.createwallet(wallet_name='rescan_slow_nonactive', disable_private_keys=True, blank=True) 97 with node.assert_debug_log(['slow variant inspecting all blocks']): 98 w = node.get_wallet_rpc('rescan_slow_nonactive') 99 w.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors]) 100 txids_slow_nonactive = self.get_wallet_txids(node, 'rescan_slow_nonactive') 101 102 self.log.info("Verify that all rescans found the same txs in slow and fast variants") 103 assert_equal(len(txids_slow), num_txs) 104 assert_equal(len(txids_fast), num_txs) 105 assert_equal(len(txids_slow_nonactive), num_txs) 106 assert_equal(len(txids_fast_nonactive), num_txs) 107 assert_equal(sorted(txids_slow), sorted(txids_fast)) 108 assert_equal(sorted(txids_slow_nonactive), sorted(txids_fast_nonactive)) 109 110 111 if __name__ == '__main__': 112 WalletFastRescanTest(__file__).main()