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