What changed, and why it matters
This commit only adds a new functional test to Bitcoin Core. It checks that the wallet correctly refuses to spend an unconfirmed TRUC (version 3) transaction output when that output already has an unconfirmed parent—in other words, it prevents creating a third unconfirmed generation in a chain. There is no change to production wallet, consensus, or networking code, so it does not introduce or fix a live security vulnerability on its own.
No action required. Review as normal test-only addition if auditing test coverage.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff adds one test method, test_spend_third_generation(), to test/functional/wallet_v3_txs.py and registers it in the test run list. The test creates two generations of unconfirmed version-3 transactions via sendall, then verifies that a third generation send/sendall/fundrawtransaction call fails with an ‘Insufficient funds’ or ‘Total value of UTXO pool too low to pay for transaction’ error. This is purely test coverage for existing wallet coin-selection/UTXO eligibility behavior; no implementation code is modified.
Changed components
test/functional/wallet_v3_txs.pyInspect captured patch +45 / −0
diff --git a/test/functional/wallet_v3_txs.py b/test/functional/wallet_v3_txs.py
index db9f1483..a037c1bc 100755
--- a/test/functional/wallet_v3_txs.py
+++ b/test/functional/wallet_v3_txs.py
@@ -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()
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.