wallet_rescan_unconfirmed.py raw

   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