/ test / functional / wallet_fast_rescan.py
wallet_fast_rescan.py
 1  #!/usr/bin/env python3
 2  # Copyright (c) 2022 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_DESCRIPTORS = 9  # number of descriptors (8 default ranged ones + 1 fixed non-ranged one)
18  NUM_BLOCKS = 6       # number of blocks to mine
19  
20  
21  class WalletFastRescanTest(BitcoinTestFramework):
22      def set_test_params(self):
23          self.num_nodes = 1
24          self.extra_args = [[f'-keypool={KEYPOOL_SIZE}', '-blockfilterindex=1']]
25  
26      def skip_test_if_missing_module(self):
27          self.skip_if_no_wallet()
28  
29      def get_wallet_txids(self, node: TestNode, wallet_name: str) -> list[str]:
30          w = node.get_wallet_rpc(wallet_name)
31          txs = w.listtransactions('*', 1000000)
32          return [tx['txid'] for tx in txs]
33  
34      def run_test(self):
35          node = self.nodes[0]
36          wallet = MiniWallet(node)
37  
38          self.log.info("Create descriptor wallet with backup")
39          WALLET_BACKUP_FILENAME = node.datadir_path / 'wallet.bak'
40          node.createwallet(wallet_name='topup_test', descriptors=True)
41          w = node.get_wallet_rpc('topup_test')
42          fixed_key = get_generate_key()
43          print(w.importdescriptors([{"desc": descsum_create(f"wpkh({fixed_key.privkey})"), "timestamp": "now"}]))
44          descriptors = w.listdescriptors()['descriptors']
45          assert_equal(len(descriptors), NUM_DESCRIPTORS)
46          w.backupwallet(WALLET_BACKUP_FILENAME)
47  
48          self.log.info("Create txs sending to end range address of each descriptor, triggering top-ups")
49          for i in range(NUM_BLOCKS):
50              self.log.info(f"Block {i+1}/{NUM_BLOCKS}")
51              for desc_info in w.listdescriptors()['descriptors']:
52                  if 'range' in desc_info:
53                      start_range, end_range = desc_info['range']
54                      addr = w.deriveaddresses(desc_info['desc'], [end_range, end_range])[0]
55                      spk = address_to_scriptpubkey(addr)
56                      self.log.info(f"-> range [{start_range},{end_range}], last address {addr}")
57                  else:
58                      spk = bytes.fromhex(fixed_key.p2wpkh_script)
59                      self.log.info(f"-> fixed non-range descriptor address {fixed_key.p2wpkh_addr}")
60                  wallet.send_to(from_node=node, scriptPubKey=spk, amount=10000)
61              self.generate(node, 1)
62  
63          self.log.info("Import wallet backup with block filter index")
64          with node.assert_debug_log(['fast variant using block filters']):
65              node.restorewallet('rescan_fast', WALLET_BACKUP_FILENAME)
66          txids_fast = self.get_wallet_txids(node, 'rescan_fast')
67  
68          self.log.info("Import non-active descriptors with block filter index")
69          node.createwallet(wallet_name='rescan_fast_nonactive', descriptors=True, disable_private_keys=True, blank=True)
70          with node.assert_debug_log(['fast variant using block filters']):
71              w = node.get_wallet_rpc('rescan_fast_nonactive')
72              w.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors])
73          txids_fast_nonactive = self.get_wallet_txids(node, 'rescan_fast_nonactive')
74  
75          self.restart_node(0, [f'-keypool={KEYPOOL_SIZE}', '-blockfilterindex=0'])
76          self.log.info("Import wallet backup w/o block filter index")
77          with node.assert_debug_log(['slow variant inspecting all blocks']):
78              node.restorewallet("rescan_slow", WALLET_BACKUP_FILENAME)
79          txids_slow = self.get_wallet_txids(node, 'rescan_slow')
80  
81          self.log.info("Import non-active descriptors w/o block filter index")
82          node.createwallet(wallet_name='rescan_slow_nonactive', descriptors=True, disable_private_keys=True, blank=True)
83          with node.assert_debug_log(['slow variant inspecting all blocks']):
84              w = node.get_wallet_rpc('rescan_slow_nonactive')
85              w.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors])
86          txids_slow_nonactive = self.get_wallet_txids(node, 'rescan_slow_nonactive')
87  
88          self.log.info("Verify that all rescans found the same txs in slow and fast variants")
89          assert_equal(len(txids_slow), NUM_DESCRIPTORS * NUM_BLOCKS)
90          assert_equal(len(txids_fast), NUM_DESCRIPTORS * NUM_BLOCKS)
91          assert_equal(len(txids_slow_nonactive), NUM_DESCRIPTORS * NUM_BLOCKS)
92          assert_equal(len(txids_fast_nonactive), NUM_DESCRIPTORS * NUM_BLOCKS)
93          assert_equal(sorted(txids_slow), sorted(txids_fast))
94          assert_equal(sorted(txids_slow_nonactive), sorted(txids_fast_nonactive))
95  
96  
97  if __name__ == '__main__':
98      WalletFastRescanTest(__file__).main()