Merge bitcoin/bitcoin#33528: wallet: don't consider unconfirmed TRUC coins with ancestors

dcd42d6d8f160ae8bc12c152099a6e6473658e30 [test] wallet send 3 generation TRUC (glozow)
e753fadfd01cb8a4a8de3bddc7391481551cca89 [wallet] never try to spend from unconfirmed TRUC that already has ancestors (glozow)

Pull request description:

  Addresses https://github.com/bitcoin/bitcoin/issues/33368#issuecomment-3319935660

  There is not an explicit check that the to-be-created wallet transaction would be within the {TRUC, normal} ancestor limits. This means that the wallet may create a transaction that violates these limits, but fail to broadcast it in `CommitTransaction`.

  This appears to be expected behavior for the normal ancestor limits (and any other situation in which the wallet creates a tx that was rejected by mempool) and AFAIK the transaction will be rebroadcast at some point after the ancestors confirm.

  1ed00a0d39/test/functional/wallet_basic.py (L502-L506)

  It's a bit complex to address this for the normal ancestor limit, and probably unrealistic for the wallet to check all possible mempool policies in coin selection, but it's quite trivial for TRUC: just skip any unconfirmed UTXOs that have any ancestors. I think it would be much more helpful to the user to say there are insufficient funds.

ACKs for top commit:
  achow101:
    ACK dcd42d6d8f160ae8bc12c152099a6e6473658e30
  monlovesmango:
    ACK dcd42d6d8f160ae8bc12c152099a6e6473658e30
  rkrux:
    lgtm ACK dcd42d6d8f160ae8bc12c152099a6e6473658e30

Tree-SHA512: b4cf9685bf0593c356dc0d6644835d53e3d7089f42b65f647795257dc7f5dac90c5ee493b41ee30a1c1beb880a859db8e049d3c64a43d5ca9b3e6482ff6bddd5
This commit is contained in:
merge-script 2025-12-04 09:47:26 +00:00
commit ad452a1e65
No known key found for this signature in database
GPG Key ID: 2EEB9F5CC09526C1
2 changed files with 50 additions and 0 deletions

View File

@ -403,6 +403,11 @@ CoinsResult AvailableCoins(const CWallet& wallet,
if (wtx.tx->version != TRUC_VERSION) continue;
// this unconfirmed v3 transaction already has a child
if (wtx.truc_child_in_mempool.has_value()) continue;
// this unconfirmed v3 transaction has a parent: spending would create a third generation
size_t ancestors, descendants;
wallet.chain().getTransactionAncestry(wtx.tx->GetHash(), ancestors, descendants);
if (ancestors > 1) continue;
} else {
if (wtx.tx->version == TRUC_VERSION) continue;
Assume(!wtx.truc_child_in_mempool.has_value());

View File

@ -119,6 +119,7 @@ class WalletV3Test(BitcoinTestFramework):
self.sendall_truc_child_weight_limit()
self.mix_non_truc_versions()
self.cant_spend_multiple_unconfirmed_truc_outputs()
self.test_spend_third_generation()
@cleanup
def tx_spends_unconfirmed_tx_with_wrong_version(self, version_a, version_b):
@ -585,5 +586,49 @@ class WalletV3Test(BitcoinTestFramework):
{'include_unsafe' : True}
)
@cleanup
def test_spend_third_generation(self):
self.log.info("Test that we can't spend an unconfirmed TRUC output that already has an unconfirmed parent")
# Generation 1: Consolidate all UTXOs into one output using sendall
self.charlie.sendall([self.charlie.getnewaddress()], version=3)
outputs1 = self.charlie.listunspent(minconf=0)
assert_equal(len(outputs1), 1)
# Generation 2: to ensure no change address is created, do another sendall
self.charlie.sendall([self.charlie.getnewaddress()], version=3)
outputs2 = self.charlie.listunspent(minconf=0)
assert_equal(len(outputs2), 1)
total_amount = sum([utxo['amount'] for utxo in outputs2])
# Generation 3: try to send half of total amount to Alice
outputs = {self.alice.getnewaddress(): total_amount / 2}
assert_raises_rpc_error(
-4,
"Insufficient funds",
self.charlie.send,
outputs,
version=3
)
# Also doesn't work with fundrawtransaction
raw_tx = self.charlie.createrawtransaction(inputs=[], outputs=outputs, version=3)
assert_raises_rpc_error(
-4,
"Insufficient funds",
self.charlie.fundrawtransaction,
raw_tx,
{'include_unsafe' : True}
)
# Also doesn't work with sendall
assert_raises_rpc_error(
-6,
"Total value of UTXO pool too low to pay for transaction",
self.charlie.sendall,
[self.alice.getnewaddress()],
version=3
)
if __name__ == '__main__':
WalletV3Test(__file__).main()