test: add coverage for issue 34206

This commit is contained in:
Greg Sanders
2026-01-09 08:35:13 -05:00
parent 4c7cfd37ad
commit d09a19fd41

View File

@@ -19,6 +19,10 @@ from test_framework.script import (
from test_framework.script_util import bulk_vout
from test_framework.blocktools import (
create_empty_fork,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@@ -84,6 +88,12 @@ class WalletV3Test(BitcoinTestFramework):
test_func(2, 3)
test_func(3, 2)
def trigger_reorg(self, fork_blocks):
"""Trigger reorg of the fork blocks."""
for block in fork_blocks:
self.nodes[0].submitblock(block.serialize().hex())
assert_equal(self.nodes[0].getbestblockhash(), fork_blocks[-1].hash_hex)
def run_test(self):
self.nodes[0].createwallet("alice")
self.alice = self.nodes[0].get_wallet_rpc("alice")
@@ -120,6 +130,7 @@ class WalletV3Test(BitcoinTestFramework):
self.mix_non_truc_versions()
self.cant_spend_multiple_unconfirmed_truc_outputs()
self.test_spend_third_generation()
self.test_coins_availability_reorg()
@cleanup
def tx_spends_unconfirmed_tx_with_wrong_version(self, version_a, version_b):
@@ -630,5 +641,100 @@ class WalletV3Test(BitcoinTestFramework):
version=3
)
@cleanup
def test_coins_availability_reorg(self):
self.log.info("Test coin availability after reorg with v2 parent and truc child")
# Prep fork blocks
fork_blocks = create_empty_fork(self.nodes[0])
# Send funds to alice so she can create transactions
outputs = {self.alice.getnewaddress(): 5.0}
self.send_tx(self.charlie, [], outputs, 2)
self.generate(self.nodes[0], 1)
# Alice creates a v2 transaction with 2 outputs
alice_unspent = self.alice.listunspent()[0]
v2_outputs = [
{self.alice.getnewaddress(): 2.5},
{self.alice.getnewaddress(): 2.4999},
]
v2_txid = self.send_tx(self.alice, [alice_unspent], v2_outputs, 2)
# Mine the v2 transaction in one block
self.generate(self.nodes[0], 1)
# Get the output from the v2 transaction for chaining
v2_utxo = self.alice.listunspent(minconf=1)[0]
assert_equal(v2_utxo["txid"], v2_txid)
# Alice creates a truc child chaining from the v2 utxo
truc_outputs = {self.alice.getnewaddress(): v2_utxo["amount"] - Decimal("0.0001")}
truc_txid = self.send_tx(self.alice, [v2_utxo], truc_outputs, 3)
# Mine the truc transaction in a second block
self.generate(self.nodes[0], 1)
# Verify both transactions are confirmed
wallet_tx_v2 = self.alice.gettransaction(v2_txid)
wallet_tx_truc = self.alice.gettransaction(truc_txid)
assert_equal(wallet_tx_v2["confirmations"], 2)
assert_equal(wallet_tx_truc["confirmations"], 1)
# Check that their versions are correct
assert_equal(self.alice.decoderawtransaction(wallet_tx_v2["hex"])["version"], 2)
assert_equal(self.alice.decoderawtransaction(wallet_tx_truc["hex"])["version"], 3)
# Check listunspent before reorg - should have the truc output
unspent_before = self.alice.listunspent()
truc_output_txids = [u["txid"] for u in unspent_before]
assert truc_txid in truc_output_txids
# Trigger the reorg
self.trigger_reorg(fork_blocks)
# The TRUC transaction is now in a cluster of size 3, which is only permitted in a reorg.
assert_equal(self.nodes[0].getmempoolcluster(truc_txid)["txcount"], 3)
# After reorg, both transactions should be back in mempool
mempool = self.nodes[0].getrawmempool()
assert v2_txid in mempool
assert truc_txid in mempool
# Check listunspent after reorg - the truc output should still appear
# as unconfirmed since the transaction is in the mempool
unspent_after = self.alice.listunspent(minconf=0)
unspent_txids_after = [u["txid"] for u in unspent_after]
assert truc_txid in unspent_txids_after
total_unconfirmed_amount = sum([u["amount"] for u in self.alice.listunspent(minconf=0)])
# We cannot create a transaction spending both outputs, regardless of version.
output_too_high = {self.bob.getnewaddress(): total_unconfirmed_amount - Decimal("1")}
for version in [2, 3]:
raw_output_too_high = self.alice.createrawtransaction(inputs=[], outputs=output_too_high, version=version)
assert_raises_rpc_error(
-4,
"Insufficient funds",
self.alice.fundrawtransaction,
raw_output_too_high,
{'include_unsafe' : True}
)
# Now try to create a v2 transaction - this triggers AvailableCoins with
# check_version_trucness=true. The v2 parent has truc_child_in_mempool set
# because the truc child is in mempool.
new_v2_outputs = {self.bob.getnewaddress(): 0.1}
raw_v2_child = self.alice.createrawtransaction(inputs=[], outputs=new_v2_outputs, version=2)
raw_v2_with_v3_sibling = self.alice.fundrawtransaction(raw_v2_child, {'include_unsafe': True})
# See that this transaction can be added to mempool
signed_raw_v2_with_v3_sibling = self.alice.signrawtransactionwithwallet(raw_v2_with_v3_sibling["hex"])
self.alice.sendrawtransaction(signed_raw_v2_with_v3_sibling["hex"])
# This TRUC transaction is in a cluster of size 4
assert_equal(self.nodes[0].getmempoolcluster(truc_txid)["txcount"], 4)
if __name__ == '__main__':
WalletV3Test(__file__).main()