1 #!/usr/bin/env python3
2 # Copyright (c) 2023-present The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or https://www.opensource.org/licenses/mit-license.php.
5 6 """Test wallet-reindex interaction"""
7 8 import time
9 10 from test_framework.blocktools import COINBASE_MATURITY
11 from test_framework.test_framework import BitcoinTestFramework
12 from test_framework.util import (
13 assert_equal,
14 )
15 BLOCK_TIME = 60 * 10
16 17 class WalletReindexTest(BitcoinTestFramework):
18 def set_test_params(self):
19 self.num_nodes = 1
20 self.setup_clean_chain = True
21 22 def skip_test_if_missing_module(self):
23 self.skip_if_no_wallet()
24 25 def advance_time(self, node, secs):
26 self.node_time += secs
27 node.setmocktime(self.node_time)
28 29 # Verify the wallet updates the birth time accordingly when it detects a transaction
30 # with a time older than the oldest descriptor timestamp.
31 # This could happen when the user blindly imports a descriptor with 'timestamp=now'.
32 def birthtime_test(self, node, miner_wallet):
33 self.log.info("Test birth time update during tx scanning")
34 # Fund address to test
35 wallet_addr = miner_wallet.getnewaddress()
36 tx_id = miner_wallet.sendtoaddress(wallet_addr, 2)
37 38 # Generate 50 blocks, one every 10 min to surpass the 2 hours rescan window the wallet has
39 for _ in range(50):
40 self.generate(node, 1)
41 self.advance_time(node, BLOCK_TIME)
42 43 # Now create a new wallet, and import the descriptor
44 node.createwallet(wallet_name='watch_only', disable_private_keys=True, load_on_startup=True)
45 wallet_watch_only = node.get_wallet_rpc('watch_only')
46 # Blank wallets don't have a birth time
47 assert 'birthtime' not in wallet_watch_only.getwalletinfo()
48 49 # Import address with timestamp=now.
50 wallet_watch_only.importdescriptors([{"desc": miner_wallet.getaddressinfo(wallet_addr)["desc"], "timestamp": "now"}])
51 assert_equal(len(wallet_watch_only.listtransactions()), 0)
52 53 # Depending on the wallet type, the birth time changes.
54 wallet_birthtime = wallet_watch_only.getwalletinfo()['birthtime']
55 # As blocks were generated every 10 min, the chain MTP timestamp is node_time - 60 min.
56 assert_equal(self.node_time - BLOCK_TIME * 6, wallet_birthtime)
57 58 # Rescan the wallet to detect the missing transaction
59 wallet_watch_only.rescanblockchain()
60 assert_equal(wallet_watch_only.gettransaction(tx_id)['confirmations'], 50)
61 assert_equal(wallet_watch_only.getbalances()['mine']['trusted'], 2)
62 63 self.log.info("Reindex ...") # restart_node waits for it to finish
64 self.restart_node(0, extra_args=[ f'-mocktime={self.node_time}'])
65 66 # Verify the transaction is still 'confirmed' after reindex
67 wallet_watch_only = node.get_wallet_rpc('watch_only')
68 tx_info = wallet_watch_only.gettransaction(tx_id)
69 assert_equal(tx_info['confirmations'], 50)
70 71 # Depending on the wallet type, the birth time changes.
72 # For descriptors, verify the wallet updated the birth time to the transaction time
73 assert_equal(tx_info['time'], wallet_watch_only.getwalletinfo()['birthtime'])
74 75 wallet_watch_only.unloadwallet()
76 77 def run_test(self):
78 node = self.nodes[0]
79 self.node_time = int(time.time())
80 node.setmocktime(self.node_time)
81 82 # Fund miner
83 node.createwallet(wallet_name='miner', load_on_startup=True)
84 miner_wallet = node.get_wallet_rpc('miner')
85 self.generatetoaddress(node, COINBASE_MATURITY + 10, miner_wallet.getnewaddress())
86 87 # Tests
88 self.birthtime_test(node, miner_wallet)
89 90 91 if __name__ == '__main__':
92 WalletReindexTest(__file__).main()
93