aboutsummaryrefslogtreecommitdiff
path: root/test/functional/wallet_rescan_unconfirmed.py
blob: ad9fa081c21e664653b55b5a629dd9e1f4733535 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#!/usr/bin/env python3
# Copyright (c) 2024 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test that descriptor wallets rescan mempool transactions properly when importing."""

from test_framework.address import (
    address_to_scriptpubkey,
    ADDRESS_BCRT1_UNSPENDABLE,
)
from test_framework.messages import COIN
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
from test_framework.wallet import MiniWallet
from test_framework.wallet_util import test_address


class WalletRescanUnconfirmed(BitcoinTestFramework):
    def add_options(self, parser):
        self.add_wallet_options(parser, legacy=False)

    def set_test_params(self):
        self.num_nodes = 1

    def skip_test_if_missing_module(self):
        self.skip_if_no_wallet()
        self.skip_if_no_sqlite()

    def run_test(self):
        self.log.info("Create wallets and mine initial chain")
        node = self.nodes[0]
        tester_wallet = MiniWallet(node)

        node.createwallet(wallet_name='w0', disable_private_keys=False)
        w0 = node.get_wallet_rpc('w0')

        self.log.info("Create a parent tx and mine it in a block that will later be disconnected")
        parent_address = w0.getnewaddress()
        tx_parent_to_reorg = tester_wallet.send_to(
            from_node=node,
            scriptPubKey=address_to_scriptpubkey(parent_address),
            amount=COIN,
        )
        assert tx_parent_to_reorg["txid"] in node.getrawmempool()
        block_to_reorg = self.generate(tester_wallet, 1)[0]
        assert_equal(len(node.getrawmempool()), 0)
        node.syncwithvalidationinterfacequeue()
        assert_equal(w0.gettransaction(tx_parent_to_reorg["txid"])["confirmations"], 1)

        # Create an unconfirmed child transaction from the parent tx, sending all
        # the funds to an unspendable address. Importantly, no change output is created so the
        # transaction can't be recognized using its outputs. The wallet rescan needs to know the
        # inputs of the transaction to detect it, so the parent must be processed before the child.
        w0_utxos = w0.listunspent()

        self.log.info("Create a child tx and wait for it to propagate to all mempools")
        # The only UTXO available to spend is tx_parent_to_reorg.
        assert_equal(len(w0_utxos), 1)
        assert_equal(w0_utxos[0]["txid"], tx_parent_to_reorg["txid"])
        tx_child_unconfirmed_sweep = w0.sendall([ADDRESS_BCRT1_UNSPENDABLE])
        assert tx_child_unconfirmed_sweep["txid"] in node.getrawmempool()
        node.syncwithvalidationinterfacequeue()

        self.log.info("Mock a reorg, causing parent to re-enter mempools after its child")
        node.invalidateblock(block_to_reorg)
        assert tx_parent_to_reorg["txid"] in node.getrawmempool()

        self.log.info("Import descriptor wallet on another node")
        descriptors_to_import = [{"desc": w0.getaddressinfo(parent_address)['parent_desc'], "timestamp": 0, "label": "w0 import"}]

        node.createwallet(wallet_name="w1", disable_private_keys=True)
        w1 = node.get_wallet_rpc("w1")
        w1.importdescriptors(descriptors_to_import)

        self.log.info("Check that the importing node has properly rescanned mempool transactions")
        # Check that parent address is correctly determined as ismine
        test_address(w1, parent_address, solvable=True, ismine=True)
        # This would raise a JSONRPCError if the transactions were not identified as belonging to the wallet.
        assert_equal(w1.gettransaction(tx_parent_to_reorg["txid"])["confirmations"], 0)
        assert_equal(w1.gettransaction(tx_child_unconfirmed_sweep["txid"])["confirmations"], 0)

if __name__ == '__main__':
    WalletRescanUnconfirmed().main()