1 #!/usr/bin/env python3
2 # Copyright (c) 2024-present 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 descriptor wallets rescan mempool transactions properly when importing."""
6 7 from test_framework.address import (
8 address_to_scriptpubkey,
9 ADDRESS_BCRT1_UNSPENDABLE,
10 )
11 from test_framework.messages import COIN
12 from test_framework.test_framework import BitcoinTestFramework
13 from test_framework.util import assert_equal
14 from test_framework.wallet import MiniWallet
15 from test_framework.wallet_util import test_address
16 17 18 class WalletRescanUnconfirmed(BitcoinTestFramework):
19 def set_test_params(self):
20 self.num_nodes = 1
21 22 def skip_test_if_missing_module(self):
23 self.skip_if_no_wallet()
24 25 def run_test(self):
26 self.log.info("Create wallets and mine initial chain")
27 node = self.nodes[0]
28 tester_wallet = MiniWallet(node)
29 30 node.createwallet(wallet_name='w0', disable_private_keys=False)
31 w0 = node.get_wallet_rpc('w0')
32 33 self.log.info("Create a parent tx and mine it in a block that will later be disconnected")
34 parent_address = w0.getnewaddress()
35 tx_parent_to_reorg = tester_wallet.send_to(
36 from_node=node,
37 scriptPubKey=address_to_scriptpubkey(parent_address),
38 amount=COIN,
39 )
40 assert tx_parent_to_reorg["txid"] in node.getrawmempool()
41 block_to_reorg = self.generate(tester_wallet, 1)[0]
42 assert_equal(len(node.getrawmempool()), 0)
43 node.syncwithvalidationinterfacequeue()
44 assert_equal(w0.gettransaction(tx_parent_to_reorg["txid"])["confirmations"], 1)
45 46 # Create an unconfirmed child transaction from the parent tx, sending all
47 # the funds to an unspendable address. Importantly, no change output is created so the
48 # transaction can't be recognized using its outputs. The wallet rescan needs to know the
49 # inputs of the transaction to detect it, so the parent must be processed before the child.
50 w0_utxos = w0.listunspent()
51 52 self.log.info("Create a child tx and wait for it to propagate to all mempools")
53 # The only UTXO available to spend is tx_parent_to_reorg.
54 assert_equal(len(w0_utxos), 1)
55 assert_equal(w0_utxos[0]["txid"], tx_parent_to_reorg["txid"])
56 tx_child_unconfirmed_sweep = w0.sendall(recipients=[ADDRESS_BCRT1_UNSPENDABLE], options={"locktime":0})
57 assert tx_child_unconfirmed_sweep["txid"] in node.getrawmempool()
58 node.syncwithvalidationinterfacequeue()
59 60 self.log.info("Mock a reorg, causing parent to re-enter mempools after its child")
61 node.invalidateblock(block_to_reorg)
62 assert tx_parent_to_reorg["txid"] in node.getrawmempool()
63 64 self.log.info("Import descriptor wallet on another node")
65 # descriptor is ranged - label not allowed
66 descriptors_to_import = [{"desc": w0.getaddressinfo(parent_address)['parent_desc'], "timestamp": 0}]
67 68 node.createwallet(wallet_name="w1", disable_private_keys=True)
69 w1 = node.get_wallet_rpc("w1")
70 w1.importdescriptors(descriptors_to_import)
71 72 self.log.info("Check that the importing node has properly rescanned mempool transactions")
73 # Check that parent address is correctly determined as ismine
74 test_address(w1, parent_address, solvable=True, ismine=True)
75 # This would raise a JSONRPCError if the transactions were not identified as belonging to the wallet.
76 assert_equal(w1.gettransaction(tx_parent_to_reorg["txid"])["confirmations"], 0)
77 assert_equal(w1.gettransaction(tx_child_unconfirmed_sweep["txid"])["confirmations"], 0)
78 79 if __name__ == '__main__':
80 WalletRescanUnconfirmed(__file__).main()
81