Skip to content

Commit 0582932

Browse files
committed
test: add test for fast rescan using block filters (top-up detection)
1 parent ca48a46 commit 0582932

File tree

2 files changed

+103
-0
lines changed

2 files changed

+103
-0
lines changed

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
# vv Tests less than 30s vv
137137
'wallet_keypool_topup.py --legacy-wallet',
138138
'wallet_keypool_topup.py --descriptors',
139+
'wallet_fast_rescan.py --descriptors',
139140
'feature_fee_estimation.py',
140141
'interface_zmq.py',
141142
'rpc_invalid_address_message.py',

test/functional/wallet_fast_rescan.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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+
import os
8+
from typing import List
9+
10+
from test_framework.descriptors import descsum_create
11+
from test_framework.test_framework import BitcoinTestFramework
12+
from test_framework.test_node import TestNode
13+
from test_framework.util import assert_equal
14+
from test_framework.wallet import MiniWallet
15+
from test_framework.wallet_util import get_generate_key
16+
17+
18+
KEYPOOL_SIZE = 100 # smaller than default size to speed-up test
19+
NUM_DESCRIPTORS = 9 # number of descriptors (8 default ranged ones + 1 fixed non-ranged one)
20+
NUM_BLOCKS = 6 # number of blocks to mine
21+
22+
23+
class WalletFastRescanTest(BitcoinTestFramework):
24+
def set_test_params(self):
25+
self.num_nodes = 1
26+
self.extra_args = [[f'-keypool={KEYPOOL_SIZE}', '-blockfilterindex=1']]
27+
28+
def skip_test_if_missing_module(self):
29+
self.skip_if_no_wallet()
30+
self.skip_if_no_sqlite()
31+
32+
def get_wallet_txids(self, node: TestNode, wallet_name: str) -> List[str]:
33+
w = node.get_wallet_rpc(wallet_name)
34+
txs = w.listtransactions('*', 1000000)
35+
return [tx['txid'] for tx in txs]
36+
37+
def run_test(self):
38+
node = self.nodes[0]
39+
wallet = MiniWallet(node)
40+
wallet.rescan_utxos()
41+
42+
self.log.info("Create descriptor wallet with backup")
43+
WALLET_BACKUP_FILENAME = os.path.join(node.datadir, '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(f"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 = bytes.fromhex(w.getaddressinfo(addr)['scriptPubKey'])
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().main()

0 commit comments

Comments
 (0)