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