Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2022-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.5"""Test that fast rescan using block filters for descriptor wallets detects6top-ups correctly and finds the same transactions than the slow variant."""7from test_framework.address import address_to_scriptpubkey8from test_framework.descriptors import descsum_create9from test_framework.test_framework import BitcoinTestFramework10from test_framework.test_node import TestNode11from test_framework.util import assert_equal12from test_framework.wallet import MiniWallet13from test_framework.wallet_util import get_generate_key16KEYPOOL_SIZE = 100 # smaller than default size to speed-up test17NUM_BLOCKS = 6 # number of blocks to mine20class WalletFastRescanTest(BitcoinTestFramework):21def set_test_params(self):22self.num_nodes = 123self.extra_args = [[f'-keypool={KEYPOOL_SIZE}', '-blockfilterindex=1']]25def skip_test_if_missing_module(self):26self.skip_if_no_wallet()28def get_wallet_txids(self, node: TestNode, wallet_name: str) -> list[str]:29w = node.get_wallet_rpc(wallet_name)30txs = w.listtransactions('*', 1000000)31return [tx['txid'] for tx in txs]33def run_test(self):34node = self.nodes[0]35funding_wallet = MiniWallet(node)37self.log.info("Create descriptor wallet with backup")38WALLET_BACKUP_FILENAME = node.datadir_path / 'wallet.bak'39node.createwallet(wallet_name='topup_test')40w = node.get_wallet_rpc('topup_test')41fixed_key = get_generate_key()42w.importdescriptors([{"desc": descsum_create(f"wpkh({fixed_key.privkey})"), "timestamp": "now"}])43descriptors = w.listdescriptors()['descriptors']44w.backupwallet(WALLET_BACKUP_FILENAME)46num_txs = 048fast_rescan_messages = []49def append_fast_rescan_message():50chain_info = self.nodes[0].getblockchaininfo()51fast_rescan_messages.append(f"Fast rescan: inspect block {chain_info['blocks']} [{chain_info['bestblockhash']}] (filter matched)")53self.log.info("Create tx sending to non-ranged descriptors")54self.log.debug(f"Block 1/{NUM_BLOCKS}")55spk = bytes.fromhex(fixed_key.p2wpkh_script)56self.log.debug(f"-> fixed non-range descriptor address {fixed_key.p2wpkh_addr}")57funding_wallet.send_to(from_node=node, scriptPubKey=spk, amount=10000)58num_txs += 159self.generate(node, 1)60append_fast_rescan_message()62self.log.info("Create txs sending to end range address of each descriptor, triggering top-ups")63for i in range(1, NUM_BLOCKS):64self.log.debug(f"Block {i+1}/{NUM_BLOCKS}")65# Get descriptors with updated ranges66ranged_descs = [desc for desc in w.listdescriptors()['descriptors'] if 'range' in desc]67for desc_info in ranged_descs:68start_range, end_range = desc_info['range']69addr = w.deriveaddresses(desc_info['desc'], [end_range, end_range])[0]70spk = address_to_scriptpubkey(addr)71self.log.debug(f"-> range [{start_range},{end_range}], last address {addr}")72funding_wallet.send_to(from_node=node, scriptPubKey=spk, amount=10000)73num_txs += 174self.generate(node, 1)75append_fast_rescan_message()77self.log.info("Import wallet backup with block filter index")78with node.assert_debug_log(['fast variant using block filters', *fast_rescan_messages]):79node.restorewallet('rescan_fast', WALLET_BACKUP_FILENAME)80txids_fast = self.get_wallet_txids(node, 'rescan_fast')82self.log.info("Import non-active descriptors with block filter index")83node.createwallet(wallet_name='rescan_fast_nonactive', disable_private_keys=True, blank=True)84with node.assert_debug_log(['fast variant using block filters', *fast_rescan_messages]):85w = node.get_wallet_rpc('rescan_fast_nonactive')86w.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors])87txids_fast_nonactive = self.get_wallet_txids(node, 'rescan_fast_nonactive')89self.restart_node(0, [f'-keypool={KEYPOOL_SIZE}', '-blockfilterindex=0'])90self.log.info("Import wallet backup w/o block filter index")91with node.assert_debug_log(['slow variant inspecting all blocks']):92node.restorewallet("rescan_slow", WALLET_BACKUP_FILENAME)93txids_slow = self.get_wallet_txids(node, 'rescan_slow')95self.log.info("Import non-active descriptors w/o block filter index")96node.createwallet(wallet_name='rescan_slow_nonactive', disable_private_keys=True, blank=True)97with node.assert_debug_log(['slow variant inspecting all blocks']):98w = node.get_wallet_rpc('rescan_slow_nonactive')99w.importdescriptors([{"desc": descriptor['desc'], "timestamp": 0} for descriptor in descriptors])100txids_slow_nonactive = self.get_wallet_txids(node, 'rescan_slow_nonactive')102self.log.info("Verify that all rescans found the same txs in slow and fast variants")103assert_equal(len(txids_slow), num_txs)104assert_equal(len(txids_fast), num_txs)105assert_equal(len(txids_slow_nonactive), num_txs)106assert_equal(len(txids_fast_nonactive), num_txs)107assert_equal(sorted(txids_slow), sorted(txids_fast))108assert_equal(sorted(txids_slow_nonactive), sorted(txids_fast_nonactive))111if __name__ == '__main__':112WalletFastRescanTest(__file__).main()
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.