|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# Copyright (c) 2024 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 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([ADDRESS_BCRT1_UNSPENDABLE]) |
| 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 | + descriptors_to_import = [{"desc": w0.getaddressinfo(parent_address)['parent_desc'], "timestamp": 0, "label": "w0 import"}] |
| 70 | + |
| 71 | + node.createwallet(wallet_name="w1", disable_private_keys=True) |
| 72 | + w1 = node.get_wallet_rpc("w1") |
| 73 | + w1.importdescriptors(descriptors_to_import) |
| 74 | + |
| 75 | + self.log.info("Check that the importing node has properly rescanned mempool transactions") |
| 76 | + # Check that parent address is correctly determined as ismine |
| 77 | + test_address(w1, parent_address, solvable=True, ismine=True) |
| 78 | + # This would raise a JSONRPCError if the transactions were not identified as belonging to the wallet. |
| 79 | + assert_equal(w1.gettransaction(tx_parent_to_reorg["txid"])["confirmations"], 0) |
| 80 | + assert_equal(w1.gettransaction(tx_child_unconfirmed_sweep["txid"])["confirmations"], 0) |
| 81 | + |
| 82 | +if __name__ == '__main__': |
| 83 | + WalletRescanUnconfirmed().main() |
0 commit comments