Repository: bitcoin/bitcoin
File: src/wallet/spend.cpp (C++)

Code before:
            // accepted.
            if (nDepth == 0 && wtx.mapValue.count("replaces_txid")) {
                safeTx = false;
            }

            // Similarly, we should not consider coins from transactions that
            // have been replaced. In the example above, we would want to prevent
            // creation of a transaction A' spending an output of A, because if
            // transaction B were initially confirmed, conflicting with A and
            // A', we wouldn't want to the user to create a transaction D
            // intending to replace A', but potentially resulting in a scenario
            // where A, A', and D could all be accepted (instead of just B and
            // D, or just A and A' like the user would want).
            if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) {
                safeTx = false;
            }

            if (nDepth == 0 && params.check_version_trucness) {
                if (coinControl->m_version == TRUC_VERSION) {
                    if (wtx.tx->version != TRUC_VERSION) continue;
                    // this unconfirmed v3 transaction already has a child
                    if (wtx.truc_child_in_mempool.has_value()) continue;
                } else {
                    if (wtx.tx->version == TRUC_VERSION) continue;
                    Assume(!wtx.truc_child_in_mempool.has_value());
                }
            }

            if (only_safe && !safeTx) {
                continue;
            }

            if (nDepth < min_depth || nDepth > max_depth) {
                continue;
            }

            tx_safe_cache[outpoint.hash] = {true, safeTx};
        }
        const auto& [tx_ok, tx_safe] = tx_safe_cache.at(outpoint.hash);
        if (!Assume(tx_ok)) {
            continue;
        }

        if (output.nValue < params.min_amount || output.nValue > params.max_amount)
            continue;

        // Skip manually selected coins (the caller can fetch them directly)
        if (coinControl && coinControl->HasSelected() && coinControl->IsSelected(outpoint))
            continue;

        if (wallet.IsLockedCoin(outpoint) && params.skip_locked)

Review comment (suggestion):
There appears to be two different ways now to check for the mempool ancestors and descendants of the transaction here. I tried the following diff and the tests pass. Maybe we can remove the `truc_child_in_mempool` class member altogether in the future?

```diff
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 5654c8f3d4..65c896892f 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -401,16 +401,15 @@ CoinsResult AvailableCoins(const CWallet& wallet,
             if (nDepth == 0 && params.check_version_trucness) {
                 if (coinControl->m_version == TRUC_VERSION) {
                     if (wtx.tx->version != TRUC_VERSION) continue;
+                    size_t ancestors, descendants;
+                    wallet.chain().getTransactionAncestry(wtx.tx->GetHash(), ancestors, descendants);
                     // this unconfirmed v3 transaction already has a child
-                    if (wtx.truc_child_in_mempool.has_value()) continue;
+                    if (descendants > 1) 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());
                 }
             }
 
diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h
index 1dbcdd2d92..7c97bb7f1a 100644
--- a/src/wallet/transaction.h
+++ b/src/wallet/transaction.h
@@ -277,10 +277,6 @@ public:
     // BlockConflicted.
     std::set<Txid> mempool_conflicts;
 
-    // Track v3 mempool tx that spends from this tx
-    // so that we don't try to create another unconfirmed child
-    std::optional<Txid> truc_child_in_mempool;
-
     template<typename Stream>
     void Serialize(Stream& s) const
     {
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index d08d6782c1..ea1d78b310 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1398,7 +1398,6 @@ void CWallet::transactionAddedToMempool(const CTransactionRef& tx) {
             if (parent_it != mapWallet.end()) {
                 CWalletTx& parent_wtx = parent_it->second;
                 if (parent_wtx.isUnconfirmed()) {
-                    parent_wtx.truc_child_in_mempool = tx->GetHash();
                     // Even though these siblings do not spend the same utxos, they can't
                     // be present in the mempool at the same time because of TRUC policy rules
                     UpdateTrucSiblingConflicts(parent_wtx, txid, /*add_conflict=*/true);
@@ -1460,16 +1459,11 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRe
     }
 
     if (tx->version == TRUC_VERSION) {
-        // If this tx has a parent, unset its truc_child_in_mempool to make it possible
-        // to spend from the parent again. If this tx was replaced by another
-        // child of the same parent, transactionAddedToMempool
-        // will update truc_child_in_mempool
         for (const CTxIn& tx_in : tx->vin) {
             auto parent_it = mapWallet.find(tx_in.prevout.hash);
             if (parent_it != mapWallet.end()) {
                 CWalletTx& parent_wtx = parent_it->second;
-                if (parent_wtx.truc_child_in_mempool == tx->GetHash()) {
-                    parent_wtx.truc_child_in_mempool = std::nullopt;
+                if (parent_wtx.isUnconfirmed()) {
                     UpdateTrucSiblingConflicts(parent_wtx, txid, /*add_conflict=*/false);
                 }
             }

``` 

Code after:
            // accepted.
            if (nDepth == 0 && wtx.mapValue.count("replaces_txid")) {
                safeTx = false;
            }

            // Similarly, we should not consider coins from transactions that
            // have been replaced. In the example above, we would want to prevent
            // creation of a transaction A' spending an output of A, because if
            // transaction B were initially confirmed, conflicting with A and
            // A', we wouldn't want to the user to create a transaction D
            // intending to replace A', but potentially resulting in a scenario
            // where A, A', and D could all be accepted (instead of just B and
            // D, or just A and A' like the user would want).
            if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) {
                safeTx = false;
            }

            if (nDepth == 0 && params.check_version_trucness) {
                if (coinControl->m_version == TRUC_VERSION) {
                    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());
                }
            }

            if (only_safe && !safeTx) {
                continue;
            }

            if (nDepth < min_depth || nDepth > max_depth) {
                continue;
            }

            tx_safe_cache[outpoint.hash] = {true, safeTx};
        }
        const auto& [tx_ok, tx_safe] = tx_safe_cache.at(outpoint.hash);
        if (!Assume(tx_ok)) {
            continue;
        }

        if (output.nValue < params.min_amount || output.nValue > params.max_amount)
            continue;