Skip to content

Commit c3d02be

Browse files
committed
[test] rescan legacy 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.
1 parent 65c05db commit c3d02be

File tree

1 file changed

+47
-9
lines changed

1 file changed

+47
-9
lines changed

test/functional/wallet_import_rescan.py

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
"""
2121

2222
from test_framework.test_framework import BitcoinTestFramework
23-
from test_framework.address import AddressType
23+
from test_framework.address import (
24+
AddressType,
25+
ADDRESS_BCRT1_UNSPENDABLE,
26+
)
2427
from test_framework.util import (
2528
assert_equal,
2629
set_node_times,
@@ -109,7 +112,7 @@ def check(self, txid=None, amount=None, confirmation_height=None):
109112

110113
address, = [ad for ad in addresses if txid in ad["txids"]]
111114
assert_equal(address["address"], self.address["address"])
112-
assert_equal(address["amount"], self.expected_balance)
115+
assert_equal(address["amount"], self.amount_received)
113116
assert_equal(address["confirmations"], confirmations)
114117
# Verify the transaction is correctly marked watchonly depending on
115118
# whether the transaction pays to an imported public key or
@@ -223,11 +226,11 @@ def run_test(self):
223226
variant.node = self.nodes[2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))]
224227
variant.do_import(variant.timestamp)
225228
if expect_rescan:
226-
variant.expected_balance = variant.initial_amount
229+
variant.amount_received = variant.initial_amount
227230
variant.expected_txs = 1
228231
variant.check(variant.initial_txid, variant.initial_amount, variant.confirmation_height)
229232
else:
230-
variant.expected_balance = 0
233+
variant.amount_received = 0
231234
variant.expected_txs = 0
232235
variant.check()
233236

@@ -247,7 +250,7 @@ def run_test(self):
247250
# Check the latest results from getbalance and listtransactions.
248251
for variant in IMPORT_VARIANTS:
249252
self.log.info('Run check for variant {}'.format(variant))
250-
variant.expected_balance += variant.sent_amount
253+
variant.amount_received += variant.sent_amount
251254
variant.expected_txs += 1
252255
variant.check(variant.sent_txid, variant.sent_amount, variant.confirmation_height)
253256

@@ -267,14 +270,45 @@ def run_test(self):
267270
address_type=variant.address_type.value,
268271
))
269272
variant.key = self.nodes[1].dumpprivkey(variant.address["address"])
270-
variant.initial_amount = get_rand_amount()
273+
variant.initial_amount = get_rand_amount() * 2
271274
variant.initial_txid = self.nodes[0].sendtoaddress(variant.address["address"], variant.initial_amount)
272275
variant.confirmation_height = 0
273276
variant.timestamp = timestamp
274277

278+
# Mine a block so these parents are confirmed
279+
assert_equal(len(self.nodes[0].getrawmempool()), len(mempool_variants))
280+
self.sync_mempools()
281+
block_to_disconnect = self.generate(self.nodes[0], 1)[0]
282+
assert_equal(len(self.nodes[0].getrawmempool()), 0)
283+
284+
# For each variant, create an unconfirmed child transaction from initial_txid, sending all
285+
# the funds to an unspendable address. Importantly, no change output is created so the
286+
# transaction can't be recognized using its outputs. The wallet rescan needs to know the
287+
# inputs of the transaction to detect it, so the parent must be processed before the child.
288+
# An equivalent test for descriptors exists in wallet_rescan_unconfirmed.py.
289+
unspent_txid_map = {txin["txid"] : txin for txin in self.nodes[1].listunspent()}
290+
for variant in mempool_variants:
291+
# Send full amount, subtracting fee from outputs, to ensure no change is created.
292+
child = self.nodes[1].send(
293+
add_to_wallet=False,
294+
inputs=[unspent_txid_map[variant.initial_txid]],
295+
outputs=[{ADDRESS_BCRT1_UNSPENDABLE : variant.initial_amount}],
296+
subtract_fee_from_outputs=[0]
297+
)
298+
variant.child_txid = child["txid"]
299+
variant.amount_received = 0
300+
self.nodes[0].sendrawtransaction(child["hex"])
301+
302+
# Mempools should contain the child transactions for each variant.
275303
assert_equal(len(self.nodes[0].getrawmempool()), len(mempool_variants))
276304
self.sync_mempools()
277305

306+
# Mock a reorg so the parent transactions are added back to the mempool
307+
for node in self.nodes:
308+
node.invalidateblock(block_to_disconnect)
309+
# Mempools should now contain the parent and child for each variant.
310+
assert_equal(len(node.getrawmempool()), 2 * len(mempool_variants))
311+
278312
# For each variation of wallet key import, invoke the import RPC and
279313
# check the results from getbalance and listtransactions.
280314
for variant in mempool_variants:
@@ -283,11 +317,15 @@ def run_test(self):
283317
variant.node = self.nodes[2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))]
284318
variant.do_import(variant.timestamp)
285319
if expect_rescan:
286-
variant.expected_balance = variant.initial_amount
320+
# Ensure both transactions were rescanned. This would raise a JSONRPCError if the
321+
# transactions were not identified as belonging to the wallet.
322+
assert_equal(variant.node.gettransaction(variant.initial_txid)['confirmations'], 0)
323+
assert_equal(variant.node.gettransaction(variant.child_txid)['confirmations'], 0)
324+
variant.amount_received = variant.initial_amount
287325
variant.expected_txs = 1
288-
variant.check(variant.initial_txid, variant.initial_amount)
326+
variant.check(variant.initial_txid, variant.initial_amount, 0)
289327
else:
290-
variant.expected_balance = 0
328+
variant.amount_received = 0
291329
variant.expected_txs = 0
292330
variant.check()
293331

0 commit comments

Comments
 (0)