1 #!/usr/bin/env python3
2 # Copyright (c) 2023-present The Limenka 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.descriptors import descsum_create
12 from test_framework.test_framework import LimenkaTestFramework
13 from test_framework.util import (
14 assert_equal,
15 )
16 BLOCK_TIME = 60 * 10
17 18 class WalletReindexTest(LimenkaTestFramework):
19 def add_options(self, parser):
20 self.add_wallet_options(parser)
21 22 def set_test_params(self):
23 self.num_nodes = 1
24 self.setup_clean_chain = True
25 26 def skip_test_if_missing_module(self):
27 self.skip_if_no_wallet()
28 29 def advance_time(self, node, secs):
30 self.node_time += secs
31 node.setmocktime(self.node_time)
32 33 # Verify the wallet updates the birth time accordingly when it detects a transaction
34 # with a time older than the oldest descriptor timestamp.
35 # This could happen when the user blindly imports a descriptor with 'timestamp=now'.
36 def birthtime_test(self, node, miner_wallet):
37 self.log.info("Test birth time update during tx scanning")
38 # Fund address to test
39 wallet_addr = miner_wallet.getnewaddress()
40 tx_id = miner_wallet.sendtoaddress(wallet_addr, 2)
41 42 # Generate 50 blocks, one every 10 min to surpass the 2 hours rescan window the wallet has
43 for _ in range(50):
44 self.generate(node, 1)
45 self.advance_time(node, BLOCK_TIME)
46 47 # Now create a new wallet, and import the descriptor
48 node.createwallet(wallet_name='watch_only', disable_private_keys=True, load_on_startup=True)
49 wallet_watch_only = node.get_wallet_rpc('watch_only')
50 # Blank wallets don't have a birth time
51 assert 'birthtime' not in wallet_watch_only.getwalletinfo()
52 53 # For a descriptors wallet: Import address with timestamp=now.
54 # For legacy wallet: There is no way of importing a script/address with a custom time. The wallet always imports it with birthtime=1.
55 # In both cases, disable rescan to not detect the transaction.
56 if self.options.descriptors:
57 import_res = wallet_watch_only.importdescriptors([{
58 'desc': descsum_create('addr(' + wallet_addr + ')'),
59 'timestamp': 'now',
60 }])
61 assert len(import_res) == 1
62 assert import_res[0]['success']
63 else:
64 wallet_watch_only.importaddress(wallet_addr, rescan=False)
65 assert_equal(len(wallet_watch_only.listtransactions()), 0)
66 67 # Depending on the wallet type, the birth time changes.
68 wallet_birthtime = wallet_watch_only.getwalletinfo()['birthtime']
69 if self.options.descriptors:
70 # As blocks were generated every 10 min, the chain MTP timestamp is node_time - 60 min.
71 assert_equal(self.node_time - BLOCK_TIME * 6, wallet_birthtime)
72 else:
73 # No way of importing scripts/addresses with a custom time on a legacy wallet.
74 # It's always set to the beginning of time.
75 assert_equal(wallet_birthtime, 1)
76 77 # Rescan the wallet to detect the missing transaction
78 wallet_watch_only.rescanblockchain()
79 assert_equal(wallet_watch_only.gettransaction(tx_id)['confirmations'], 50)
80 assert_equal(wallet_watch_only.getbalances()['mine' if self.options.descriptors else 'watchonly']['trusted'], 2)
81 82 # Reindex and wait for it to finish
83 with node.assert_debug_log(expected_msgs=["initload thread exit"]):
84 self.restart_node(0, extra_args=['-reindex=1', f'-mocktime={self.node_time}'])
85 node.syncwithvalidationinterfacequeue()
86 87 # Verify the transaction is still 'confirmed' after reindex
88 wallet_watch_only = node.get_wallet_rpc('watch_only')
89 tx_info = wallet_watch_only.gettransaction(tx_id)
90 assert_equal(tx_info['confirmations'], 50)
91 92 # Depending on the wallet type, the birth time changes.
93 if self.options.descriptors:
94 # For descriptors, verify the wallet updated the birth time to the transaction time
95 assert_equal(tx_info['time'], wallet_watch_only.getwalletinfo()['birthtime'])
96 else:
97 # For legacy, as the birth time was set to the beginning of time, verify it did not change
98 assert_equal(wallet_birthtime, 1)
99 100 wallet_watch_only.unloadwallet()
101 102 def run_test(self):
103 node = self.nodes[0]
104 self.node_time = int(time.time())
105 node.setmocktime(self.node_time)
106 107 # Fund miner
108 node.createwallet(wallet_name='miner', load_on_startup=True)
109 miner_wallet = node.get_wallet_rpc('miner')
110 self.generatetoaddress(node, COINBASE_MATURITY + 10, miner_wallet.getnewaddress())
111 112 # Tests
113 self.birthtime_test(node, miner_wallet)
114 115 116 if __name__ == '__main__':
117 WalletReindexTest(__file__).main()
118