Skip to content

Commit df30247

Browse files
glozowfurszy
andcommitted
[test] import descriptor wallet with reorged parent + IsFromMe child in mempool
Test that wallet rescans process transactions topologically, even if a parent's entry into the mempool is later than that of its child. This behavior is important because IsFromMe requires the ability to look up a transaction's inputs. Co-authored-by: furszy <matiasfurszyfer@protonmail.com>
1 parent c3d02be commit df30247

File tree

2 files changed

+84
-0
lines changed

2 files changed

+84
-0
lines changed

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@
335335
'wallet_create_tx.py --descriptors',
336336
'wallet_inactive_hdchains.py --legacy-wallet',
337337
'wallet_spend_unconfirmed.py',
338+
'wallet_rescan_unconfirmed.py --descriptors',
338339
'p2p_fingerprint.py',
339340
'feature_uacomment.py',
340341
'feature_init.py',
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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

Comments
 (0)