wallet_fast_rescan.py raw

   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()
 113