LCOV - code coverage report
Current view: top level - src/wallet - coinjoin.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 138 350 39.4 %
Date: 2026-06-25 07:23:43 Functions: 11 24 45.8 %

          Line data    Source code
       1             : // Copyright (c) 2009-2021 The Bitcoin Core developers
       2             : // Copyright (c) 2014-2025 The Dash Core developers
       3             : // Distributed under the MIT software license, see the accompanying
       4             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       5             : 
       6             : #include <wallet/coinjoin.h>
       7             : 
       8             : #include <key_io.h>
       9             : #include <wallet/receive.h>
      10             : #include <wallet/spend.h>
      11             : #include <wallet/transaction.h>
      12             : #include <wallet/wallet.h>
      13             : 
      14             : #include <coinjoin/common.h>
      15             : #include <coinjoin/options.h>
      16             : 
      17             : namespace wallet {
      18        2334 : void CWallet::InitCJSaltFromDb()
      19             : {
      20        2334 :     assert(nCoinJoinSalt.IsNull());
      21             : 
      22        2334 :     WalletBatch batch(GetDatabase());
      23        2334 :     if (!batch.ReadCoinJoinSalt(nCoinJoinSalt) && batch.ReadCoinJoinSalt(nCoinJoinSalt, true)) {
      24             :         // Migrate salt stored with legacy key
      25           0 :         batch.WriteCoinJoinSalt(nCoinJoinSalt);
      26           0 :     }
      27        2334 : }
      28             : 
      29        2348 : const uint256& CWallet::GetCoinJoinSalt()
      30             : {
      31        2348 :     if (nCoinJoinSalt.IsNull()) {
      32        2334 :         InitCJSaltFromDb();
      33        2334 :     }
      34        2348 :     return nCoinJoinSalt;
      35             : }
      36             : 
      37        1255 : bool CWallet::SetCoinJoinSalt(const uint256& cj_salt)
      38             : {
      39        1255 :     WalletBatch batch(GetDatabase());
      40             :     // Only store new salt in CWallet if database write is successful
      41        1255 :     if (batch.WriteCoinJoinSalt(cj_salt)) {
      42        1255 :         nCoinJoinSalt = cj_salt;
      43        1255 :         return true;
      44             :     }
      45           0 :     return false;
      46        1255 : }
      47             : 
      48           0 : bool CWallet::SelectTxDSInsByDenomination(int nDenom, CAmount nValueMax, std::vector<CTxDSIn>& vecTxDSInRet)
      49             : {
      50           0 :     LOCK(cs_wallet);
      51             : 
      52           0 :     vecTxDSInRet.clear();
      53           0 :     if (!CoinJoin::IsValidDenomination(nDenom)) {
      54           0 :         return false;
      55             :     }
      56             : 
      57           0 :     CAmount nDenomAmount{CoinJoin::DenominationToAmount(nDenom)};
      58           0 :     CAmount nValueTotal{0};
      59           0 :     CCoinControl coin_control(CoinType::ONLY_READY_TO_MIX);
      60           0 :     std::set<uint256> setRecentTxIds;
      61           0 :     std::vector<COutput> vCoins{AvailableCoinsListUnspent(*this, &coin_control).all()};
      62             : 
      63           0 :     WalletCJLogPrint(this, "CWallet::%s -- vCoins.size(): %d\n", __func__, vCoins.size());
      64             : 
      65           0 :     Shuffle(vCoins.rbegin(), vCoins.rend(), FastRandomContext());
      66             : 
      67           0 :     for (const auto& out : vCoins) {
      68           0 :         uint256 txHash = out.outpoint.hash;
      69           0 :         CAmount nValue = out.txout.nValue;
      70           0 :         if (setRecentTxIds.find(txHash) != setRecentTxIds.end()) continue; // no duplicate txids
      71           0 :         if (nValueTotal + nValue > nValueMax) continue;
      72           0 :         if (nValue != nDenomAmount) continue;
      73             : 
      74           0 :         CTxIn txin = CTxIn(txHash, out.outpoint.n);
      75           0 :         CScript scriptPubKey = out.txout.scriptPubKey;
      76           0 :         int nRounds = GetRealOutpointCoinJoinRounds(txin.prevout);
      77             : 
      78           0 :         nValueTotal += nValue;
      79           0 :         vecTxDSInRet.emplace_back(CTxDSIn(txin, scriptPubKey, nRounds));
      80           0 :         setRecentTxIds.emplace(txHash);
      81           0 :         WalletCJLogPrint(this, "CWallet::%s -- hash: %s, nValue: %d.%08d\n", __func__, txHash.ToString(), nValue / COIN,
      82             :                          nValue % COIN);
      83           0 :     }
      84             : 
      85           0 :     WalletCJLogPrint(this, "CWallet::%s -- setRecentTxIds.size(): %d\n", __func__, setRecentTxIds.size());
      86             : 
      87           0 :     return nValueTotal > 0;
      88           0 : }
      89             : 
      90             : struct CompareByPriority {
      91           0 :     bool operator()(const COutput& t1, const COutput& t2) const
      92             :     {
      93           0 :         return CoinJoin::CalculateAmountPriority(t1.GetEffectiveValue()) >
      94           0 :                CoinJoin::CalculateAmountPriority(t2.GetEffectiveValue());
      95             :     }
      96             : };
      97             : 
      98           0 : bool CWallet::SelectDenominatedAmounts(CAmount nValueMax, std::set<CAmount>& setAmountsRet) const
      99             : {
     100           0 :     LOCK(cs_wallet);
     101             : 
     102           0 :     setAmountsRet.clear();
     103             : 
     104           0 :     CAmount nValueTotal{0};
     105           0 :     CCoinControl coin_control(CoinType::ONLY_READY_TO_MIX);
     106             :     // CompareByPriority() cares about effective value, which is only calculable when supplied a feerate
     107           0 :     std::vector<COutput> vCoins{AvailableCoins(*this, &coin_control, /*feerate=*/CFeeRate(0)).all()};
     108             : 
     109             :     // larger denoms first
     110           0 :     std::sort(vCoins.rbegin(), vCoins.rend(), CompareByPriority());
     111             : 
     112           0 :     for (const auto& out : vCoins) {
     113           0 :         CAmount nValue = out.txout.nValue;
     114           0 :         if (nValueTotal + nValue <= nValueMax) {
     115           0 :             nValueTotal += nValue;
     116           0 :             setAmountsRet.emplace(nValue);
     117           0 :         }
     118             :     }
     119             : 
     120           0 :     return nValueTotal >= CoinJoin::GetSmallestDenomination();
     121           0 : }
     122             : 
     123           2 : std::vector<CompactTallyItem> CWallet::SelectCoinsGroupedByAddresses(bool fSkipDenominated, bool fAnonymizable,
     124             :                                                                      bool fSkipUnconfirmed, int nMaxOupointsPerAddress) const
     125             : {
     126           2 :     LOCK(cs_wallet);
     127             : 
     128           2 :     isminefilter filter = ISMINE_SPENDABLE;
     129             : 
     130             :     // Try using the cache for already confirmed mixable inputs.
     131             :     // This should only be used if nMaxOupointsPerAddress was NOT specified.
     132           2 :     if (nMaxOupointsPerAddress == -1 && fAnonymizable && fSkipUnconfirmed) {
     133           0 :         if (fSkipDenominated && fAnonymizableTallyCachedNonDenom) {
     134           0 :             LogPrint(BCLog::SELECTCOINS, "SelectCoinsGroupedByAddresses - using cache for non-denom inputs %d\n",
     135             :                      vecAnonymizableTallyCachedNonDenom.size());
     136           0 :             return vecAnonymizableTallyCachedNonDenom;
     137             :         }
     138           0 :         if (!fSkipDenominated && fAnonymizableTallyCached) {
     139           0 :             LogPrint(BCLog::SELECTCOINS, "SelectCoinsGroupedByAddresses - using cache for all inputs %d\n",
     140             :                      vecAnonymizableTallyCached.size());
     141           0 :             return vecAnonymizableTallyCached;
     142             :         }
     143           0 :     }
     144             : 
     145           2 :     CAmount nSmallestDenom = CoinJoin::GetSmallestDenomination();
     146             : 
     147             :     // Tally
     148           2 :     std::map<CTxDestination, CompactTallyItem> mapTally;
     149           2 :     std::set<uint256> setWalletTxesCounted;
     150         205 :     for (const auto& outpoint : setWalletUTXO) {
     151         203 :         if (!setWalletTxesCounted.emplace(outpoint.hash).second) continue;
     152             : 
     153         203 :         const auto it{mapWallet.find(outpoint.hash)};
     154         203 :         if (it == mapWallet.end()) continue;
     155             : 
     156         203 :         const CWalletTx& wtx{(*it).second};
     157             : 
     158         203 :         if (wtx.IsCoinBase() && GetTxBlocksToMaturity(wtx) > 0) continue;
     159           4 :         if (fSkipUnconfirmed && !CachedTxIsTrusted(*this, wtx)) continue;
     160           4 :         if (GetTxDepthInMainChain(wtx) < 0) continue;
     161             : 
     162           7 :         for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
     163           4 :             CTxDestination txdest;
     164           4 :             if (!ExtractDestination(wtx.tx->vout[i].scriptPubKey, txdest)) continue;
     165             : 
     166           3 :             isminefilter mine = IsMine(txdest);
     167           3 :             if (!(mine & filter)) continue;
     168             : 
     169           3 :             auto itTallyItem = mapTally.find(txdest);
     170           3 :             if (nMaxOupointsPerAddress != -1 && itTallyItem != mapTally.end() &&
     171           0 :                 int64_t(itTallyItem->second.outpoints.size()) >= nMaxOupointsPerAddress) {
     172           0 :                 continue;
     173             :             }
     174             : 
     175           3 :             COutPoint target_outpoint(outpoint.hash, i);
     176           3 :             if (IsSpent(target_outpoint) || IsLockedCoin(target_outpoint)) continue;
     177             : 
     178           3 :             if (fSkipDenominated && CoinJoin::IsDenominatedAmount(wtx.tx->vout[i].nValue)) continue;
     179             : 
     180           3 :             if (fAnonymizable) {
     181             :                 // ignore collaterals
     182           0 :                 if (CoinJoin::IsCollateralAmount(wtx.tx->vout[i].nValue)) continue;
     183             :                 // ignore outputs that are 10 times smaller then the smallest denomination
     184             :                 // otherwise they will just lead to higher fee / lower priority
     185           0 :                 if (wtx.tx->vout[i].nValue <= nSmallestDenom / 10) continue;
     186             :                 // ignore mixed
     187           0 :                 if (IsFullyMixed(target_outpoint)) continue;
     188           0 :             }
     189             : 
     190           3 :             if (itTallyItem == mapTally.end()) {
     191           3 :                 itTallyItem = mapTally.emplace(txdest, CompactTallyItem()).first;
     192           3 :                 itTallyItem->second.txdest = txdest;
     193           3 :             }
     194           3 :             itTallyItem->second.nAmount += wtx.tx->vout[i].nValue;
     195           3 :             itTallyItem->second.outpoints.emplace_back(COutPoint{outpoint.hash, i});
     196           3 :         }
     197             :     }
     198             : 
     199             :     // construct resulting vector
     200             :     // NOTE: vecTallyRet is "sorted" by txdest (i.e. address), just like mapTally
     201           2 :     std::vector<CompactTallyItem> vecTallyRet;
     202           5 :     for (const auto& item : mapTally) {
     203           3 :         if (fAnonymizable && item.second.nAmount < nSmallestDenom) continue;
     204           3 :         vecTallyRet.push_back(item.second);
     205             :     }
     206             : 
     207             :     // Cache already confirmed mixable entries for later use.
     208             :     // This should only be used if nMaxOupointsPerAddress was NOT specified.
     209           2 :     if (nMaxOupointsPerAddress == -1 && fAnonymizable && fSkipUnconfirmed) {
     210           0 :         if (fSkipDenominated) {
     211           0 :             vecAnonymizableTallyCachedNonDenom = vecTallyRet;
     212           0 :             fAnonymizableTallyCachedNonDenom = true;
     213           0 :         } else {
     214           0 :             vecAnonymizableTallyCached = vecTallyRet;
     215           0 :             fAnonymizableTallyCached = true;
     216             :         }
     217           0 :     }
     218             : 
     219             :     // debug
     220           2 :     if (LogAcceptDebug(BCLog::SELECTCOINS)) {
     221           2 :         std::string strMessage = "SelectCoinsGroupedByAddresses - vecTallyRet:\n";
     222           5 :         for (const auto& item : vecTallyRet) {
     223           3 :             strMessage += strprintf("  %s %f\n", EncodeDestination(item.txdest), float(item.nAmount) / COIN);
     224             :         }
     225           2 :         LogPrint(BCLog::SELECTCOINS, "%s", strMessage); /* Continued */
     226           2 :     }
     227             : 
     228           2 :     return vecTallyRet;
     229           2 : }
     230             : 
     231           0 : bool CWallet::HasCollateralInputs(bool fOnlyConfirmed) const
     232             : {
     233           0 :     LOCK(cs_wallet);
     234             : 
     235           0 :     CCoinControl coin_control(CoinType::ONLY_COINJOIN_COLLATERAL);
     236           0 :     coin_control.m_include_unsafe_inputs = !fOnlyConfirmed;
     237             : 
     238           0 :     return AvailableCoinsListUnspent(*this, &coin_control).size() > 0;
     239           0 : }
     240             : 
     241           0 : int CWallet::CountInputsWithAmount(CAmount nInputAmount) const
     242             : {
     243           0 :     CAmount nTotal = 0;
     244             : 
     245           0 :     LOCK(cs_wallet);
     246             : 
     247           0 :     for (const auto& outpoint : setWalletUTXO) {
     248           0 :         const auto it{mapWallet.find(outpoint.hash)};
     249           0 :         if (it == mapWallet.end()) continue;
     250           0 :         if (it->second.tx->vout[outpoint.n].nValue != nInputAmount) continue;
     251           0 :         if (GetTxDepthInMainChain(it->second) < 0) continue;
     252             : 
     253           0 :         nTotal++;
     254             :     }
     255             : 
     256           0 :     return nTotal;
     257           0 : }
     258             : 
     259             : // Recursively determine the rounds of a given input (How deep is the CoinJoin chain for a given input)
     260       11457 : int CWallet::GetRealOutpointCoinJoinRounds(const COutPoint& outpoint, int nRounds) const
     261             : {
     262       11457 :     LOCK(cs_wallet);
     263             : 
     264       11457 :     const int nRoundsMax{MAX_COINJOIN_ROUNDS + CCoinJoinClientOptions::GetRandomRounds()};
     265             : 
     266       11457 :     if (nRounds >= nRoundsMax) {
     267             :         // there can only be nRoundsMax rounds max
     268           0 :         return nRoundsMax - 1;
     269             :     }
     270             : 
     271       11457 :     auto pair = mapOutpointRoundsCache.emplace(outpoint, -10);
     272       11457 :     auto nRoundsRef = &pair.first->second;
     273       11457 :     if (!pair.second) {
     274             :         // we already processed it, just return what we have
     275        5854 :         return *nRoundsRef;
     276             :     }
     277             : 
     278             :     // TODO wtx should refer to a CWalletTx object, not a pointer, based on surrounding code
     279        5603 :     const CWalletTx* wtx{GetWalletTx(outpoint.hash)};
     280             : 
     281        5603 :     if (wtx == nullptr || wtx->tx == nullptr) {
     282             :         // no such tx in this wallet
     283           0 :         *nRoundsRef = -1;
     284           0 :         WalletCJLogPrint(this, "%s FAILED    %-70s %3d\n", __func__, outpoint.ToStringShort(), -1);
     285           0 :         return *nRoundsRef;
     286             :     }
     287             : 
     288             :     // bounds check
     289        5603 :     if (outpoint.n >= wtx->tx->vout.size()) {
     290             :         // should never actually hit this
     291           0 :         *nRoundsRef = -4;
     292           0 :         WalletCJLogPrint(this, "%s FAILED    %-70s %3d\n", __func__, outpoint.ToStringShort(), -4);
     293           0 :         return *nRoundsRef;
     294             :     }
     295             : 
     296        5603 :     auto txOutRef = &wtx->tx->vout[outpoint.n];
     297             : 
     298        5603 :     if (CoinJoin::IsCollateralAmount(txOutRef->nValue)) {
     299          16 :         *nRoundsRef = -3;
     300          16 :         WalletCJLogPrint(this, "%s UPDATED   %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef);
     301          16 :         return *nRoundsRef;
     302             :     }
     303             : 
     304             :     // make sure the final output is non-denominate
     305        5587 :     if (!CoinJoin::IsDenominatedAmount(txOutRef->nValue)) { // NOT DENOM
     306        5545 :         *nRoundsRef = -2;
     307        5545 :         WalletCJLogPrint(this, "%s UPDATED   %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef);
     308        5545 :         return *nRoundsRef;
     309             :     }
     310             : 
     311          84 :     for (const auto& out : wtx->tx->vout) {
     312          84 :         if (!CoinJoin::IsDenominatedAmount(out.nValue)) {
     313             :             // this one is denominated but there is another non-denominated output found in the same tx
     314          42 :             *nRoundsRef = 0;
     315          42 :             WalletCJLogPrint(this, "%s UPDATED   %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef);
     316          42 :             return *nRoundsRef;
     317             :         }
     318             :     }
     319             : 
     320             :     // make sure we spent all of it with 0 fee, reset to 0 rounds otherwise
     321           0 :     if (CachedTxGetDebit(*this, *wtx, ISMINE_SPENDABLE) != CachedTxGetCredit(*this, *wtx, ISMINE_SPENDABLE)) {
     322           0 :         *nRoundsRef = 0;
     323           0 :         WalletCJLogPrint(this, "%s UPDATED   %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef);
     324           0 :         return *nRoundsRef;
     325             :     }
     326             : 
     327           0 :     int nShortest = -10; // an initial value, should be no way to get this by calculations
     328           0 :     bool fDenomFound = false;
     329             :     // only denoms here so let's look up
     330           0 :     for (const auto& txinNext : wtx->tx->vin) {
     331           0 :         if (InputIsMine(*this, txinNext)) {
     332           0 :             int n = GetRealOutpointCoinJoinRounds(txinNext.prevout, nRounds + 1);
     333             :             // denom found, find the shortest chain or initially assign nShortest with the first found value
     334           0 :             if (n >= 0 && (n < nShortest || nShortest == -10)) {
     335           0 :                 nShortest = n;
     336           0 :                 fDenomFound = true;
     337           0 :             }
     338           0 :         }
     339             :     }
     340           0 :     *nRoundsRef = [&]() {
     341           0 :         if (fDenomFound) {
     342             :             // good, we a +1 to the shortest one but only nRoundsMax rounds max allowed
     343           0 :             return nShortest >= nRoundsMax - 1 ? nRoundsMax : nShortest + 1;
     344             :         }
     345             :         // too bad, we are the first one in that chain
     346           0 :         return 0;
     347           0 :     }();
     348           0 :     WalletCJLogPrint(this, "%s UPDATED   %-70s %3d\n", __func__, outpoint.ToStringShort(), *nRoundsRef);
     349           0 :     return *nRoundsRef;
     350       11457 : }
     351             : 
     352             : // respect current settings
     353           0 : int CWallet::GetCappedOutpointCoinJoinRounds(const COutPoint& outpoint) const
     354             : {
     355           0 :     LOCK(cs_wallet);
     356           0 :     int realCoinJoinRounds = GetRealOutpointCoinJoinRounds(outpoint);
     357           0 :     return realCoinJoinRounds > CCoinJoinClientOptions::GetRounds() ? CCoinJoinClientOptions::GetRounds()
     358           0 :                                                                     : realCoinJoinRounds;
     359           0 : }
     360             : 
     361           8 : void CWallet::ClearCoinJoinRoundsCache()
     362             : {
     363           8 :     LOCK(cs_wallet);
     364           8 :     mapOutpointRoundsCache.clear();
     365           8 :     MarkDirty();
     366             :     // Notify UI
     367           8 :     NotifyTransactionChanged(uint256::ONE, CT_UPDATED);
     368           8 : }
     369             : 
     370           0 : bool CWallet::IsDenominated(const COutPoint& outpoint) const
     371             : {
     372           0 :     LOCK(cs_wallet);
     373             : 
     374           0 :     const auto it{mapWallet.find(outpoint.hash)};
     375           0 :     if (it == mapWallet.end()) {
     376           0 :         return false;
     377             :     }
     378             : 
     379           0 :     if (outpoint.n >= it->second.tx->vout.size()) {
     380           0 :         return false;
     381             :     }
     382             : 
     383           0 :     return CoinJoin::IsDenominatedAmount(it->second.tx->vout[outpoint.n].nValue);
     384           0 : }
     385             : 
     386          54 : bool CWallet::IsFullyMixed(const COutPoint& outpoint) const
     387             : {
     388          54 :     int nRounds = GetRealOutpointCoinJoinRounds(outpoint);
     389             :     // Mix again if we don't have N rounds yet
     390          54 :     if (nRounds < CCoinJoinClientOptions::GetRounds()) return false;
     391             : 
     392             :     // Try to mix a "random" number of rounds more than minimum.
     393             :     // If we have already mixed N + MaxOffset rounds, don't mix again.
     394             :     // Otherwise, we should mix again 50% of the time, this results in an exponential decay
     395             :     // N rounds 50% N+1 25% N+2 12.5%... until we reach N + GetRandomRounds() rounds where we stop.
     396           0 :     if (nRounds < CCoinJoinClientOptions::GetRounds() + CCoinJoinClientOptions::GetRandomRounds()) {
     397           0 :         CDataStream ss(SER_GETHASH, PROTOCOL_VERSION);
     398           0 :         ss << outpoint << nCoinJoinSalt;
     399           0 :         uint256 nHash;
     400           0 :         CSHA256().Write(reinterpret_cast<const uint8_t*>(ss.data()), ss.size()).Finalize(nHash.begin());
     401           0 :         if (ReadLE64(nHash.begin()) % 2 == 0) {
     402           0 :             return false;
     403             :         }
     404           0 :     }
     405             : 
     406           0 :     return true;
     407          54 : }
     408             : 
     409       30240 : void CWallet::RecalculateMixedCredit(const uint256 hash)
     410             : {
     411       30240 :     AssertLockHeld(cs_wallet);
     412       30240 :     if (auto it = mapWallet.find(hash); it != mapWallet.end()) {
     413             :         // Recalculate all credits for this tx
     414       24763 :         it->second.MarkDirty();
     415       24763 :     }
     416       30240 :     fAnonymizableTallyCached = false;
     417       30240 :     fAnonymizableTallyCachedNonDenom = false;
     418       30240 : }
     419             : 
     420           0 : CAmount GetBalanceAnonymized(const CWallet& wallet, const CCoinControl& coinControl)
     421             : {
     422           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return 0;
     423             : 
     424           0 :     CAmount anonymized_amount{0};
     425           0 :     LOCK(wallet.cs_wallet);
     426           0 :     for (auto pcoin : wallet.GetSpendableTXs()) {
     427           0 :         anonymized_amount += CachedTxGetAnonymizedCredit(wallet, *pcoin, coinControl);
     428             :     }
     429           0 :     return anonymized_amount;
     430           0 : }
     431             : 
     432           0 : CAmount CWallet::GetAnonymizableBalance(bool fSkipDenominated, bool fSkipUnconfirmed) const
     433             : {
     434           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return 0;
     435             : 
     436           0 :     std::vector<CompactTallyItem> vecTally = SelectCoinsGroupedByAddresses(fSkipDenominated, true, fSkipUnconfirmed);
     437           0 :     if (vecTally.empty()) return 0;
     438             : 
     439           0 :     CAmount nTotal = 0;
     440             : 
     441           0 :     const CAmount nSmallestDenom{CoinJoin::GetSmallestDenomination()};
     442           0 :     const CAmount nMixingCollateral{CoinJoin::GetCollateralAmount()};
     443           0 :     for (const auto& item : vecTally) {
     444           0 :         bool fIsDenominated = CoinJoin::IsDenominatedAmount(item.nAmount);
     445           0 :         if (fSkipDenominated && fIsDenominated) continue;
     446             :         // assume that the fee to create denoms should be mixing collateral at max
     447           0 :         if (item.nAmount >= nSmallestDenom + (fIsDenominated ? 0 : nMixingCollateral)) nTotal += item.nAmount;
     448             :     }
     449             : 
     450           0 :     return nTotal;
     451           0 : }
     452             : 
     453             : // Note: calculated including unconfirmed,
     454             : // that's ok as long as we use it for informational purposes only
     455           0 : float CWallet::GetAverageAnonymizedRounds() const
     456             : {
     457           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return 0;
     458             : 
     459           0 :     int nTotal = 0;
     460           0 :     int nCount = 0;
     461             : 
     462           0 :     LOCK(cs_wallet);
     463           0 :     for (const auto& outpoint : setWalletUTXO) {
     464           0 :         if (!IsDenominated(outpoint)) continue;
     465             : 
     466           0 :         nTotal += GetCappedOutpointCoinJoinRounds(outpoint);
     467           0 :         nCount++;
     468             :     }
     469             : 
     470           0 :     if (nCount == 0) return 0;
     471             : 
     472           0 :     return (float)nTotal / nCount;
     473           0 : }
     474             : 
     475             : // Note: calculated including unconfirmed,
     476             : // that's ok as long as we use it for informational purposes only
     477           0 : CAmount CWallet::GetNormalizedAnonymizedBalance() const
     478             : {
     479           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return 0;
     480             : 
     481           0 :     CAmount nTotal = 0;
     482             : 
     483           0 :     LOCK(cs_wallet);
     484           0 :     for (const auto& outpoint : setWalletUTXO) {
     485           0 :         const auto it{mapWallet.find(outpoint.hash)};
     486           0 :         if (it == mapWallet.end()) continue;
     487             : 
     488           0 :         CAmount nValue = it->second.tx->vout[outpoint.n].nValue;
     489           0 :         if (!CoinJoin::IsDenominatedAmount(nValue)) continue;
     490           0 :         if (GetTxDepthInMainChain(it->second) < 0) continue;
     491             : 
     492           0 :         int nRounds = GetCappedOutpointCoinJoinRounds(outpoint);
     493           0 :         nTotal += nValue * nRounds / CCoinJoinClientOptions::GetRounds();
     494             :     }
     495             : 
     496           0 :     return nTotal;
     497           0 : }
     498             : 
     499           0 : CAmount CachedTxGetAnonymizedCredit(const CWallet& wallet, const CWalletTx& wtx, const CCoinControl& coinControl)
     500             : {
     501           0 :     AssertLockHeld(wallet.cs_wallet);
     502             : 
     503             :     // Exclude coinbase and conflicted txes
     504           0 :     if (wtx.IsCoinBase() || wallet.GetTxDepthInMainChain(wtx) < 0) return 0;
     505             : 
     506           0 :     CAmount nCredit = 0;
     507           0 :     uint256 hashTx = wtx.GetHash();
     508           0 :     for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
     509           0 :         const CTxOut& txout{wtx.tx->vout[i]};
     510           0 :         const COutPoint outpoint(hashTx, i);
     511             : 
     512           0 :         if (coinControl.HasSelected() && !coinControl.IsSelected(outpoint)) {
     513           0 :             continue;
     514             :         }
     515             : 
     516           0 :         if (wallet.IsSpent(outpoint) || !CoinJoin::IsDenominatedAmount(txout.nValue)) {
     517           0 :             continue;
     518             :         }
     519             : 
     520           0 :         if (!wallet.IsFullyMixed(outpoint)) {
     521           0 :             continue;
     522             :         }
     523             : 
     524           0 :         nCredit += OutputGetCredit(wallet, txout, ISMINE_SPENDABLE);
     525           0 :         if (!MoneyRange(nCredit)) {
     526           0 :             throw std::runtime_error(std::string(__func__) + ": value out of range");
     527             :         }
     528           0 :     }
     529             : 
     530           0 :     return nCredit;
     531           0 : }
     532             : 
     533      170843 : CoinJoinCredits CachedTxGetAvailableCoinJoinCredits(const CWallet& wallet, const CWalletTx& wtx)
     534             : {
     535      170843 :     CoinJoinCredits ret;
     536             : 
     537      170843 :     AssertLockHeld(wallet.cs_wallet);
     538             : 
     539             :     // Must wait until coinbase is safely deep enough in the chain before valuing it
     540      170843 :     if (wtx.IsCoinBase() && wallet.GetTxBlocksToMaturity(wtx) > 0) return ret;
     541             : 
     542       51435 :     int nDepth = wallet.GetTxDepthInMainChain(wtx);
     543       51435 :     if (nDepth < 0) return ret;
     544             : 
     545       51395 :     ret.is_unconfirmed = CachedTxIsTrusted(wallet, wtx) && nDepth == 0;
     546             : 
     547       51395 :     if (wtx.m_amounts[CWalletTx::ANON_CREDIT].m_cached[ISMINE_SPENDABLE]) {
     548       35747 :         if (ret.is_unconfirmed && wtx.m_amounts[CWalletTx::DENOM_UCREDIT].m_cached[ISMINE_SPENDABLE]) {
     549       10023 :             return {wtx.m_amounts[CWalletTx::ANON_CREDIT].m_value[ISMINE_SPENDABLE],
     550        6682 :                     wtx.m_amounts[CWalletTx::DENOM_UCREDIT].m_value[ISMINE_SPENDABLE], ret.is_unconfirmed};
     551       32406 :         } else if (!ret.is_unconfirmed && wtx.m_amounts[CWalletTx::DENOM_CREDIT].m_cached[ISMINE_SPENDABLE]) {
     552       97218 :             return {wtx.m_amounts[CWalletTx::ANON_CREDIT].m_value[ISMINE_SPENDABLE],
     553       64812 :                     wtx.m_amounts[CWalletTx::DENOM_CREDIT].m_value[ISMINE_SPENDABLE], ret.is_unconfirmed};
     554             :         }
     555           0 :     }
     556             : 
     557       15648 :     uint256 hashTx = wtx.GetHash();
     558       34650 :     for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) {
     559       19002 :         const CTxOut& txout{wtx.tx->vout[i]};
     560       19002 :         const COutPoint outpoint(hashTx, i);
     561             : 
     562       19002 :         if (wallet.IsSpent(outpoint) || !CoinJoin::IsDenominatedAmount(txout.nValue)) {
     563       18960 :             continue;
     564             :         }
     565             : 
     566          42 :         const CAmount credit{OutputGetCredit(wallet, txout, ISMINE_SPENDABLE)};
     567          42 :         if (wallet.IsFullyMixed(outpoint)) {
     568           0 :             ret.m_anonymized += credit;
     569           0 :             if (!MoneyRange(ret.m_anonymized)) {
     570           0 :                 throw std::runtime_error(std::string(__func__) + ": value out of range");
     571             :             }
     572           0 :         }
     573             : 
     574          42 :         ret.m_denominated += credit;
     575          42 :         if (!MoneyRange(ret.m_denominated)) {
     576           0 :             throw std::runtime_error(std::string(__func__) + ": value out of range");
     577             :         }
     578          42 :     }
     579             : 
     580       15648 :     wtx.m_amounts[CWalletTx::ANON_CREDIT].Set(ISMINE_SPENDABLE, ret.m_anonymized);
     581       15648 :     if (ret.is_unconfirmed) {
     582         177 :         wtx.m_amounts[CWalletTx::DENOM_UCREDIT].Set(ISMINE_SPENDABLE, ret.m_denominated);
     583         177 :     } else {
     584       15471 :         wtx.m_amounts[CWalletTx::DENOM_CREDIT].Set(ISMINE_SPENDABLE, ret.m_denominated);
     585             :     }
     586       15648 :     return ret;
     587      170843 : }
     588             : } // namespace wallet

Generated by: LCOV version 1.16