LCOV - code coverage report
Current view: top level - src/coinjoin - server.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 55 534 10.3 %
Date: 2026-06-25 07:23:43 Functions: 14 53 26.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/server.h>
       6             : 
       7             : #include <active/masternode.h>
       8             : #include <evo/deterministicmns.h>
       9             : #include <masternode/meta.h>
      10             : #include <masternode/sync.h>
      11             : 
      12             : #include <core_io.h>
      13             : #include <net.h>
      14             : #include <net_processing.h>
      15             : #include <netmessagemaker.h>
      16             : #include <scheduler.h>
      17             : #include <script/interpreter.h>
      18             : #include <shutdown.h>
      19             : #include <streams.h>
      20             : #include <txmempool.h>
      21             : #include <util/moneystr.h>
      22             : #include <util/system.h>
      23             : #include <validation.h>
      24             : 
      25             : #include <univalue.h>
      26             : 
      27             : #include <ranges>
      28             : 
      29        1320 : CCoinJoinServer::CCoinJoinServer(PeerManagerInternal* peer_manager, ChainstateManager& chainman, CConnman& _connman,
      30             :                                  CDeterministicMNManager& dmnman, CDSTXManager& dstxman, CMasternodeMetaMan& mn_metaman,
      31             :                                  CTxMemPool& mempool, const CActiveMasternodeManager& mn_activeman,
      32             :                                  const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman) :
      33         660 :     NetHandler(peer_manager),
      34         660 :     m_chainman{chainman},
      35         660 :     connman{_connman},
      36         660 :     m_dmnman{dmnman},
      37         660 :     m_dstxman{dstxman},
      38         660 :     m_mn_metaman{mn_metaman},
      39         660 :     mempool{mempool},
      40         660 :     m_mn_activeman{mn_activeman},
      41         660 :     m_mn_sync{mn_sync},
      42         660 :     m_isman{isman},
      43         660 :     vecSessionCollaterals{},
      44         660 :     fUnitTest{false}
      45        1320 : {
      46        1320 : }
      47             : 
      48        1980 : CCoinJoinServer::~CCoinJoinServer() = default;
      49             : 
      50       94137 : void CCoinJoinServer::ProcessMessage(CNode& peer, const std::string& msg_type, CDataStream& vRecv)
      51             : {
      52       94137 :     if (!m_mn_sync.IsBlockchainSynced()) return;
      53             : 
      54       94137 :     if (msg_type == NetMsgType::DSACCEPT) {
      55           0 :         ProcessDSACCEPT(peer, vRecv);
      56       94137 :     } else if (msg_type == NetMsgType::DSQUEUE) {
      57           0 :         ProcessDSQUEUE(peer.GetId(), vRecv);
      58       94137 :     } else if (msg_type == NetMsgType::DSVIN) {
      59           0 :         ProcessDSVIN(peer, vRecv);
      60       94137 :     } else if (msg_type == NetMsgType::DSSIGNFINALTX) {
      61           0 :         ProcessDSSIGNFINALTX(vRecv);
      62           0 :     }
      63       94137 : }
      64             : 
      65           0 : void CCoinJoinServer::ProcessDSACCEPT(CNode& peer, CDataStream& vRecv)
      66             : {
      67           0 :     assert(m_mn_metaman.IsValid());
      68             : 
      69           0 :     if (IsSessionReady()) {
      70             :         // too many users in this session already, reject new ones
      71           0 :         LogPrint(BCLog::COINJOIN, "DSACCEPT -- queue is already full!\n");
      72           0 :         PushStatus(peer, STATUS_REJECTED, ERR_QUEUE_FULL);
      73           0 :         return;
      74             :     }
      75             : 
      76           0 :     CCoinJoinAccept dsa;
      77           0 :     vRecv >> dsa;
      78             : 
      79           0 :     LogPrint(BCLog::COINJOIN, "DSACCEPT -- nDenom %d (%s)  txCollateral %s", dsa.nDenom, CoinJoin::DenominationToString(dsa.nDenom), dsa.txCollateral.ToString()); /* Continued */
      80             : 
      81           0 :     auto mnList = m_dmnman.GetListAtChainTip();
      82           0 :     auto dmn = mnList.GetValidMNByCollateral(m_mn_activeman.GetOutPoint());
      83           0 :     if (!dmn) {
      84           0 :         PushStatus(peer, STATUS_REJECTED, ERR_MN_LIST);
      85           0 :         return;
      86             :     }
      87             : 
      88           0 :     if (vecSessionCollaterals.empty()) {
      89             :         {
      90           0 :             const auto hasQueue = m_queueman.TryHasQueueFromMasternode(m_mn_activeman.GetOutPoint());
      91           0 :             if (!hasQueue.has_value()) return;
      92           0 :             if (*hasQueue) {
      93             :                 // refuse to create another queue this often
      94           0 :                 LogPrint(BCLog::COINJOIN, "DSACCEPT -- last dsq is still in queue, refuse to mix\n");
      95           0 :                 PushStatus(peer, STATUS_REJECTED, ERR_RECENT);
      96           0 :                 return;
      97             :             }
      98             :         }
      99             : 
     100           0 :         if (m_mn_metaman.IsMixingThresholdExceeded(dmn->proTxHash, mnList.GetCounts().enabled())) {
     101           0 :             if (fLogIPs) {
     102           0 :                 LogPrint(BCLog::COINJOIN, "DSACCEPT -- last dsq too recent, must wait: peer=%d, addr=%s\n",
     103             :                          peer.GetId(), peer.addr.ToStringAddrPort());
     104           0 :             } else {
     105           0 :                 LogPrint(BCLog::COINJOIN, "DSACCEPT -- last dsq too recent, must wait: peer=%d\n", peer.GetId());
     106             :             }
     107           0 :             PushStatus(peer, STATUS_REJECTED, ERR_RECENT);
     108           0 :             return;
     109             :         }
     110           0 :     }
     111             : 
     112           0 :     PoolMessage nMessageID = MSG_NOERR;
     113             : 
     114           0 :     bool fResult = nSessionID == 0 ? CreateNewSession(dsa, nMessageID)
     115           0 :             : AddUserToExistingSession(dsa, nMessageID);
     116           0 :     if (fResult) {
     117           0 :         LogPrint(BCLog::COINJOIN, "DSACCEPT -- is compatible, please submit!\n");
     118           0 :         PushStatus(peer, STATUS_ACCEPTED, nMessageID);
     119           0 :         return;
     120             :     } else {
     121           0 :         LogPrint(BCLog::COINJOIN, "DSACCEPT -- not compatible with existing transactions!\n");
     122           0 :         PushStatus(peer, STATUS_REJECTED, nMessageID);
     123           0 :         return;
     124             :     }
     125           0 : }
     126             : 
     127           0 : void CCoinJoinServer::ProcessDSQUEUE(NodeId from, CDataStream& vRecv)
     128             : {
     129           0 :     assert(m_mn_metaman.IsValid());
     130             : 
     131           0 :     CCoinJoinQueue dsq;
     132           0 :     vRecv >> dsq;
     133             : 
     134           0 :     WITH_LOCK(cs_main, m_peer_manager->PeerEraseObjectRequest(from, CInv{MSG_DSQ, dsq.GetHash()}));
     135             : 
     136             :     // Validate denomination first
     137           0 :     if (!CoinJoin::IsValidDenomination(dsq.nDenom)) {
     138           0 :         LogPrint(BCLog::COINJOIN, "DSQUEUE -- invalid denomination %d from peer %d\n", dsq.nDenom, from);
     139           0 :         m_peer_manager->PeerMisbehaving(from, 10);
     140           0 :         return;
     141             :     }
     142             : 
     143           0 :     if (dsq.masternodeOutpoint.IsNull() && dsq.m_protxHash.IsNull()) {
     144           0 :         m_peer_manager->PeerMisbehaving(from, 100);
     145           0 :         return;
     146             :     }
     147             : 
     148           0 :     const auto tip_mn_list = m_dmnman.GetListAtChainTip();
     149           0 :     if (dsq.masternodeOutpoint.IsNull()) {
     150           0 :         if (auto dmn = tip_mn_list.GetValidMN(dsq.m_protxHash)) {
     151           0 :             dsq.masternodeOutpoint = dmn->collateralOutpoint;
     152           0 :         } else {
     153           0 :             m_peer_manager->PeerMisbehaving(from, 10);
     154           0 :             return;
     155             :         }
     156           0 :     }
     157             : 
     158             :     {
     159           0 :         const auto isDup = m_queueman.TryCheckDuplicate(dsq);
     160           0 :         if (!isDup.has_value()) return;
     161           0 :         if (*isDup) {
     162           0 :             LogPrint(BCLog::COINJOIN, "DSQUEUE -- Peer %d is sending WAY too many dsq messages for a masternode with collateral %s\n", from, dsq.masternodeOutpoint.ToStringShort());
     163           0 :             return;
     164             :         }
     165             :     }
     166             : 
     167           0 :     LogPrint(BCLog::COINJOIN, "DSQUEUE -- %s new\n", dsq.ToString());
     168             : 
     169           0 :     if (dsq.IsTimeOutOfBounds()) return;
     170             : 
     171           0 :     auto dmn = tip_mn_list.GetValidMNByCollateral(dsq.masternodeOutpoint);
     172           0 :     if (!dmn) return;
     173             : 
     174           0 :     if (dsq.m_protxHash.IsNull()) {
     175           0 :         dsq.m_protxHash = dmn->proTxHash;
     176           0 :     }
     177             : 
     178           0 :     if (!dsq.CheckSignature(dmn->pdmnState->pubKeyOperator.Get())) {
     179           0 :         m_peer_manager->PeerMisbehaving(from, 10);
     180           0 :         return;
     181             :     }
     182             : 
     183           0 :     if (!dsq.fReady) {
     184             :         //don't allow a few nodes to dominate the queuing process
     185           0 :         if (m_mn_metaman.IsMixingThresholdExceeded(dmn->proTxHash, tip_mn_list.GetCounts().enabled())) {
     186           0 :             LogPrint(BCLog::COINJOIN, "DSQUEUE -- node sending too many dsq messages, masternode=%s\n", dmn->proTxHash.ToString());
     187           0 :             return;
     188             :         }
     189           0 :         m_mn_metaman.AllowMixing(dmn->proTxHash);
     190             : 
     191           0 :         LogPrint(BCLog::COINJOIN, "DSQUEUE -- new CoinJoin queue, masternode=%s, queue=%s\n", dmn->proTxHash.ToString(), dsq.ToString());
     192             : 
     193           0 :         if (!m_queueman.TryAddQueue(dsq)) return;
     194           0 :         m_peer_manager->PeerRelayDSQ(dsq);
     195           0 :     }
     196           0 : }
     197             : 
     198           0 : void CCoinJoinServer::ProcessDSVIN(CNode& peer, CDataStream& vRecv)
     199             : {
     200             :     //do we have enough users in the current session?
     201           0 :     if (!IsSessionReady()) {
     202           0 :         LogPrint(BCLog::COINJOIN, "DSVIN -- session not complete!\n");
     203           0 :         PushStatus(peer, STATUS_REJECTED, ERR_SESSION);
     204           0 :         return;
     205             :     }
     206             : 
     207           0 :     CCoinJoinEntry entry;
     208           0 :     vRecv >> entry;
     209             : 
     210           0 :     LogPrint(BCLog::COINJOIN, "DSVIN -- txCollateral %s", entry.txCollateral->ToString()); /* Continued */
     211             : 
     212           0 :     PoolMessage nMessageID = MSG_NOERR;
     213             : 
     214           0 :     entry.addr = peer.addr;
     215           0 :     if (AddEntry(entry, nMessageID)) {
     216           0 :         PushStatus(peer, STATUS_ACCEPTED, nMessageID);
     217           0 :         CheckPool();
     218           0 :         LOCK(cs_coinjoin);
     219           0 :         RelayStatus(STATUS_ACCEPTED);
     220           0 :     } else {
     221           0 :         PushStatus(peer, STATUS_REJECTED, nMessageID);
     222             :     }
     223           0 : }
     224             : 
     225           0 : void CCoinJoinServer::ProcessDSSIGNFINALTX(CDataStream& vRecv)
     226             : {
     227           0 :     std::vector<CTxIn> vecTxIn;
     228           0 :     vRecv >> vecTxIn;
     229             : 
     230           0 :     LogPrint(BCLog::COINJOIN, "DSSIGNFINALTX -- vecTxIn.size() %s\n", vecTxIn.size());
     231             : 
     232           0 :     int nTxInIndex = 0;
     233           0 :     int nTxInsCount = (int)vecTxIn.size();
     234             : 
     235           0 :     for (const auto& txin : vecTxIn) {
     236           0 :         nTxInIndex++;
     237           0 :         if (!AddScriptSig(txin)) {
     238           0 :             LogPrint(BCLog::COINJOIN, "DSSIGNFINALTX -- AddScriptSig() failed at %d/%d, session: %d\n", nTxInIndex, nTxInsCount, nSessionID);
     239           0 :             LOCK(cs_coinjoin);
     240           0 :             RelayStatus(STATUS_REJECTED);
     241             :             return;
     242           0 :         }
     243           0 :         LogPrint(BCLog::COINJOIN, "DSSIGNFINALTX -- AddScriptSig() %d/%d success\n", nTxInIndex, nTxInsCount);
     244             :     }
     245             :     // all is good
     246           0 :     CheckPool();
     247           0 : }
     248             : 
     249           0 : void CCoinJoinServer::SetNull()
     250             : {
     251           0 :     AssertLockHeld(cs_coinjoin);
     252             :     // MN side
     253           0 :     vecSessionCollaterals.clear();
     254             : 
     255           0 :     CCoinJoinBaseSession::SetNull();
     256           0 :     m_queueman.SetNull();
     257           0 : }
     258             : 
     259             : //
     260             : // Check the mixing progress and send client updates if a Masternode
     261             : //
     262       55208 : void CCoinJoinServer::CheckPool()
     263             : {
     264       55208 :     if (int entries = GetEntriesCount(); entries != 0)
     265           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CheckPool -- entries count %lu\n", entries);
     266             : 
     267             :     // If we have an entry for each collateral, then create final tx
     268       55208 :     if (nState == POOL_STATE_ACCEPTING_ENTRIES && size_t(GetEntriesCount()) == vecSessionCollaterals.size()) {
     269           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CheckPool -- FINALIZE TRANSACTIONS\n");
     270           0 :         CreateFinalTransaction();
     271           0 :         return;
     272             :     }
     273             : 
     274             :     // Check for Time Out
     275             :     // If we timed out while accepting entries, then if we have more than minimum, create final tx
     276       55208 :     if (nState == POOL_STATE_ACCEPTING_ENTRIES && CCoinJoinServer::HasTimedOut() &&
     277           0 :         GetEntriesCount() >= CoinJoin::GetMinPoolParticipants()) {
     278             :         // Punish misbehaving participants
     279           0 :         ChargeFees();
     280             :         // Try to complete this session ignoring the misbehaving ones
     281           0 :         CreateFinalTransaction();
     282           0 :         return;
     283             :     }
     284             : 
     285             :     // If we have all the signatures, try to compile the transaction
     286       55208 :     if (nState == POOL_STATE_SIGNING && IsSignaturesComplete()) {
     287           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CheckPool -- SIGNING\n");
     288           0 :         CommitFinalTransaction();
     289           0 :         return;
     290             :     }
     291       55208 : }
     292             : 
     293           0 : void CCoinJoinServer::CreateFinalTransaction()
     294             : {
     295           0 :     AssertLockNotHeld(cs_coinjoin);
     296           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CreateFinalTransaction -- FINALIZE TRANSACTIONS\n");
     297             : 
     298           0 :     LOCK(cs_coinjoin);
     299             : 
     300           0 :     CMutableTransaction txNew;
     301             : 
     302             :     // make our new transaction
     303           0 :     for (const auto& entry : vecEntries) {
     304           0 :         for (const auto& txout : entry.vecTxOut) {
     305           0 :             txNew.vout.push_back(txout);
     306             :         }
     307           0 :         for (const auto& txdsin : entry.vecTxDSIn) {
     308           0 :             txNew.vin.push_back(txdsin);
     309             :         }
     310             :     }
     311             : 
     312           0 :     sort(txNew.vin.begin(), txNew.vin.end(), CompareInputBIP69());
     313           0 :     sort(txNew.vout.begin(), txNew.vout.end(), CompareOutputBIP69());
     314             : 
     315           0 :     finalMutableTransaction = txNew;
     316           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CreateFinalTransaction -- finalMutableTransaction=%s", /* Continued */
     317             :              txNew.ToString());
     318             : 
     319             :     // request signatures from clients
     320           0 :     SetState(POOL_STATE_SIGNING);
     321           0 :     RelayFinalTransaction(CTransaction(finalMutableTransaction));
     322           0 : }
     323             : 
     324           0 : void CCoinJoinServer::CommitFinalTransaction()
     325             : {
     326           0 :     AssertLockNotHeld(cs_coinjoin);
     327             : 
     328           0 :     CTransactionRef finalTransaction = WITH_LOCK(cs_coinjoin, return MakeTransactionRef(finalMutableTransaction));
     329           0 :     uint256 hashTx = finalTransaction->GetHash();
     330             : 
     331           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CommitFinalTransaction -- finalTransaction=%s", /* Continued */
     332             :              finalTransaction->ToString());
     333             : 
     334             :     {
     335             :         // See if the transaction is valid
     336           0 :         TRY_LOCK(::cs_main, lockMain);
     337           0 :         mempool.PrioritiseTransaction(hashTx, 0.1 * COIN);
     338           0 :         if (!lockMain || !ATMPIfSaneFee(m_chainman, finalTransaction)) {
     339           0 :             LogPrint(BCLog::COINJOIN, /* Continued */
     340             :                      "CCoinJoinServer::CommitFinalTransaction -- ATMPIfSaneFee() error: Transaction not valid\n");
     341           0 :             WITH_LOCK(cs_coinjoin, SetNull());
     342             :             // not much we can do in this case, just notify clients
     343           0 :             RelayCompletedTransaction(ERR_INVALID_TX);
     344           0 :             return;
     345             :         }
     346           0 :     }
     347             : 
     348           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CommitFinalTransaction -- CREATING DSTX\n");
     349             : 
     350             :     // create and sign masternode dstx transaction
     351           0 :     if (!m_dstxman.GetDSTX(hashTx)) {
     352           0 :         CCoinJoinBroadcastTx dstxNew(finalTransaction, m_mn_activeman.GetOutPoint(), m_mn_activeman.GetProTxHash(),
     353           0 :                                      GetAdjustedTime());
     354           0 :         dstxNew.vchSig = m_mn_activeman.SignBasic(dstxNew.GetSignatureHash());
     355           0 :         m_dstxman.AddDSTX(dstxNew);
     356           0 :     }
     357             : 
     358           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CommitFinalTransaction -- TRANSMITTING DSTX\n");
     359             : 
     360           0 :     CInv inv(MSG_DSTX, hashTx);
     361           0 :     m_peer_manager->PeerRelayInv(inv);
     362             : 
     363             :     // Tell the clients it was successful
     364           0 :     RelayCompletedTransaction(MSG_SUCCESS);
     365             : 
     366             :     // Randomly charge clients
     367           0 :     ChargeRandomFees();
     368             : 
     369             :     // Reset
     370           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CommitFinalTransaction -- COMPLETED -- RESETTING\n");
     371           0 :     WITH_LOCK(cs_coinjoin, SetNull());
     372           0 : }
     373             : 
     374             : //
     375             : // Charge clients a fee if they're abusive
     376             : //
     377             : // Why bother? CoinJoin uses collateral to ensure abuse to the process is kept to a minimum.
     378             : // The submission and signing stages are completely separate. In the cases where
     379             : // a client submits a transaction then refused to sign, there must be a cost. Otherwise, they
     380             : // would be able to do this over and over again and bring the mixing to a halt.
     381             : //
     382             : // How does this work? Messages to Masternodes come in via NetMsgType::DSVIN, these require a valid collateral
     383             : // transaction for the client to be able to enter the pool. This transaction is kept by the Masternode
     384             : // until the transaction is either complete or fails.
     385             : //
     386           0 : void CCoinJoinServer::ChargeFees() const
     387             : {
     388           0 :     AssertLockNotHeld(cs_coinjoin);
     389             : 
     390             :     //we don't need to charge collateral for every offence.
     391           0 :     if (GetRand<int>(/*nMax=*/100) > 33) return;
     392             : 
     393           0 :     std::vector<CTransactionRef> vecOffendersCollaterals;
     394             : 
     395           0 :     if (nState == POOL_STATE_ACCEPTING_ENTRIES) {
     396           0 :         LOCK(cs_coinjoin);
     397           0 :         for (const auto& txCollateral : vecSessionCollaterals) {
     398           0 :             bool fFound = std::ranges::any_of(vecEntries, [&txCollateral](const auto& entry) {
     399           0 :                 return *entry.txCollateral == *txCollateral;
     400             :             });
     401             : 
     402             :             // This queue entry didn't send us the promised transaction
     403           0 :             if (!fFound) {
     404           0 :                 LogPrint(BCLog::COINJOIN, /* Continued */
     405             :                          "CCoinJoinServer::ChargeFees -- found uncooperative node (didn't send transaction), found "
     406             :                          "offence\n");
     407           0 :                 vecOffendersCollaterals.push_back(txCollateral);
     408           0 :             }
     409             :         }
     410           0 :     }
     411             : 
     412           0 :     if (nState == POOL_STATE_SIGNING) {
     413             :         // who didn't sign?
     414           0 :         LOCK(cs_coinjoin);
     415           0 :         for (const auto& entry : vecEntries) {
     416           0 :             for (const auto& txdsin : entry.vecTxDSIn) {
     417           0 :                 if (!txdsin.fHasSig) {
     418           0 :                     LogPrint(BCLog::COINJOIN, /* Continued */
     419             :                              "CCoinJoinServer::ChargeFees -- found uncooperative node (didn't sign), found offence\n");
     420           0 :                     vecOffendersCollaterals.push_back(entry.txCollateral);
     421           0 :                 }
     422             :             }
     423             :         }
     424           0 :     }
     425             : 
     426             :     // no offences found
     427           0 :     if (vecOffendersCollaterals.empty()) return;
     428             : 
     429             :     //mostly offending? Charge sometimes
     430           0 :     if (vecOffendersCollaterals.size() >= vecSessionCollaterals.size() - 1 && GetRand<int>(/*nMax=*/100) > 33) return;
     431             : 
     432             :     //everyone is an offender? That's not right
     433           0 :     if (vecOffendersCollaterals.size() >= vecSessionCollaterals.size()) return;
     434             : 
     435             :     //charge one of the offenders randomly
     436           0 :     Shuffle(vecOffendersCollaterals.begin(), vecOffendersCollaterals.end(), FastRandomContext());
     437             : 
     438           0 :     if (nState == POOL_STATE_ACCEPTING_ENTRIES || nState == POOL_STATE_SIGNING) {
     439           0 :         LogPrint(BCLog::COINJOIN, /* Continued */
     440             :                  "CCoinJoinServer::ChargeFees -- found uncooperative node (didn't %s transaction), charging fees: %s",
     441             :                  (nState == POOL_STATE_SIGNING) ? "sign" : "send", vecOffendersCollaterals[0]->ToString());
     442           0 :         ConsumeCollateral(vecOffendersCollaterals[0]);
     443           0 :     }
     444           0 : }
     445             : 
     446             : /*
     447             :     Charge the collateral randomly.
     448             :     Mixing is completely free, to pay miners we randomly pay the collateral of users.
     449             : 
     450             :     Collateral Fee Charges:
     451             : 
     452             :     Being that mixing has "no fees" we need to have some kind of cost associated
     453             :     with using it to stop abuse. Otherwise, it could serve as an attack vector and
     454             :     allow endless transaction that would bloat Dash and make it unusable. To
     455             :     stop these kinds of attacks 1 in 10 successful transactions are charged. This
     456             :     adds up to a cost of 0.001DRK per transaction on average.
     457             : */
     458           0 : void CCoinJoinServer::ChargeRandomFees() const
     459             : {
     460           0 :     for (const auto& txCollateral : vecSessionCollaterals) {
     461           0 :         if (GetRand<int>(/*nMax=*/100) > 10) return;
     462           0 :         LogPrint(BCLog::COINJOIN, /* Continued */
     463             :                  "CCoinJoinServer::ChargeRandomFees -- charging random fees, txCollateral=%s", txCollateral->ToString());
     464           0 :         ConsumeCollateral(txCollateral);
     465             :     }
     466           0 : }
     467             : 
     468           0 : void CCoinJoinServer::ConsumeCollateral(const CTransactionRef& txref) const
     469             : {
     470           0 :     LOCK(::cs_main);
     471           0 :     if (!ATMPIfSaneFee(m_chainman, txref)) {
     472           0 :         LogPrint(BCLog::COINJOIN, "%s -- ATMPIfSaneFee failed\n", __func__);
     473           0 :     } else {
     474           0 :         m_peer_manager->PeerRelayTransaction(txref->GetHash());
     475           0 :         LogPrint(BCLog::COINJOIN, "%s -- Collateral was consumed\n", __func__);
     476             :     }
     477           0 : }
     478             : 
     479       55208 : bool CCoinJoinServer::HasTimedOut() const
     480             : {
     481       55208 :     if (nState == POOL_STATE_IDLE) return false;
     482             : 
     483           0 :     int nTimeout = (nState == POOL_STATE_SIGNING) ? COINJOIN_SIGNING_TIMEOUT : COINJOIN_QUEUE_TIMEOUT;
     484             : 
     485           0 :     return GetTime() - nTimeLastSuccessfulStep >= nTimeout;
     486       55208 : }
     487             : 
     488             : //
     489             : // Check for extraneous timeout
     490             : //
     491       55208 : void CCoinJoinServer::CheckTimeout()
     492             : {
     493       55208 :     m_queueman.CheckQueue();
     494             : 
     495             :     // Too early to do anything
     496       55208 :     if (!CCoinJoinServer::HasTimedOut()) return;
     497             : 
     498           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CheckTimeout -- %s timed out -- resetting\n",
     499             :         (nState == POOL_STATE_SIGNING) ? "Signing" : "Session");
     500           0 :     ChargeFees();
     501           0 :     WITH_LOCK(cs_coinjoin, SetNull());
     502       55208 : }
     503             : 
     504             : /*
     505             :     Check to see if we're ready for submissions from clients
     506             :     After receiving multiple dsa messages, the queue will switch to "accepting entries"
     507             :     which is the active state right before merging the transaction
     508             : */
     509       55208 : void CCoinJoinServer::CheckForCompleteQueue()
     510             : {
     511       55208 :     if (nState == POOL_STATE_QUEUE && IsSessionReady()) {
     512           0 :         SetState(POOL_STATE_ACCEPTING_ENTRIES);
     513             : 
     514           0 :         CCoinJoinQueue dsq(nSessionDenom, m_mn_activeman.GetOutPoint(), m_mn_activeman.GetProTxHash(),
     515           0 :                            GetAdjustedTime(), true);
     516           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CheckForCompleteQueue -- queue is ready, signing and relaying (%s) " /* Continued */
     517             :                                      "with %d participants\n", dsq.ToString(), vecSessionCollaterals.size());
     518           0 :         dsq.vchSig = m_mn_activeman.SignBasic(dsq.GetSignatureHash());
     519           0 :         m_peer_manager->PeerRelayDSQ(dsq);
     520           0 :         m_queueman.AddQueue(std::move(dsq));
     521           0 :     }
     522       55208 : }
     523             : 
     524             : // Check to make sure a given input matches an input in the pool and its scriptSig is valid
     525           0 : bool CCoinJoinServer::IsInputScriptSigValid(const CTxIn& txin) const
     526             : {
     527           0 :     AssertLockHeld(cs_coinjoin);
     528           0 :     CMutableTransaction txNew;
     529           0 :     txNew.vin.clear();
     530           0 :     txNew.vout.clear();
     531             : 
     532           0 :     int nTxInIndex = -1;
     533           0 :     CScript sigPubKey = CScript();
     534             : 
     535             :     {
     536           0 :         int i = 0;
     537           0 :         for (const auto &entry: vecEntries) {
     538           0 :             for (const auto &txout: entry.vecTxOut) {
     539           0 :                 txNew.vout.push_back(txout);
     540             :             }
     541           0 :             for (const auto &txdsin: entry.vecTxDSIn) {
     542           0 :                 txNew.vin.push_back(txdsin);
     543             : 
     544           0 :                 if (txdsin.prevout == txin.prevout) {
     545           0 :                     nTxInIndex = i;
     546           0 :                     sigPubKey = txdsin.prevPubKey;
     547           0 :                 }
     548           0 :                 i++;
     549             :             }
     550             :         }
     551             :     }
     552           0 :     if (nTxInIndex >= 0) { //might have to do this one input at a time?
     553           0 :         txNew.vin[nTxInIndex].scriptSig = txin.scriptSig;
     554           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::IsInputScriptSigValid -- verifying scriptSig %s\n", ScriptToAsmStr(txin.scriptSig).substr(0, 24));
     555             :         // TODO we're using amount=0 here but we should use the correct amount. This works because Dash ignores the amount while signing/verifying (only used in Bitcoin/Segwit)
     556           0 :         if (!VerifyScript(txNew.vin[nTxInIndex].scriptSig, sigPubKey, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, MutableTransactionSignatureChecker(&txNew, nTxInIndex, 0, MissingDataBehavior::ASSERT_FAIL))) {
     557           0 :             LogPrint(BCLog::COINJOIN, "CCoinJoinServer::IsInputScriptSigValid -- VerifyScript() failed on input %d\n", nTxInIndex);
     558           0 :             return false;
     559             :         }
     560           0 :     } else {
     561           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::IsInputScriptSigValid -- Failed to find matching input in pool, %s\n", txin.ToString());
     562           0 :         return false;
     563             :     }
     564             : 
     565           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::IsInputScriptSigValid -- Successfully validated input and scriptSig\n");
     566           0 :     return true;
     567           0 : }
     568             : 
     569             : //
     570             : // Add a client's transaction inputs/outputs to the pool
     571             : //
     572           0 : bool CCoinJoinServer::AddEntry(const CCoinJoinEntry& entry, PoolMessage& nMessageIDRet)
     573             : {
     574           0 :     AssertLockNotHeld(cs_coinjoin);
     575             : 
     576           0 :     if (size_t(GetEntriesCount()) >= vecSessionCollaterals.size()) {
     577           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- ERROR: entries is full!\n", __func__);
     578           0 :         nMessageIDRet = ERR_ENTRIES_FULL;
     579           0 :         return false;
     580             :     }
     581             : 
     582           0 :     if (!CoinJoin::IsCollateralValid(m_chainman, m_isman, mempool, *entry.txCollateral)) {
     583           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- ERROR: collateral not valid!\n", __func__);
     584           0 :         nMessageIDRet = ERR_INVALID_COLLATERAL;
     585           0 :         return false;
     586             :     }
     587             : 
     588           0 :     if (entry.vecTxDSIn.size() > COINJOIN_ENTRY_MAX_SIZE) {
     589           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- ERROR: too many inputs! %d/%d\n", __func__, entry.vecTxDSIn.size(), COINJOIN_ENTRY_MAX_SIZE);
     590           0 :         nMessageIDRet = ERR_MAXIMUM;
     591           0 :         ConsumeCollateral(entry.txCollateral);
     592           0 :         return false;
     593             :     }
     594             : 
     595           0 :     std::vector<CTxIn> vin;
     596           0 :     for (const auto& txin : entry.vecTxDSIn) {
     597           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- txin=%s\n", __func__, txin.ToString());
     598           0 :         LOCK(cs_coinjoin);
     599           0 :         for (const auto& inner_entry : vecEntries) {
     600           0 :             if (std::ranges::any_of(inner_entry.vecTxDSIn,
     601           0 :                                     [&txin](const auto& txdsin) { return txdsin.prevout == txin.prevout; })) {
     602           0 :                 LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- ERROR: already have this txin in entries\n", __func__);
     603           0 :                 nMessageIDRet = ERR_ALREADY_HAVE;
     604             :                 // Two peers sent the same input? Can't really say who is the malicious one here,
     605             :                 // could be that someone is picking someone else's inputs randomly trying to force
     606             :                 // collateral consumption. Do not punish.
     607           0 :                 return false;
     608             :             }
     609             :         }
     610           0 :         vin.emplace_back(txin);
     611           0 :     }
     612             : 
     613           0 :     bool fConsumeCollateral{false};
     614           0 :     if (!IsValidInOuts(m_chainman.ActiveChainstate(), m_isman, mempool, vin, entry.vecTxOut, nMessageIDRet,
     615             :                        &fConsumeCollateral)) {
     616           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- ERROR! IsValidInOuts() failed: %s\n", __func__, CoinJoin::GetMessageByID(nMessageIDRet).translated);
     617           0 :         if (fConsumeCollateral) {
     618           0 :             ConsumeCollateral(entry.txCollateral);
     619           0 :         }
     620           0 :         return false;
     621             :     }
     622             : 
     623           0 :     WITH_LOCK(cs_coinjoin, vecEntries.push_back(entry));
     624             : 
     625           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- adding entry %d of %d required\n", __func__, GetEntriesCount(), CoinJoin::GetMaxPoolParticipants());
     626           0 :     nMessageIDRet = MSG_ENTRIES_ADDED;
     627             : 
     628           0 :     return true;
     629           0 : }
     630             : 
     631           0 : bool CCoinJoinServer::AddScriptSig(const CTxIn& txinNew)
     632             : {
     633           0 :     AssertLockNotHeld(cs_coinjoin);
     634           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::AddScriptSig -- scriptSig=%s\n", ScriptToAsmStr(txinNew.scriptSig).substr(0, 24));
     635             : 
     636           0 :     LOCK(cs_coinjoin);
     637           0 :     for (const auto& entry : vecEntries) {
     638           0 :         if (std::ranges::any_of(entry.vecTxDSIn,
     639           0 :                                 [&txinNew](const auto& txdsin) { return txdsin.scriptSig == txinNew.scriptSig; })) {
     640           0 :             LogPrint(BCLog::COINJOIN, "CCoinJoinServer::AddScriptSig -- already exists\n");
     641           0 :             return false;
     642             :         }
     643             :     }
     644             : 
     645           0 :     if (!IsInputScriptSigValid(txinNew)) {
     646           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::AddScriptSig -- Invalid scriptSig\n");
     647           0 :         return false;
     648             :     }
     649             : 
     650           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::AddScriptSig -- scriptSig=%s new\n", ScriptToAsmStr(txinNew.scriptSig).substr(0, 24));
     651             : 
     652           0 :     for (auto& txin : finalMutableTransaction.vin) {
     653           0 :         if (txin.prevout == txinNew.prevout && txin.nSequence == txinNew.nSequence) {
     654           0 :             txin.scriptSig = txinNew.scriptSig;
     655           0 :             LogPrint(BCLog::COINJOIN, "CCoinJoinServer::AddScriptSig -- adding to finalMutableTransaction, scriptSig=%s\n", ScriptToAsmStr(txinNew.scriptSig).substr(0, 24));
     656           0 :         }
     657             :     }
     658           0 :     for (auto& entry : vecEntries) {
     659           0 :         if (entry.AddScriptSig(txinNew)) {
     660           0 :             LogPrint(BCLog::COINJOIN, "CCoinJoinServer::AddScriptSig -- adding to entries, scriptSig=%s\n", ScriptToAsmStr(txinNew.scriptSig).substr(0, 24));
     661           0 :             return true;
     662             :         }
     663             :     }
     664             : 
     665           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::AddScriptSig -- Couldn't set sig!\n");
     666           0 :     return false;
     667           0 : }
     668             : 
     669             : // Check to make sure everything is signed
     670           0 : bool CCoinJoinServer::IsSignaturesComplete() const
     671             : {
     672           0 :     AssertLockNotHeld(cs_coinjoin);
     673           0 :     LOCK(cs_coinjoin);
     674             : 
     675           0 :     return std::ranges::all_of(vecEntries, [](const auto& entry) {
     676           0 :         return std::ranges::all_of(entry.vecTxDSIn, [](const auto& txdsin) { return txdsin.fHasSig; });
     677             :     });
     678           0 : }
     679             : 
     680           0 : bool CCoinJoinServer::IsAcceptableDSA(const CCoinJoinAccept& dsa, PoolMessage& nMessageIDRet) const
     681             : {
     682             :     // is denom even something legit?
     683           0 :     if (!CoinJoin::IsValidDenomination(dsa.nDenom)) {
     684           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- denom not valid!\n", __func__);
     685           0 :         nMessageIDRet = ERR_DENOM;
     686           0 :         return false;
     687             :     }
     688             : 
     689             :     // check collateral
     690           0 :     if (!fUnitTest && !CoinJoin::IsCollateralValid(m_chainman, m_isman, mempool, CTransaction(dsa.txCollateral))) {
     691           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- collateral not valid!\n", __func__);
     692           0 :         nMessageIDRet = ERR_INVALID_COLLATERAL;
     693           0 :         return false;
     694             :     }
     695             : 
     696           0 :     return true;
     697           0 : }
     698             : 
     699           0 : bool CCoinJoinServer::CreateNewSession(const CCoinJoinAccept& dsa, PoolMessage& nMessageIDRet)
     700             : {
     701           0 :     if (nSessionID != 0) return false;
     702             : 
     703             :     // new session can only be started in idle mode
     704           0 :     if (nState != POOL_STATE_IDLE) {
     705           0 :         nMessageIDRet = ERR_MODE;
     706           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CreateNewSession -- incompatible mode: nState=%d\n", nState);
     707           0 :         return false;
     708             :     }
     709             : 
     710           0 :     if (!IsAcceptableDSA(dsa, nMessageIDRet)) {
     711           0 :         return false;
     712             :     }
     713             : 
     714             :     // start new session
     715           0 :     nMessageIDRet = MSG_NOERR;
     716           0 :     nSessionID = GetRand<int>(/*nMax=*/999999) + 1;
     717           0 :     nSessionDenom = dsa.nDenom;
     718             : 
     719           0 :     SetState(POOL_STATE_QUEUE);
     720             : 
     721           0 :     if (!fUnitTest) {
     722             :         //broadcast that I'm accepting entries, only if it's the first entry through
     723           0 :         CCoinJoinQueue dsq(nSessionDenom, m_mn_activeman.GetOutPoint(), m_mn_activeman.GetProTxHash(),
     724           0 :                            GetAdjustedTime(), false);
     725           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CreateNewSession -- signing and relaying new queue: %s\n", dsq.ToString());
     726           0 :         dsq.vchSig = m_mn_activeman.SignBasic(dsq.GetSignatureHash());
     727           0 :         m_peer_manager->PeerRelayDSQ(dsq);
     728           0 :         m_queueman.AddQueue(std::move(dsq));
     729           0 :     }
     730             : 
     731           0 :     vecSessionCollaterals.push_back(MakeTransactionRef(dsa.txCollateral));
     732           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CreateNewSession -- new session created, nSessionID: %d  nSessionDenom: %d (%s)  vecSessionCollaterals.size(): %d  CoinJoin::GetMaxPoolParticipants(): %d\n",
     733             :         nSessionID, nSessionDenom, CoinJoin::DenominationToString(nSessionDenom), vecSessionCollaterals.size(), CoinJoin::GetMaxPoolParticipants());
     734             : 
     735           0 :     return true;
     736           0 : }
     737             : 
     738           0 : bool CCoinJoinServer::AddUserToExistingSession(const CCoinJoinAccept& dsa, PoolMessage& nMessageIDRet)
     739             : {
     740           0 :     if (nSessionID == 0 || IsSessionReady()) return false;
     741             : 
     742           0 :     if (!IsAcceptableDSA(dsa, nMessageIDRet)) {
     743           0 :         return false;
     744             :     }
     745             : 
     746             :     // we only add new users to an existing session when we are in queue mode
     747           0 :     if (nState != POOL_STATE_QUEUE) {
     748           0 :         nMessageIDRet = ERR_MODE;
     749           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::AddUserToExistingSession -- incompatible mode: nState=%d\n", nState);
     750           0 :         return false;
     751             :     }
     752             : 
     753           0 :     if (dsa.nDenom != nSessionDenom) {
     754           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::AddUserToExistingSession -- incompatible denom %d (%s) != nSessionDenom %d (%s)\n",
     755             :             dsa.nDenom, CoinJoin::DenominationToString(dsa.nDenom), nSessionDenom, CoinJoin::DenominationToString(nSessionDenom));
     756           0 :         nMessageIDRet = ERR_DENOM;
     757           0 :         return false;
     758             :     }
     759             : 
     760             :     // count new user as accepted to an existing session
     761             : 
     762           0 :     nMessageIDRet = MSG_NOERR;
     763           0 :     vecSessionCollaterals.push_back(MakeTransactionRef(dsa.txCollateral));
     764             : 
     765           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::AddUserToExistingSession -- new user accepted, nSessionID: %d  nSessionDenom: %d (%s)  vecSessionCollaterals.size(): %d  CoinJoin::GetMaxPoolParticipants(): %d\n",
     766             :         nSessionID, nSessionDenom, CoinJoin::DenominationToString(nSessionDenom), vecSessionCollaterals.size(), CoinJoin::GetMaxPoolParticipants());
     767             : 
     768           0 :     return true;
     769           0 : }
     770             : 
     771             : // Returns true if either max size has been reached or if the mix timed out and min size was reached
     772           0 : bool CCoinJoinServer::IsSessionReady() const
     773             : {
     774           0 :     if (nState == POOL_STATE_QUEUE) {
     775           0 :         if ((int)vecSessionCollaterals.size() >= CoinJoin::GetMaxPoolParticipants()) {
     776           0 :             return true;
     777             :         }
     778           0 :         if (CCoinJoinServer::HasTimedOut() && (int)vecSessionCollaterals.size() >= CoinJoin::GetMinPoolParticipants()) {
     779           0 :             return true;
     780             :         }
     781           0 :     }
     782           0 :     if (nState == POOL_STATE_ACCEPTING_ENTRIES) {
     783           0 :         return true;
     784             :     }
     785           0 :     return false;
     786           0 : }
     787             : 
     788           0 : void CCoinJoinServer::RelayFinalTransaction(const CTransaction& txFinal)
     789             : {
     790           0 :     AssertLockHeld(cs_coinjoin);
     791           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- nSessionID: %d  nSessionDenom: %d (%s)\n",
     792             :         __func__, nSessionID, nSessionDenom, CoinJoin::DenominationToString(nSessionDenom));
     793             : 
     794             :     // final mixing tx with empty signatures should be relayed to mixing participants only
     795           0 :     for (const auto& entry : vecEntries) {
     796           0 :         bool fOk = connman.ForNode(entry.addr, [&txFinal, this](CNode* pnode) {
     797           0 :             CNetMsgMaker msgMaker(pnode->GetCommonVersion());
     798           0 :             connman.PushMessage(pnode, msgMaker.Make(NetMsgType::DSFINALTX, nSessionID.load(), txFinal));
     799           0 :             return true;
     800           0 :         });
     801           0 :         if (!fOk) {
     802             :             // no such node? maybe this client disconnected or our own connection went down
     803           0 :             RelayStatus(STATUS_REJECTED);
     804           0 :             break;
     805             :         }
     806             :     }
     807           0 : }
     808             : 
     809           0 : void CCoinJoinServer::PushStatus(CNode& peer, PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID) const
     810             : {
     811           0 :     CCoinJoinStatusUpdate psssup(nSessionID, nState, 0, nStatusUpdate, nMessageID);
     812           0 :     connman.PushMessage(&peer, CNetMsgMaker(peer.GetCommonVersion()).Make(NetMsgType::DSSTATUSUPDATE, psssup));
     813           0 : }
     814             : 
     815           0 : void CCoinJoinServer::RelayStatus(PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID)
     816             : {
     817           0 :     AssertLockHeld(cs_coinjoin);
     818           0 :     unsigned int nDisconnected{};
     819             :     // status updates should be relayed to mixing participants only
     820           0 :     for (const auto& entry : vecEntries) {
     821             :         // make sure everyone is still connected
     822           0 :         bool fOk = connman.ForNode(entry.addr, [&nStatusUpdate, &nMessageID, this](CNode* pnode) {
     823           0 :             PushStatus(*pnode, nStatusUpdate, nMessageID);
     824           0 :             return true;
     825             :         });
     826           0 :         if (!fOk) {
     827             :             // no such node? maybe this client disconnected or our own connection went down
     828           0 :             ++nDisconnected;
     829           0 :         }
     830             :     }
     831           0 :     if (nDisconnected == 0) return; // all is clear
     832             : 
     833             :     // something went wrong
     834           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- can't continue, %llu client(s) disconnected, nSessionID: %d  nSessionDenom: %d (%s)\n",
     835             :         __func__, nDisconnected, nSessionID, nSessionDenom, CoinJoin::DenominationToString(nSessionDenom));
     836             : 
     837             :     // notify everyone else that this session should be terminated
     838           0 :     for (const auto& entry : vecEntries) {
     839           0 :         connman.ForNode(entry.addr, [this](CNode* pnode) {
     840           0 :             PushStatus(*pnode, STATUS_REJECTED, MSG_NOERR);
     841           0 :             return true;
     842             :         });
     843             :     }
     844             : 
     845           0 :     if (nDisconnected == vecEntries.size()) {
     846             :         // all clients disconnected, there is probably some issues with our own connection
     847             :         // do not charge any fees, just reset the pool
     848           0 :         SetNull();
     849           0 :     }
     850           0 : }
     851             : 
     852           0 : void CCoinJoinServer::RelayCompletedTransaction(PoolMessage nMessageID)
     853             : {
     854           0 :     AssertLockNotHeld(cs_coinjoin);
     855           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- nSessionID: %d  nSessionDenom: %d (%s)\n",
     856             :         __func__, nSessionID, nSessionDenom, CoinJoin::DenominationToString(nSessionDenom));
     857             : 
     858             :     // final mixing tx with empty signatures should be relayed to mixing participants only
     859           0 :     LOCK(cs_coinjoin);
     860           0 :     for (const auto& entry : vecEntries) {
     861           0 :         bool fOk = connman.ForNode(entry.addr, [&nMessageID, this](CNode* pnode) {
     862           0 :             CNetMsgMaker msgMaker(pnode->GetCommonVersion());
     863           0 :             connman.PushMessage(pnode, msgMaker.Make(NetMsgType::DSCOMPLETE, nSessionID.load(), nMessageID));
     864           0 :             return true;
     865           0 :         });
     866           0 :         if (!fOk) {
     867             :             // no such node? maybe client disconnected or our own connection went down
     868           0 :             RelayStatus(STATUS_REJECTED);
     869           0 :             break;
     870             :         }
     871             :     }
     872           0 : }
     873             : 
     874           0 : void CCoinJoinServer::SetState(PoolState nStateNew)
     875             : {
     876           0 :     if (nStateNew == POOL_STATE_ERROR) {
     877           0 :         LogPrint(BCLog::COINJOIN, "CCoinJoinServer::SetState -- Can't set state to ERROR as a Masternode. \n");
     878           0 :         return;
     879             :     }
     880             : 
     881           0 :     LogPrint(BCLog::COINJOIN, "CCoinJoinServer::SetState -- nState: %d, nStateNew: %d\n", nState, nStateNew);
     882           0 :     nTimeLastSuccessfulStep = GetTime();
     883           0 :     nState = nStateNew;
     884           0 : }
     885             : 
     886         660 : void CCoinJoinServer::Schedule(CScheduler& scheduler)
     887             : {
     888        1320 :     scheduler.scheduleEvery(
     889       57671 :         [this]() -> void {
     890       57011 :             if (!m_mn_sync.IsBlockchainSynced()) return;
     891       55363 :             if (ShutdownRequested()) return;
     892             : 
     893       55208 :             CheckForCompleteQueue();
     894       55208 :             CheckPool();
     895       55208 :             CheckTimeout();
     896       57011 :         },
     897         660 :         std::chrono::seconds{1});
     898         660 : }
     899             : 
     900           0 : void CCoinJoinServer::GetJsonInfo(UniValue& obj) const
     901             : {
     902           0 :     obj.clear();
     903           0 :     obj.setObject();
     904           0 :     obj.pushKV("queue_size",    m_queueman.GetQueueSize());
     905           0 :     obj.pushKV("denomination",  ValueFromAmount(CoinJoin::DenominationToAmount(nSessionDenom)));
     906           0 :     obj.pushKV("state",         GetStateString());
     907           0 :     obj.pushKV("entries_count", GetEntriesCount());
     908           0 : }
     909             : 
     910       33371 : bool CCoinJoinServer::AlreadyHave(const CInv& inv)
     911             : {
     912       33371 :     return (inv.type == MSG_DSQ) ? m_queueman.HasQueue(inv.hash) : false;
     913             : }
     914             : 
     915         814 : bool CCoinJoinServer::ProcessGetData(CNode& pfrom, const CInv& inv, CConnman& connman, const CNetMsgMaker& msgMaker)
     916             : {
     917         814 :     if (inv.type != MSG_DSQ) return false;
     918             : 
     919           0 :     auto opt_dsq = m_queueman.GetQueueFromHash(inv.hash);
     920           0 :     if (!opt_dsq.has_value()) return false;
     921             : 
     922           0 :     connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::DSQUEUE, *opt_dsq));
     923           0 :     return true;
     924         814 : }

Generated by: LCOV version 1.16