LCOV - code coverage report
Current view: top level - src/coinjoin - client.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 18 1023 1.8 %
Date: 2026-06-25 07:23:51 Functions: 9 79 11.4 %

          Line data    Source code
       1             : // Copyright (c) 2014-2025 The Dash Core developers
       2             : // Distributed under the MIT/X11 software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <coinjoin/client.h>
       6             : 
       7             : #include <coinjoin/options.h>
       8             : #include <evo/deterministicmns.h>
       9             : #include <masternode/meta.h>
      10             : #include <masternode/sync.h>
      11             : #include <rpc/evo_util.h>
      12             : #include <util/helpers.h>
      13             : #include <wallet/coinjoin.h>
      14             : 
      15             : #include <chain.h>
      16             : #include <chainparams.h>
      17             : #include <core_io.h>
      18             : #include <net.h>
      19             : #include <netmessagemaker.h>
      20             : #include <shutdown.h>
      21             : #include <util/check.h>
      22             : #include <util/moneystr.h>
      23             : #include <util/system.h>
      24             : #include <util/translation.h>
      25             : #include <version.h>
      26             : #include <wallet/coincontrol.h>
      27             : #include <wallet/coinselection.h>
      28             : #include <wallet/receive.h>
      29             : #include <wallet/spend.h>
      30             : 
      31             : #include <memory>
      32             : #include <ranges>
      33             : 
      34             : #include <univalue.h>
      35             : 
      36             : using wallet::CCoinControl;
      37             : using wallet::CompactTallyItem;
      38             : using wallet::COutput;
      39             : using wallet::CoinType;
      40             : using wallet::CWallet;
      41             : using wallet::ReserveDestination;
      42             : 
      43          45 : CCoinJoinClientManager::CCoinJoinClientManager(const std::shared_ptr<wallet::CWallet>& wallet,
      44             :                                                CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman,
      45             :                                                const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman,
      46             :                                                CoinJoinQueueManager* queueman) :
      47          15 :     m_wallet{wallet},
      48          15 :     m_dmnman{dmnman},
      49          15 :     m_mn_metaman{mn_metaman},
      50          15 :     m_mn_sync{mn_sync},
      51          15 :     m_isman{isman},
      52          15 :     m_queueman{queueman}
      53          15 : {
      54          15 : }
      55             : 
      56          30 : CCoinJoinClientManager::~CCoinJoinClientManager() = default;
      57             : 
      58           0 : void CCoinJoinClientManager::ProcessMessage(CNode& peer, CChainState& active_chainstate, CConnman& connman, const CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv)
      59             : {
      60           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return;
      61           0 :     if (!m_mn_sync.IsBlockchainSynced()) return;
      62             : 
      63           0 :     if (!CheckDiskSpace(gArgs.GetDataDirNet())) {
      64           0 :         ResetPool();
      65           0 :         StopMixing();
      66           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::ProcessMessage -- Not enough disk space, disabling CoinJoin.\n");
      67           0 :         return;
      68             :     }
      69             : 
      70           0 :     if (msg_type == NetMsgType::DSSTATUSUPDATE ||
      71           0 :                msg_type == NetMsgType::DSFINALTX ||
      72           0 :                msg_type == NetMsgType::DSCOMPLETE) {
      73           0 :         AssertLockNotHeld(cs_deqsessions);
      74           0 :         LOCK(cs_deqsessions);
      75           0 :         for (auto& session : deqSessions) {
      76           0 :             session.ProcessMessage(peer, active_chainstate, connman, mempool, msg_type, vRecv);
      77             :         }
      78           0 :     }
      79           0 : }
      80             : 
      81           0 : CCoinJoinClientSession::CCoinJoinClientSession(const std::shared_ptr<CWallet>& wallet, CCoinJoinClientManager& clientman,
      82             :                                                CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman,
      83             :                                                const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman) :
      84           0 :     m_wallet(wallet),
      85           0 :     m_clientman(clientman),
      86           0 :     m_dmnman(dmnman),
      87           0 :     m_mn_metaman(mn_metaman),
      88           0 :     m_mn_sync(mn_sync),
      89           0 :     m_isman{isman}
      90           0 : {}
      91             : 
      92           0 : void CCoinJoinClientSession::ProcessMessage(CNode& peer, CChainState& active_chainstate, CConnman& connman, const CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv)
      93             : {
      94           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return;
      95           0 :     if (!m_mn_sync.IsBlockchainSynced()) return;
      96             : 
      97           0 :     if (!mixingMasternode) return;
      98           0 :     if (mixingMasternode->pdmnState->netInfo->GetPrimary() != peer.addr) return;
      99             : 
     100           0 :     if (msg_type == NetMsgType::DSSTATUSUPDATE) {
     101           0 :         CCoinJoinStatusUpdate psssup;
     102           0 :         vRecv >> psssup;
     103             : 
     104           0 :         ProcessPoolStateUpdate(psssup);
     105           0 :     } else if (msg_type == NetMsgType::DSFINALTX) {
     106             :         int nMsgSessionID;
     107           0 :         vRecv >> nMsgSessionID;
     108           0 :         CTransaction txNew(deserialize, vRecv);
     109             : 
     110           0 :         if (nSessionID != nMsgSessionID) {
     111           0 :             WalletCJLogPrint(m_wallet, "DSFINALTX -- message doesn't match current CoinJoin session: nSessionID: %d  nMsgSessionID: %d\n", nSessionID.load(), nMsgSessionID);
     112           0 :             return;
     113             :         }
     114             : 
     115           0 :         WalletCJLogPrint(m_wallet, "DSFINALTX -- txNew %s", txNew.ToString()); /* Continued */
     116             : 
     117             :         // check to see if input is spent already? (and probably not confirmed)
     118           0 :         SignFinalTransaction(peer, active_chainstate, connman, mempool, txNew);
     119           0 :     } else if (msg_type == NetMsgType::DSCOMPLETE) {
     120             :         int nMsgSessionID;
     121             :         PoolMessage nMsgMessageID;
     122           0 :         vRecv >> nMsgSessionID >> nMsgMessageID;
     123             : 
     124           0 :         if (nMsgMessageID < MSG_POOL_MIN || nMsgMessageID > MSG_POOL_MAX) {
     125           0 :             WalletCJLogPrint(m_wallet, "DSCOMPLETE -- nMsgMessageID is out of bounds: %d\n", nMsgMessageID);
     126           0 :             return;
     127             :         }
     128             : 
     129           0 :         if (nSessionID != nMsgSessionID) {
     130           0 :             WalletCJLogPrint(m_wallet, "DSCOMPLETE -- message doesn't match current CoinJoin session: nSessionID: %d  nMsgSessionID: %d\n", nSessionID.load(), nMsgSessionID);
     131           0 :             return;
     132             :         }
     133             : 
     134           0 :         WalletCJLogPrint(m_wallet, "DSCOMPLETE -- nMsgSessionID %d  nMsgMessageID %d (%s)\n", nMsgSessionID, nMsgMessageID, CoinJoin::GetMessageByID(nMsgMessageID).translated);
     135             : 
     136           0 :         CompletedTransaction(nMsgMessageID);
     137           0 :     }
     138           0 : }
     139             : 
     140           2 : bool CCoinJoinClientManager::StartMixing() {
     141           2 :     bool expected{false};
     142           2 :     return fMixing.compare_exchange_strong(expected, true);
     143             : }
     144             : 
     145           1 : void CCoinJoinClientManager::StopMixing() {
     146           1 :     fMixing = false;
     147           1 : }
     148             : 
     149           3 : bool CCoinJoinClientManager::IsMixing() const
     150             : {
     151           3 :     return fMixing;
     152             : }
     153             : 
     154           0 : void CCoinJoinClientSession::ResetPool()
     155             : {
     156           0 :     txMyCollateral = CMutableTransaction();
     157           0 :     UnlockCoins();
     158           0 :     WITH_LOCK(m_wallet->cs_wallet, keyHolderStorage.ReturnAll());
     159           0 :     WITH_LOCK(cs_coinjoin, SetNull());
     160           0 : }
     161             : 
     162           0 : void CCoinJoinClientManager::ResetPool()
     163             : {
     164           0 :     nCachedLastSuccessBlock = 0;
     165           0 :     AssertLockNotHeld(cs_deqsessions);
     166           0 :     LOCK(cs_deqsessions);
     167           0 :     for (auto& session : deqSessions) {
     168           0 :         session.ResetPool();
     169             :     }
     170           0 :     deqSessions.clear();
     171           0 : }
     172             : 
     173           0 : void CCoinJoinClientSession::SetNull()
     174             : {
     175           0 :     AssertLockHeld(cs_coinjoin);
     176             :     // Client side
     177           0 :     mixingMasternode = nullptr;
     178           0 :     pendingDsaRequest = CPendingDsaRequest();
     179             : 
     180           0 :     CCoinJoinBaseSession::SetNull();
     181           0 : }
     182             : 
     183             : //
     184             : // Unlock coins after mixing fails or succeeds
     185             : //
     186           0 : void CCoinJoinClientSession::UnlockCoins()
     187             : {
     188           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return;
     189             : 
     190           0 :     while (true) {
     191           0 :         TRY_LOCK(m_wallet->cs_wallet, lockWallet);
     192           0 :         if (!lockWallet) {
     193           0 :             UninterruptibleSleep(std::chrono::milliseconds{50});
     194           0 :             continue;
     195             :         }
     196           0 :         for (const auto& outpoint : vecOutPointLocked)
     197           0 :             m_wallet->UnlockCoin(outpoint);
     198           0 :         break;
     199           0 :     }
     200             : 
     201           0 :     vecOutPointLocked.clear();
     202           0 : }
     203             : 
     204           0 : bilingual_str CCoinJoinClientSession::GetStatus(bool fWaitForBlock) const
     205             : {
     206             :     static int nStatusMessageProgress = 0;
     207           0 :     nStatusMessageProgress += 10;
     208           0 :     std::string strSuffix;
     209             : 
     210           0 :     if (fWaitForBlock || !m_mn_sync.IsBlockchainSynced()) {
     211           0 :         return strAutoDenomResult;
     212             :     }
     213             : 
     214           0 :     switch (nState) {
     215             :     case POOL_STATE_IDLE:
     216           0 :         return strprintf(_("%s is idle."), gCoinJoinName);
     217             :     case POOL_STATE_QUEUE:
     218           0 :         if (nStatusMessageProgress % 70 <= 30)
     219           0 :             strSuffix = ".";
     220           0 :         else if (nStatusMessageProgress % 70 <= 50)
     221           0 :             strSuffix = "..";
     222             :         else
     223           0 :             strSuffix = "...";
     224           0 :         return strprintf(_("Submitted to masternode, waiting in queue %s"), strSuffix);
     225             :     case POOL_STATE_ACCEPTING_ENTRIES:
     226           0 :         return strAutoDenomResult;
     227             :     case POOL_STATE_SIGNING:
     228           0 :         if (nStatusMessageProgress % 70 <= 40)
     229           0 :             return _("Found enough users, signing…");
     230           0 :         else if (nStatusMessageProgress % 70 <= 50)
     231           0 :             strSuffix = ".";
     232           0 :         else if (nStatusMessageProgress % 70 <= 60)
     233           0 :             strSuffix = "..";
     234             :         else
     235           0 :             strSuffix = "...";
     236           0 :         return strprintf(_("Found enough users, signing ( waiting %s )"), strSuffix);
     237             :     case POOL_STATE_ERROR:
     238           0 :         return strprintf(_("%s request incomplete:"), gCoinJoinName) + strLastMessage + Untranslated(" ") + _("Will retry…");
     239             :     default:
     240           0 :         return strprintf(_("Unknown state: id = %u"), nState);
     241             :     }
     242           0 : }
     243             : 
     244           0 : std::vector<std::string> CCoinJoinClientManager::GetStatuses() const
     245             : {
     246           0 :     AssertLockNotHeld(cs_deqsessions);
     247             : 
     248           0 :     bool fWaitForBlock{WaitForAnotherBlock()};
     249           0 :     std::vector<std::string> ret;
     250             : 
     251           0 :     LOCK(cs_deqsessions);
     252           0 :     for (const auto& session : deqSessions) {
     253           0 :         ret.push_back(session.GetStatus(fWaitForBlock).original);
     254             :     }
     255           0 :     return ret;
     256           0 : }
     257             : 
     258           0 : std::string CCoinJoinClientManager::GetSessionDenoms()
     259             : {
     260           0 :     std::string strSessionDenoms;
     261             : 
     262           0 :     AssertLockNotHeld(cs_deqsessions);
     263           0 :     LOCK(cs_deqsessions);
     264           0 :     for (const auto& session : deqSessions) {
     265           0 :         strSessionDenoms += CoinJoin::DenominationToString(session.nSessionDenom);
     266           0 :         strSessionDenoms += "; ";
     267             :     }
     268           0 :     return strSessionDenoms.empty() ? "N/A" : strSessionDenoms;
     269           0 : }
     270             : 
     271           0 : bool CCoinJoinClientSession::GetMixingMasternodeInfo(CDeterministicMNCPtr& ret) const
     272             : {
     273           0 :     ret = mixingMasternode;
     274           0 :     return ret != nullptr;
     275             : }
     276             : 
     277           0 : bool CCoinJoinClientManager::GetMixingMasternodesInfo(std::vector<CDeterministicMNCPtr>& vecDmnsRet) const
     278             : {
     279           0 :     AssertLockNotHeld(cs_deqsessions);
     280           0 :     LOCK(cs_deqsessions);
     281           0 :     for (const auto& session : deqSessions) {
     282           0 :         CDeterministicMNCPtr dmn;
     283           0 :         if (session.GetMixingMasternodeInfo(dmn)) {
     284           0 :             vecDmnsRet.push_back(dmn);
     285           0 :         }
     286           0 :     }
     287           0 :     return !vecDmnsRet.empty();
     288           0 : }
     289             : 
     290             : //
     291             : // Check session timeouts
     292             : //
     293           0 : bool CCoinJoinClientSession::CheckTimeout()
     294             : {
     295           0 :     if (nState == POOL_STATE_IDLE) return false;
     296             : 
     297           0 :     if (nState == POOL_STATE_ERROR) {
     298           0 :         if (GetTime() - nTimeLastSuccessfulStep >= 10) {
     299             :             // reset after being in POOL_STATE_ERROR for 10 or more seconds
     300           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- resetting session %d\n", __func__, nSessionID.load());
     301           0 :             WITH_LOCK(cs_coinjoin, SetNull());
     302           0 :         }
     303           0 :         return false;
     304             :     }
     305             : 
     306           0 :     int nLagTime = 10; // give the server a few extra seconds before resetting.
     307           0 :     int nTimeout = (nState == POOL_STATE_SIGNING) ? COINJOIN_SIGNING_TIMEOUT : COINJOIN_QUEUE_TIMEOUT;
     308           0 :     bool fTimeout = GetTime() - nTimeLastSuccessfulStep >= nTimeout + nLagTime;
     309             : 
     310           0 :     if (!fTimeout) return false;
     311             : 
     312           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- %s %d timed out (%ds)\n", __func__,
     313             :         (nState == POOL_STATE_SIGNING) ? "Signing at session" : "Session", nSessionID.load(), nTimeout);
     314             : 
     315           0 :     SetState(POOL_STATE_ERROR);
     316           0 :     UnlockCoins();
     317           0 :     WITH_LOCK(m_wallet->cs_wallet, keyHolderStorage.ReturnAll());
     318           0 :     nTimeLastSuccessfulStep = GetTime();
     319           0 :     strLastMessage = CoinJoin::GetMessageByID(ERR_SESSION);
     320             : 
     321           0 :     return true;
     322           0 : }
     323             : 
     324             : //
     325             : // Check all queues and sessions for timeouts
     326             : //
     327           0 : void CCoinJoinClientManager::CheckTimeout()
     328             : {
     329           0 :     AssertLockNotHeld(cs_deqsessions);
     330             : 
     331           0 :     if (!CCoinJoinClientOptions::IsEnabled() || !IsMixing()) return;
     332             : 
     333           0 :     LOCK(cs_deqsessions);
     334           0 :     for (auto& session : deqSessions) {
     335           0 :         if (session.CheckTimeout()) {
     336           0 :             strAutoDenomResult = _("Session timed out.");
     337           0 :         }
     338             :     }
     339           0 : }
     340             : 
     341             : //
     342             : // Execute a mixing denomination via a Masternode.
     343             : // This is only ran from clients
     344             : //
     345           0 : bool CCoinJoinClientSession::SendDenominate(const std::vector<std::pair<CTxDSIn, CTxOut> >& vecPSInOutPairsIn, CConnman& connman)
     346             : {
     347           0 :     if (CTransaction(txMyCollateral).IsNull()) {
     348           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClient:SendDenominate -- CoinJoin collateral not set\n");
     349           0 :         return false;
     350             :     }
     351             : 
     352             :     // we should already be connected to a Masternode
     353           0 :     if (!nSessionID) {
     354           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::SendDenominate -- No Masternode has been selected yet.\n");
     355           0 :         UnlockCoins();
     356           0 :         keyHolderStorage.ReturnAll();
     357           0 :         WITH_LOCK(cs_coinjoin, SetNull());
     358           0 :         return false;
     359             :     }
     360             : 
     361           0 :     if (!CheckDiskSpace(gArgs.GetDataDirNet())) {
     362           0 :         UnlockCoins();
     363           0 :         keyHolderStorage.ReturnAll();
     364           0 :         WITH_LOCK(cs_coinjoin, SetNull());
     365           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::SendDenominate -- Not enough disk space.\n");
     366           0 :         return false;
     367             :     }
     368             : 
     369           0 :     SetState(POOL_STATE_ACCEPTING_ENTRIES);
     370           0 :     strLastMessage = Untranslated("");
     371             : 
     372           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::SendDenominate -- Added transaction to pool.\n");
     373             : 
     374           0 :     CMutableTransaction tx; // for debug purposes only
     375           0 :     std::vector<CTxDSIn> vecTxDSInTmp;
     376           0 :     std::vector<CTxOut> vecTxOutTmp;
     377             : 
     378           0 :     for (const auto& [txDsIn, txOut] : vecPSInOutPairsIn) {
     379           0 :         vecTxDSInTmp.emplace_back(txDsIn);
     380           0 :         vecTxOutTmp.emplace_back(txOut);
     381           0 :         tx.vin.emplace_back(txDsIn);
     382           0 :         tx.vout.emplace_back(txOut);
     383             :     }
     384             : 
     385           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::SendDenominate -- Submitting partial tx %s", tx.ToString()); /* Continued */
     386             : 
     387             :     // store our entry for later use
     388           0 :     LOCK(cs_coinjoin);
     389           0 :     vecEntries.emplace_back(vecTxDSInTmp, vecTxOutTmp, CTransaction(txMyCollateral));
     390           0 :     RelayIn(vecEntries.back(), connman);
     391           0 :     nTimeLastSuccessfulStep = GetTime();
     392             : 
     393           0 :     return true;
     394           0 : }
     395             : 
     396             : // Process incoming messages from Masternode updating the progress of mixing
     397           0 : void CCoinJoinClientSession::ProcessPoolStateUpdate(CCoinJoinStatusUpdate psssup)
     398             : {
     399             :     // do not update state when mixing client state is one of these
     400           0 :     if (nState == POOL_STATE_IDLE || nState == POOL_STATE_ERROR) return;
     401             : 
     402           0 :     if (psssup.nState < POOL_STATE_MIN || psssup.nState > POOL_STATE_MAX) {
     403           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- psssup.nState is out of bounds: %d\n", __func__, psssup.nState);
     404           0 :         return;
     405             :     }
     406             : 
     407           0 :     if (psssup.nMessageID < MSG_POOL_MIN || psssup.nMessageID > MSG_POOL_MAX) {
     408           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- psssup.nMessageID is out of bounds: %d\n", __func__, psssup.nMessageID);
     409           0 :         return;
     410             :     }
     411             : 
     412           0 :     bilingual_str strMessageTmp = CoinJoin::GetMessageByID(psssup.nMessageID);
     413           0 :     strAutoDenomResult = _("Masternode:") + Untranslated(" ") + strMessageTmp;
     414             : 
     415           0 :     switch (psssup.nStatusUpdate) {
     416             :         case STATUS_REJECTED: {
     417           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- rejected by Masternode: %s\n", __func__, strMessageTmp.translated);
     418           0 :             SetState(POOL_STATE_ERROR);
     419           0 :             UnlockCoins();
     420           0 :             WITH_LOCK(m_wallet->cs_wallet, keyHolderStorage.ReturnAll());
     421           0 :             nTimeLastSuccessfulStep = GetTime();
     422           0 :             strLastMessage = strMessageTmp;
     423           0 :             break;
     424             :         }
     425             :         case STATUS_ACCEPTED: {
     426           0 :             if (nState == psssup.nState && psssup.nState == POOL_STATE_QUEUE && nSessionID == 0 && psssup.nSessionID != 0) {
     427             :                 // new session id should be set only in POOL_STATE_QUEUE state
     428           0 :                 nSessionID = psssup.nSessionID;
     429           0 :                 nTimeLastSuccessfulStep = GetTime();
     430           0 :                 strMessageTmp = strMessageTmp + strprintf(Untranslated(" Set nSessionID to %d."), nSessionID);
     431           0 :             }
     432           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- accepted by Masternode: %s\n", __func__, strMessageTmp.translated);
     433           0 :             break;
     434             :         }
     435             :         default: {
     436           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- psssup.nStatusUpdate is out of bounds: %d\n", __func__, psssup.nStatusUpdate);
     437           0 :             break;
     438             :         }
     439             :     }
     440           0 : }
     441             : 
     442             : //
     443             : // After we receive the finalized transaction from the Masternode, we must
     444             : // check it to make sure it's what we want, then sign it if we agree.
     445             : // If we refuse to sign, it's possible we'll be charged collateral
     446             : //
     447           0 : bool CCoinJoinClientSession::SignFinalTransaction(CNode& peer, CChainState& active_chainstate, CConnman& connman, const CTxMemPool& mempool, const CTransaction& finalTransactionNew)
     448             : {
     449           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return false;
     450             : 
     451           0 :     if (!mixingMasternode) return false;
     452             : 
     453           0 :     LOCK(m_wallet->cs_wallet);
     454           0 :     LOCK(cs_coinjoin);
     455             : 
     456           0 :     finalMutableTransaction = CMutableTransaction{finalTransactionNew};
     457           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- finalMutableTransaction=%s", __func__, finalMutableTransaction.ToString()); /* Continued */
     458             : 
     459             :     // STEP 1: check final transaction general rules
     460             : 
     461             :     // Make sure it's BIP69 compliant
     462           0 :     sort(finalMutableTransaction.vin.begin(), finalMutableTransaction.vin.end(), CompareInputBIP69());
     463           0 :     sort(finalMutableTransaction.vout.begin(), finalMutableTransaction.vout.end(), CompareOutputBIP69());
     464             : 
     465           0 :     if (finalMutableTransaction.GetHash() != finalTransactionNew.GetHash()) {
     466           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- ERROR! Masternode %s is not BIP69 compliant!\n", __func__, mixingMasternode->proTxHash.ToString());
     467           0 :         UnlockCoins();
     468           0 :         keyHolderStorage.ReturnAll();
     469           0 :         SetNull();
     470           0 :         return false;
     471             :     }
     472             : 
     473             :     // Make sure all inputs/outputs are valid
     474           0 :     PoolMessage nMessageID{MSG_NOERR};
     475           0 :     if (!IsValidInOuts(active_chainstate, m_isman, mempool, finalMutableTransaction.vin, finalMutableTransaction.vout,
     476             :                        nMessageID, nullptr)) {
     477           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- ERROR! IsValidInOuts() failed: %s\n", __func__, CoinJoin::GetMessageByID(nMessageID).translated);
     478           0 :         UnlockCoins();
     479           0 :         keyHolderStorage.ReturnAll();
     480           0 :         SetNull();
     481           0 :         return false;
     482             :     }
     483             : 
     484             :     // STEP 2: make sure our own inputs/outputs are present, otherwise refuse to sign
     485             : 
     486           0 :     std::map<COutPoint, Coin> coins;
     487             : 
     488           0 :     for (const auto &entry: vecEntries) {
     489             :         // Check that the final transaction has all our outputs
     490           0 :         for (const auto &txout: entry.vecTxOut) {
     491           0 :             bool fFound = std::ranges::any_of(finalMutableTransaction.vout,
     492           0 :                                               [&txout](const auto& txoutFinal) { return txoutFinal == txout; });
     493           0 :             if (!fFound) {
     494             :                 // Something went wrong and we'll refuse to sign. It's possible we'll be charged collateral. But that's
     495             :                 // better than signing if the transaction doesn't look like what we wanted.
     496           0 :                 WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- an output is missing, refusing to sign! txout=%s\n", __func__, txout.ToString());
     497           0 :                 UnlockCoins();
     498           0 :                 keyHolderStorage.ReturnAll();
     499           0 :                 SetNull();
     500           0 :                 return false;
     501             :             }
     502             :         }
     503             : 
     504           0 :         for (const auto& txdsin : entry.vecTxDSIn) {
     505             :             /* Sign my transaction and all outputs */
     506           0 :             int nMyInputIndex = -1;
     507           0 :             CScript prevPubKey = CScript();
     508             : 
     509           0 :             for (const auto i : util::irange(finalMutableTransaction.vin.size())) {
     510             :                 // cppcheck-suppress useStlAlgorithm
     511           0 :                 if (finalMutableTransaction.vin[i] == txdsin) {
     512           0 :                     nMyInputIndex = i;
     513           0 :                     prevPubKey = txdsin.prevPubKey;
     514           0 :                     break;
     515             :                 }
     516             :             }
     517             : 
     518           0 :             if (nMyInputIndex == -1) {
     519             :                 // Can't find one of my own inputs, refuse to sign. It's possible we'll be charged collateral. But that's
     520             :                 // better than signing if the transaction doesn't look like what we wanted.
     521           0 :                 WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- missing input! txdsin=%s\n", __func__, txdsin.ToString());
     522           0 :                 UnlockCoins();
     523           0 :                 keyHolderStorage.ReturnAll();
     524           0 :                 SetNull();
     525           0 :                 return false;
     526             :             }
     527             : 
     528           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- found my input %i\n", __func__, nMyInputIndex);
     529             :             // add a pair with an empty value
     530           0 :             coins[finalMutableTransaction.vin.at(nMyInputIndex).prevout];
     531           0 :         }
     532             :     }
     533             : 
     534             :     // fill values for found outpoints
     535           0 :     m_wallet->chain().findCoins(coins);
     536           0 :     std::map<int, bilingual_str> signing_errors;
     537           0 :     m_wallet->SignTransaction(finalMutableTransaction, coins, SIGHASH_ALL | SIGHASH_ANYONECANPAY, signing_errors);
     538             : 
     539           0 :     for (const auto& [input_index, error_string] : signing_errors) {
     540             :         // NOTE: this is a partial signing so it's expected for SignTransaction to return
     541             :         // "Input not found or already spent" errors for inputs that aren't ours
     542           0 :         if (error_string.original != "Input not found or already spent") {
     543           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- signing input %d failed: %s!\n", __func__, input_index, error_string.original);
     544           0 :             UnlockCoins();
     545           0 :             keyHolderStorage.ReturnAll();
     546           0 :             SetNull();
     547           0 :             return false;
     548             :         }
     549             :     }
     550             : 
     551           0 :     std::vector<CTxIn> signed_inputs;
     552           0 :     for (const auto& txin : finalMutableTransaction.vin) {
     553           0 :         if (coins.find(txin.prevout) != coins.end()) {
     554           0 :             signed_inputs.push_back(txin);
     555           0 :         }
     556             :     }
     557             : 
     558           0 :     if (signed_inputs.empty()) {
     559           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- can't sign anything!\n", __func__);
     560           0 :         UnlockCoins();
     561           0 :         keyHolderStorage.ReturnAll();
     562           0 :         SetNull();
     563           0 :         return false;
     564             :     }
     565             : 
     566             :     // push all of our signatures to the Masternode
     567           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- pushing signed inputs to the masternode, finalMutableTransaction=%s", __func__, finalMutableTransaction.ToString()); /* Continued */
     568           0 :     CNetMsgMaker msgMaker(peer.GetCommonVersion());
     569           0 :     connman.PushMessage(&peer, msgMaker.Make(NetMsgType::DSSIGNFINALTX, signed_inputs));
     570           0 :     SetState(POOL_STATE_SIGNING);
     571           0 :     nTimeLastSuccessfulStep = GetTime();
     572             : 
     573           0 :     return true;
     574           0 : }
     575             : 
     576             : // mixing transaction was completed (failed or successful)
     577           0 : void CCoinJoinClientSession::CompletedTransaction(PoolMessage nMessageID)
     578             : {
     579           0 :     if (nMessageID == MSG_SUCCESS) {
     580           0 :         m_clientman.UpdatedSuccessBlock();
     581           0 :         keyHolderStorage.KeepAll();
     582           0 :         WalletCJLogPrint(m_wallet, "CompletedTransaction -- success\n");
     583           0 :     } else {
     584           0 :         WITH_LOCK(m_wallet->cs_wallet, keyHolderStorage.ReturnAll());
     585           0 :         WalletCJLogPrint(m_wallet, "CompletedTransaction -- error\n");
     586             :     }
     587           0 :     UnlockCoins();
     588           0 :     WITH_LOCK(cs_coinjoin, SetNull());
     589           0 :     strLastMessage = CoinJoin::GetMessageByID(nMessageID);
     590           0 : }
     591             : 
     592           0 : void CCoinJoinClientManager::UpdatedSuccessBlock()
     593             : {
     594           0 :     nCachedLastSuccessBlock = nCachedBlockHeight;
     595           0 : }
     596             : 
     597           0 : bool CCoinJoinClientManager::WaitForAnotherBlock() const
     598             : {
     599           0 :     if (!m_mn_sync.IsBlockchainSynced()) return true;
     600             : 
     601           0 :     if (CCoinJoinClientOptions::IsMultiSessionEnabled()) return false;
     602             : 
     603           0 :     return nCachedBlockHeight - nCachedLastSuccessBlock < nMinBlocksToWait;
     604           0 : }
     605             : 
     606           0 : bool CCoinJoinClientManager::CheckAutomaticBackup()
     607             : {
     608           0 :     if (!CCoinJoinClientOptions::IsEnabled() || !IsMixing()) return false;
     609             : 
     610             :     // We don't need auto-backups for descriptor wallets
     611           0 :     if (!m_wallet->IsLegacy()) return true;
     612             : 
     613           0 :     switch (nWalletBackups) {
     614             :     case 0:
     615           0 :         strAutoDenomResult = _("Automatic backups disabled") + Untranslated(", ") + _("no mixing available.");
     616           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original);
     617           0 :         StopMixing();
     618           0 :         m_wallet->nKeysLeftSinceAutoBackup = 0; // no backup, no "keys since last backup"
     619           0 :         return false;
     620             :     case -1:
     621             :         // Automatic backup failed, nothing else we can do until user fixes the issue manually.
     622             :         // There is no way to bring user attention in daemon mode, so we just update status and
     623             :         // keep spamming if debug is on.
     624           0 :         strAutoDenomResult = _("ERROR! Failed to create automatic backup") + Untranslated(", ") + _("see debug.log for details.");
     625           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original);
     626           0 :         return false;
     627             :     case -2:
     628             :         // We were able to create automatic backup but keypool was not replenished because wallet is locked.
     629             :         // There is no way to bring user attention in daemon mode, so we just update status and
     630             :         // keep spamming if debug is on.
     631           0 :         strAutoDenomResult = _("WARNING! Failed to replenish keypool, please unlock your wallet to do so.") + Untranslated(", ") + _("see debug.log for details.");
     632           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original);
     633           0 :         return false;
     634             :     }
     635             : 
     636           0 :     if (m_wallet->nKeysLeftSinceAutoBackup < COINJOIN_KEYS_THRESHOLD_STOP) {
     637             :         // We should never get here via mixing itself but probably something else is still actively using keypool
     638           0 :         strAutoDenomResult = strprintf(_("Very low number of keys left: %d") + Untranslated(", ") +
     639           0 :                                            _("no mixing available."),
     640           0 :                                        m_wallet->nKeysLeftSinceAutoBackup);
     641           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original);
     642             :         // It's getting really dangerous, stop mixing
     643           0 :         StopMixing();
     644           0 :         return false;
     645           0 :     } else if (m_wallet->nKeysLeftSinceAutoBackup < COINJOIN_KEYS_THRESHOLD_WARNING) {
     646             :         // Low number of keys left, but it's still more or less safe to continue
     647           0 :         strAutoDenomResult = strprintf(_("Very low number of keys left: %d"), m_wallet->nKeysLeftSinceAutoBackup);
     648           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original);
     649             : 
     650           0 :         if (fCreateAutoBackups) {
     651           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- Trying to create new backup.\n");
     652           0 :             bilingual_str errorString;
     653           0 :             std::vector<bilingual_str> warnings;
     654             : 
     655           0 :             if (!m_wallet->AutoBackupWallet("", errorString, warnings)) {
     656           0 :                 if (!warnings.empty()) {
     657             :                     // There were some issues saving backup but yet more or less safe to continue
     658           0 :                     WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- WARNING! Something went wrong on automatic backup: %s\n", Join(warnings, Untranslated("\n")).translated);
     659           0 :                 }
     660           0 :                 if (!errorString.original.empty()) {
     661             :                     // Things are really broken
     662           0 :                     strAutoDenomResult = _("ERROR! Failed to create automatic backup") + Untranslated(": ") + errorString;
     663           0 :                     WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original);
     664           0 :                     return false;
     665             :                 }
     666           0 :             }
     667           0 :         } else {
     668             :             // Wait for something else (e.g. GUI action) to create automatic backup for us
     669           0 :             return false;
     670             :         }
     671           0 :     }
     672             : 
     673           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- Keys left since latest backup: %d\n",
     674             :                      m_wallet->nKeysLeftSinceAutoBackup);
     675             : 
     676           0 :     return true;
     677           0 : }
     678             : 
     679             : //
     680             : // Passively run mixing in the background to mix funds based on the given configuration.
     681             : //
     682           0 : bool CCoinJoinClientSession::DoAutomaticDenominating(ChainstateManager& chainman, CConnman& connman,
     683             :                                                      const CTxMemPool& mempool, bool fDryRun)
     684             : {
     685           0 :     if (nState != POOL_STATE_IDLE) return false;
     686             : 
     687           0 :     if (!m_mn_sync.IsBlockchainSynced()) {
     688           0 :         strAutoDenomResult = _("Can't mix while sync in progress.");
     689           0 :         return false;
     690             :     }
     691             : 
     692           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return false;
     693             : 
     694             :     CAmount nBalanceNeedsAnonymized;
     695             : 
     696             :     {
     697           0 :         LOCK(m_wallet->cs_wallet);
     698             : 
     699           0 :         if (!fDryRun && m_wallet->IsLocked(true)) {
     700           0 :             strAutoDenomResult = _("Wallet is locked.");
     701           0 :             return false;
     702             :         }
     703             : 
     704           0 :         if (GetEntriesCount() > 0) {
     705           0 :             strAutoDenomResult = _("Mixing in progress…");
     706           0 :             return false;
     707             :         }
     708             : 
     709           0 :         TRY_LOCK(cs_coinjoin, lockDS);
     710           0 :         if (!lockDS) {
     711           0 :             strAutoDenomResult = _("Lock is already in place.");
     712           0 :             return false;
     713             :         }
     714             : 
     715           0 :         if (m_dmnman.GetListAtChainTip().GetCounts().enabled() == 0 &&
     716           0 :             Params().NetworkIDString() != CBaseChainParams::REGTEST) {
     717           0 :             strAutoDenomResult = _("No Masternodes detected.");
     718           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::DoAutomaticDenominating -- %s\n", strAutoDenomResult.original);
     719           0 :             return false;
     720             :         }
     721             : 
     722           0 :         const auto bal = GetBalance(*m_wallet);
     723             : 
     724             :         // check if there is anything left to do
     725           0 :         CAmount nBalanceAnonymized = bal.m_anonymized;
     726           0 :         nBalanceNeedsAnonymized = CCoinJoinClientOptions::GetAmount() * COIN - nBalanceAnonymized;
     727             : 
     728           0 :         if (nBalanceNeedsAnonymized < 0) {
     729           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::DoAutomaticDenominating -- Nothing to do\n");
     730             :             // nothing to do, just keep it in idle mode
     731           0 :             return false;
     732             :         }
     733             : 
     734           0 :         CAmount nValueMin = CoinJoin::GetSmallestDenomination();
     735             : 
     736             :         // if there are no confirmed DS collateral inputs yet
     737           0 :         if (!m_wallet->HasCollateralInputs()) {
     738             :             // should have some additional amount for them
     739           0 :             nValueMin += CoinJoin::GetMaxCollateralAmount();
     740           0 :         }
     741             : 
     742             :         // including denoms but applying some restrictions
     743           0 :         CAmount nBalanceAnonymizable = m_wallet->GetAnonymizableBalance();
     744             : 
     745             :         // mixable balance is way too small
     746           0 :         if (nBalanceAnonymizable < nValueMin) {
     747           0 :             strAutoDenomResult = _("Not enough funds to mix.");
     748           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::DoAutomaticDenominating -- %s\n", strAutoDenomResult.original);
     749           0 :             return false;
     750             :         }
     751             : 
     752             :         // excluding denoms
     753           0 :         CAmount nBalanceAnonimizableNonDenom = m_wallet->GetAnonymizableBalance(true);
     754             :         // denoms
     755           0 :         CAmount nBalanceDenominatedConf = bal.m_denominated_trusted;
     756           0 :         CAmount nBalanceDenominatedUnconf = bal.m_denominated_untrusted_pending;
     757           0 :         CAmount nBalanceDenominated = nBalanceDenominatedConf + nBalanceDenominatedUnconf;
     758           0 :         CAmount nBalanceToDenominate = CCoinJoinClientOptions::GetAmount() * COIN - nBalanceDenominated;
     759             : 
     760             :         // adjust nBalanceNeedsAnonymized to consume final denom
     761           0 :         if (nBalanceDenominated - nBalanceAnonymized > nBalanceNeedsAnonymized) {
     762           0 :             auto denoms = CoinJoin::GetStandardDenominations();
     763           0 :             CAmount nAdditionalDenom{0};
     764           0 :             for (const auto& denom : denoms) {
     765           0 :                 if (nBalanceNeedsAnonymized < denom) {
     766           0 :                     nAdditionalDenom = denom;
     767           0 :                 } else {
     768           0 :                     break;
     769             :                 }
     770             :             }
     771           0 :             nBalanceNeedsAnonymized += nAdditionalDenom;
     772           0 :         }
     773             : 
     774           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::DoAutomaticDenominating -- current stats:\n"
     775             :             "    nValueMin: %s\n"
     776             :             "    nBalanceAnonymizable: %s\n"
     777             :             "    nBalanceAnonymized: %s\n"
     778             :             "    nBalanceNeedsAnonymized: %s\n"
     779             :             "    nBalanceAnonimizableNonDenom: %s\n"
     780             :             "    nBalanceDenominatedConf: %s\n"
     781             :             "    nBalanceDenominatedUnconf: %s\n"
     782             :             "    nBalanceDenominated: %s\n"
     783             :             "    nBalanceToDenominate: %s\n",
     784             :             FormatMoney(nValueMin),
     785             :             FormatMoney(nBalanceAnonymizable),
     786             :             FormatMoney(nBalanceAnonymized),
     787             :             FormatMoney(nBalanceNeedsAnonymized),
     788             :             FormatMoney(nBalanceAnonimizableNonDenom),
     789             :             FormatMoney(nBalanceDenominatedConf),
     790             :             FormatMoney(nBalanceDenominatedUnconf),
     791             :             FormatMoney(nBalanceDenominated),
     792             :             FormatMoney(nBalanceToDenominate)
     793             :             );
     794             : 
     795           0 :         if (fDryRun) return true;
     796             : 
     797             :         // Check if we have should create more denominated inputs i.e.
     798             :         // there are funds to denominate and denominated balance does not exceed
     799             :         // max amount to mix yet.
     800           0 :         if (nBalanceAnonimizableNonDenom >= nValueMin + CoinJoin::GetCollateralAmount() && nBalanceToDenominate > 0) {
     801           0 :             CreateDenominated(nBalanceToDenominate);
     802           0 :         }
     803             : 
     804             :         //check if we have the collateral sized inputs
     805           0 :         if (!m_wallet->HasCollateralInputs()) {
     806           0 :             return !m_wallet->HasCollateralInputs(false) && MakeCollateralAmounts();
     807             :         }
     808             : 
     809           0 :         if (nSessionID) {
     810           0 :             strAutoDenomResult = _("Mixing in progress…");
     811           0 :             return false;
     812             :         }
     813             : 
     814             :         // Initial phase, find a Masternode
     815             :         // Clean if there is anything left from previous session
     816           0 :         UnlockCoins();
     817           0 :         keyHolderStorage.ReturnAll();
     818           0 :         SetNull();
     819             : 
     820             :         // should be no unconfirmed denoms in non-multi-session mode
     821           0 :         if (!CCoinJoinClientOptions::IsMultiSessionEnabled() && nBalanceDenominatedUnconf > 0) {
     822           0 :             strAutoDenomResult = _("Found unconfirmed denominated outputs, will wait till they confirm to continue.");
     823           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::DoAutomaticDenominating -- %s\n", strAutoDenomResult.original);
     824           0 :             return false;
     825             :         }
     826             : 
     827             :         //check our collateral and create new if needed
     828           0 :         std::string strReason;
     829           0 :         if (CTransaction(txMyCollateral).IsNull()) {
     830           0 :             if (!CreateCollateralTransaction(txMyCollateral, strReason)) {
     831           0 :                 WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::DoAutomaticDenominating -- create collateral error:%s\n", strReason);
     832           0 :                 return false;
     833             :             }
     834           0 :         } else {
     835           0 :             if (!CoinJoin::IsCollateralValid(chainman, m_isman, mempool, CTransaction(txMyCollateral))) {
     836           0 :                 WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::DoAutomaticDenominating -- invalid collateral, recreating...\n");
     837           0 :                 if (!CreateCollateralTransaction(txMyCollateral, strReason)) {
     838           0 :                     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::DoAutomaticDenominating -- create collateral error: %s\n", strReason);
     839           0 :                     return false;
     840             :                 }
     841           0 :             }
     842             :         }
     843             :         // lock the funds we're going to use for our collateral
     844           0 :         for (const auto& txin : txMyCollateral.vin) {
     845           0 :             m_wallet->LockCoin(txin.prevout);
     846           0 :             vecOutPointLocked.push_back(txin.prevout);
     847             :         }
     848           0 :     } // LOCK(m_wallet->cs_wallet);
     849             : 
     850             :     // Always attempt to join an existing queue
     851           0 :     if (JoinExistingQueue(nBalanceNeedsAnonymized, connman)) {
     852           0 :         return true;
     853             :     }
     854             : 
     855             :     // If we were unable to find/join an existing queue then start a new one.
     856           0 :     if (StartNewQueue(nBalanceNeedsAnonymized, connman)) return true;
     857             : 
     858           0 :     strAutoDenomResult = _("No compatible Masternode found.");
     859           0 :     return false;
     860           0 : }
     861             : 
     862           0 : bool CCoinJoinClientManager::DoAutomaticDenominating(ChainstateManager& chainman, CConnman& connman,
     863             :                                                      const CTxMemPool& mempool, bool fDryRun)
     864             : {
     865           0 :     if (!CCoinJoinClientOptions::IsEnabled() || !IsMixing()) return false;
     866             : 
     867           0 :     if (!m_mn_sync.IsBlockchainSynced()) {
     868           0 :         strAutoDenomResult = _("Can't mix while sync in progress.");
     869           0 :         return false;
     870             :     }
     871             : 
     872           0 :     if (!fDryRun && m_wallet->IsLocked(true)) {
     873           0 :         strAutoDenomResult = _("Wallet is locked.");
     874           0 :         return false;
     875             :     }
     876             : 
     877           0 :     int nMnCountEnabled = m_dmnman.GetListAtChainTip().GetCounts().enabled();
     878             : 
     879             :     // If we've used 90% of the Masternode list then drop the oldest first ~30%
     880           0 :     int nThreshold_high = nMnCountEnabled * 0.9;
     881           0 :     int nThreshold_low = nThreshold_high * 0.7;
     882           0 :     size_t used_count{m_mn_metaman.GetUsedMasternodesCount()};
     883             : 
     884           0 :     WalletCJLogPrint(m_wallet, "Checking threshold - used: %d, threshold: %d\n", (int)used_count, nThreshold_high);
     885             : 
     886           0 :     if ((int)used_count > nThreshold_high) {
     887           0 :         m_mn_metaman.RemoveUsedMasternodes(used_count - nThreshold_low);
     888           0 :         WalletCJLogPrint(m_wallet, "  new used: %d, threshold: %d\n", (int)m_mn_metaman.GetUsedMasternodesCount(),
     889             :                          nThreshold_high);
     890           0 :     }
     891             : 
     892           0 :     bool fResult = true;
     893           0 :     AssertLockNotHeld(cs_deqsessions);
     894           0 :     LOCK(cs_deqsessions);
     895           0 :     if (int(deqSessions.size()) < CCoinJoinClientOptions::GetSessions()) {
     896           0 :         deqSessions.emplace_back(m_wallet, *this, m_dmnman, m_mn_metaman, m_mn_sync, m_isman);
     897           0 :     }
     898           0 :     for (auto& session : deqSessions) {
     899           0 :         if (!CheckAutomaticBackup()) return false;
     900             : 
     901           0 :         if (WaitForAnotherBlock()) {
     902           0 :             strAutoDenomResult = _("Last successful action was too recent.");
     903           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::DoAutomaticDenominating -- %s\n", strAutoDenomResult.original);
     904           0 :             return false;
     905             :         }
     906             : 
     907           0 :         fResult &= session.DoAutomaticDenominating(chainman, connman, mempool, fDryRun);
     908             :     }
     909             : 
     910           0 :     return fResult;
     911           0 : }
     912             : 
     913           0 : void CCoinJoinClientManager::AddUsedMasternode(const uint256& proTxHash)
     914             : {
     915           0 :     m_mn_metaman.AddUsedMasternode(proTxHash);
     916           0 : }
     917             : 
     918           0 : CDeterministicMNCPtr CCoinJoinClientManager::GetRandomNotUsedMasternode()
     919             : {
     920           0 :     auto mnList = m_dmnman.GetListAtChainTip();
     921             : 
     922           0 :     size_t nCountEnabled = mnList.GetCounts().enabled();
     923           0 :     size_t nCountNotExcluded{nCountEnabled - m_mn_metaman.GetUsedMasternodesCount()};
     924             : 
     925           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- %d enabled masternodes, %d masternodes to choose from\n", __func__, nCountEnabled, nCountNotExcluded);
     926           0 :     if (nCountNotExcluded < 1) {
     927           0 :         return nullptr;
     928             :     }
     929             : 
     930             :     // fill a vector
     931           0 :     std::vector<CDeterministicMNCPtr> vpMasternodesShuffled;
     932           0 :     vpMasternodesShuffled.reserve(nCountEnabled);
     933           0 :     mnList.ForEachMNShared(/*onlyValid=*/true,
     934           0 :                            [&vpMasternodesShuffled](const auto& dmn) { vpMasternodesShuffled.emplace_back(dmn); });
     935             : 
     936             :     // shuffle pointers
     937           0 :     Shuffle(vpMasternodesShuffled.begin(), vpMasternodesShuffled.end(), FastRandomContext());
     938             : 
     939             :     // loop through - using direct O(1) lookup instead of creating a set copy
     940           0 :     for (const auto& dmn : vpMasternodesShuffled) {
     941           0 :         if (m_mn_metaman.IsUsedMasternode(dmn->proTxHash)) {
     942           0 :             continue;
     943             :         }
     944             : 
     945           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- found, masternode=%s\n", __func__,
     946             :                          dmn->proTxHash.ToString());
     947           0 :         return dmn;
     948             :     }
     949             : 
     950           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- failed\n", __func__);
     951           0 :     return nullptr;
     952           0 : }
     953             : 
     954           0 : static int WinnersToSkip()
     955             : {
     956           0 :     return (Params().NetworkIDString() == CBaseChainParams::DEVNET ||
     957           0 :             Params().NetworkIDString() == CBaseChainParams::REGTEST)
     958             :             ? 1 : 8;
     959           0 : }
     960             : 
     961           0 : bool CCoinJoinClientSession::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman)
     962             : {
     963           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return false;
     964             : 
     965           0 :     const auto mnList = m_dmnman.GetListAtChainTip();
     966           0 :     const int nWeightedMnCount = mnList.GetCounts().m_valid_weighted;
     967             : 
     968             :     // Look through the queues and see if anything matches
     969           0 :     CCoinJoinQueue dsq;
     970           0 :     while (m_clientman.GetQueueItemAndTry(dsq)) {
     971           0 :         auto dmn = mnList.GetValidMNByCollateral(dsq.masternodeOutpoint);
     972             : 
     973           0 :         if (!dmn) {
     974           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::JoinExistingQueue -- dsq masternode is not in masternode list, masternode=%s\n", dsq.masternodeOutpoint.ToStringShort());
     975           0 :             continue;
     976             :         }
     977             : 
     978             :         // skip next mn payments winners
     979           0 :         if (dmn->pdmnState->nLastPaidHeight + nWeightedMnCount < mnList.GetHeight() + WinnersToSkip()) {
     980           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::JoinExistingQueue -- skipping winner, masternode=%s\n", dmn->proTxHash.ToString());
     981           0 :             continue;
     982             :         }
     983             : 
     984             :         // mixing rate limit i.e. nLastDsq check should already pass in DSQUEUE ProcessMessage
     985             :         // in order for dsq to get into vecCoinJoinQueue, so we should be safe to mix already,
     986             :         // no need for additional verification here
     987             : 
     988           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::JoinExistingQueue -- trying queue: %s\n", dsq.ToString());
     989             : 
     990           0 :         std::vector<CTxDSIn> vecTxDSInTmp;
     991             : 
     992             :         // Try to match their denominations if possible, select exact number of denominations
     993           0 :         if (!m_wallet->SelectTxDSInsByDenomination(dsq.nDenom, nBalanceNeedsAnonymized, vecTxDSInTmp)) {
     994           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::JoinExistingQueue -- Couldn't match denomination %d (%s)\n", dsq.nDenom, CoinJoin::DenominationToString(dsq.nDenom));
     995           0 :             continue;
     996             :         }
     997             : 
     998           0 :         m_clientman.AddUsedMasternode(dmn->proTxHash);
     999             : 
    1000           0 :         if (connman.IsMasternodeOrDisconnectRequested(dmn->pdmnState->netInfo->GetPrimary())) {
    1001           0 :             WalletCJLogPrint(m_wallet, /* Continued */
    1002             :                              "CCoinJoinClientSession::JoinExistingQueue -- skipping connection, masternode=%s\n", dmn->proTxHash.ToString());
    1003           0 :             continue;
    1004             :         }
    1005             : 
    1006           0 :         nSessionDenom = dsq.nDenom;
    1007           0 :         mixingMasternode = dmn;
    1008           0 :         pendingDsaRequest = CPendingDsaRequest(dmn->proTxHash, CCoinJoinAccept(nSessionDenom, txMyCollateral));
    1009           0 :         connman.AddPendingMasternode(dmn->proTxHash);
    1010           0 :         SetState(POOL_STATE_QUEUE);
    1011           0 :         nTimeLastSuccessfulStep = GetTime();
    1012           0 :         WalletCJLogPrint(m_wallet, /* Continued */
    1013             :                          "CCoinJoinClientSession::JoinExistingQueue -- pending connection, masternode=%s, nSessionDenom=%d (%s)\n",
    1014             :                          dmn->proTxHash.ToString(), nSessionDenom, CoinJoin::DenominationToString(nSessionDenom));
    1015           0 :         strAutoDenomResult = _("Trying to connect…");
    1016           0 :         return true;
    1017           0 :     }
    1018           0 :     strAutoDenomResult = _("Failed to find mixing queue to join");
    1019           0 :     return false;
    1020           0 : }
    1021             : 
    1022           0 : bool CCoinJoinClientSession::StartNewQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman)
    1023             : {
    1024           0 :     assert(m_mn_metaman.IsValid());
    1025             : 
    1026           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return false;
    1027           0 :     if (nBalanceNeedsAnonymized <= 0) return false;
    1028             : 
    1029           0 :     int nTries = 0;
    1030           0 :     const auto mnList = m_dmnman.GetListAtChainTip();
    1031           0 :     const auto mnCounts = mnList.GetCounts();
    1032           0 :     const int nMnCount = mnCounts.enabled();
    1033           0 :     const int nWeightedMnCount = mnCounts.m_valid_weighted;
    1034             : 
    1035             :     // find available denominated amounts
    1036           0 :     std::set<CAmount> setAmounts;
    1037           0 :     if (!m_wallet->SelectDenominatedAmounts(nBalanceNeedsAnonymized, setAmounts)) {
    1038             :         // this should never happen
    1039           0 :         strAutoDenomResult = _("Can't mix: no compatible inputs found!");
    1040           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::StartNewQueue -- %s\n", strAutoDenomResult.original);
    1041           0 :         return false;
    1042             :     }
    1043             : 
    1044             :     // otherwise, try one randomly
    1045           0 :     while (nTries < 10) {
    1046           0 :         auto dmn = m_clientman.GetRandomNotUsedMasternode();
    1047           0 :         if (!dmn) {
    1048           0 :             strAutoDenomResult = _("Can't find random Masternode.");
    1049           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::StartNewQueue -- %s\n", strAutoDenomResult.original);
    1050           0 :             return false;
    1051             :         }
    1052             : 
    1053           0 :         m_clientman.AddUsedMasternode(dmn->proTxHash);
    1054             : 
    1055             :         // skip next mn payments winners
    1056           0 :         if (dmn->pdmnState->nLastPaidHeight + nWeightedMnCount < mnList.GetHeight() + WinnersToSkip()) {
    1057           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::StartNewQueue -- skipping winner, masternode=%s\n", dmn->proTxHash.ToString());
    1058           0 :             nTries++;
    1059           0 :             continue;
    1060             :         }
    1061             : 
    1062           0 :         if (m_mn_metaman.IsMixingThresholdExceeded(dmn->proTxHash, nMnCount)) {
    1063           0 :             WalletCJLogPrint(m_wallet, /* Continued */
    1064             :                              "CCoinJoinClientSession::StartNewQueue -- too early to mix with node masternode=%s\n",
    1065             :                              dmn->proTxHash.ToString());
    1066           0 :             nTries++;
    1067           0 :             continue;
    1068             :         }
    1069             : 
    1070           0 :         if (connman.IsMasternodeOrDisconnectRequested(dmn->pdmnState->netInfo->GetPrimary())) {
    1071           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::StartNewQueue -- skipping connection, masternode=%s\n",
    1072             :                              dmn->proTxHash.ToString());
    1073           0 :             nTries++;
    1074           0 :             continue;
    1075             :         }
    1076             : 
    1077           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::StartNewQueue -- attempting connection, masternode=%s, tries=%s\n",
    1078             :                          dmn->proTxHash.ToString(), nTries);
    1079             : 
    1080             :         // try to get a single random denom out of setAmounts
    1081           0 :         while (nSessionDenom == 0) {
    1082           0 :             for (auto it = setAmounts.rbegin(); it != setAmounts.rend(); ++it) {
    1083           0 :                 if (setAmounts.size() > 1 && GetRand<size_t>(/*nMax=*/2)) continue;
    1084           0 :                 nSessionDenom = CoinJoin::AmountToDenomination(*it);
    1085           0 :                 break;
    1086             :             }
    1087             :         }
    1088             : 
    1089           0 :         mixingMasternode = dmn;
    1090           0 :         connman.AddPendingMasternode(dmn->proTxHash);
    1091           0 :         pendingDsaRequest = CPendingDsaRequest(dmn->proTxHash, CCoinJoinAccept(nSessionDenom, txMyCollateral));
    1092           0 :         SetState(POOL_STATE_QUEUE);
    1093           0 :         nTimeLastSuccessfulStep = GetTime();
    1094           0 :         WalletCJLogPrint( /* Continued */
    1095             :             m_wallet, "CCoinJoinClientSession::StartNewQueue -- pending connection, masternode=%s, nSessionDenom=%d (%s)\n",
    1096             :             dmn->proTxHash.ToString(), nSessionDenom, CoinJoin::DenominationToString(nSessionDenom));
    1097           0 :         strAutoDenomResult = _("Trying to connect…");
    1098           0 :         return true;
    1099           0 :     }
    1100           0 :     strAutoDenomResult = _("Failed to start a new mixing queue");
    1101           0 :     return false;
    1102           0 : }
    1103             : 
    1104           0 : bool CCoinJoinClientSession::ProcessPendingDsaRequest(CConnman& connman)
    1105             : {
    1106           0 :     if (!pendingDsaRequest) return false;
    1107             : 
    1108           0 :     CService mn_addr;
    1109           0 :     if (auto dmn = m_dmnman.GetListAtChainTip().GetMN(pendingDsaRequest.GetProTxHash())) {
    1110           0 :         mn_addr = Assert(dmn->pdmnState)->netInfo->GetPrimary();
    1111           0 :     } else {
    1112           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- cannot find address to connect, masternode=%s\n", __func__,
    1113             :             pendingDsaRequest.GetProTxHash().ToString());
    1114           0 :         WITH_LOCK(cs_coinjoin, SetNull());
    1115           0 :         return false;
    1116             :     }
    1117             : 
    1118           0 :     bool fDone = connman.ForNode(mn_addr, [this, &connman](CNode* pnode) {
    1119           0 :         WalletCJLogPrint(m_wallet, "-- processing dsa queue for addr=%s\n", pnode->addr.ToStringAddrPort());
    1120           0 :         nTimeLastSuccessfulStep = GetTime();
    1121           0 :         CNetMsgMaker msgMaker(pnode->GetCommonVersion());
    1122           0 :         connman.PushMessage(pnode, msgMaker.Make(NetMsgType::DSACCEPT, pendingDsaRequest.GetDSA()));
    1123           0 :         return true;
    1124           0 :     });
    1125             : 
    1126           0 :     if (fDone) {
    1127           0 :         pendingDsaRequest = CPendingDsaRequest();
    1128           0 :     } else if (pendingDsaRequest.IsExpired()) {
    1129           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- failed to connect, masternode=%s\n", __func__,
    1130             :                          pendingDsaRequest.GetProTxHash().ToString());
    1131           0 :         WITH_LOCK(cs_coinjoin, SetNull());
    1132           0 :     }
    1133             : 
    1134           0 :     return fDone;
    1135           0 : }
    1136             : 
    1137           0 : void CCoinJoinClientManager::ProcessPendingDsaRequest(CConnman& connman)
    1138             : {
    1139           0 :     AssertLockNotHeld(cs_deqsessions);
    1140           0 :     LOCK(cs_deqsessions);
    1141           0 :     for (auto& session : deqSessions) {
    1142           0 :         if (session.ProcessPendingDsaRequest(connman)) {
    1143           0 :             strAutoDenomResult = _("Mixing in progress…");
    1144           0 :         }
    1145             :     }
    1146           0 : }
    1147             : 
    1148           0 : bool CCoinJoinClientManager::TrySubmitDenominate(const uint256& proTxHash, CConnman& connman)
    1149             : {
    1150           0 :     AssertLockNotHeld(cs_deqsessions);
    1151           0 :     LOCK(cs_deqsessions);
    1152           0 :     for (auto& session : deqSessions) {
    1153           0 :         CDeterministicMNCPtr mnMixing;
    1154           0 :         if (session.GetMixingMasternodeInfo(mnMixing) && mnMixing->proTxHash == proTxHash && session.GetState() == POOL_STATE_QUEUE) {
    1155           0 :             session.SubmitDenominate(connman);
    1156           0 :             return true;
    1157             :         }
    1158           0 :     }
    1159           0 :     return false;
    1160           0 : }
    1161             : 
    1162           0 : bool CCoinJoinClientManager::MarkAlreadyJoinedQueueAsTried(CCoinJoinQueue& dsq) const
    1163             : {
    1164           0 :     AssertLockNotHeld(cs_deqsessions);
    1165           0 :     LOCK(cs_deqsessions);
    1166           0 :     for (const auto& session : deqSessions) {
    1167           0 :         CDeterministicMNCPtr mnMixing;
    1168           0 :         if (session.GetMixingMasternodeInfo(mnMixing) && mnMixing->collateralOutpoint == dsq.masternodeOutpoint) {
    1169           0 :             dsq.fTried = true;
    1170           0 :             return true;
    1171             :         }
    1172           0 :     }
    1173           0 :     return false;
    1174           0 : }
    1175             : 
    1176           0 : bool CCoinJoinClientManager::GetQueueItemAndTry(CCoinJoinQueue& dsq) const
    1177             : {
    1178           0 :     return m_queueman && m_queueman->GetQueueItemAndTry(dsq);
    1179             : }
    1180             : 
    1181           0 : bool CCoinJoinClientSession::SubmitDenominate(CConnman& connman)
    1182             : {
    1183           0 :     LOCK(m_wallet->cs_wallet);
    1184             : 
    1185           0 :     std::string strError;
    1186           0 :     std::vector<CTxDSIn> vecTxDSIn;
    1187           0 :     std::vector<std::pair<CTxDSIn, CTxOut> > vecPSInOutPairsTmp;
    1188             : 
    1189           0 :     if (!SelectDenominate(strError, vecTxDSIn)) {
    1190           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::SubmitDenominate -- SelectDenominate failed, error: %s\n", strError);
    1191           0 :         return false;
    1192             :     }
    1193             : 
    1194           0 :     std::vector<std::pair<int, size_t> > vecInputsByRounds;
    1195             : 
    1196           0 :     for (const auto i : util::irange(CCoinJoinClientOptions::GetRounds() + CCoinJoinClientOptions::GetRandomRounds())) {
    1197           0 :         if (PrepareDenominate(i, i, strError, vecTxDSIn, vecPSInOutPairsTmp, true)) {
    1198           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::SubmitDenominate -- Running CoinJoin denominate for %d rounds, success\n", i);
    1199           0 :             vecInputsByRounds.emplace_back(i, vecPSInOutPairsTmp.size());
    1200           0 :         } else {
    1201           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::SubmitDenominate -- Running CoinJoin denominate for %d rounds, error: %s\n", i, strError);
    1202             :         }
    1203             :     }
    1204             : 
    1205             :     // more inputs first, for equal input count prefer the one with fewer rounds
    1206           0 :     std::sort(vecInputsByRounds.begin(), vecInputsByRounds.end(), [](const auto& a, const auto& b) {
    1207           0 :         return a.second > b.second || (a.second == b.second && a.first < b.first);
    1208             :     });
    1209             : 
    1210           0 :     WalletCJLogPrint(m_wallet, "vecInputsByRounds for denom %d\n", nSessionDenom);
    1211           0 :     for (const auto& pair : vecInputsByRounds) {
    1212           0 :         WalletCJLogPrint(m_wallet, "vecInputsByRounds: rounds: %d, inputs: %d\n", pair.first, pair.second);
    1213             :     }
    1214             : 
    1215           0 :     int nRounds = vecInputsByRounds.begin()->first;
    1216           0 :     if (PrepareDenominate(nRounds, nRounds, strError, vecTxDSIn, vecPSInOutPairsTmp)) {
    1217           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::SubmitDenominate -- Running CoinJoin denominate for %d rounds, success\n", nRounds);
    1218           0 :         return SendDenominate(vecPSInOutPairsTmp, connman);
    1219             :     }
    1220             : 
    1221             :     // We failed? That's strange but let's just make final attempt and try to mix everything
    1222           0 :     if (PrepareDenominate(0, CCoinJoinClientOptions::GetRounds() - 1, strError, vecTxDSIn, vecPSInOutPairsTmp)) {
    1223           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::SubmitDenominate -- Running CoinJoin denominate for all rounds, success\n");
    1224           0 :         return SendDenominate(vecPSInOutPairsTmp, connman);
    1225             :     }
    1226             : 
    1227             :     // Should never actually get here but just in case
    1228           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::SubmitDenominate -- Running CoinJoin denominate for all rounds, error: %s\n", strError);
    1229           0 :     strAutoDenomResult = Untranslated(strError);
    1230           0 :     return false;
    1231           0 : }
    1232             : 
    1233           0 : bool CCoinJoinClientSession::SelectDenominate(std::string& strErrorRet, std::vector<CTxDSIn>& vecTxDSInRet)
    1234             : {
    1235           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return false;
    1236             : 
    1237           0 :     if (m_wallet->IsLocked(true)) {
    1238           0 :         strErrorRet = "Wallet locked, unable to create transaction!";
    1239           0 :         return false;
    1240             :     }
    1241             : 
    1242           0 :     if (GetEntriesCount() > 0) {
    1243           0 :         strErrorRet = "Already have pending entries in the CoinJoin pool";
    1244           0 :         return false;
    1245             :     }
    1246             : 
    1247           0 :     vecTxDSInRet.clear();
    1248             : 
    1249           0 :     bool fSelected = m_wallet->SelectTxDSInsByDenomination(nSessionDenom, CoinJoin::GetMaxPoolAmount(), vecTxDSInRet);
    1250           0 :     if (!fSelected) {
    1251           0 :         strErrorRet = "Can't select current denominated inputs";
    1252           0 :         return false;
    1253             :     }
    1254             : 
    1255           0 :     return true;
    1256           0 : }
    1257             : 
    1258           0 : bool CCoinJoinClientSession::PrepareDenominate(int nMinRounds, int nMaxRounds, std::string& strErrorRet, const std::vector<CTxDSIn>& vecTxDSIn, std::vector<std::pair<CTxDSIn, CTxOut> >& vecPSInOutPairsRet, bool fDryRun)
    1259             : {
    1260           0 :     AssertLockHeld(m_wallet->cs_wallet);
    1261             : 
    1262           0 :     if (!CoinJoin::IsValidDenomination(nSessionDenom)) {
    1263           0 :         strErrorRet = "Incorrect session denom";
    1264           0 :         return false;
    1265             :     }
    1266           0 :     CAmount nDenomAmount = CoinJoin::DenominationToAmount(nSessionDenom);
    1267             : 
    1268             :     // NOTE: No need to randomize order of inputs because they were
    1269             :     // initially shuffled in CWallet::SelectTxDSInsByDenomination already.
    1270           0 :     size_t nSteps{0};
    1271           0 :     vecPSInOutPairsRet.clear();
    1272             : 
    1273             :     // Try to add up to COINJOIN_ENTRY_MAX_SIZE of every needed denomination
    1274           0 :     for (const auto& entry : vecTxDSIn) {
    1275           0 :         if (nSteps >= COINJOIN_ENTRY_MAX_SIZE) break;
    1276           0 :         if (entry.nRounds < nMinRounds || entry.nRounds > nMaxRounds) continue;
    1277             : 
    1278           0 :         CScript scriptDenom;
    1279           0 :         if (fDryRun) {
    1280           0 :             scriptDenom = CScript();
    1281           0 :         } else {
    1282             :             // randomly skip some inputs when we have at least one of the same denom already
    1283             :             // TODO: make it adjustable via options/cmd-line params
    1284           0 :             if (nSteps >= 1 && GetRand<size_t>(/*nMax=*/5) == 0) {
    1285             :                 // still count it as a step to randomize number of inputs
    1286             :                 // if we have more than (or exactly) COINJOIN_ENTRY_MAX_SIZE of them
    1287           0 :                 ++nSteps;
    1288           0 :                 continue;
    1289             :             }
    1290           0 :             scriptDenom = keyHolderStorage.AddKey(m_wallet.get());
    1291             :         }
    1292           0 :         vecPSInOutPairsRet.emplace_back(entry, CTxOut(nDenomAmount, scriptDenom));
    1293             :         // step is complete
    1294           0 :         ++nSteps;
    1295           0 :     }
    1296             : 
    1297           0 :     if (vecPSInOutPairsRet.empty()) {
    1298           0 :         keyHolderStorage.ReturnAll();
    1299           0 :         strErrorRet = "Can't prepare current denominated outputs";
    1300           0 :         return false;
    1301             :     }
    1302             : 
    1303           0 :     if (fDryRun) {
    1304           0 :         return true;
    1305             :     }
    1306             : 
    1307           0 :     for (const auto& [txDsIn, txDsOut] : vecPSInOutPairsRet) {
    1308           0 :         m_wallet->LockCoin(txDsIn.prevout);
    1309           0 :         vecOutPointLocked.push_back(txDsIn.prevout);
    1310             :     }
    1311             : 
    1312           0 :     return true;
    1313           0 : }
    1314             : 
    1315             : // Create collaterals by looping through inputs grouped by addresses
    1316           0 : bool CCoinJoinClientSession::MakeCollateralAmounts()
    1317             : {
    1318           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return false;
    1319             : 
    1320           0 :     LOCK(m_wallet->cs_wallet);
    1321             : 
    1322             :     // NOTE: We do not allow txes larger than 100 kB, so we have to limit number of inputs here.
    1323             :     // We still want to consume a lot of inputs to avoid creating only smaller denoms though.
    1324             :     // Knowing that each CTxIn is at least 148 B big, 400 inputs should take 400 x ~148 B = ~60 kB.
    1325             :     // This still leaves more than enough room for another data of typical MakeCollateralAmounts tx.
    1326           0 :     std::vector<CompactTallyItem> vecTally = m_wallet->SelectCoinsGroupedByAddresses(false, false, true, 400);
    1327           0 :     if (vecTally.empty()) {
    1328           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::MakeCollateralAmounts -- SelectCoinsGroupedByAddresses can't find any inputs!\n");
    1329           0 :         return false;
    1330             :     }
    1331             : 
    1332             :     // Start from the smallest balances first to consume tiny amounts and cleanup UTXO a bit
    1333           0 :     std::sort(vecTally.begin(), vecTally.end(), [](const CompactTallyItem& a, const CompactTallyItem& b) {
    1334           0 :         return a.nAmount < b.nAmount;
    1335             :     });
    1336             : 
    1337             :     // First try to use only non-denominated funds
    1338           0 :     if (std::ranges::any_of(vecTally, [&](const auto& item) EXCLUSIVE_LOCKS_REQUIRED(m_wallet->cs_wallet) {
    1339           0 :             return MakeCollateralAmounts(item, false);
    1340             :         })) {
    1341           0 :         return true;
    1342             :     }
    1343             : 
    1344             :     // There should be at least some denominated funds we should be able to break in pieces to continue mixing
    1345           0 :     if (std::ranges::any_of(vecTally, [&](const auto& item) EXCLUSIVE_LOCKS_REQUIRED(m_wallet->cs_wallet) {
    1346           0 :             return MakeCollateralAmounts(item, true);
    1347             :         })) {
    1348           0 :         return true;
    1349             :     }
    1350             : 
    1351             :     // If we got here then something is terribly broken actually
    1352           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::MakeCollateralAmounts -- ERROR: Can't make collaterals!\n");
    1353           0 :     return false;
    1354           0 : }
    1355             : 
    1356             : // Split up large inputs or create fee sized inputs
    1357           0 : bool CCoinJoinClientSession::MakeCollateralAmounts(const CompactTallyItem& tallyItem, bool fTryDenominated)
    1358             : {
    1359             :     // TODO: consider refactoring to remove duplicated code with CCoinJoinClientSession::CreateDenominated
    1360           0 :     AssertLockHeld(m_wallet->cs_wallet);
    1361             : 
    1362           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return false;
    1363             : 
    1364             :     // Denominated input is always a single one, so we can check its amount directly and return early
    1365           0 :     if (!fTryDenominated && tallyItem.outpoints.size() == 1 && CoinJoin::IsDenominatedAmount(tallyItem.nAmount)) {
    1366           0 :         return false;
    1367             :     }
    1368             : 
    1369             :     // Skip single inputs that can be used as collaterals already
    1370           0 :     if (tallyItem.outpoints.size() == 1 && CoinJoin::IsCollateralAmount(tallyItem.nAmount)) {
    1371           0 :         return false;
    1372             :     }
    1373             : 
    1374           0 :     CTransactionBuilder txBuilder(*m_wallet, tallyItem);
    1375             : 
    1376           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- Start %s\n", __func__, txBuilder.ToString());
    1377             : 
    1378             :     // Skip way too tiny amounts. Smallest we want is minimum collateral amount in a one output tx
    1379           0 :     if (!txBuilder.CouldAddOutput(CoinJoin::GetCollateralAmount())) {
    1380           0 :         return false;
    1381             :     }
    1382             : 
    1383           0 :     int nCase{0}; // Just for debug logs
    1384           0 :     if (txBuilder.CouldAddOutputs({CoinJoin::GetMaxCollateralAmount(), CoinJoin::GetCollateralAmount()})) {
    1385           0 :         nCase = 1;
    1386             :         // <case1>, see TransactionRecord::decomposeTransaction
    1387             :         // Out1 == CoinJoin::GetMaxCollateralAmount()
    1388             :         // Out2 >= CoinJoin::GetCollateralAmount()
    1389             : 
    1390           0 :         txBuilder.AddOutput(CoinJoin::GetMaxCollateralAmount());
    1391             :         // Note, here we first add a zero amount output to get the remainder after all fees and then assign it
    1392           0 :         CTransactionBuilderOutput* out = txBuilder.AddOutput();
    1393           0 :         CAmount nAmountLeft = txBuilder.GetAmountLeft();
    1394             :         // If remainder is denominated add one duff to the fee
    1395           0 :         out->UpdateAmount(CoinJoin::IsDenominatedAmount(nAmountLeft) ? nAmountLeft - 1 : nAmountLeft);
    1396             : 
    1397           0 :     } else if (txBuilder.CouldAddOutputs({CoinJoin::GetCollateralAmount(), CoinJoin::GetCollateralAmount()})) {
    1398           0 :         nCase = 2;
    1399             :         // <case2>, see TransactionRecord::decomposeTransaction
    1400             :         // Out1 CoinJoin::IsCollateralAmount()
    1401             :         // Out2 CoinJoin::IsCollateralAmount()
    1402             : 
    1403             :         // First add two outputs to get the available value after all fees
    1404           0 :         CTransactionBuilderOutput* out1 = txBuilder.AddOutput();
    1405           0 :         CTransactionBuilderOutput* out2 = txBuilder.AddOutput();
    1406             : 
    1407             :         // Create two equal outputs from the available value. This adds one duff to the fee if txBuilder.GetAmountLeft() is odd.
    1408           0 :         CAmount nAmountOutputs = txBuilder.GetAmountLeft() / 2;
    1409             : 
    1410           0 :         assert(CoinJoin::IsCollateralAmount(nAmountOutputs));
    1411             : 
    1412           0 :         out1->UpdateAmount(nAmountOutputs);
    1413           0 :         out2->UpdateAmount(nAmountOutputs);
    1414             : 
    1415           0 :     } else { // still at least possible to add one CoinJoin::GetCollateralAmount() output
    1416           0 :         nCase = 3;
    1417             :         // <case3>, see TransactionRecord::decomposeTransaction
    1418             :         // Out1 CoinJoin::IsCollateralAmount()
    1419             :         // Out2 Skipped
    1420           0 :         CTransactionBuilderOutput* out = txBuilder.AddOutput();
    1421           0 :         out->UpdateAmount(txBuilder.GetAmountLeft());
    1422             : 
    1423           0 :         assert(CoinJoin::IsCollateralAmount(out->GetAmount()));
    1424             :     }
    1425             : 
    1426           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- Done with case %d: %s\n", __func__, nCase, txBuilder.ToString());
    1427             : 
    1428           0 :     assert(txBuilder.IsDust(txBuilder.GetAmountLeft()));
    1429             : 
    1430           0 :     bilingual_str strResult;
    1431           0 :     if (!txBuilder.Commit(strResult)) {
    1432           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- Commit failed: %s\n", __func__, strResult.original);
    1433           0 :         return false;
    1434             :     }
    1435             : 
    1436           0 :     m_clientman.UpdatedSuccessBlock();
    1437             : 
    1438           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- txid: %s\n", __func__, strResult.original);
    1439             : 
    1440           0 :     return true;
    1441           0 : }
    1442             : 
    1443           0 : bool CCoinJoinClientSession::CreateCollateralTransaction(CMutableTransaction& txCollateral, std::string& strReason)
    1444             : {
    1445           0 :     AssertLockHeld(m_wallet->cs_wallet);
    1446             : 
    1447           0 :     CCoinControl coin_control(CoinType::ONLY_COINJOIN_COLLATERAL);
    1448           0 :     std::vector<COutput> vCoins{AvailableCoinsListUnspent(*m_wallet, &coin_control).all()};
    1449           0 :     if (vCoins.empty()) {
    1450           0 :         strReason = strprintf("%s requires a collateral transaction and could not locate an acceptable input!", gCoinJoinName);
    1451           0 :         return false;
    1452             :     }
    1453             : 
    1454           0 :     const auto& output = vCoins.at(GetRand(vCoins.size()));
    1455           0 :     const CTxOut txout = output.txout;
    1456             : 
    1457           0 :     txCollateral.vin.clear();
    1458           0 :     txCollateral.vin.emplace_back(output.outpoint.hash, output.outpoint.n);
    1459           0 :     txCollateral.vout.clear();
    1460             : 
    1461             :     // pay collateral charge in fees
    1462             :     // NOTE: no need for protobump patch here,
    1463             :     // CoinJoin::IsCollateralAmount in GetCollateralTxDSIn should already take care of this
    1464           0 :     if (txout.nValue >= CoinJoin::GetCollateralAmount() * 2) {
    1465             :         // make our change address
    1466           0 :         CScript scriptChange;
    1467           0 :         ReserveDestination reserveDest(m_wallet.get());
    1468           0 :         auto dest_opt = reserveDest.GetReservedDestination(true);
    1469           0 :         assert(dest_opt); // should never fail, as we just unlocked
    1470           0 :         scriptChange = GetScriptForDestination(*dest_opt);
    1471           0 :         reserveDest.KeepDestination();
    1472             :         // return change
    1473           0 :         txCollateral.vout.emplace_back(txout.nValue - CoinJoin::GetCollateralAmount(), scriptChange);
    1474           0 :     } else { // txout.nValue < CoinJoin::GetCollateralAmount() * 2
    1475             :         // create dummy data output only and pay everything as a fee
    1476           0 :         txCollateral.vout.emplace_back(0, CScript() << OP_RETURN);
    1477             :     }
    1478             : 
    1479           0 :     if (!m_wallet->SignTransaction(txCollateral)) {
    1480           0 :         strReason = "Unable to sign collateral transaction!";
    1481           0 :         return false;
    1482             :     }
    1483             : 
    1484           0 :     return true;
    1485           0 : }
    1486             : 
    1487             : // Create denominations by looping through inputs grouped by addresses
    1488           0 : bool CCoinJoinClientSession::CreateDenominated(CAmount nBalanceToDenominate)
    1489             : {
    1490           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return false;
    1491             : 
    1492           0 :     LOCK(m_wallet->cs_wallet);
    1493             : 
    1494             :     // NOTE: We do not allow txes larger than 100 kB, so we have to limit number of inputs here.
    1495             :     // We still want to consume a lot of inputs to avoid creating only smaller denoms though.
    1496             :     // Knowing that each CTxIn is at least 148 B big, 400 inputs should take 400 x ~148 B = ~60 kB.
    1497             :     // This still leaves more than enough room for another data of typical CreateDenominated tx.
    1498           0 :     std::vector<CompactTallyItem> vecTally = m_wallet->SelectCoinsGroupedByAddresses(true, true, true, 400);
    1499           0 :     if (vecTally.empty()) {
    1500           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::CreateDenominated -- SelectCoinsGroupedByAddresses can't find any inputs!\n");
    1501           0 :         return false;
    1502             :     }
    1503             : 
    1504             :     // Start from the largest balances first to speed things up by creating txes with larger/largest denoms included
    1505           0 :     std::sort(vecTally.begin(), vecTally.end(), [](const CompactTallyItem& a, const CompactTallyItem& b) {
    1506           0 :         return a.nAmount > b.nAmount;
    1507             :     });
    1508             : 
    1509           0 :     bool fCreateMixingCollaterals = !m_wallet->HasCollateralInputs();
    1510             : 
    1511           0 :     for (const auto& item : vecTally) {
    1512           0 :         if (!CreateDenominated(nBalanceToDenominate, item, fCreateMixingCollaterals)) continue;
    1513           0 :         return true;
    1514             :     }
    1515             : 
    1516           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::CreateDenominated -- failed!\n");
    1517           0 :     return false;
    1518           0 : }
    1519             : 
    1520             : // Create denominations
    1521           0 : bool CCoinJoinClientSession::CreateDenominated(CAmount nBalanceToDenominate, const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals)
    1522             : {
    1523           0 :     AssertLockHeld(m_wallet->cs_wallet);
    1524             : 
    1525           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return false;
    1526             : 
    1527             :     // denominated input is always a single one, so we can check its amount directly and return early
    1528           0 :     if (tallyItem.outpoints.size() == 1 && CoinJoin::IsDenominatedAmount(tallyItem.nAmount)) {
    1529           0 :         return false;
    1530             :     }
    1531             : 
    1532           0 :     CTransactionBuilder txBuilder(*m_wallet, tallyItem);
    1533             : 
    1534           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- Start %s\n", __func__, txBuilder.ToString());
    1535             : 
    1536             :     // ****** Add an output for mixing collaterals ************ /
    1537             : 
    1538           0 :     if (fCreateMixingCollaterals && !txBuilder.AddOutput(CoinJoin::GetMaxCollateralAmount())) {
    1539           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- Failed to add collateral output\n", __func__);
    1540           0 :         return false;
    1541             :     }
    1542             : 
    1543             :     // ****** Add outputs for denoms ************ /
    1544             : 
    1545           0 :     bool fAddFinal = true;
    1546           0 :     auto denoms = CoinJoin::GetStandardDenominations();
    1547             : 
    1548           0 :     std::map<CAmount, int> mapDenomCount;
    1549           0 :     for (auto nDenomValue : denoms) {
    1550           0 :         mapDenomCount.insert(std::pair<CAmount, int>(nDenomValue, m_wallet->CountInputsWithAmount(nDenomValue)));
    1551             :     }
    1552             : 
    1553             :     // Will generate outputs for the createdenoms up to coinjoinmaxdenoms per denom
    1554             : 
    1555             :     // This works in the way creating PS denoms has traditionally worked, assuming enough funds,
    1556             :     // it will start with the smallest denom then create 11 of those, then go up to the next biggest denom create 11
    1557             :     // and repeat. Previously, once the largest denom was reached, as many would be created were created as possible and
    1558             :     // then any remaining was put into a change address and denominations were created in the same manner a block later.
    1559             :     // Now, in this system, so long as we don't reach COINJOIN_DENOM_OUTPUTS_THRESHOLD outputs the process repeats in
    1560             :     // the same transaction, creating up to nCoinJoinDenomsHardCap per denomination in a single transaction.
    1561             : 
    1562           0 :     while (txBuilder.CouldAddOutput(CoinJoin::GetSmallestDenomination()) && txBuilder.CountOutputs() < COINJOIN_DENOM_OUTPUTS_THRESHOLD) {
    1563           0 :         for (auto it = denoms.rbegin(); it != denoms.rend(); ++it) {
    1564           0 :             CAmount nDenomValue = *it;
    1565           0 :             auto currentDenomIt = mapDenomCount.find(nDenomValue);
    1566             : 
    1567           0 :             int nOutputs = 0;
    1568             : 
    1569           0 :             const auto& strFunc = __func__;
    1570           0 :             auto needMoreOutputs = [&]() {
    1571           0 :                 if (txBuilder.CouldAddOutput(nDenomValue)) {
    1572           0 :                     if (fAddFinal && nBalanceToDenominate > 0 && nBalanceToDenominate < nDenomValue) {
    1573           0 :                         fAddFinal = false; // add final denom only once, only the smalest possible one
    1574           0 :                         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- 1 - FINAL - nDenomValue: %f, nBalanceToDenominate: %f, nOutputs: %d, %s\n",
    1575             :                                                      strFunc, (float) nDenomValue / COIN, (float) nBalanceToDenominate / COIN, nOutputs, txBuilder.ToString());
    1576           0 :                         return true;
    1577           0 :                     } else if (nBalanceToDenominate >= nDenomValue) {
    1578           0 :                         return true;
    1579             :                     }
    1580           0 :                 }
    1581           0 :                 return false;
    1582           0 :             };
    1583             : 
    1584             :             // add each output up to 11 times or until it can't be added again or until we reach nCoinJoinDenomsGoal
    1585           0 :             while (needMoreOutputs() && nOutputs <= 10 && currentDenomIt->second < CCoinJoinClientOptions::GetDenomsGoal()) {
    1586             :                 // Add output and subtract denomination amount
    1587           0 :                 if (txBuilder.AddOutput(nDenomValue)) {
    1588           0 :                     ++nOutputs;
    1589           0 :                     ++currentDenomIt->second;
    1590           0 :                     nBalanceToDenominate -= nDenomValue;
    1591           0 :                     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- 1 - nDenomValue: %f, nBalanceToDenominate: %f, nOutputs: %d, %s\n",
    1592             :                                                  __func__, (float) nDenomValue / COIN, (float) nBalanceToDenominate / COIN, nOutputs, txBuilder.ToString());
    1593           0 :                 } else {
    1594           0 :                     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- 1 - Error: AddOutput failed for nDenomValue: %f, nBalanceToDenominate: %f, nOutputs: %d, %s\n",
    1595             :                                                  __func__, (float) nDenomValue / COIN, (float) nBalanceToDenominate / COIN, nOutputs, txBuilder.ToString());
    1596           0 :                     return false;
    1597             :                 }
    1598             : 
    1599             :             }
    1600             : 
    1601           0 :             if (txBuilder.GetAmountLeft() == 0 || nBalanceToDenominate <= 0) break;
    1602           0 :         }
    1603             : 
    1604           0 :         bool finished = true;
    1605           0 :         for (const auto& [denom, count] : mapDenomCount) {
    1606             :             // Check if this specific denom could use another loop, check that there aren't nCoinJoinDenomsGoal of this
    1607             :             // denom and that our nValueLeft/nBalanceToDenominate is enough to create one of these denoms, if so, loop again.
    1608           0 :             if (count < CCoinJoinClientOptions::GetDenomsGoal() && txBuilder.CouldAddOutput(denom) && nBalanceToDenominate > 0) {
    1609           0 :                 finished = false;
    1610           0 :                 WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- 1 - NOT finished - nDenomValue: %f, count: %d, nBalanceToDenominate: %f, %s\n",
    1611             :                                              __func__, (float) denom / COIN, count, (float) nBalanceToDenominate / COIN, txBuilder.ToString());
    1612           0 :                 break;
    1613             :             }
    1614           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- 1 - FINISHED - nDenomValue: %f, count: %d, nBalanceToDenominate: %f, %s\n",
    1615             :                                          __func__, (float) denom / COIN, count, (float) nBalanceToDenominate / COIN, txBuilder.ToString());
    1616             :         }
    1617             : 
    1618           0 :         if (finished) break;
    1619             :     }
    1620             : 
    1621             :     // Now that nCoinJoinDenomsGoal worth of each denom have been created or the max number of denoms given the value of the input, do something with the remainder.
    1622           0 :     if (txBuilder.CouldAddOutput(CoinJoin::GetSmallestDenomination()) && nBalanceToDenominate >= CoinJoin::GetSmallestDenomination() && txBuilder.CountOutputs() < COINJOIN_DENOM_OUTPUTS_THRESHOLD) {
    1623           0 :         CAmount nLargestDenomValue = denoms.front();
    1624             : 
    1625           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- 2 - Process remainder: %s\n", __func__, txBuilder.ToString());
    1626             : 
    1627           0 :         auto countPossibleOutputs = [&](CAmount nAmount) -> int {
    1628           0 :             std::vector<CAmount> vecOutputs;
    1629           0 :             while (true) {
    1630             :                 // Create a potential output
    1631           0 :                 vecOutputs.push_back(nAmount);
    1632           0 :                 if (!txBuilder.CouldAddOutputs(vecOutputs) || txBuilder.CountOutputs() + vecOutputs.size() > COINJOIN_DENOM_OUTPUTS_THRESHOLD) {
    1633             :                     // If it's not possible to add it due to insufficient amount left or total number of outputs exceeds
    1634             :                     // COINJOIN_DENOM_OUTPUTS_THRESHOLD drop the output again and stop trying.
    1635           0 :                     vecOutputs.pop_back();
    1636           0 :                     break;
    1637             :                 }
    1638             :             }
    1639           0 :             return static_cast<int>(vecOutputs.size());
    1640           0 :         };
    1641             : 
    1642             :         // Go big to small
    1643           0 :         for (auto nDenomValue : denoms) {
    1644           0 :             if (nBalanceToDenominate <= 0) break;
    1645           0 :             int nOutputs = 0;
    1646             : 
    1647             :             // Number of denoms we can create given our denom and the amount of funds we have left
    1648           0 :             int denomsToCreateValue = countPossibleOutputs(nDenomValue);
    1649             :             // Prefer overshooting the target balance by larger denoms (hence `+1`) instead of a more
    1650             :             // accurate approximation by many smaller denoms. This is ok because when we get here we
    1651             :             // should have nCoinJoinDenomsGoal of each smaller denom already. Also, without `+1`
    1652             :             // we can end up in a situation when there is already nCoinJoinDenomsHardCap of smaller
    1653             :             // denoms, yet we can't mix the remaining nBalanceToDenominate because it's smaller than
    1654             :             // nDenomValue (and thus denomsToCreateBal == 0), so the target would never get reached
    1655             :             // even when there is enough funds for that.
    1656           0 :             int denomsToCreateBal = (nBalanceToDenominate / nDenomValue) + 1;
    1657             :             // Use the smaller value
    1658           0 :             int denomsToCreate = denomsToCreateValue > denomsToCreateBal ? denomsToCreateBal : denomsToCreateValue;
    1659           0 :             WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- 2 - nBalanceToDenominate: %f, nDenomValue: %f, denomsToCreateValue: %d, denomsToCreateBal: %d\n",
    1660             :                                          __func__, (float) nBalanceToDenominate / COIN, (float) nDenomValue / COIN, denomsToCreateValue, denomsToCreateBal);
    1661           0 :             auto it = mapDenomCount.find(nDenomValue);
    1662           0 :             for (const auto i : util::irange(denomsToCreate)) {
    1663             :                 // Never go above the cap unless it's the largest denom
    1664           0 :                 if (nDenomValue != nLargestDenomValue && it->second >= CCoinJoinClientOptions::GetDenomsHardCap()) break;
    1665             : 
    1666             :                 // Increment helpers, add output and subtract denomination amount
    1667           0 :                 if (txBuilder.AddOutput(nDenomValue)) {
    1668           0 :                     nOutputs++;
    1669           0 :                     it->second++;
    1670           0 :                     nBalanceToDenominate -= nDenomValue;
    1671           0 :                 } else {
    1672           0 :                     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- 2 - Error: AddOutput failed at %d/%d, %s\n", __func__, i + 1, denomsToCreate, txBuilder.ToString());
    1673           0 :                     break;
    1674             :                 }
    1675           0 :                 WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- 2 - nDenomValue: %f, nBalanceToDenominate: %f, nOutputs: %d, %s\n",
    1676             :                                              __func__, (float) nDenomValue / COIN, (float) nBalanceToDenominate / COIN, nOutputs, txBuilder.ToString());
    1677           0 :                 if (txBuilder.CountOutputs() >= COINJOIN_DENOM_OUTPUTS_THRESHOLD) break;
    1678             :             }
    1679           0 :             if (txBuilder.CountOutputs() >= COINJOIN_DENOM_OUTPUTS_THRESHOLD) break;
    1680             :         }
    1681           0 :     }
    1682             : 
    1683           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- 3 - nBalanceToDenominate: %f, %s\n", __func__, (float) nBalanceToDenominate / COIN, txBuilder.ToString());
    1684             : 
    1685           0 :     for (const auto& [denom, count] : mapDenomCount) {
    1686           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- 3 - DONE - nDenomValue: %f, count: %d\n", __func__, (float) denom / COIN, count);
    1687             :     }
    1688             : 
    1689             :     // No reasons to create mixing collaterals if we can't create denoms to mix
    1690           0 :     if ((fCreateMixingCollaterals && txBuilder.CountOutputs() == 1) || txBuilder.CountOutputs() == 0) {
    1691           0 :         return false;
    1692             :     }
    1693             : 
    1694           0 :     bilingual_str strResult;
    1695           0 :     if (!txBuilder.Commit(strResult)) {
    1696           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- Commit failed: %s\n", __func__, strResult.original);
    1697           0 :         return false;
    1698             :     }
    1699             : 
    1700             :     // use the same nCachedLastSuccessBlock as for DS mixing to prevent race
    1701           0 :     m_clientman.UpdatedSuccessBlock();
    1702             : 
    1703           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- txid: %s\n", __func__, strResult.original);
    1704             : 
    1705           0 :     return true;
    1706           0 : }
    1707             : 
    1708           0 : void CCoinJoinClientSession::RelayIn(const CCoinJoinEntry& entry, CConnman& connman) const
    1709             : {
    1710           0 :     if (!mixingMasternode) return;
    1711             : 
    1712           0 :     connman.ForNode(mixingMasternode->pdmnState->netInfo->GetPrimary(), [&entry, &connman, this](CNode* pnode) {
    1713           0 :         WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::RelayIn -- found master, relaying message to %s\n",
    1714             :                          pnode->addr.ToStringAddrPort());
    1715           0 :         CNetMsgMaker msgMaker(pnode->GetCommonVersion());
    1716           0 :         connman.PushMessage(pnode, msgMaker.Make(NetMsgType::DSVIN, entry));
    1717           0 :         return true;
    1718           0 :     });
    1719           0 : }
    1720             : 
    1721           0 : void CCoinJoinClientSession::SetState(PoolState nStateNew)
    1722             : {
    1723           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::SetState -- nState: %d, nStateNew: %d\n", nState.load(), nStateNew);
    1724           0 :     nState = nStateNew;
    1725           0 : }
    1726             : 
    1727           0 : void CCoinJoinClientManager::UpdatedBlockTip(const CBlockIndex* pindex)
    1728             : {
    1729           0 :     nCachedBlockHeight = pindex->nHeight;
    1730           0 :     WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::UpdatedBlockTip -- nCachedBlockHeight: %d\n", nCachedBlockHeight);
    1731           0 : }
    1732             : 
    1733           0 : void CCoinJoinClientManager::DoMaintenance(ChainstateManager& chainman, CConnman& connman, const CTxMemPool& mempool)
    1734             : {
    1735           0 :     if (!CCoinJoinClientOptions::IsEnabled()) return;
    1736             : 
    1737           0 :     if (!m_mn_sync.IsBlockchainSynced() || ShutdownRequested()) return;
    1738             : 
    1739             :     static int nTick = 0;
    1740           0 :     static int nDoAutoNextRun = nTick + COINJOIN_AUTO_TIMEOUT_MIN;
    1741             : 
    1742           0 :     nTick++;
    1743           0 :     CheckTimeout();
    1744           0 :     ProcessPendingDsaRequest(connman);
    1745           0 :     if (nDoAutoNextRun == nTick) {
    1746           0 :         DoAutomaticDenominating(chainman, connman, mempool);
    1747           0 :         nDoAutoNextRun = nTick + COINJOIN_AUTO_TIMEOUT_MIN + GetRand<int>(/*nMax=*/COINJOIN_AUTO_TIMEOUT_MAX - COINJOIN_AUTO_TIMEOUT_MIN);
    1748           0 :     }
    1749           0 : }
    1750             : 
    1751           0 : void CCoinJoinClientSession::GetJsonInfo(UniValue& obj) const
    1752             : {
    1753           0 :     assert(obj.isObject());
    1754           0 :     if (mixingMasternode != nullptr) {
    1755           0 :         assert(mixingMasternode->pdmnState);
    1756           0 :         obj.pushKV("protxhash", mixingMasternode->proTxHash.ToString());
    1757           0 :         obj.pushKV("outpoint", mixingMasternode->collateralOutpoint.ToStringShort());
    1758           0 :         if (m_wallet->chain().rpcEnableDeprecated("service")) {
    1759           0 :             obj.pushKV("service", mixingMasternode->pdmnState->netInfo->GetPrimary().ToStringAddrPort());
    1760           0 :         }
    1761           0 :         obj.pushKV("addrs_core_p2p", mixingMasternode->pdmnState->netInfo->ToJson(NetInfoPurpose::CORE_P2P));
    1762           0 :     }
    1763           0 :     obj.pushKV("denomination", ValueFromAmount(CoinJoin::DenominationToAmount(nSessionDenom)));
    1764           0 :     obj.pushKV("state", GetStateString());
    1765           0 :     obj.pushKV("entries_count", GetEntriesCount());
    1766           0 : }
    1767             : 
    1768           0 : void CCoinJoinClientManager::GetJsonInfo(UniValue& obj) const
    1769             : {
    1770           0 :     assert(obj.isObject());
    1771           0 :     obj.pushKV("running", IsMixing());
    1772             : 
    1773           0 :     UniValue arrSessions(UniValue::VARR);
    1774           0 :     AssertLockNotHeld(cs_deqsessions);
    1775           0 :     LOCK(cs_deqsessions);
    1776           0 :     for (const auto& session : deqSessions) {
    1777           0 :         if (session.GetState() != POOL_STATE_IDLE) {
    1778           0 :             UniValue objSession(UniValue::VOBJ);
    1779           0 :             session.GetJsonInfo(objSession);
    1780           0 :             arrSessions.push_back(objSession);
    1781           0 :         }
    1782             :     }
    1783           0 :     obj.pushKV("sessions", arrSessions);
    1784           0 : }
    1785             : 

Generated by: LCOV version 1.16