LCOV - code coverage report
Current view: top level - src/coinjoin - walletman.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 33 145 22.8 %
Date: 2026-06-25 07:23:51 Functions: 11 33 33.3 %

          Line data    Source code
       1             : // Copyright (c) 2023-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             : #if defined(HAVE_CONFIG_H)
       6             : #include <config/bitcoin-config.h>
       7             : #endif
       8             : 
       9             : #include <coinjoin/walletman.h>
      10             : #include <evo/deterministicmns.h>
      11             : #include <logging.h>
      12             : #include <masternode/meta.h>
      13             : #include <masternode/sync.h>
      14             : #include <msg_result.h>
      15             : #include <net.h>
      16             : #include <protocol.h>
      17             : #include <scheduler.h>
      18             : #include <shutdown.h>
      19             : #include <streams.h>
      20             : 
      21             : #ifdef ENABLE_WALLET
      22             : #include <coinjoin/client.h>
      23             : #endif // ENABLE_WALLET
      24             : 
      25             : #include <memory>
      26             : #include <ranges>
      27             : 
      28             : #ifdef ENABLE_WALLET
      29             : class CJWalletManagerImpl final : public CJWalletManager
      30             : {
      31             : public:
      32             :     CJWalletManagerImpl(ChainstateManager& chainman, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman,
      33             :                         CTxMemPool& mempool, const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman,
      34             :                         bool relay_txes);
      35             :     ~CJWalletManagerImpl() override;
      36             : 
      37             : public:
      38             :     void Schedule(CConnman& connman, CScheduler& scheduler) override;
      39             : 
      40             : public:
      41             :     bool hasQueue(const uint256& hash) const override;
      42             :     CCoinJoinClientManager* getClient(const std::string& name) override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map);
      43             :     MessageProcessingResult processMessage(CNode& peer, CChainState& chainstate, CConnman& connman, CTxMemPool& mempool,
      44             :                                            std::string_view msg_type, CDataStream& vRecv) override EXCLUSIVE_LOCKS_REQUIRED(!cs_ProcessDSQueue, !cs_wallet_manager_map);
      45             :     std::optional<CCoinJoinQueue> getQueueFromHash(const uint256& hash) const override;
      46             :     std::optional<int> getQueueSize() const override;
      47             :     std::vector<CDeterministicMNCPtr> getMixingMasternodes() override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map);
      48             :     void addWallet(const std::shared_ptr<wallet::CWallet>& wallet) override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map);
      49             :     void removeWallet(const std::string& name) override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map);
      50             :     void flushWallet(const std::string& name) override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map);
      51             : 
      52             : protected:
      53             :     // CValidationInterface
      54             :     void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map);
      55             : 
      56             : private:
      57             :     const bool m_relay_txes;
      58             :     ChainstateManager& m_chainman;
      59             :     CDeterministicMNManager& m_dmnman;
      60             :     CMasternodeMetaMan& m_mn_metaman;
      61             :     CTxMemPool& m_mempool;
      62             :     const CMasternodeSync& m_mn_sync;
      63             :     const llmq::CInstantSendManager& m_isman;
      64             : 
      65             :     // m_queueman is declared before the wallet map so that it is destroyed
      66             :     // after all CCoinJoinClientManager instances (which hold a raw pointer to it).
      67             :     // Null when relay_txes is false (no queue processing).
      68             :     const std::unique_ptr<CoinJoinQueueManager> m_queueman;
      69             : 
      70             :     mutable Mutex cs_ProcessDSQueue;
      71             : 
      72             :     mutable Mutex cs_wallet_manager_map;
      73             :     std::map<const std::string, std::unique_ptr<CCoinJoinClientManager>> m_wallet_manager_map GUARDED_BY(cs_wallet_manager_map);
      74             : 
      75             :     void DoMaintenance(CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map);
      76             : 
      77             :     [[nodiscard]] MessageProcessingResult ProcessDSQueue(NodeId from, CConnman& connman, std::string_view msg_type,
      78             :                                                          CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_ProcessDSQueue, !cs_wallet_manager_map);
      79             : 
      80             :     template <typename Callable>
      81           0 :     void ForEachCJClientMan(Callable&& func) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map)
      82             :     {
      83           0 :         LOCK(cs_wallet_manager_map);
      84           0 :         for (auto& [_, clientman] : m_wallet_manager_map) {
      85           0 :             func(*clientman);
      86             :         }
      87           0 :     }
      88             : 
      89             :     template <typename Callable>
      90           0 :     bool ForAnyCJClientMan(Callable&& func) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map)
      91             :     {
      92           0 :         LOCK(cs_wallet_manager_map);
      93           0 :         return std::ranges::any_of(m_wallet_manager_map, [&](auto& pair) { return func(*pair.second); });
      94           0 :     }
      95             : };
      96             : 
      97         534 : CJWalletManagerImpl::CJWalletManagerImpl(ChainstateManager& chainman, CDeterministicMNManager& dmnman,
      98             :                                          CMasternodeMetaMan& mn_metaman, CTxMemPool& mempool,
      99             :                                          const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman,
     100             :                                          bool relay_txes) :
     101         178 :     m_relay_txes{relay_txes},
     102         178 :     m_chainman{chainman},
     103         178 :     m_dmnman{dmnman},
     104         178 :     m_mn_metaman{mn_metaman},
     105         178 :     m_mempool{mempool},
     106         178 :     m_mn_sync{mn_sync},
     107         178 :     m_isman{isman},
     108         178 :     m_queueman{m_relay_txes ? std::make_unique<CoinJoinQueueManager>() : nullptr}
     109         356 : {
     110         356 : }
     111             : 
     112         534 : CJWalletManagerImpl::~CJWalletManagerImpl()
     113         356 : {
     114         178 :     LOCK(cs_wallet_manager_map);
     115         178 :     for (auto& [_, clientman] : m_wallet_manager_map) {
     116           0 :         clientman.reset();
     117             :     }
     118         534 : }
     119             : 
     120           0 : void CJWalletManagerImpl::Schedule(CConnman& connman, CScheduler& scheduler)
     121             : {
     122           0 :     if (!m_relay_txes) return;
     123           0 :     scheduler.scheduleEvery(std::bind(&CJWalletManagerImpl::DoMaintenance, this, std::ref(connman)),
     124           0 :                             std::chrono::seconds{1});
     125           0 : }
     126             : 
     127           0 : void CJWalletManagerImpl::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload)
     128             : {
     129           0 :     if (fInitialDownload || pindexNew == pindexFork) // In IBD or blocks were disconnected without any new ones
     130           0 :         return;
     131             : 
     132           0 :     ForEachCJClientMan([&pindexNew](CCoinJoinClientManager& clientman) { clientman.UpdatedBlockTip(pindexNew); });
     133           0 : }
     134             : 
     135           0 : bool CJWalletManagerImpl::hasQueue(const uint256& hash) const
     136             : {
     137           0 :     if (m_queueman) {
     138           0 :         return m_queueman->HasQueue(hash);
     139             :     }
     140           0 :     return false;
     141           0 : }
     142             : 
     143           1 : CCoinJoinClientManager* CJWalletManagerImpl::getClient(const std::string& name)
     144             : {
     145           1 :     LOCK(cs_wallet_manager_map);
     146           1 :     auto it = m_wallet_manager_map.find(name);
     147           1 :     return (it != m_wallet_manager_map.end()) ? it->second.get() : nullptr;
     148           1 : }
     149             : 
     150           0 : std::optional<CCoinJoinQueue> CJWalletManagerImpl::getQueueFromHash(const uint256& hash) const
     151             : {
     152           0 :     if (m_queueman) {
     153           0 :         return m_queueman->GetQueueFromHash(hash);
     154             :     }
     155           0 :     return std::nullopt;
     156           0 : }
     157             : 
     158           0 : std::optional<int> CJWalletManagerImpl::getQueueSize() const
     159             : {
     160           0 :     if (m_queueman) {
     161           0 :         return m_queueman->GetQueueSize();
     162             :     }
     163           0 :     return std::nullopt;
     164           0 : }
     165             : 
     166           0 : std::vector<CDeterministicMNCPtr> CJWalletManagerImpl::getMixingMasternodes()
     167             : {
     168           0 :     std::vector<CDeterministicMNCPtr> ret{};
     169           0 :     ForEachCJClientMan([&](const CCoinJoinClientManager& clientman) { clientman.GetMixingMasternodesInfo(ret); });
     170           0 :     return ret;
     171           0 : }
     172             : 
     173          15 : void CJWalletManagerImpl::addWallet(const std::shared_ptr<wallet::CWallet>& wallet)
     174             : {
     175          15 :     LOCK(cs_wallet_manager_map);
     176          30 :     m_wallet_manager_map.try_emplace(wallet->GetName(),
     177          30 :                                      std::make_unique<CCoinJoinClientManager>(wallet, m_dmnman, m_mn_metaman, m_mn_sync,
     178          15 :                                                                               m_isman, m_queueman.get()));
     179          15 : }
     180             : 
     181           0 : void CJWalletManagerImpl::flushWallet(const std::string& name)
     182             : {
     183           0 :     auto* clientman = Assert(getClient(name));
     184           0 :     clientman->ResetPool();
     185           0 :     clientman->StopMixing();
     186           0 : }
     187             : 
     188          11 : void CJWalletManagerImpl::removeWallet(const std::string& name)
     189             : {
     190          11 :     LOCK(cs_wallet_manager_map);
     191          11 :     m_wallet_manager_map.erase(name);
     192          11 : }
     193             : 
     194           0 : void CJWalletManagerImpl::DoMaintenance(CConnman& connman)
     195             : {
     196           0 :     if (m_queueman && m_mn_sync.IsBlockchainSynced() && !ShutdownRequested()) {
     197           0 :         m_queueman->CheckQueue();
     198           0 :     }
     199           0 :     LOCK(cs_wallet_manager_map);
     200           0 :     for (auto& [_, clientman] : m_wallet_manager_map) {
     201           0 :         clientman->DoMaintenance(m_chainman, connman, m_mempool);
     202             :     }
     203           0 : }
     204             : 
     205           0 : MessageProcessingResult CJWalletManagerImpl::processMessage(CNode& pfrom, CChainState& chainstate, CConnman& connman,
     206             :                                                             CTxMemPool& mempool, std::string_view msg_type,
     207             :                                                             CDataStream& vRecv)
     208             : {
     209           0 :     ForEachCJClientMan([&](CCoinJoinClientManager& clientman) {
     210           0 :         clientman.ProcessMessage(pfrom, chainstate, connman, mempool, msg_type, vRecv);
     211           0 :     });
     212           0 :     if (m_queueman) {
     213           0 :         return ProcessDSQueue(pfrom.GetId(), connman, msg_type, vRecv);
     214             :     }
     215           0 :     return {};
     216           0 : }
     217             : 
     218           0 : MessageProcessingResult CJWalletManagerImpl::ProcessDSQueue(NodeId from, CConnman& connman, std::string_view msg_type,
     219             :                                                             CDataStream& vRecv)
     220             : {
     221           0 :     assert(m_queueman);
     222             : 
     223           0 :     if (msg_type != NetMsgType::DSQUEUE) {
     224           0 :         return {};
     225             :     }
     226           0 :     if (!m_mn_sync.IsBlockchainSynced()) return {};
     227             : 
     228           0 :     assert(m_mn_metaman.IsValid());
     229             : 
     230           0 :     CCoinJoinQueue dsq;
     231           0 :     vRecv >> dsq;
     232             : 
     233           0 :     MessageProcessingResult ret{};
     234           0 :     ret.m_to_erase = CInv{MSG_DSQ, dsq.GetHash()};
     235             : 
     236           0 :     if (dsq.masternodeOutpoint.IsNull() && dsq.m_protxHash.IsNull()) {
     237           0 :         ret.m_error = MisbehavingError{100};
     238           0 :         return ret;
     239             :     }
     240             : 
     241           0 :     const auto tip_mn_list = m_dmnman.GetListAtChainTip();
     242           0 :     if (dsq.masternodeOutpoint.IsNull()) {
     243           0 :         if (auto dmn = tip_mn_list.GetValidMN(dsq.m_protxHash)) {
     244           0 :             dsq.masternodeOutpoint = dmn->collateralOutpoint;
     245           0 :         } else {
     246           0 :             ret.m_error = MisbehavingError{10};
     247           0 :             return ret;
     248             :         }
     249           0 :     }
     250             : 
     251             :     {
     252           0 :         LOCK(cs_ProcessDSQueue);
     253             : 
     254           0 :         if (m_queueman->HasQueue(dsq.GetHash())) return ret;
     255             : 
     256           0 :         if (m_queueman->HasQueueFromMasternode(dsq.masternodeOutpoint, dsq.fReady)) {
     257             :             // no way the same mn can send another dsq with the same readiness this soon
     258           0 :             LogPrint(BCLog::COINJOIN, /* Continued */
     259             :                      "DSQUEUE -- Peer %d is sending WAY too many dsq messages for a masternode with collateral %s\n",
     260             :                      from, dsq.masternodeOutpoint.ToStringShort());
     261           0 :             return ret;
     262             :         }
     263             : 
     264           0 :         LogPrint(BCLog::COINJOIN, "DSQUEUE -- %s new\n", dsq.ToString());
     265             : 
     266           0 :         if (dsq.IsTimeOutOfBounds()) return ret;
     267             : 
     268           0 :         auto dmn = tip_mn_list.GetValidMNByCollateral(dsq.masternodeOutpoint);
     269           0 :         if (!dmn) return ret;
     270             : 
     271           0 :         if (dsq.m_protxHash.IsNull()) {
     272           0 :             dsq.m_protxHash = dmn->proTxHash;
     273           0 :         }
     274             : 
     275           0 :         if (!dsq.CheckSignature(dmn->pdmnState->pubKeyOperator.Get())) {
     276           0 :             ret.m_error = MisbehavingError{10};
     277           0 :             return ret;
     278             :         }
     279             : 
     280             :         // if the queue is ready, submit if we can
     281           0 :         if (dsq.fReady && ForAnyCJClientMan([&connman, &dmn](CCoinJoinClientManager& clientman) {
     282           0 :                 return clientman.TrySubmitDenominate(dmn->proTxHash, connman);
     283             :             })) {
     284           0 :             LogPrint(BCLog::COINJOIN, "DSQUEUE -- CoinJoin queue is ready, masternode=%s, queue=%s\n",
     285             :                      dmn->proTxHash.ToString(), dsq.ToString());
     286           0 :             return ret;
     287             :         } else {
     288           0 :             if (m_mn_metaman.IsMixingThresholdExceeded(dmn->proTxHash, tip_mn_list.GetCounts().enabled())) {
     289           0 :                 LogPrint(BCLog::COINJOIN, "DSQUEUE -- Masternode %s is sending too many dsq messages\n",
     290             :                          dmn->proTxHash.ToString());
     291           0 :                 return ret;
     292             :             }
     293           0 :             m_mn_metaman.AllowMixing(dmn->proTxHash);
     294             : 
     295           0 :             LogPrint(BCLog::COINJOIN, "DSQUEUE -- new CoinJoin queue, masternode=%s, queue=%s\n",
     296             :                      dmn->proTxHash.ToString(), dsq.ToString());
     297             : 
     298           0 :             ForAnyCJClientMan(
     299           0 :                 [&dsq](CCoinJoinClientManager& clientman) { return clientman.MarkAlreadyJoinedQueueAsTried(dsq); });
     300             : 
     301           0 :             m_queueman->AddQueue(dsq);
     302             :         }
     303           0 :     } // cs_ProcessDSQueue
     304           0 :     return ret;
     305           0 : }
     306             : #endif // ENABLE_WALLET
     307             : 
     308         178 : std::unique_ptr<CJWalletManager> CJWalletManager::make(ChainstateManager& chainman, CDeterministicMNManager& dmnman,
     309             :                                                        CMasternodeMetaMan& mn_metaman, CTxMemPool& mempool,
     310             :                                                        const CMasternodeSync& mn_sync,
     311             :                                                        const llmq::CInstantSendManager& isman, bool relay_txes)
     312             : {
     313             : #ifdef ENABLE_WALLET
     314         178 :     return std::make_unique<CJWalletManagerImpl>(chainman, dmnman, mn_metaman, mempool, mn_sync, isman, relay_txes);
     315             : #else
     316             :     // Cannot be constructed if wallet support isn't built
     317             :     return nullptr;
     318             : #endif // ENABLE_WALLET
     319             : }

Generated by: LCOV version 1.16