wallet: throw error at conflicting tx versions in pre-selected inputs
What changed, and why it matters
This Bitcoin Core wallet patch adds a safety check that stops users from accidentally mixing two different transaction formats (version 2 and the newer version 3, also called TRUC) in the same unconfirmed spend. Before this change, the wallet could let a user pre-select an unconfirmed input whose transaction version didn't match the version of the new transaction being built. That mismatch could produce an invalid or non-standard transaction, potentially causing the spend to be rejected by the network or by mempool rules. The fix makes the wallet detect the conflict early and return a clear error instead of silently building a problematic transaction.
Treat as a hardening/bug-fix patch. Review related TRUC/v3 transaction construction paths to ensure no other version-mismatch cases exist, especially around external inputs and coin-control overrides. No immediate emergency response is indicated, but the patch should be included in the next maintenance release.
Security signals we found
Transaction-version mismatch in unconfirmed ancestor chain
Potential creation of invalid or non-standard TRUC/v3 transactions
Mempool policy violation risk for unconfirmed inputs
Missing input validation now enforced in wallet coin selection
Evidence from the diff
In src/wallet/spend.cpp, inside FetchSelectedInputs, the code now inspects the parent wallet transaction of each pre-selected input. If the parent is unconfirmed (GetTxDepthInMainChain(parent_tx) == 0) and there is a version mismatch between the parent’s transaction version and coin_control.m_version (the version selected for the new transaction), the function returns a util::Error. Specifically, it rejects: (a) spending an unconfirmed version 3 (TRUC_VERSION) input with a non-version-3 transaction, and (b) spending an unconfirmed non-version-3 input with a version 3 transaction. Confirmed inputs are not subject to this restriction because their version no longer affects mempool ancestor/descendant policy in the same way. External inputs are left untouched by this check.
Changed components
src/wallet/spend.cppCWallet coin selection / FetchSelectedInputsCoinControl transaction version handlingTRUC (version 3) transaction supportInspect captured patch +8 / −0
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 91cdc4a8..b65317b8 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -283,6 +283,14 @@ util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const
if (input_bytes == -1) {
input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control);
}
+ const CWalletTx& parent_tx = txo->GetWalletTx();
+ if (wallet.GetTxDepthInMainChain(parent_tx) == 0) {
+ if (parent_tx.tx->version == TRUC_VERSION && coin_control.m_version != TRUC_VERSION) {
+ return util::Error{strprintf(_("Can't spend unconfirmed version 3 pre-selected input with a version %d tx"), coin_control.m_version)};
+ } else if (coin_control.m_version == TRUC_VERSION && parent_tx.tx->version != TRUC_VERSION) {
+ return util::Error{strprintf(_("Can't spend unconfirmed version %d pre-selected input with a version 3 tx"), parent_tx.tx->version)};
+ }
+ }
} else {
// The input is external. We did not find the tx in mapWallet.
const auto out{coin_control.GetExternalOutput(outpoint)};
Why this scored 41/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.