From 689b4ec2223c7bb7c7a5c40543cafdb136f8ec37 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 16 Mar 2026 15:18:28 +0700 Subject: [PATCH 01/13] refactor: remove passing pointer to unique_ptr to more clear sequence of object initializtion in CJ --- src/coinjoin/client.cpp | 16 +++++++++------- src/coinjoin/client.h | 6 ++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index a1bd10b14a4a..100416079d8e 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -192,15 +192,13 @@ void CCoinJoinClientManager::ProcessMessage(CNode& peer, CChainState& active_cha CCoinJoinClientSession::CCoinJoinClientSession(const std::shared_ptr& wallet, CCoinJoinClientManager& clientman, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, - const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman, - const std::unique_ptr& queueman) : + const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman) : m_wallet(wallet), m_clientman(clientman), m_dmnman(dmnman), m_mn_metaman(mn_metaman), m_mn_sync(mn_sync), - m_isman{isman}, - m_queueman(queueman) + m_isman{isman} {} void CCoinJoinClientSession::ProcessMessage(CNode& peer, CChainState& active_chainstate, CConnman& connman, const CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv) @@ -1007,7 +1005,7 @@ bool CCoinJoinClientManager::DoAutomaticDenominating(ChainstateManager& chainman AssertLockNotHeld(cs_deqsessions); LOCK(cs_deqsessions); if (int(deqSessions.size()) < CCoinJoinClientOptions::GetSessions()) { - deqSessions.emplace_back(m_wallet, *this, m_dmnman, m_mn_metaman, m_mn_sync, m_isman, m_queueman); + deqSessions.emplace_back(m_wallet, *this, m_dmnman, m_mn_metaman, m_mn_sync, m_isman); } for (auto& session : deqSessions) { if (!CheckAutomaticBackup()) return false; @@ -1075,14 +1073,13 @@ static int WinnersToSkip() bool CCoinJoinClientSession::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman) { if (!CCoinJoinClientOptions::IsEnabled()) return false; - if (m_queueman == nullptr) return false; const auto mnList = m_dmnman.GetListAtChainTip(); const int nWeightedMnCount = mnList.GetCounts().m_valid_weighted; // Look through the queues and see if anything matches CCoinJoinQueue dsq; - while (m_queueman->GetQueueItemAndTry(dsq)) { + while (m_clientman.GetQueueItemAndTry(dsq)) { auto dmn = mnList.GetValidMNByCollateral(dsq.masternodeOutpoint); if (!dmn) { @@ -1288,6 +1285,11 @@ bool CCoinJoinClientManager::MarkAlreadyJoinedQueueAsTried(CCoinJoinQueue& dsq) return false; } +bool CCoinJoinClientManager::GetQueueItemAndTry(CCoinJoinQueue& dsq) const +{ + return m_queueman && m_queueman->GetQueueItemAndTry(dsq); +} + bool CCoinJoinClientSession::SubmitDenominate(CConnman& connman) { LOCK(m_wallet->cs_wallet); diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index 5ecf433010f1..e262e711310f 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -130,8 +130,6 @@ class CCoinJoinClientSession : public CCoinJoinBaseSession CMasternodeMetaMan& m_mn_metaman; const CMasternodeSync& m_mn_sync; const llmq::CInstantSendManager& m_isman; - const std::unique_ptr& m_queueman; - std::vector vecOutPointLocked; bilingual_str strLastMessage; @@ -185,8 +183,7 @@ class CCoinJoinClientSession : public CCoinJoinBaseSession public: explicit CCoinJoinClientSession(const std::shared_ptr& wallet, CCoinJoinClientManager& clientman, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, - const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman, - const std::unique_ptr& queueman); + const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman); void ProcessMessage(CNode& peer, CChainState& active_chainstate, CConnman& connman, const CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv); @@ -299,6 +296,7 @@ class CCoinJoinClientManager bool TrySubmitDenominate(const uint256& proTxHash, CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); bool MarkAlreadyJoinedQueueAsTried(CCoinJoinQueue& dsq) const EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); + bool GetQueueItemAndTry(CCoinJoinQueue& dsq) const; void CheckTimeout() EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); From e2d8247fd0e5232c4933f09a97d050bb57f389fc Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 16 Mar 2026 15:22:36 +0700 Subject: [PATCH 02/13] refactor: pass directly reference to CCoinJoinClientManager& instead unique_ptr inside ForEachCJClientMan --- src/coinjoin/client.cpp | 10 ++++------ src/coinjoin/client.h | 4 ++-- src/coinjoin/walletman.cpp | 8 ++++---- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index 100416079d8e..a626b8a12847 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -119,9 +119,8 @@ MessageProcessingResult CCoinJoinClientQueueManager::ProcessMessage(NodeId from, } // if the queue is ready, submit if we can - if (dsq.fReady && - m_walletman.ForAnyCJClientMan([&connman, &dmn](std::unique_ptr& clientman) { - return clientman->TrySubmitDenominate(dmn->proTxHash, connman); + if (dsq.fReady && m_walletman.ForAnyCJClientMan([&connman, &dmn](CCoinJoinClientManager& clientman) { + return clientman.TrySubmitDenominate(dmn->proTxHash, connman); })) { LogPrint(BCLog::COINJOIN, "DSQUEUE -- CoinJoin queue is ready, masternode=%s, queue=%s\n", dmn->proTxHash.ToString(), dsq.ToString()); return ret; @@ -135,9 +134,8 @@ MessageProcessingResult CCoinJoinClientQueueManager::ProcessMessage(NodeId from, LogPrint(BCLog::COINJOIN, "DSQUEUE -- new CoinJoin queue, masternode=%s, queue=%s\n", dmn->proTxHash.ToString(), dsq.ToString()); - m_walletman.ForAnyCJClientMan([&dsq](const std::unique_ptr& clientman) { - return clientman->MarkAlreadyJoinedQueueAsTried(dsq); - }); + m_walletman.ForAnyCJClientMan( + [&dsq](CCoinJoinClientManager& clientman) { return clientman.MarkAlreadyJoinedQueueAsTried(dsq); }); WITH_LOCK(cs_vecqueue, vecCoinJoinQueue.push_back(dsq)); } diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index e262e711310f..d52f1cc64a57 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -97,7 +97,7 @@ class CoinJoinWalletManager { { LOCK(cs_wallet_manager_map); for (auto&& [_, clientman] : m_wallet_manager_map) { - func(clientman); + func(*clientman); } }; @@ -105,7 +105,7 @@ class CoinJoinWalletManager { bool ForAnyCJClientMan(Callable&& func) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map) { LOCK(cs_wallet_manager_map); - return std::ranges::any_of(m_wallet_manager_map, [&](auto& pair) { return func(pair.second); }); + return std::ranges::any_of(m_wallet_manager_map, [&](auto& pair) { return func(*pair.second); }); }; private: diff --git a/src/coinjoin/walletman.cpp b/src/coinjoin/walletman.cpp index 50b552d766fb..e4088934deee 100644 --- a/src/coinjoin/walletman.cpp +++ b/src/coinjoin/walletman.cpp @@ -80,7 +80,7 @@ void CJWalletManagerImpl::UpdatedBlockTip(const CBlockIndex* pindexNew, const CB return; walletman.ForEachCJClientMan( - [&pindexNew](std::unique_ptr& clientman) { clientman->UpdatedBlockTip(pindexNew); }); + [&pindexNew](CCoinJoinClientManager& clientman) { clientman.UpdatedBlockTip(pindexNew); }); } bool CJWalletManagerImpl::hasQueue(const uint256& hash) const @@ -100,8 +100,8 @@ MessageProcessingResult CJWalletManagerImpl::processMessage(CNode& pfrom, CChain CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv) { - walletman.ForEachCJClientMan([&](std::unique_ptr& clientman) { - clientman->ProcessMessage(pfrom, chainstate, connman, mempool, msg_type, vRecv); + walletman.ForEachCJClientMan([&](CCoinJoinClientManager& clientman) { + clientman.ProcessMessage(pfrom, chainstate, connman, mempool, msg_type, vRecv); }); if (queueman) { return queueman->ProcessMessage(pfrom.GetId(), connman, msg_type, vRecv); @@ -129,7 +129,7 @@ std::vector CJWalletManagerImpl::getMixingMasternodes() { std::vector ret{}; walletman.ForEachCJClientMan( - [&](const std::unique_ptr& clientman) { clientman->GetMixingMasternodesInfo(ret); }); + [&](const CCoinJoinClientManager& clientman) { clientman.GetMixingMasternodesInfo(ret); }); return ret; } From 3b1c2da12cfe67a928e25b701954d443132a7c0a Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 17 Mar 2026 17:48:48 +0700 Subject: [PATCH 03/13] refactor: inline the middleman class CoinJoinWalletManager to CJWalletManagerImpl CJWalletManagerImpl is private implementation of CJWalletManager It helps to hide internal details of CJWalletManagerImpl which used public CCoinJoinClientManager It reduce amount of abstractions and complexity of implementation. --- src/coinjoin/client.cpp | 72 +++------------------ src/coinjoin/client.h | 69 +++++--------------- src/coinjoin/walletman.cpp | 125 ++++++++++++++++++++++++++++--------- 3 files changed, 121 insertions(+), 145 deletions(-) diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index a626b8a12847..4b21c2d6230c 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -40,9 +40,10 @@ using wallet::CoinType; using wallet::CWallet; using wallet::ReserveDestination; -CCoinJoinClientQueueManager::CCoinJoinClientQueueManager(CoinJoinWalletManager& walletman, CDeterministicMNManager& dmnman, - CMasternodeMetaMan& mn_metaman, const CMasternodeSync& mn_sync) : - m_walletman{walletman}, +CCoinJoinClientQueueManager::CCoinJoinClientQueueManager(CoinJoinQueueNotify& wallet_notify, + CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, + const CMasternodeSync& mn_sync) : + m_wallet_notify{wallet_notify}, m_dmnman{dmnman}, m_mn_metaman{mn_metaman}, m_mn_sync{mn_sync} @@ -119,9 +120,7 @@ MessageProcessingResult CCoinJoinClientQueueManager::ProcessMessage(NodeId from, } // if the queue is ready, submit if we can - if (dsq.fReady && m_walletman.ForAnyCJClientMan([&connman, &dmn](CCoinJoinClientManager& clientman) { - return clientman.TrySubmitDenominate(dmn->proTxHash, connman); - })) { + if (dsq.fReady && m_wallet_notify.TrySubmitDenominate(dmn->proTxHash, connman)) { LogPrint(BCLog::COINJOIN, "DSQUEUE -- CoinJoin queue is ready, masternode=%s, queue=%s\n", dmn->proTxHash.ToString(), dsq.ToString()); return ret; } else { @@ -134,8 +133,7 @@ MessageProcessingResult CCoinJoinClientQueueManager::ProcessMessage(NodeId from, LogPrint(BCLog::COINJOIN, "DSQUEUE -- new CoinJoin queue, masternode=%s, queue=%s\n", dmn->proTxHash.ToString(), dsq.ToString()); - m_walletman.ForAnyCJClientMan( - [&dsq](CCoinJoinClientManager& clientman) { return clientman.MarkAlreadyJoinedQueueAsTried(dsq); }); + m_wallet_notify.MarkAlreadyJoinedQueueAsTried(dsq); WITH_LOCK(cs_vecqueue, vecCoinJoinQueue.push_back(dsq)); } @@ -153,7 +151,7 @@ void CCoinJoinClientQueueManager::DoMaintenance() CCoinJoinClientManager::CCoinJoinClientManager(const std::shared_ptr& wallet, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman, - const std::unique_ptr& queueman) : + CCoinJoinClientQueueManager* queueman) : m_wallet{wallet}, m_dmnman{dmnman}, m_mn_metaman{mn_metaman}, @@ -1893,59 +1891,3 @@ void CCoinJoinClientManager::GetJsonInfo(UniValue& obj) const obj.pushKV("sessions", arrSessions); } -CoinJoinWalletManager::CoinJoinWalletManager(ChainstateManager& chainman, CDeterministicMNManager& dmnman, - CMasternodeMetaMan& mn_metaman, const CTxMemPool& mempool, - const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman, - const std::unique_ptr& queueman) : - m_chainman{chainman}, - m_dmnman{dmnman}, - m_mn_metaman{mn_metaman}, - m_mempool{mempool}, - m_mn_sync{mn_sync}, - m_isman{isman}, - m_queueman{queueman} -{ -} - -CoinJoinWalletManager::~CoinJoinWalletManager() -{ - LOCK(cs_wallet_manager_map); - for (auto& [wallet_name, cj_man] : m_wallet_manager_map) { - cj_man.reset(); - } -} - -void CoinJoinWalletManager::Add(const std::shared_ptr& wallet) -{ - LOCK(cs_wallet_manager_map); - m_wallet_manager_map.try_emplace(wallet->GetName(), - std::make_unique(wallet, m_dmnman, m_mn_metaman, m_mn_sync, - m_isman, m_queueman)); -} - -void CoinJoinWalletManager::DoMaintenance(CConnman& connman) -{ - LOCK(cs_wallet_manager_map); - for (auto& [_, clientman] : m_wallet_manager_map) { - clientman->DoMaintenance(m_chainman, connman, m_mempool); - } -} - -void CoinJoinWalletManager::Remove(const std::string& name) { - LOCK(cs_wallet_manager_map); - m_wallet_manager_map.erase(name); -} - -void CoinJoinWalletManager::Flush(const std::string& name) -{ - auto clientman = Assert(Get(name)); - clientman->ResetPool(); - clientman->StopMixing(); -} - -CCoinJoinClientManager* CoinJoinWalletManager::Get(const std::string& name) const -{ - LOCK(cs_wallet_manager_map); - auto it = m_wallet_manager_map.find(name); - return (it != m_wallet_manager_map.end()) ? it->second.get() : nullptr; -} diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index d52f1cc64a57..c374a626333b 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -28,7 +28,6 @@ class ChainstateManager; class CMasternodeMetaMan; class CMasternodeSync; class CNode; -class CoinJoinWalletManager; class CTxMemPool; class UniValue; @@ -70,55 +69,21 @@ class CPendingDsaRequest } }; -class CoinJoinWalletManager { -public: - using wallet_name_cjman_map = std::map>; - +/** Callback interface used by CCoinJoinClientQueueManager to dispatch queue + * events to per-wallet client managers without depending on a concrete + * wallet manager class. + */ +class CoinJoinQueueNotify +{ public: - CoinJoinWalletManager() = delete; - CoinJoinWalletManager(const CoinJoinWalletManager&) = delete; - CoinJoinWalletManager& operator=(const CoinJoinWalletManager&) = delete; - explicit CoinJoinWalletManager(ChainstateManager& chainman, CDeterministicMNManager& dmnman, - CMasternodeMetaMan& mn_metaman, const CTxMemPool& mempool, - const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman, - const std::unique_ptr& queueman); - ~CoinJoinWalletManager(); + virtual ~CoinJoinQueueNotify() = default; - void Add(const std::shared_ptr& wallet) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); - void DoMaintenance(CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); - - void Remove(const std::string& name) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); - void Flush(const std::string& name) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); - - CCoinJoinClientManager* Get(const std::string& name) const EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); - - template - void ForEachCJClientMan(Callable&& func) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map) - { - LOCK(cs_wallet_manager_map); - for (auto&& [_, clientman] : m_wallet_manager_map) { - func(*clientman); - } - }; - - template - bool ForAnyCJClientMan(Callable&& func) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map) - { - LOCK(cs_wallet_manager_map); - return std::ranges::any_of(m_wallet_manager_map, [&](auto& pair) { return func(*pair.second); }); - }; - -private: - ChainstateManager& m_chainman; - CDeterministicMNManager& m_dmnman; - CMasternodeMetaMan& m_mn_metaman; - const CTxMemPool& m_mempool; - const CMasternodeSync& m_mn_sync; - const llmq::CInstantSendManager& m_isman; - const std::unique_ptr& m_queueman; + //! A queue is ready: attempt to submit denominate to the given masternode + //! across all active wallets. Returns true if any wallet accepted. + virtual bool TrySubmitDenominate(const uint256& proTxHash, CConnman& connman) = 0; - mutable Mutex cs_wallet_manager_map; - wallet_name_cjman_map m_wallet_manager_map GUARDED_BY(cs_wallet_manager_map); + //! A new queue was announced: mark it as already-tried in all active wallets. + virtual void MarkAlreadyJoinedQueueAsTried(CCoinJoinQueue& dsq) = 0; }; class CCoinJoinClientSession : public CCoinJoinBaseSession @@ -214,7 +179,7 @@ class CCoinJoinClientSession : public CCoinJoinBaseSession class CCoinJoinClientQueueManager : public CCoinJoinBaseManager { private: - CoinJoinWalletManager& m_walletman; + CoinJoinQueueNotify& m_wallet_notify; CDeterministicMNManager& m_dmnman; CMasternodeMetaMan& m_mn_metaman; const CMasternodeSync& m_mn_sync; @@ -225,7 +190,7 @@ class CCoinJoinClientQueueManager : public CCoinJoinBaseManager CCoinJoinClientQueueManager() = delete; CCoinJoinClientQueueManager(const CCoinJoinClientQueueManager&) = delete; CCoinJoinClientQueueManager& operator=(const CCoinJoinClientQueueManager&) = delete; - explicit CCoinJoinClientQueueManager(CoinJoinWalletManager& walletman, CDeterministicMNManager& dmnman, + explicit CCoinJoinClientQueueManager(CoinJoinQueueNotify& wallet_notify, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, const CMasternodeSync& mn_sync); ~CCoinJoinClientQueueManager(); @@ -245,7 +210,8 @@ class CCoinJoinClientManager CMasternodeMetaMan& m_mn_metaman; const CMasternodeSync& m_mn_sync; const llmq::CInstantSendManager& m_isman; - const std::unique_ptr& m_queueman; + //! Non-owning pointer; null when relay_txes is disabled (no queue processing). + CCoinJoinClientQueueManager* const m_queueman; mutable Mutex cs_deqsessions; // TODO: or map ?? @@ -274,8 +240,7 @@ class CCoinJoinClientManager CCoinJoinClientManager& operator=(const CCoinJoinClientManager&) = delete; explicit CCoinJoinClientManager(const std::shared_ptr& wallet, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, const CMasternodeSync& mn_sync, - const llmq::CInstantSendManager& isman, - const std::unique_ptr& queueman); + const llmq::CInstantSendManager& isman, CCoinJoinClientQueueManager* queueman); ~CCoinJoinClientManager(); void ProcessMessage(CNode& peer, CChainState& active_chainstate, CConnman& connman, const CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); diff --git a/src/coinjoin/walletman.cpp b/src/coinjoin/walletman.cpp index e4088934deee..fa5059c6e239 100644 --- a/src/coinjoin/walletman.cpp +++ b/src/coinjoin/walletman.cpp @@ -7,27 +7,26 @@ #endif #include - +#include #include #include #include -#include - #ifdef ENABLE_WALLET #include #endif // ENABLE_WALLET #include +#include #ifdef ENABLE_WALLET -class CJWalletManagerImpl final : public CJWalletManager +class CJWalletManagerImpl final : public CJWalletManager, public CoinJoinQueueNotify { public: CJWalletManagerImpl(ChainstateManager& chainman, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, CTxMemPool& mempool, const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman, bool relay_txes); - virtual ~CJWalletManagerImpl() = default; + ~CJWalletManagerImpl() override; public: void Schedule(CConnman& connman, CScheduler& scheduler) override; @@ -44,15 +43,47 @@ class CJWalletManagerImpl final : public CJWalletManager void removeWallet(const std::string& name) override; void flushWallet(const std::string& name) override; + // CoinJoinQueueNotify + bool TrySubmitDenominate(const uint256& proTxHash, CConnman& connman) override; + void MarkAlreadyJoinedQueueAsTried(CCoinJoinQueue& dsq) override; + protected: // CValidationInterface void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override; private: const bool m_relay_txes; + ChainstateManager& m_chainman; + CDeterministicMNManager& m_dmnman; + CMasternodeMetaMan& m_mn_metaman; + CTxMemPool& m_mempool; + const CMasternodeSync& m_mn_sync; + const llmq::CInstantSendManager& m_isman; + + // queueman is declared before the wallet map so that it is destroyed + // after all CCoinJoinClientManager instances (which hold a raw pointer to it). + const std::unique_ptr m_queueman; + + mutable Mutex cs_wallet_manager_map; + std::map> m_wallet_manager_map GUARDED_BY(cs_wallet_manager_map); + + void DoMaintenance(CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); + + template + void ForEachCJClientMan(Callable&& func) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map) + { + LOCK(cs_wallet_manager_map); + for (auto& [_, clientman] : m_wallet_manager_map) { + func(*clientman); + } + } - CoinJoinWalletManager walletman; - const std::unique_ptr queueman; + template + bool ForAnyCJClientMan(Callable&& func) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map) + { + LOCK(cs_wallet_manager_map); + return std::ranges::any_of(m_wallet_manager_map, [&](auto& pair) { return func(*pair.second); }); + } }; CJWalletManagerImpl::CJWalletManagerImpl(ChainstateManager& chainman, CDeterministicMNManager& dmnman, @@ -60,17 +91,30 @@ CJWalletManagerImpl::CJWalletManagerImpl(ChainstateManager& chainman, CDetermini const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman, bool relay_txes) : m_relay_txes{relay_txes}, - walletman{chainman, dmnman, mn_metaman, mempool, mn_sync, isman, queueman}, - queueman{m_relay_txes ? std::make_unique(walletman, dmnman, mn_metaman, mn_sync) : nullptr} + m_chainman{chainman}, + m_dmnman{dmnman}, + m_mn_metaman{mn_metaman}, + m_mempool{mempool}, + m_mn_sync{mn_sync}, + m_isman{isman}, + m_queueman{m_relay_txes ? std::make_unique(*this, dmnman, mn_metaman, mn_sync) : nullptr} { } +CJWalletManagerImpl::~CJWalletManagerImpl() +{ + LOCK(cs_wallet_manager_map); + for (auto& [_, clientman] : m_wallet_manager_map) { + clientman.reset(); + } +} + void CJWalletManagerImpl::Schedule(CConnman& connman, CScheduler& scheduler) { if (!m_relay_txes) return; - scheduler.scheduleEvery(std::bind(&CCoinJoinClientQueueManager::DoMaintenance, std::ref(*queueman)), + scheduler.scheduleEvery(std::bind(&CCoinJoinClientQueueManager::DoMaintenance, std::ref(*m_queueman)), std::chrono::seconds{1}); - scheduler.scheduleEvery(std::bind(&CoinJoinWalletManager::DoMaintenance, std::ref(walletman), std::ref(connman)), + scheduler.scheduleEvery(std::bind(&CJWalletManagerImpl::DoMaintenance, this, std::ref(connman)), std::chrono::seconds{1}); } @@ -79,48 +123,49 @@ void CJWalletManagerImpl::UpdatedBlockTip(const CBlockIndex* pindexNew, const CB if (fInitialDownload || pindexNew == pindexFork) // In IBD or blocks were disconnected without any new ones return; - walletman.ForEachCJClientMan( - [&pindexNew](CCoinJoinClientManager& clientman) { clientman.UpdatedBlockTip(pindexNew); }); + ForEachCJClientMan([&pindexNew](CCoinJoinClientManager& clientman) { clientman.UpdatedBlockTip(pindexNew); }); } bool CJWalletManagerImpl::hasQueue(const uint256& hash) const { - if (queueman) { - return queueman->HasQueue(hash); + if (m_queueman) { + return m_queueman->HasQueue(hash); } return false; } CCoinJoinClientManager* CJWalletManagerImpl::getClient(const std::string& name) { - return walletman.Get(name); + LOCK(cs_wallet_manager_map); + auto it = m_wallet_manager_map.find(name); + return (it != m_wallet_manager_map.end()) ? it->second.get() : nullptr; } MessageProcessingResult CJWalletManagerImpl::processMessage(CNode& pfrom, CChainState& chainstate, CConnman& connman, CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv) { - walletman.ForEachCJClientMan([&](CCoinJoinClientManager& clientman) { + ForEachCJClientMan([&](CCoinJoinClientManager& clientman) { clientman.ProcessMessage(pfrom, chainstate, connman, mempool, msg_type, vRecv); }); - if (queueman) { - return queueman->ProcessMessage(pfrom.GetId(), connman, msg_type, vRecv); + if (m_queueman) { + return m_queueman->ProcessMessage(pfrom.GetId(), connman, msg_type, vRecv); } return {}; } std::optional CJWalletManagerImpl::getQueueFromHash(const uint256& hash) const { - if (queueman) { - return queueman->GetQueueFromHash(hash); + if (m_queueman) { + return m_queueman->GetQueueFromHash(hash); } return std::nullopt; } std::optional CJWalletManagerImpl::getQueueSize() const { - if (queueman) { - return queueman->GetQueueSize(); + if (m_queueman) { + return m_queueman->GetQueueSize(); } return std::nullopt; } @@ -128,24 +173,48 @@ std::optional CJWalletManagerImpl::getQueueSize() const std::vector CJWalletManagerImpl::getMixingMasternodes() { std::vector ret{}; - walletman.ForEachCJClientMan( - [&](const CCoinJoinClientManager& clientman) { clientman.GetMixingMasternodesInfo(ret); }); + ForEachCJClientMan([&](const CCoinJoinClientManager& clientman) { clientman.GetMixingMasternodesInfo(ret); }); return ret; } void CJWalletManagerImpl::addWallet(const std::shared_ptr& wallet) { - walletman.Add(wallet); + LOCK(cs_wallet_manager_map); + m_wallet_manager_map.try_emplace(wallet->GetName(), + std::make_unique(wallet, m_dmnman, m_mn_metaman, m_mn_sync, + m_isman, m_queueman.get())); } void CJWalletManagerImpl::flushWallet(const std::string& name) { - walletman.Flush(name); + auto* clientman = Assert(getClient(name)); + clientman->ResetPool(); + clientman->StopMixing(); } void CJWalletManagerImpl::removeWallet(const std::string& name) { - walletman.Remove(name); + LOCK(cs_wallet_manager_map); + m_wallet_manager_map.erase(name); +} + +void CJWalletManagerImpl::DoMaintenance(CConnman& connman) +{ + LOCK(cs_wallet_manager_map); + for (auto& [_, clientman] : m_wallet_manager_map) { + clientman->DoMaintenance(m_chainman, connman, m_mempool); + } +} + +bool CJWalletManagerImpl::TrySubmitDenominate(const uint256& proTxHash, CConnman& connman) +{ + return ForAnyCJClientMan( + [&](CCoinJoinClientManager& clientman) { return clientman.TrySubmitDenominate(proTxHash, connman); }); +} + +void CJWalletManagerImpl::MarkAlreadyJoinedQueueAsTried(CCoinJoinQueue& dsq) +{ + ForAnyCJClientMan([&](CCoinJoinClientManager& clientman) { return clientman.MarkAlreadyJoinedQueueAsTried(dsq); }); } #endif // ENABLE_WALLET From 72229ce2770cc4d70e2b930fffbd9c8ecb3c7165 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 17 Mar 2026 21:08:37 +0700 Subject: [PATCH 04/13] refactor: inline class CCoinJoinClientQueueManager to CJWalletManagerImpl It reduce amount of abstractions and levels of inheritance While CJWalletManagerImpl is already private --- src/coinjoin/client.cpp | 110 +------------------------ src/coinjoin/client.h | 53 +------------ src/coinjoin/coinjoin.h | 20 ++++- src/coinjoin/walletman.cpp | 159 ++++++++++++++++++++++++++++--------- 4 files changed, 146 insertions(+), 196 deletions(-) diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index 4b21c2d6230c..171eeeeb989a 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -40,118 +40,10 @@ using wallet::CoinType; using wallet::CWallet; using wallet::ReserveDestination; -CCoinJoinClientQueueManager::CCoinJoinClientQueueManager(CoinJoinQueueNotify& wallet_notify, - CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, - const CMasternodeSync& mn_sync) : - m_wallet_notify{wallet_notify}, - m_dmnman{dmnman}, - m_mn_metaman{mn_metaman}, - m_mn_sync{mn_sync} -{ -} - -CCoinJoinClientQueueManager::~CCoinJoinClientQueueManager() = default; - -MessageProcessingResult CCoinJoinClientQueueManager::ProcessMessage(NodeId from, CConnman& connman, - std::string_view msg_type, CDataStream& vRecv) -{ - if (msg_type != NetMsgType::DSQUEUE) { - return {}; - } - if (!m_mn_sync.IsBlockchainSynced()) return {}; - - assert(m_mn_metaman.IsValid()); - - CCoinJoinQueue dsq; - vRecv >> dsq; - - MessageProcessingResult ret{}; - ret.m_to_erase = CInv{MSG_DSQ, dsq.GetHash()}; - - if (dsq.masternodeOutpoint.IsNull() && dsq.m_protxHash.IsNull()) { - ret.m_error = MisbehavingError{100}; - return ret; - } - - const auto tip_mn_list = m_dmnman.GetListAtChainTip(); - if (dsq.masternodeOutpoint.IsNull()) { - if (auto dmn = tip_mn_list.GetValidMN(dsq.m_protxHash)) { - dsq.masternodeOutpoint = dmn->collateralOutpoint; - } else { - ret.m_error = MisbehavingError{10}; - return ret; - } - } - - { - LOCK(cs_ProcessDSQueue); - - { - LOCK(cs_vecqueue); - // process every dsq only once - for (const auto &q: vecCoinJoinQueue) { - if (q == dsq) { - return ret; - } - if (q.fReady == dsq.fReady && q.masternodeOutpoint == dsq.masternodeOutpoint) { - // no way the same mn can send another dsq with the same readiness this soon - LogPrint(BCLog::COINJOIN, /* Continued */ - "DSQUEUE -- Peer %d is sending WAY too many dsq messages for a masternode with collateral %s\n", - from, dsq.masternodeOutpoint.ToStringShort()); - return ret; - } - } - } // cs_vecqueue - - LogPrint(BCLog::COINJOIN, "DSQUEUE -- %s new\n", dsq.ToString()); - - if (dsq.IsTimeOutOfBounds()) return ret; - - auto dmn = tip_mn_list.GetValidMNByCollateral(dsq.masternodeOutpoint); - if (!dmn) return ret; - - if (dsq.m_protxHash.IsNull()) { - dsq.m_protxHash = dmn->proTxHash; - } - - if (!dsq.CheckSignature(dmn->pdmnState->pubKeyOperator.Get())) { - ret.m_error = MisbehavingError{10}; - return ret; - } - - // if the queue is ready, submit if we can - if (dsq.fReady && m_wallet_notify.TrySubmitDenominate(dmn->proTxHash, connman)) { - LogPrint(BCLog::COINJOIN, "DSQUEUE -- CoinJoin queue is ready, masternode=%s, queue=%s\n", dmn->proTxHash.ToString(), dsq.ToString()); - return ret; - } else { - if (m_mn_metaman.IsMixingThresholdExceeded(dmn->proTxHash, tip_mn_list.GetCounts().enabled())) { - LogPrint(BCLog::COINJOIN, "DSQUEUE -- Masternode %s is sending too many dsq messages\n", - dmn->proTxHash.ToString()); - return ret; - } - m_mn_metaman.AllowMixing(dmn->proTxHash); - - LogPrint(BCLog::COINJOIN, "DSQUEUE -- new CoinJoin queue, masternode=%s, queue=%s\n", dmn->proTxHash.ToString(), dsq.ToString()); - - m_wallet_notify.MarkAlreadyJoinedQueueAsTried(dsq); - - WITH_LOCK(cs_vecqueue, vecCoinJoinQueue.push_back(dsq)); - } - } // cs_ProcessDSQueue - return ret; -} - -void CCoinJoinClientQueueManager::DoMaintenance() -{ - if (!m_mn_sync.IsBlockchainSynced() || ShutdownRequested()) return; - - CheckQueue(); -} - CCoinJoinClientManager::CCoinJoinClientManager(const std::shared_ptr& wallet, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman, - CCoinJoinClientQueueManager* queueman) : + CCoinJoinBaseManager* queueman) : m_wallet{wallet}, m_dmnman{dmnman}, m_mn_metaman{mn_metaman}, diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index c374a626333b..546517941cee 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -8,10 +8,6 @@ #include #include #include -#include - -#include -#include #include #include @@ -21,7 +17,6 @@ #include class CCoinJoinClientManager; -class CCoinJoinClientQueueManager; class CConnman; class CDeterministicMNManager; class ChainstateManager; @@ -69,23 +64,6 @@ class CPendingDsaRequest } }; -/** Callback interface used by CCoinJoinClientQueueManager to dispatch queue - * events to per-wallet client managers without depending on a concrete - * wallet manager class. - */ -class CoinJoinQueueNotify -{ -public: - virtual ~CoinJoinQueueNotify() = default; - - //! A queue is ready: attempt to submit denominate to the given masternode - //! across all active wallets. Returns true if any wallet accepted. - virtual bool TrySubmitDenominate(const uint256& proTxHash, CConnman& connman) = 0; - - //! A new queue was announced: mark it as already-tried in all active wallets. - virtual void MarkAlreadyJoinedQueueAsTried(CCoinJoinQueue& dsq) = 0; -}; - class CCoinJoinClientSession : public CCoinJoinBaseSession { private: @@ -174,32 +152,6 @@ class CCoinJoinClientSession : public CCoinJoinBaseSession void GetJsonInfo(UniValue& obj) const; }; -/** Used to keep track of mixing queues - */ -class CCoinJoinClientQueueManager : public CCoinJoinBaseManager -{ -private: - CoinJoinQueueNotify& m_wallet_notify; - CDeterministicMNManager& m_dmnman; - CMasternodeMetaMan& m_mn_metaman; - const CMasternodeSync& m_mn_sync; - - mutable Mutex cs_ProcessDSQueue; - -public: - CCoinJoinClientQueueManager() = delete; - CCoinJoinClientQueueManager(const CCoinJoinClientQueueManager&) = delete; - CCoinJoinClientQueueManager& operator=(const CCoinJoinClientQueueManager&) = delete; - explicit CCoinJoinClientQueueManager(CoinJoinQueueNotify& wallet_notify, CDeterministicMNManager& dmnman, - CMasternodeMetaMan& mn_metaman, const CMasternodeSync& mn_sync); - ~CCoinJoinClientQueueManager(); - - [[nodiscard]] MessageProcessingResult ProcessMessage(NodeId from, CConnman& connman, std::string_view msg_type, - CDataStream& vRecv) - EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue, !cs_ProcessDSQueue); - void DoMaintenance(); -}; - /** Used to keep track of current status of mixing pool */ class CCoinJoinClientManager @@ -211,7 +163,7 @@ class CCoinJoinClientManager const CMasternodeSync& m_mn_sync; const llmq::CInstantSendManager& m_isman; //! Non-owning pointer; null when relay_txes is disabled (no queue processing). - CCoinJoinClientQueueManager* const m_queueman; + CCoinJoinBaseManager* const m_queueman; mutable Mutex cs_deqsessions; // TODO: or map ?? @@ -240,7 +192,8 @@ class CCoinJoinClientManager CCoinJoinClientManager& operator=(const CCoinJoinClientManager&) = delete; explicit CCoinJoinClientManager(const std::shared_ptr& wallet, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, const CMasternodeSync& mn_sync, - const llmq::CInstantSendManager& isman, CCoinJoinClientQueueManager* queueman); + const llmq::CInstantSendManager& isman, + CCoinJoinBaseManager* queueman); ~CCoinJoinClientManager(); void ProcessMessage(CNode& peer, CChainState& active_chainstate, CConnman& connman, const CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); diff --git a/src/coinjoin/coinjoin.h b/src/coinjoin/coinjoin.h index 7cfd1c09efaf..bdbcfc133c05 100644 --- a/src/coinjoin/coinjoin.h +++ b/src/coinjoin/coinjoin.h @@ -331,12 +331,14 @@ class CCoinJoinBaseManager std::vector vecCoinJoinQueue GUARDED_BY(cs_vecqueue); void SetNull() EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); - void CheckQueue() EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); public: CCoinJoinBaseManager(); virtual ~CCoinJoinBaseManager(); + //! Remove timed-out queue entries. Call periodically (e.g. every second). + void CheckQueue() EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); + int GetQueueSize() const EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue) { LOCK(cs_vecqueue); return vecCoinJoinQueue.size(); } bool GetQueueItemAndTry(CCoinJoinQueue& dsqRet) EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); @@ -351,6 +353,22 @@ class CCoinJoinBaseManager LOCK(cs_vecqueue); return util::find_if_opt(vecCoinJoinQueue, [&queueHash](const auto& q) { return q.GetHash() == queueHash; }); } + + //! True if any queue entry matches the given masternode outpoint and readiness state. + //! Used to detect when a masternode is broadcasting queues too quickly. + bool HasQueueFromMasternode(const COutPoint& outpoint, bool fReady) const EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue) + { + LOCK(cs_vecqueue); + return std::any_of(vecCoinJoinQueue.begin(), vecCoinJoinQueue.end(), + [&](const auto& q) { return q.masternodeOutpoint == outpoint && q.fReady == fReady; }); + } + + //! Append a queue entry (caller must have already checked for duplicates). + void AddQueue(CCoinJoinQueue dsq) EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue) + { + LOCK(cs_vecqueue); + vecCoinJoinQueue.push_back(std::move(dsq)); + } }; // Various helpers and dstx manager implementation diff --git a/src/coinjoin/walletman.cpp b/src/coinjoin/walletman.cpp index fa5059c6e239..2427cf4e91fb 100644 --- a/src/coinjoin/walletman.cpp +++ b/src/coinjoin/walletman.cpp @@ -8,8 +8,14 @@ #include #include +#include +#include +#include +#include #include +#include #include +#include #include #ifdef ENABLE_WALLET @@ -20,7 +26,7 @@ #include #ifdef ENABLE_WALLET -class CJWalletManagerImpl final : public CJWalletManager, public CoinJoinQueueNotify +class CJWalletManagerImpl final : public CJWalletManager { public: CJWalletManagerImpl(ChainstateManager& chainman, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, @@ -43,10 +49,6 @@ class CJWalletManagerImpl final : public CJWalletManager, public CoinJoinQueueNo void removeWallet(const std::string& name) override; void flushWallet(const std::string& name) override; - // CoinJoinQueueNotify - bool TrySubmitDenominate(const uint256& proTxHash, CConnman& connman) override; - void MarkAlreadyJoinedQueueAsTried(CCoinJoinQueue& dsq) override; - protected: // CValidationInterface void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override; @@ -60,15 +62,22 @@ class CJWalletManagerImpl final : public CJWalletManager, public CoinJoinQueueNo const CMasternodeSync& m_mn_sync; const llmq::CInstantSendManager& m_isman; - // queueman is declared before the wallet map so that it is destroyed + // m_basequeueman is declared before the wallet map so that it is destroyed // after all CCoinJoinClientManager instances (which hold a raw pointer to it). - const std::unique_ptr m_queueman; + // Null when relay_txes is false (no queue processing). + const std::unique_ptr m_basequeueman; + + mutable Mutex cs_ProcessDSQueue; mutable Mutex cs_wallet_manager_map; std::map> m_wallet_manager_map GUARDED_BY(cs_wallet_manager_map); void DoMaintenance(CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); + [[nodiscard]] MessageProcessingResult ProcessDSQueue(NodeId from, CConnman& connman, + std::string_view msg_type, CDataStream& vRecv) + EXCLUSIVE_LOCKS_REQUIRED(!cs_ProcessDSQueue); + template void ForEachCJClientMan(Callable&& func) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map) { @@ -97,7 +106,7 @@ CJWalletManagerImpl::CJWalletManagerImpl(ChainstateManager& chainman, CDetermini m_mempool{mempool}, m_mn_sync{mn_sync}, m_isman{isman}, - m_queueman{m_relay_txes ? std::make_unique(*this, dmnman, mn_metaman, mn_sync) : nullptr} + m_basequeueman{m_relay_txes ? std::make_unique() : nullptr} { } @@ -112,8 +121,6 @@ CJWalletManagerImpl::~CJWalletManagerImpl() void CJWalletManagerImpl::Schedule(CConnman& connman, CScheduler& scheduler) { if (!m_relay_txes) return; - scheduler.scheduleEvery(std::bind(&CCoinJoinClientQueueManager::DoMaintenance, std::ref(*m_queueman)), - std::chrono::seconds{1}); scheduler.scheduleEvery(std::bind(&CJWalletManagerImpl::DoMaintenance, this, std::ref(connman)), std::chrono::seconds{1}); } @@ -128,8 +135,8 @@ void CJWalletManagerImpl::UpdatedBlockTip(const CBlockIndex* pindexNew, const CB bool CJWalletManagerImpl::hasQueue(const uint256& hash) const { - if (m_queueman) { - return m_queueman->HasQueue(hash); + if (m_basequeueman) { + return m_basequeueman->HasQueue(hash); } return false; } @@ -141,31 +148,18 @@ CCoinJoinClientManager* CJWalletManagerImpl::getClient(const std::string& name) return (it != m_wallet_manager_map.end()) ? it->second.get() : nullptr; } -MessageProcessingResult CJWalletManagerImpl::processMessage(CNode& pfrom, CChainState& chainstate, CConnman& connman, - CTxMemPool& mempool, std::string_view msg_type, - CDataStream& vRecv) -{ - ForEachCJClientMan([&](CCoinJoinClientManager& clientman) { - clientman.ProcessMessage(pfrom, chainstate, connman, mempool, msg_type, vRecv); - }); - if (m_queueman) { - return m_queueman->ProcessMessage(pfrom.GetId(), connman, msg_type, vRecv); - } - return {}; -} - std::optional CJWalletManagerImpl::getQueueFromHash(const uint256& hash) const { - if (m_queueman) { - return m_queueman->GetQueueFromHash(hash); + if (m_basequeueman) { + return m_basequeueman->GetQueueFromHash(hash); } return std::nullopt; } std::optional CJWalletManagerImpl::getQueueSize() const { - if (m_queueman) { - return m_queueman->GetQueueSize(); + if (m_basequeueman) { + return m_basequeueman->GetQueueSize(); } return std::nullopt; } @@ -181,8 +175,8 @@ void CJWalletManagerImpl::addWallet(const std::shared_ptr& wall { LOCK(cs_wallet_manager_map); m_wallet_manager_map.try_emplace(wallet->GetName(), - std::make_unique(wallet, m_dmnman, m_mn_metaman, m_mn_sync, - m_isman, m_queueman.get())); + std::make_unique(wallet, m_dmnman, m_mn_metaman, + m_mn_sync, m_isman, m_basequeueman.get())); } void CJWalletManagerImpl::flushWallet(const std::string& name) @@ -200,21 +194,114 @@ void CJWalletManagerImpl::removeWallet(const std::string& name) void CJWalletManagerImpl::DoMaintenance(CConnman& connman) { + if (m_basequeueman && m_mn_sync.IsBlockchainSynced() && !ShutdownRequested()) { + m_basequeueman->CheckQueue(); + } LOCK(cs_wallet_manager_map); for (auto& [_, clientman] : m_wallet_manager_map) { clientman->DoMaintenance(m_chainman, connman, m_mempool); } } -bool CJWalletManagerImpl::TrySubmitDenominate(const uint256& proTxHash, CConnman& connman) +MessageProcessingResult CJWalletManagerImpl::processMessage(CNode& pfrom, CChainState& chainstate, CConnman& connman, + CTxMemPool& mempool, std::string_view msg_type, + CDataStream& vRecv) { - return ForAnyCJClientMan( - [&](CCoinJoinClientManager& clientman) { return clientman.TrySubmitDenominate(proTxHash, connman); }); + ForEachCJClientMan([&](CCoinJoinClientManager& clientman) { + clientman.ProcessMessage(pfrom, chainstate, connman, mempool, msg_type, vRecv); + }); + if (m_basequeueman) { + return ProcessDSQueue(pfrom.GetId(), connman, msg_type, vRecv); + } + return {}; } -void CJWalletManagerImpl::MarkAlreadyJoinedQueueAsTried(CCoinJoinQueue& dsq) +MessageProcessingResult CJWalletManagerImpl::ProcessDSQueue(NodeId from, CConnman& connman, + std::string_view msg_type, CDataStream& vRecv) { - ForAnyCJClientMan([&](CCoinJoinClientManager& clientman) { return clientman.MarkAlreadyJoinedQueueAsTried(dsq); }); + assert(m_basequeueman); + + if (msg_type != NetMsgType::DSQUEUE) { + return {}; + } + if (!m_mn_sync.IsBlockchainSynced()) return {}; + + assert(m_mn_metaman.IsValid()); + + CCoinJoinQueue dsq; + vRecv >> dsq; + + MessageProcessingResult ret{}; + ret.m_to_erase = CInv{MSG_DSQ, dsq.GetHash()}; + + if (dsq.masternodeOutpoint.IsNull() && dsq.m_protxHash.IsNull()) { + ret.m_error = MisbehavingError{100}; + return ret; + } + + const auto tip_mn_list = m_dmnman.GetListAtChainTip(); + if (dsq.masternodeOutpoint.IsNull()) { + if (auto dmn = tip_mn_list.GetValidMN(dsq.m_protxHash)) { + dsq.masternodeOutpoint = dmn->collateralOutpoint; + } else { + ret.m_error = MisbehavingError{10}; + return ret; + } + } + + { + LOCK(cs_ProcessDSQueue); + + if (m_basequeueman->HasQueue(dsq.GetHash())) return ret; + + if (m_basequeueman->HasQueueFromMasternode(dsq.masternodeOutpoint, dsq.fReady)) { + // no way the same mn can send another dsq with the same readiness this soon + LogPrint(BCLog::COINJOIN, /* Continued */ + "DSQUEUE -- Peer %d is sending WAY too many dsq messages for a masternode with collateral %s\n", + from, dsq.masternodeOutpoint.ToStringShort()); + return ret; + } + + LogPrint(BCLog::COINJOIN, "DSQUEUE -- %s new\n", dsq.ToString()); + + if (dsq.IsTimeOutOfBounds()) return ret; + + auto dmn = tip_mn_list.GetValidMNByCollateral(dsq.masternodeOutpoint); + if (!dmn) return ret; + + if (dsq.m_protxHash.IsNull()) { + dsq.m_protxHash = dmn->proTxHash; + } + + if (!dsq.CheckSignature(dmn->pdmnState->pubKeyOperator.Get())) { + ret.m_error = MisbehavingError{10}; + return ret; + } + + // if the queue is ready, submit if we can + if (dsq.fReady && ForAnyCJClientMan([&connman, &dmn](CCoinJoinClientManager& clientman) { + return clientman.TrySubmitDenominate(dmn->proTxHash, connman); + })) { + LogPrint(BCLog::COINJOIN, "DSQUEUE -- CoinJoin queue is ready, masternode=%s, queue=%s\n", dmn->proTxHash.ToString(), dsq.ToString()); + return ret; + } else { + if (m_mn_metaman.IsMixingThresholdExceeded(dmn->proTxHash, tip_mn_list.GetCounts().enabled())) { + LogPrint(BCLog::COINJOIN, "DSQUEUE -- Masternode %s is sending too many dsq messages\n", + dmn->proTxHash.ToString()); + return ret; + } + m_mn_metaman.AllowMixing(dmn->proTxHash); + + LogPrint(BCLog::COINJOIN, "DSQUEUE -- new CoinJoin queue, masternode=%s, queue=%s\n", dmn->proTxHash.ToString(), dsq.ToString()); + + ForAnyCJClientMan([&dsq](CCoinJoinClientManager& clientman) { + return clientman.MarkAlreadyJoinedQueueAsTried(dsq); + }); + + m_basequeueman->AddQueue(dsq); + } + } // cs_ProcessDSQueue + return ret; } #endif // ENABLE_WALLET From a0f39f22f5f0e792bb45f3a4c565226aa4187d2e Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 18 Mar 2026 01:55:23 +0700 Subject: [PATCH 05/13] refactor: removed CCoinJoinBaseManager from inheritance of CCoinJoinServer and make it a member --- src/coinjoin/client.h | 3 +-- src/coinjoin/coinjoin.cpp | 26 ++++++++++++++++++++ src/coinjoin/coinjoin.h | 12 ++++++++-- src/coinjoin/server.cpp | 49 +++++++++++++------------------------- src/coinjoin/server.h | 10 ++++---- src/coinjoin/walletman.cpp | 24 +++++++++---------- 6 files changed, 72 insertions(+), 52 deletions(-) diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index 546517941cee..83c0481de0ce 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -192,8 +192,7 @@ class CCoinJoinClientManager CCoinJoinClientManager& operator=(const CCoinJoinClientManager&) = delete; explicit CCoinJoinClientManager(const std::shared_ptr& wallet, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, const CMasternodeSync& mn_sync, - const llmq::CInstantSendManager& isman, - CCoinJoinBaseManager* queueman); + const llmq::CInstantSendManager& isman, CCoinJoinBaseManager* queueman); ~CCoinJoinClientManager(); void ProcessMessage(CNode& peer, CChainState& active_chainstate, CConnman& connman, const CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); diff --git a/src/coinjoin/coinjoin.cpp b/src/coinjoin/coinjoin.cpp index 9b4d8953c48f..18b44420c5bf 100644 --- a/src/coinjoin/coinjoin.cpp +++ b/src/coinjoin/coinjoin.cpp @@ -143,6 +143,32 @@ void CCoinJoinBaseManager::CheckQueue() } } +std::optional CCoinJoinBaseManager::TryHasQueueFromMasternode(const COutPoint& outpoint) const +{ + TRY_LOCK(cs_vecqueue, lockDS); + if (!lockDS) return std::nullopt; + return std::ranges::any_of(vecCoinJoinQueue, [&outpoint](const auto& q) { return q.masternodeOutpoint == outpoint; }); +} + +std::optional CCoinJoinBaseManager::TryCheckDuplicate(const CCoinJoinQueue& dsq) const +{ + TRY_LOCK(cs_vecqueue, lockDS); + if (!lockDS) return std::nullopt; + for (const auto& q : vecCoinJoinQueue) { + if (q == dsq) return true; + if (q.fReady == dsq.fReady && q.masternodeOutpoint == dsq.masternodeOutpoint) return true; + } + return false; +} + +bool CCoinJoinBaseManager::TryAddQueue(CCoinJoinQueue dsq) +{ + TRY_LOCK(cs_vecqueue, lockDS); + if (!lockDS) return false; + vecCoinJoinQueue.push_back(std::move(dsq)); + return true; +} + bool CCoinJoinBaseManager::GetQueueItemAndTry(CCoinJoinQueue& dsqRet) { TRY_LOCK(cs_vecqueue, lockDS); diff --git a/src/coinjoin/coinjoin.h b/src/coinjoin/coinjoin.h index bdbcfc133c05..99bea86b127f 100644 --- a/src/coinjoin/coinjoin.h +++ b/src/coinjoin/coinjoin.h @@ -330,12 +330,12 @@ class CCoinJoinBaseManager // The current mixing sessions in progress on the network std::vector vecCoinJoinQueue GUARDED_BY(cs_vecqueue); - void SetNull() EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); - public: CCoinJoinBaseManager(); virtual ~CCoinJoinBaseManager(); + void SetNull() EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); + //! Remove timed-out queue entries. Call periodically (e.g. every second). void CheckQueue() EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); @@ -362,6 +362,12 @@ class CCoinJoinBaseManager return std::any_of(vecCoinJoinQueue.begin(), vecCoinJoinQueue.end(), [&](const auto& q) { return q.masternodeOutpoint == outpoint && q.fReady == fReady; }); } + //! TRY_LOCK variant: returns nullopt if lock can't be acquired; true if any queue entry has this + //! outpoint (any readiness). + [[nodiscard]] std::optional TryHasQueueFromMasternode(const COutPoint& outpoint) const EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); + //! TRY_LOCK combined duplicate check: returns nullopt if lock can't be acquired; true if dsq is + //! an exact duplicate or the masternode is sending too many dsqs with the same readiness. + [[nodiscard]] std::optional TryCheckDuplicate(const CCoinJoinQueue& dsq) const EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); //! Append a queue entry (caller must have already checked for duplicates). void AddQueue(CCoinJoinQueue dsq) EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue) @@ -369,6 +375,8 @@ class CCoinJoinBaseManager LOCK(cs_vecqueue); vecCoinJoinQueue.push_back(std::move(dsq)); } + //! TRY_LOCK variant of AddQueue: returns false if the lock cannot be acquired. + bool TryAddQueue(CCoinJoinQueue dsq) EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); }; // Various helpers and dstx manager implementation diff --git a/src/coinjoin/server.cpp b/src/coinjoin/server.cpp index 4abeb79ba526..d968c1b0c567 100644 --- a/src/coinjoin/server.cpp +++ b/src/coinjoin/server.cpp @@ -87,13 +87,9 @@ void CCoinJoinServer::ProcessDSACCEPT(CNode& peer, CDataStream& vRecv) if (vecSessionCollaterals.empty()) { { - TRY_LOCK(cs_vecqueue, lockRecv); - if (!lockRecv) return; - - auto mnOutpoint = m_mn_activeman.GetOutPoint(); - - if (std::ranges::any_of(vecCoinJoinQueue, - [&mnOutpoint](const auto& q) { return q.masternodeOutpoint == mnOutpoint; })) { + const auto hasQueue = m_queueman.TryHasQueueFromMasternode(m_mn_activeman.GetOutPoint()); + if (!hasQueue.has_value()) return; + if (*hasQueue) { // refuse to create another queue this often LogPrint(BCLog::COINJOIN, "DSACCEPT -- last dsq is still in queue, refuse to mix\n"); PushStatus(peer, STATUS_REJECTED, ERR_RECENT); @@ -160,21 +156,13 @@ void CCoinJoinServer::ProcessDSQUEUE(NodeId from, CDataStream& vRecv) } { - TRY_LOCK(cs_vecqueue, lockRecv); - if (!lockRecv) return; - - // process every dsq only once - for (const auto& q : vecCoinJoinQueue) { - if (q == dsq) { - return; - } - if (q.fReady == dsq.fReady && q.masternodeOutpoint == dsq.masternodeOutpoint) { - // no way the same mn can send another dsq with the same readiness this soon - LogPrint(BCLog::COINJOIN, "DSQUEUE -- Peer %d is sending WAY too many dsq messages for a masternode with collateral %s\n", from, dsq.masternodeOutpoint.ToStringShort()); - return; - } + const auto isDup = m_queueman.TryCheckDuplicate(dsq); + if (!isDup.has_value()) return; + if (*isDup) { + LogPrint(BCLog::COINJOIN, "DSQUEUE -- Peer %d is sending WAY too many dsq messages for a masternode with collateral %s\n", from, dsq.masternodeOutpoint.ToStringShort()); + return; } - } // cs_vecqueue + } LogPrint(BCLog::COINJOIN, "DSQUEUE -- %s new\n", dsq.ToString()); @@ -202,9 +190,7 @@ void CCoinJoinServer::ProcessDSQUEUE(NodeId from, CDataStream& vRecv) LogPrint(BCLog::COINJOIN, "DSQUEUE -- new CoinJoin queue, masternode=%s, queue=%s\n", dmn->proTxHash.ToString(), dsq.ToString()); - TRY_LOCK(cs_vecqueue, lockRecv); - if (!lockRecv) return; - vecCoinJoinQueue.push_back(dsq); + if (!m_queueman.TryAddQueue(dsq)) return; m_peer_manager->PeerRelayDSQ(dsq); } } @@ -267,7 +253,7 @@ void CCoinJoinServer::SetNull() vecSessionCollaterals.clear(); CCoinJoinBaseSession::SetNull(); - CCoinJoinBaseManager::SetNull(); + m_queueman.SetNull(); } // @@ -504,7 +490,7 @@ bool CCoinJoinServer::HasTimedOut() const // void CCoinJoinServer::CheckTimeout() { - CheckQueue(); + m_queueman.CheckQueue(); // Too early to do anything if (!CCoinJoinServer::HasTimedOut()) return; @@ -531,7 +517,7 @@ void CCoinJoinServer::CheckForCompleteQueue() "with %d participants\n", dsq.ToString(), vecSessionCollaterals.size()); dsq.vchSig = m_mn_activeman.SignBasic(dsq.GetSignatureHash()); m_peer_manager->PeerRelayDSQ(dsq); - WITH_LOCK(cs_vecqueue, vecCoinJoinQueue.push_back(dsq)); + m_queueman.AddQueue(std::move(dsq)); } } @@ -739,8 +725,7 @@ bool CCoinJoinServer::CreateNewSession(const CCoinJoinAccept& dsa, PoolMessage& LogPrint(BCLog::COINJOIN, "CCoinJoinServer::CreateNewSession -- signing and relaying new queue: %s\n", dsq.ToString()); dsq.vchSig = m_mn_activeman.SignBasic(dsq.GetSignatureHash()); m_peer_manager->PeerRelayDSQ(dsq); - LOCK(cs_vecqueue); - vecCoinJoinQueue.push_back(dsq); + m_queueman.AddQueue(std::move(dsq)); } vecSessionCollaterals.push_back(MakeTransactionRef(dsa.txCollateral)); @@ -916,7 +901,7 @@ void CCoinJoinServer::GetJsonInfo(UniValue& obj) const { obj.clear(); obj.setObject(); - obj.pushKV("queue_size", GetQueueSize()); + obj.pushKV("queue_size", m_queueman.GetQueueSize()); obj.pushKV("denomination", ValueFromAmount(CoinJoin::DenominationToAmount(nSessionDenom))); obj.pushKV("state", GetStateString()); obj.pushKV("entries_count", GetEntriesCount()); @@ -924,14 +909,14 @@ void CCoinJoinServer::GetJsonInfo(UniValue& obj) const bool CCoinJoinServer::AlreadyHave(const CInv& inv) { - return (inv.type == MSG_DSQ) ? HasQueue(inv.hash) : false; + return (inv.type == MSG_DSQ) ? m_queueman.HasQueue(inv.hash) : false; } bool CCoinJoinServer::ProcessGetData(CNode& pfrom, const CInv& inv, CConnman& connman, const CNetMsgMaker& msgMaker) { if (inv.type != MSG_DSQ) return false; - auto opt_dsq = GetQueueFromHash(inv.hash); + auto opt_dsq = m_queueman.GetQueueFromHash(inv.hash); if (!opt_dsq.has_value()) return false; connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::DSQUEUE, *opt_dsq)); diff --git a/src/coinjoin/server.h b/src/coinjoin/server.h index f02ce7dce305..7380faeb2918 100644 --- a/src/coinjoin/server.h +++ b/src/coinjoin/server.h @@ -25,9 +25,11 @@ class UniValue; /** Used to keep track of current status of mixing pool */ -class CCoinJoinServer : public CCoinJoinBaseSession, public CCoinJoinBaseManager, public NetHandler +class CCoinJoinServer : public CCoinJoinBaseSession, public NetHandler { private: + CCoinJoinBaseManager m_queueman; + ChainstateManager& m_chainman; CConnman& connman; CDeterministicMNManager& m_dmnman; @@ -64,7 +66,7 @@ class CCoinJoinServer : public CCoinJoinBaseSession, public CCoinJoinBaseManager /// Is this nDenom and txCollateral acceptable? bool IsAcceptableDSA(const CCoinJoinAccept& dsa, PoolMessage& nMessageIDRet) const; - bool CreateNewSession(const CCoinJoinAccept& dsa, PoolMessage& nMessageIDRet) EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); + bool CreateNewSession(const CCoinJoinAccept& dsa, PoolMessage& nMessageIDRet); bool AddUserToExistingSession(const CCoinJoinAccept& dsa, PoolMessage& nMessageIDRet); /// Do we have enough users to take entries? bool IsSessionReady() const; @@ -83,8 +85,8 @@ class CCoinJoinServer : public CCoinJoinBaseSession, public CCoinJoinBaseManager void RelayStatus(PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID = MSG_NOERR) EXCLUSIVE_LOCKS_REQUIRED(cs_coinjoin); void RelayCompletedTransaction(PoolMessage nMessageID) EXCLUSIVE_LOCKS_REQUIRED(!cs_coinjoin); - void ProcessDSACCEPT(CNode& peer, CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); - void ProcessDSQUEUE(NodeId from, CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); + void ProcessDSACCEPT(CNode& peer, CDataStream& vRecv); + void ProcessDSQUEUE(NodeId from, CDataStream& vRecv); void ProcessDSVIN(CNode& peer, CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_coinjoin); void ProcessDSSIGNFINALTX(CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_coinjoin); diff --git a/src/coinjoin/walletman.cpp b/src/coinjoin/walletman.cpp index 2427cf4e91fb..fa4cfd93752c 100644 --- a/src/coinjoin/walletman.cpp +++ b/src/coinjoin/walletman.cpp @@ -74,9 +74,8 @@ class CJWalletManagerImpl final : public CJWalletManager void DoMaintenance(CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); - [[nodiscard]] MessageProcessingResult ProcessDSQueue(NodeId from, CConnman& connman, - std::string_view msg_type, CDataStream& vRecv) - EXCLUSIVE_LOCKS_REQUIRED(!cs_ProcessDSQueue); + [[nodiscard]] MessageProcessingResult ProcessDSQueue(NodeId from, CConnman& connman, std::string_view msg_type, + CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_ProcessDSQueue); template void ForEachCJClientMan(Callable&& func) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map) @@ -175,8 +174,8 @@ void CJWalletManagerImpl::addWallet(const std::shared_ptr& wall { LOCK(cs_wallet_manager_map); m_wallet_manager_map.try_emplace(wallet->GetName(), - std::make_unique(wallet, m_dmnman, m_mn_metaman, - m_mn_sync, m_isman, m_basequeueman.get())); + std::make_unique(wallet, m_dmnman, m_mn_metaman, m_mn_sync, + m_isman, m_basequeueman.get())); } void CJWalletManagerImpl::flushWallet(const std::string& name) @@ -216,8 +215,8 @@ MessageProcessingResult CJWalletManagerImpl::processMessage(CNode& pfrom, CChain return {}; } -MessageProcessingResult CJWalletManagerImpl::ProcessDSQueue(NodeId from, CConnman& connman, - std::string_view msg_type, CDataStream& vRecv) +MessageProcessingResult CJWalletManagerImpl::ProcessDSQueue(NodeId from, CConnman& connman, std::string_view msg_type, + CDataStream& vRecv) { assert(m_basequeueman); @@ -282,7 +281,8 @@ MessageProcessingResult CJWalletManagerImpl::ProcessDSQueue(NodeId from, CConnma if (dsq.fReady && ForAnyCJClientMan([&connman, &dmn](CCoinJoinClientManager& clientman) { return clientman.TrySubmitDenominate(dmn->proTxHash, connman); })) { - LogPrint(BCLog::COINJOIN, "DSQUEUE -- CoinJoin queue is ready, masternode=%s, queue=%s\n", dmn->proTxHash.ToString(), dsq.ToString()); + LogPrint(BCLog::COINJOIN, "DSQUEUE -- CoinJoin queue is ready, masternode=%s, queue=%s\n", + dmn->proTxHash.ToString(), dsq.ToString()); return ret; } else { if (m_mn_metaman.IsMixingThresholdExceeded(dmn->proTxHash, tip_mn_list.GetCounts().enabled())) { @@ -292,11 +292,11 @@ MessageProcessingResult CJWalletManagerImpl::ProcessDSQueue(NodeId from, CConnma } m_mn_metaman.AllowMixing(dmn->proTxHash); - LogPrint(BCLog::COINJOIN, "DSQUEUE -- new CoinJoin queue, masternode=%s, queue=%s\n", dmn->proTxHash.ToString(), dsq.ToString()); + LogPrint(BCLog::COINJOIN, "DSQUEUE -- new CoinJoin queue, masternode=%s, queue=%s\n", + dmn->proTxHash.ToString(), dsq.ToString()); - ForAnyCJClientMan([&dsq](CCoinJoinClientManager& clientman) { - return clientman.MarkAlreadyJoinedQueueAsTried(dsq); - }); + ForAnyCJClientMan( + [&dsq](CCoinJoinClientManager& clientman) { return clientman.MarkAlreadyJoinedQueueAsTried(dsq); }); m_basequeueman->AddQueue(dsq); } From 6fae09aae5e1d36d2b5e22a6bce7d3c993add210 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 18 Mar 2026 02:16:01 +0700 Subject: [PATCH 06/13] refactor: remove inheritance from CCoinJoinBaseManager in src/test/coinjoin_basemanager_tests.cpp --- src/test/coinjoin_basemanager_tests.cpp | 33 ++++++------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/src/test/coinjoin_basemanager_tests.cpp b/src/test/coinjoin_basemanager_tests.cpp index 5e4bd4b0e1a0..d22a7be9e77d 100644 --- a/src/test/coinjoin_basemanager_tests.cpp +++ b/src/test/coinjoin_basemanager_tests.cpp @@ -5,27 +5,10 @@ #include #include -#include #include #include -class TestBaseManager : public CCoinJoinBaseManager -{ -public: - void PushQueue(const CCoinJoinQueue& q) - { - LOCK(cs_vecqueue); - vecCoinJoinQueue.push_back(q); - } - size_t QueueSize() const - { - LOCK(cs_vecqueue); - return vecCoinJoinQueue.size(); - } - void CallCheckQueue() { CheckQueue(); } -}; - BOOST_FIXTURE_TEST_SUITE(coinjoin_basemanager_tests, BasicTestingSetup) static CCoinJoinQueue MakeQueue(int denom, int64_t nTime, bool fReady, const COutPoint& outpoint) @@ -41,27 +24,27 @@ static CCoinJoinQueue MakeQueue(int denom, int64_t nTime, bool fReady, const COu BOOST_AUTO_TEST_CASE(checkqueue_removes_timeouts) { - TestBaseManager man; + CCoinJoinBaseManager man; const int denom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); const int64_t now = GetAdjustedTime(); // Non-expired - man.PushQueue(MakeQueue(denom, now, false, COutPoint(uint256S("11"), 0))); + man.AddQueue(MakeQueue(denom, now, false, COutPoint(uint256S("11"), 0))); // Expired (too old) - man.PushQueue(MakeQueue(denom, now - COINJOIN_QUEUE_TIMEOUT - 1, false, COutPoint(uint256S("12"), 0))); + man.AddQueue(MakeQueue(denom, now - COINJOIN_QUEUE_TIMEOUT - 1, false, COutPoint(uint256S("12"), 0))); - BOOST_CHECK_EQUAL(man.QueueSize(), 2U); - man.CallCheckQueue(); + BOOST_CHECK_EQUAL(man.GetQueueSize(), 2); + man.CheckQueue(); // One should be removed - BOOST_CHECK_EQUAL(man.QueueSize(), 1U); + BOOST_CHECK_EQUAL(man.GetQueueSize(), 1); } BOOST_AUTO_TEST_CASE(getqueueitem_marks_tried_once) { - TestBaseManager man; + CCoinJoinBaseManager man; const int denom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); const int64_t now = GetAdjustedTime(); CCoinJoinQueue dsq = MakeQueue(denom, now, false, COutPoint(uint256S("21"), 0)); - man.PushQueue(dsq); + man.AddQueue(dsq); CCoinJoinQueue picked; // First retrieval should succeed From 40cd08d299150bcd3a6953ac52dbc772db8c7bde Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 18 Mar 2026 02:30:11 +0700 Subject: [PATCH 07/13] refactor: rename CCoinJoinBaseManager to CoinJoinQueueManager and make it non-virtual --- contrib/auto_gdb/simple_class_obj.py | 2 +- src/Makefile.test.include | 1 - src/coinjoin/client.cpp | 2 +- src/coinjoin/client.h | 4 +- src/coinjoin/coinjoin.cpp | 18 +++----- src/coinjoin/coinjoin.h | 8 +--- src/coinjoin/server.h | 2 +- src/coinjoin/walletman.cpp | 34 +++++++-------- src/test/coinjoin_basemanager_tests.cpp | 57 ------------------------- src/test/coinjoin_queue_tests.cpp | 43 +++++++++++++++++++ 10 files changed, 74 insertions(+), 97 deletions(-) delete mode 100644 src/test/coinjoin_basemanager_tests.cpp diff --git a/contrib/auto_gdb/simple_class_obj.py b/contrib/auto_gdb/simple_class_obj.py index 19752b72fafe..6fec8d9525cc 100644 --- a/contrib/auto_gdb/simple_class_obj.py +++ b/contrib/auto_gdb/simple_class_obj.py @@ -14,7 +14,7 @@ "CMasternodeBroadcast", "CMasternodePing", "CMasternodeMan", "CDarksendQueue", "CDarkSendEntry", "CTransaction", "CMutableTransaction", "CCoinJoinBaseSession", - "CCoinJoinBaseManager", "CCoinJoinClientSession", + "CoinJoinQueueManager", "CCoinJoinClientSession", "CCoinJoinClientManager", "CCoinJoinServer", "CMasternodePayments", "CMasternodePaymentVote", "CMasternodeBlockPayees", "CMasternodePayee", "CInstantSend", "CTxLockRequest", diff --git a/src/Makefile.test.include b/src/Makefile.test.include index dd6dda7178c3..61d50f0a0b08 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -122,7 +122,6 @@ BITCOIN_TESTS =\ test/governance_validators_tests.cpp \ test/coinjoin_inouts_tests.cpp \ test/coinjoin_dstxmanager_tests.cpp \ - test/coinjoin_basemanager_tests.cpp \ test/coinjoin_queue_tests.cpp \ test/hash_tests.cpp \ test/httpserver_tests.cpp \ diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index 171eeeeb989a..6474cbd708bb 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -43,7 +43,7 @@ using wallet::ReserveDestination; CCoinJoinClientManager::CCoinJoinClientManager(const std::shared_ptr& wallet, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, const CMasternodeSync& mn_sync, const llmq::CInstantSendManager& isman, - CCoinJoinBaseManager* queueman) : + CoinJoinQueueManager* queueman) : m_wallet{wallet}, m_dmnman{dmnman}, m_mn_metaman{mn_metaman}, diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index 83c0481de0ce..3785d307099e 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -163,7 +163,7 @@ class CCoinJoinClientManager const CMasternodeSync& m_mn_sync; const llmq::CInstantSendManager& m_isman; //! Non-owning pointer; null when relay_txes is disabled (no queue processing). - CCoinJoinBaseManager* const m_queueman; + CoinJoinQueueManager* const m_queueman; mutable Mutex cs_deqsessions; // TODO: or map ?? @@ -192,7 +192,7 @@ class CCoinJoinClientManager CCoinJoinClientManager& operator=(const CCoinJoinClientManager&) = delete; explicit CCoinJoinClientManager(const std::shared_ptr& wallet, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, const CMasternodeSync& mn_sync, - const llmq::CInstantSendManager& isman, CCoinJoinBaseManager* queueman); + const llmq::CInstantSendManager& isman, CoinJoinQueueManager* queueman); ~CCoinJoinClientManager(); void ProcessMessage(CNode& peer, CChainState& active_chainstate, CConnman& connman, const CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); diff --git a/src/coinjoin/coinjoin.cpp b/src/coinjoin/coinjoin.cpp index 18b44420c5bf..010a81c877b7 100644 --- a/src/coinjoin/coinjoin.cpp +++ b/src/coinjoin/coinjoin.cpp @@ -116,17 +116,13 @@ void CCoinJoinBaseSession::SetNull() nTimeLastSuccessfulStep = GetTime(); } -CCoinJoinBaseManager::CCoinJoinBaseManager() = default; - -CCoinJoinBaseManager::~CCoinJoinBaseManager() = default; - -void CCoinJoinBaseManager::SetNull() +void CoinJoinQueueManager::SetNull() { LOCK(cs_vecqueue); vecCoinJoinQueue.clear(); } -void CCoinJoinBaseManager::CheckQueue() +void CoinJoinQueueManager::CheckQueue() { TRY_LOCK(cs_vecqueue, lockDS); if (!lockDS) return; // it's ok to fail here, we run this quite frequently @@ -135,7 +131,7 @@ void CCoinJoinBaseManager::CheckQueue() auto it = vecCoinJoinQueue.begin(); while (it != vecCoinJoinQueue.end()) { if (it->IsTimeOutOfBounds()) { - LogPrint(BCLog::COINJOIN, "CCoinJoinBaseManager::%s -- Removing a queue (%s)\n", __func__, it->ToString()); + LogPrint(BCLog::COINJOIN, "CoinJoinQueueManager::%s -- Removing a queue (%s)\n", __func__, it->ToString()); it = vecCoinJoinQueue.erase(it); } else { ++it; @@ -143,14 +139,14 @@ void CCoinJoinBaseManager::CheckQueue() } } -std::optional CCoinJoinBaseManager::TryHasQueueFromMasternode(const COutPoint& outpoint) const +std::optional CoinJoinQueueManager::TryHasQueueFromMasternode(const COutPoint& outpoint) const { TRY_LOCK(cs_vecqueue, lockDS); if (!lockDS) return std::nullopt; return std::ranges::any_of(vecCoinJoinQueue, [&outpoint](const auto& q) { return q.masternodeOutpoint == outpoint; }); } -std::optional CCoinJoinBaseManager::TryCheckDuplicate(const CCoinJoinQueue& dsq) const +std::optional CoinJoinQueueManager::TryCheckDuplicate(const CCoinJoinQueue& dsq) const { TRY_LOCK(cs_vecqueue, lockDS); if (!lockDS) return std::nullopt; @@ -161,7 +157,7 @@ std::optional CCoinJoinBaseManager::TryCheckDuplicate(const CCoinJoinQueue return false; } -bool CCoinJoinBaseManager::TryAddQueue(CCoinJoinQueue dsq) +bool CoinJoinQueueManager::TryAddQueue(CCoinJoinQueue dsq) { TRY_LOCK(cs_vecqueue, lockDS); if (!lockDS) return false; @@ -169,7 +165,7 @@ bool CCoinJoinBaseManager::TryAddQueue(CCoinJoinQueue dsq) return true; } -bool CCoinJoinBaseManager::GetQueueItemAndTry(CCoinJoinQueue& dsqRet) +bool CoinJoinQueueManager::GetQueueItemAndTry(CCoinJoinQueue& dsqRet) { TRY_LOCK(cs_vecqueue, lockDS); if (!lockDS) return false; // it's ok to fail here, we run this quite frequently diff --git a/src/coinjoin/coinjoin.h b/src/coinjoin/coinjoin.h index 99bea86b127f..ea073f14f79e 100644 --- a/src/coinjoin/coinjoin.h +++ b/src/coinjoin/coinjoin.h @@ -321,19 +321,15 @@ class CCoinJoinBaseSession int GetEntriesCountLocked() const EXCLUSIVE_LOCKS_REQUIRED(cs_coinjoin) { return vecEntries.size(); } }; -// base class -class CCoinJoinBaseManager +class CoinJoinQueueManager { -protected: +private: mutable Mutex cs_vecqueue; // The current mixing sessions in progress on the network std::vector vecCoinJoinQueue GUARDED_BY(cs_vecqueue); public: - CCoinJoinBaseManager(); - virtual ~CCoinJoinBaseManager(); - void SetNull() EXCLUSIVE_LOCKS_REQUIRED(!cs_vecqueue); //! Remove timed-out queue entries. Call periodically (e.g. every second). diff --git a/src/coinjoin/server.h b/src/coinjoin/server.h index 7380faeb2918..4c900cacc08d 100644 --- a/src/coinjoin/server.h +++ b/src/coinjoin/server.h @@ -28,7 +28,7 @@ class UniValue; class CCoinJoinServer : public CCoinJoinBaseSession, public NetHandler { private: - CCoinJoinBaseManager m_queueman; + CoinJoinQueueManager m_queueman; ChainstateManager& m_chainman; CConnman& connman; diff --git a/src/coinjoin/walletman.cpp b/src/coinjoin/walletman.cpp index fa4cfd93752c..53bb30c6aa9d 100644 --- a/src/coinjoin/walletman.cpp +++ b/src/coinjoin/walletman.cpp @@ -62,10 +62,10 @@ class CJWalletManagerImpl final : public CJWalletManager const CMasternodeSync& m_mn_sync; const llmq::CInstantSendManager& m_isman; - // m_basequeueman is declared before the wallet map so that it is destroyed + // m_queueman is declared before the wallet map so that it is destroyed // after all CCoinJoinClientManager instances (which hold a raw pointer to it). // Null when relay_txes is false (no queue processing). - const std::unique_ptr m_basequeueman; + const std::unique_ptr m_queueman; mutable Mutex cs_ProcessDSQueue; @@ -105,7 +105,7 @@ CJWalletManagerImpl::CJWalletManagerImpl(ChainstateManager& chainman, CDetermini m_mempool{mempool}, m_mn_sync{mn_sync}, m_isman{isman}, - m_basequeueman{m_relay_txes ? std::make_unique() : nullptr} + m_queueman{m_relay_txes ? std::make_unique() : nullptr} { } @@ -134,8 +134,8 @@ void CJWalletManagerImpl::UpdatedBlockTip(const CBlockIndex* pindexNew, const CB bool CJWalletManagerImpl::hasQueue(const uint256& hash) const { - if (m_basequeueman) { - return m_basequeueman->HasQueue(hash); + if (m_queueman) { + return m_queueman->HasQueue(hash); } return false; } @@ -149,16 +149,16 @@ CCoinJoinClientManager* CJWalletManagerImpl::getClient(const std::string& name) std::optional CJWalletManagerImpl::getQueueFromHash(const uint256& hash) const { - if (m_basequeueman) { - return m_basequeueman->GetQueueFromHash(hash); + if (m_queueman) { + return m_queueman->GetQueueFromHash(hash); } return std::nullopt; } std::optional CJWalletManagerImpl::getQueueSize() const { - if (m_basequeueman) { - return m_basequeueman->GetQueueSize(); + if (m_queueman) { + return m_queueman->GetQueueSize(); } return std::nullopt; } @@ -175,7 +175,7 @@ void CJWalletManagerImpl::addWallet(const std::shared_ptr& wall LOCK(cs_wallet_manager_map); m_wallet_manager_map.try_emplace(wallet->GetName(), std::make_unique(wallet, m_dmnman, m_mn_metaman, m_mn_sync, - m_isman, m_basequeueman.get())); + m_isman, m_queueman.get())); } void CJWalletManagerImpl::flushWallet(const std::string& name) @@ -193,8 +193,8 @@ void CJWalletManagerImpl::removeWallet(const std::string& name) void CJWalletManagerImpl::DoMaintenance(CConnman& connman) { - if (m_basequeueman && m_mn_sync.IsBlockchainSynced() && !ShutdownRequested()) { - m_basequeueman->CheckQueue(); + if (m_queueman && m_mn_sync.IsBlockchainSynced() && !ShutdownRequested()) { + m_queueman->CheckQueue(); } LOCK(cs_wallet_manager_map); for (auto& [_, clientman] : m_wallet_manager_map) { @@ -209,7 +209,7 @@ MessageProcessingResult CJWalletManagerImpl::processMessage(CNode& pfrom, CChain ForEachCJClientMan([&](CCoinJoinClientManager& clientman) { clientman.ProcessMessage(pfrom, chainstate, connman, mempool, msg_type, vRecv); }); - if (m_basequeueman) { + if (m_queueman) { return ProcessDSQueue(pfrom.GetId(), connman, msg_type, vRecv); } return {}; @@ -218,7 +218,7 @@ MessageProcessingResult CJWalletManagerImpl::processMessage(CNode& pfrom, CChain MessageProcessingResult CJWalletManagerImpl::ProcessDSQueue(NodeId from, CConnman& connman, std::string_view msg_type, CDataStream& vRecv) { - assert(m_basequeueman); + assert(m_queueman); if (msg_type != NetMsgType::DSQUEUE) { return {}; @@ -251,9 +251,9 @@ MessageProcessingResult CJWalletManagerImpl::ProcessDSQueue(NodeId from, CConnma { LOCK(cs_ProcessDSQueue); - if (m_basequeueman->HasQueue(dsq.GetHash())) return ret; + if (m_queueman->HasQueue(dsq.GetHash())) return ret; - if (m_basequeueman->HasQueueFromMasternode(dsq.masternodeOutpoint, dsq.fReady)) { + if (m_queueman->HasQueueFromMasternode(dsq.masternodeOutpoint, dsq.fReady)) { // no way the same mn can send another dsq with the same readiness this soon LogPrint(BCLog::COINJOIN, /* Continued */ "DSQUEUE -- Peer %d is sending WAY too many dsq messages for a masternode with collateral %s\n", @@ -298,7 +298,7 @@ MessageProcessingResult CJWalletManagerImpl::ProcessDSQueue(NodeId from, CConnma ForAnyCJClientMan( [&dsq](CCoinJoinClientManager& clientman) { return clientman.MarkAlreadyJoinedQueueAsTried(dsq); }); - m_basequeueman->AddQueue(dsq); + m_queueman->AddQueue(dsq); } } // cs_ProcessDSQueue return ret; diff --git a/src/test/coinjoin_basemanager_tests.cpp b/src/test/coinjoin_basemanager_tests.cpp deleted file mode 100644 index d22a7be9e77d..000000000000 --- a/src/test/coinjoin_basemanager_tests.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2025 The Dash Core developers -// Distributed under the MIT/X11 software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include - -#include -#include - -#include - -BOOST_FIXTURE_TEST_SUITE(coinjoin_basemanager_tests, BasicTestingSetup) - -static CCoinJoinQueue MakeQueue(int denom, int64_t nTime, bool fReady, const COutPoint& outpoint) -{ - CCoinJoinQueue q; - q.nDenom = denom; - q.masternodeOutpoint = outpoint; - q.m_protxHash = uint256::ONE; - q.nTime = nTime; - q.fReady = fReady; - return q; -} - -BOOST_AUTO_TEST_CASE(checkqueue_removes_timeouts) -{ - CCoinJoinBaseManager man; - const int denom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); - const int64_t now = GetAdjustedTime(); - // Non-expired - man.AddQueue(MakeQueue(denom, now, false, COutPoint(uint256S("11"), 0))); - // Expired (too old) - man.AddQueue(MakeQueue(denom, now - COINJOIN_QUEUE_TIMEOUT - 1, false, COutPoint(uint256S("12"), 0))); - - BOOST_CHECK_EQUAL(man.GetQueueSize(), 2); - man.CheckQueue(); - // One should be removed - BOOST_CHECK_EQUAL(man.GetQueueSize(), 1); -} - -BOOST_AUTO_TEST_CASE(getqueueitem_marks_tried_once) -{ - CCoinJoinBaseManager man; - const int denom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); - const int64_t now = GetAdjustedTime(); - CCoinJoinQueue dsq = MakeQueue(denom, now, false, COutPoint(uint256S("21"), 0)); - man.AddQueue(dsq); - - CCoinJoinQueue picked; - // First retrieval should succeed - BOOST_CHECK(man.GetQueueItemAndTry(picked)); - // No other items left to try (picked is marked tried inside) - CCoinJoinQueue picked2; - BOOST_CHECK(!man.GetQueueItemAndTry(picked2)); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/coinjoin_queue_tests.cpp b/src/test/coinjoin_queue_tests.cpp index a39ad552dbc2..a3e44281b230 100644 --- a/src/test/coinjoin_queue_tests.cpp +++ b/src/test/coinjoin_queue_tests.cpp @@ -145,4 +145,47 @@ BOOST_AUTO_TEST_CASE(calculate_amount_priority_guard) BOOST_CHECK_EQUAL(CoinJoin::CalculateAmountPriority(MAX_MONEY + 1), 0); } +static CCoinJoinQueue MakeQueue(int denom, int64_t nTime, bool fReady, const COutPoint& outpoint) +{ + CCoinJoinQueue q; + q.nDenom = denom; + q.masternodeOutpoint = outpoint; + q.m_protxHash = uint256::ONE; + q.nTime = nTime; + q.fReady = fReady; + return q; +} + +BOOST_AUTO_TEST_CASE(queuemanager_checkqueue_removes_timeouts) +{ + CoinJoinQueueManager man; + const int denom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); + const int64_t now = GetAdjustedTime(); + // Non-expired + man.AddQueue(MakeQueue(denom, now, false, COutPoint(uint256S("11"), 0))); + // Expired (too old) + man.AddQueue(MakeQueue(denom, now - COINJOIN_QUEUE_TIMEOUT - 1, false, COutPoint(uint256S("12"), 0))); + + BOOST_CHECK_EQUAL(man.GetQueueSize(), 2); + man.CheckQueue(); + // One should be removed + BOOST_CHECK_EQUAL(man.GetQueueSize(), 1); +} + +BOOST_AUTO_TEST_CASE(queuemanager_getqueueitem_marks_tried_once) +{ + CoinJoinQueueManager man; + const int denom = CoinJoin::AmountToDenomination(CoinJoin::GetSmallestDenomination()); + const int64_t now = GetAdjustedTime(); + CCoinJoinQueue dsq = MakeQueue(denom, now, false, COutPoint(uint256S("21"), 0)); + man.AddQueue(dsq); + + CCoinJoinQueue picked; + // First retrieval should succeed + BOOST_CHECK(man.GetQueueItemAndTry(picked)); + // No other items left to try (picked is marked tried inside) + CCoinJoinQueue picked2; + BOOST_CHECK(!man.GetQueueItemAndTry(picked2)); +} + BOOST_AUTO_TEST_SUITE_END() From eb2142db5b0a1dc1712d718796dc8fad304ce7ff Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 27 Mar 2026 16:43:28 +0700 Subject: [PATCH 08/13] fix: added missing thread annotation for CJWalletManagerImpl --- src/coinjoin/walletman.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/coinjoin/walletman.cpp b/src/coinjoin/walletman.cpp index 53bb30c6aa9d..a54ac93795b4 100644 --- a/src/coinjoin/walletman.cpp +++ b/src/coinjoin/walletman.cpp @@ -39,19 +39,19 @@ class CJWalletManagerImpl final : public CJWalletManager public: bool hasQueue(const uint256& hash) const override; - CCoinJoinClientManager* getClient(const std::string& name) override; + CCoinJoinClientManager* getClient(const std::string& name) override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); MessageProcessingResult processMessage(CNode& peer, CChainState& chainstate, CConnman& connman, CTxMemPool& mempool, - std::string_view msg_type, CDataStream& vRecv) override; + std::string_view msg_type, CDataStream& vRecv) override EXCLUSIVE_LOCKS_REQUIRED(!cs_ProcessDSQueue, !cs_wallet_manager_map); std::optional getQueueFromHash(const uint256& hash) const override; std::optional getQueueSize() const override; - std::vector getMixingMasternodes() override; - void addWallet(const std::shared_ptr& wallet) override; - void removeWallet(const std::string& name) override; - void flushWallet(const std::string& name) override; + std::vector getMixingMasternodes() override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); + void addWallet(const std::shared_ptr& wallet) override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); + void removeWallet(const std::string& name) override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); + void flushWallet(const std::string& name) override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); protected: // CValidationInterface - void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override; + void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); private: const bool m_relay_txes; @@ -75,7 +75,7 @@ class CJWalletManagerImpl final : public CJWalletManager void DoMaintenance(CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); [[nodiscard]] MessageProcessingResult ProcessDSQueue(NodeId from, CConnman& connman, std::string_view msg_type, - CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_ProcessDSQueue); + CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_ProcessDSQueue, !cs_wallet_manager_map); template void ForEachCJClientMan(Callable&& func) EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map) From 52eb3165b15546e8d6204dda27d9dcd8ac7aef6f Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sun, 29 Mar 2026 20:55:20 +0700 Subject: [PATCH 09/13] fix: linkage error for non-wallet builds for further changes /usr/bin/ld: libbitcoin_node.a(libbitcoin_node_a-walletman.o): in function `CJWalletManagerImpl::UpdatedBlockTip(CBlockIndex const*, CBlockIndex const*, bool)::{lambda(CCoinJoinClientManager&)#1}::operator()(CCoinJoinClientManager&) const': DASH/src/coinjoin/walletman.cpp:132:(.text+0x51c): undefined reference to `CCoinJoinClientManager::UpdatedBlockTip(CBlockIndex const*)' --- src/Makefile.am | 2 +- src/coinjoin/walletman.cpp | 2 +- src/init.cpp | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index a36f08be79c5..dd6fc88a0f45 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -501,7 +501,6 @@ libbitcoin_node_a_SOURCES = \ chainlock/signing.cpp \ coinjoin/coinjoin.cpp \ coinjoin/server.cpp \ - coinjoin/walletman.cpp \ consensus/tx_verify.cpp \ dbwrapper.cpp \ deploymentstatus.cpp \ @@ -658,6 +657,7 @@ libbitcoin_wallet_a_SOURCES = \ coinjoin/client.cpp \ coinjoin/interfaces.cpp \ coinjoin/util.cpp \ + coinjoin/walletman.cpp \ wallet/bip39.cpp \ wallet/coinjoin.cpp \ wallet/coincontrol.cpp \ diff --git a/src/coinjoin/walletman.cpp b/src/coinjoin/walletman.cpp index a54ac93795b4..9f582c3a77db 100644 --- a/src/coinjoin/walletman.cpp +++ b/src/coinjoin/walletman.cpp @@ -314,6 +314,6 @@ std::unique_ptr CJWalletManager::make(ChainstateManager& chainm return std::make_unique(chainman, dmnman, mn_metaman, mempool, mn_sync, isman, relay_txes); #else // Cannot be constructed if wallet support isn't built - return nullptr; + assert(false); #endif // ENABLE_WALLET } diff --git a/src/init.cpp b/src/init.cpp index 1a3656130f89..06253c098fda 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2219,8 +2219,10 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } else { assert(!node.cj_walletman); // Can return nullptr if built without wallet support, must check before use +#ifdef ENABLE_WALLET node.cj_walletman = CJWalletManager::make(chainman, *node.dmnman, *node.mn_metaman, *node.mempool, *node.mn_sync, *node.llmq_ctx->isman, !ignores_incoming_txs); +#endif } if (node.cj_walletman) { From 866940736ac3eacdf98dfc720f51506597febe71 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sat, 28 Mar 2026 02:32:24 +0700 Subject: [PATCH 10/13] refactor: remove CoinJoinClientImpl wrapper and return bare pointer to interfaces::CoinJoin::Client --- src/coinjoin/client.h | 16 +++++++++- src/coinjoin/interfaces.cpp | 59 ++----------------------------------- src/interfaces/coinjoin.h | 2 +- src/qt/walletmodel.cpp | 2 +- src/qt/walletmodel.h | 2 +- src/wallet/wallet.cpp | 2 +- 6 files changed, 21 insertions(+), 62 deletions(-) diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index 3785d307099e..6697c1d101f2 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -154,7 +155,7 @@ class CCoinJoinClientSession : public CCoinJoinBaseSession /** Used to keep track of current status of mixing pool */ -class CCoinJoinClientManager +class CCoinJoinClientManager : public interfaces::CoinJoin::Client { private: const std::shared_ptr m_wallet; @@ -230,6 +231,19 @@ class CCoinJoinClientManager EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); void GetJsonInfo(UniValue& obj) const EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); + + // interfaces::CoinJoin::Client overrides + void resetCachedBlocks() override { nCachedNumBlocks = std::numeric_limits::max(); } + void resetPool() override { ResetPool(); } + int getCachedBlocks() override { return nCachedNumBlocks; } + void getJsonInfo(UniValue& obj) override { GetJsonInfo(obj); } + std::vector getSessionStatuses() override { return GetStatuses(); } + std::string getSessionDenoms() override { return GetSessionDenoms(); } + void setCachedBlocks(int nCachedBlocks) override { nCachedNumBlocks = nCachedBlocks; } + void disableAutobackups() override { fCreateAutoBackups = false; } + bool isMixing() override { return IsMixing(); } + bool startMixing() override { return StartMixing(); } + void stopMixing() override { StopMixing(); } }; #endif // BITCOIN_COINJOIN_CLIENT_H diff --git a/src/coinjoin/interfaces.cpp b/src/coinjoin/interfaces.cpp index 8dd645025561..a8137dc57b3d 100644 --- a/src/coinjoin/interfaces.cpp +++ b/src/coinjoin/interfaces.cpp @@ -20,60 +20,6 @@ using wallet::CWallet; namespace coinjoin { namespace { -class CoinJoinClientImpl : public interfaces::CoinJoin::Client -{ - CCoinJoinClientManager& m_clientman; - -public: - explicit CoinJoinClientImpl(CCoinJoinClientManager& clientman) - : m_clientman(clientman) {} - - void resetCachedBlocks() override - { - m_clientman.nCachedNumBlocks = std::numeric_limits::max(); - } - void resetPool() override - { - m_clientman.ResetPool(); - } - void disableAutobackups() override - { - m_clientman.fCreateAutoBackups = false; - } - int getCachedBlocks() override - { - return m_clientman.nCachedNumBlocks; - } - void getJsonInfo(UniValue& obj) override - { - return m_clientman.GetJsonInfo(obj); - } - std::string getSessionDenoms() override - { - return m_clientman.GetSessionDenoms(); - } - std::vector getSessionStatuses() override - { - return m_clientman.GetStatuses(); - } - void setCachedBlocks(int nCachedBlocks) override - { - m_clientman.nCachedNumBlocks = nCachedBlocks; - } - bool isMixing() override - { - return m_clientman.IsMixing(); - } - bool startMixing() override - { - return m_clientman.StartMixing(); - } - void stopMixing() override - { - m_clientman.StopMixing(); - } -}; - class CoinJoinLoaderImpl : public interfaces::CoinJoin::Loader { private: @@ -109,10 +55,9 @@ class CoinJoinLoaderImpl : public interfaces::CoinJoin::Loader { manager().flushWallet(name); } - std::unique_ptr GetClient(const std::string& name) override + interfaces::CoinJoin::Client* GetClient(const std::string& name) override { - auto clientman = manager().getClient(name); - return clientman ? std::make_unique(*clientman) : nullptr; + return manager().getClient(name); } NodeContext& m_node; diff --git a/src/interfaces/coinjoin.h b/src/interfaces/coinjoin.h index c57de9ec3fe2..0929e06674f6 100644 --- a/src/interfaces/coinjoin.h +++ b/src/interfaces/coinjoin.h @@ -46,7 +46,7 @@ class Loader //! Remove wallet from CoinJoin client manager virtual void RemoveWallet(const std::string&) = 0; virtual void FlushWallet(const std::string&) = 0; - virtual std::unique_ptr GetClient(const std::string&) = 0; + virtual Client* GetClient(const std::string&) = 0; }; } // namespace CoinJoin diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index f83760c862da..d92161a62605 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -87,7 +87,7 @@ void WalletModel::setClientModel(ClientModel* client_model) if (!m_client_model) timer->stop(); } -std::unique_ptr WalletModel::coinJoin() const +interfaces::CoinJoin::Client* WalletModel::coinJoin() const { return m_node.coinJoinLoader()->GetClient(m_wallet->getWalletName()); } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 9249282f13e7..081acc07e1c9 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -153,7 +153,7 @@ class WalletModel : public QObject interfaces::Node& node() const { return m_node; } interfaces::Wallet& wallet() const { return *m_wallet; } void setClientModel(ClientModel* client_model); - std::unique_ptr coinJoin() const; + interfaces::CoinJoin::Client* coinJoin() const; QString getWalletName() const; QString getDisplayName() const; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6ed8a693f074..e8735483b5fe 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1654,7 +1654,7 @@ void CWallet::NewKeyPoolCallback() { // Note: GetClient(*this) can return nullptr when this wallet is in the middle of its creation. // Skipping stopMixing() is fine in this case. - if (std::unique_ptr coinjoin_client = coinjoin_available() ? coinjoin_loader().GetClient(GetName()) : nullptr) { + if (auto* coinjoin_client = coinjoin_available() ? coinjoin_loader().GetClient(GetName()) : nullptr) { coinjoin_client->stopMixing(); } nKeysLeftSinceAutoBackup = 0; From 901618144f0a78f7a40ed9c05e01e45077994632 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sun, 29 Mar 2026 18:26:17 +0700 Subject: [PATCH 11/13] fix: avoid dangling pointers to CJ clients --- src/coinjoin/interfaces.cpp | 4 +- src/coinjoin/walletman.cpp | 15 ++++--- src/coinjoin/walletman.h | 5 ++- src/interfaces/coinjoin.h | 5 ++- src/qt/bitcoingui.cpp | 2 +- src/qt/optionsdialog.cpp | 2 +- src/qt/overviewpage.cpp | 66 +++++++++++++++------------ src/qt/walletmodel.cpp | 4 +- src/qt/walletmodel.h | 3 +- src/rpc/coinjoin.cpp | 71 ++++++++++++++++-------------- src/wallet/init.cpp | 17 +++---- src/wallet/test/coinjoin_tests.cpp | 15 ++++--- src/wallet/wallet.cpp | 6 +-- 13 files changed, 121 insertions(+), 94 deletions(-) diff --git a/src/coinjoin/interfaces.cpp b/src/coinjoin/interfaces.cpp index a8137dc57b3d..98e067b82bde 100644 --- a/src/coinjoin/interfaces.cpp +++ b/src/coinjoin/interfaces.cpp @@ -55,9 +55,9 @@ class CoinJoinLoaderImpl : public interfaces::CoinJoin::Loader { manager().flushWallet(name); } - interfaces::CoinJoin::Client* GetClient(const std::string& name) override + bool WithClient(const std::string& name, std::function func) override { - return manager().getClient(name); + return manager().doForClient(name, [&](CCoinJoinClientManager& mgr) { func(mgr); }); } NodeContext& m_node; diff --git a/src/coinjoin/walletman.cpp b/src/coinjoin/walletman.cpp index 9f582c3a77db..69f8a924b0a7 100644 --- a/src/coinjoin/walletman.cpp +++ b/src/coinjoin/walletman.cpp @@ -39,7 +39,7 @@ class CJWalletManagerImpl final : public CJWalletManager public: bool hasQueue(const uint256& hash) const override; - CCoinJoinClientManager* getClient(const std::string& name) override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); + bool doForClient(const std::string& name, const std::function& func) override EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet_manager_map); MessageProcessingResult processMessage(CNode& peer, CChainState& chainstate, CConnman& connman, CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv) override EXCLUSIVE_LOCKS_REQUIRED(!cs_ProcessDSQueue, !cs_wallet_manager_map); std::optional getQueueFromHash(const uint256& hash) const override; @@ -140,11 +140,13 @@ bool CJWalletManagerImpl::hasQueue(const uint256& hash) const return false; } -CCoinJoinClientManager* CJWalletManagerImpl::getClient(const std::string& name) +bool CJWalletManagerImpl::doForClient(const std::string& name, const std::function& func) { LOCK(cs_wallet_manager_map); auto it = m_wallet_manager_map.find(name); - return (it != m_wallet_manager_map.end()) ? it->second.get() : nullptr; + if (it == m_wallet_manager_map.end()) return false; + func(*it->second); + return true; } std::optional CJWalletManagerImpl::getQueueFromHash(const uint256& hash) const @@ -180,9 +182,10 @@ void CJWalletManagerImpl::addWallet(const std::shared_ptr& wall void CJWalletManagerImpl::flushWallet(const std::string& name) { - auto* clientman = Assert(getClient(name)); - clientman->ResetPool(); - clientman->StopMixing(); + doForClient(name, [](CCoinJoinClientManager& clientman) { + clientman.ResetPool(); + clientman.StopMixing(); + }); } void CJWalletManagerImpl::removeWallet(const std::string& name) diff --git a/src/coinjoin/walletman.h b/src/coinjoin/walletman.h index 720a4bdf2f81..5979cdc048f9 100644 --- a/src/coinjoin/walletman.h +++ b/src/coinjoin/walletman.h @@ -10,6 +10,7 @@ #include +#include #include #include @@ -47,7 +48,9 @@ class CJWalletManager : public CValidationInterface public: virtual bool hasQueue(const uint256& hash) const = 0; - virtual CCoinJoinClientManager* getClient(const std::string& name) = 0; + //! Execute func under the wallet manager lock for the client identified by name. + //! Returns true if the client was found and func was called, false otherwise. + virtual bool doForClient(const std::string& name, const std::function& func) = 0; virtual MessageProcessingResult processMessage(CNode& peer, CChainState& chainstate, CConnman& connman, CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv) = 0; virtual std::optional getQueueFromHash(const uint256& hash) const = 0; diff --git a/src/interfaces/coinjoin.h b/src/interfaces/coinjoin.h index 0929e06674f6..f459b1ea41b6 100644 --- a/src/interfaces/coinjoin.h +++ b/src/interfaces/coinjoin.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_INTERFACES_COINJOIN_H #define BITCOIN_INTERFACES_COINJOIN_H +#include #include #include #include @@ -46,7 +47,9 @@ class Loader //! Remove wallet from CoinJoin client manager virtual void RemoveWallet(const std::string&) = 0; virtual void FlushWallet(const std::string&) = 0; - virtual Client* GetClient(const std::string&) = 0; + //! Execute a callback with the CoinJoin client for the given wallet, under the wallet manager lock. + //! Returns false if the wallet was not found. + virtual bool WithClient(const std::string& name, std::function func) = 0; }; } // namespace CoinJoin diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 8f6800b482ec..096f29c5e8e3 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1608,7 +1608,7 @@ void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, const QStri #ifdef ENABLE_WALLET if (enableWallet) { for (const auto& wallet : m_node.walletLoader().getWallets()) { - disableAppNap |= m_node.coinJoinLoader()->GetClient(wallet->getWalletName())->isMixing(); + m_node.coinJoinLoader()->WithClient(wallet->getWalletName(), [&](auto& client) { disableAppNap |= client.isMixing(); }); } } #endif // ENABLE_WALLET diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 7be7120d9f70..b93c22cd6826 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -459,7 +459,7 @@ void OptionsDialog::on_okButton_clicked() #ifdef ENABLE_WALLET if (m_enable_wallet) { for (auto& wallet : model->node().walletLoader().getWallets()) { - model->node().coinJoinLoader()->GetClient(wallet->getWalletName())->resetCachedBlocks(); + model->node().coinJoinLoader()->WithClient(wallet->getWalletName(), [](auto& client) { client.resetCachedBlocks(); }); wallet->markDirty(); } } diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 7f11fde0f9a8..ae5172b0252c 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -337,7 +337,7 @@ void OverviewPage::setWalletModel(WalletModel *model) // Disable coinJoinClient builtin support for automatic backups while we are in GUI, // we'll handle automatic backups and user warnings in coinJoinStatus() - walletModel->coinJoin()->disableAutobackups(); + walletModel->withCoinJoin([](auto& client) { client.disableAutobackups(); }); connect(ui->toggleCoinJoin, &QPushButton::clicked, this, &OverviewPage::toggleCoinJoin); @@ -578,7 +578,11 @@ void OverviewPage::coinJoinStatus(bool fForce) int nBestHeight = clientModel->node().getNumBlocks(); // We are processing more than 1 block per second, we'll just leave - if (nBestHeight > walletModel->coinJoin()->getCachedBlocks() && GetTime() - nLastDSProgressBlockTime <= 1) return; + bool tooFast{false}; + walletModel->withCoinJoin([&](auto& client) { + tooFast = nBestHeight > client.getCachedBlocks() && GetTime() - nLastDSProgressBlockTime <= 1; + }); + if (tooFast) return; nLastDSProgressBlockTime = GetTime(); QString strKeysLeftText(tr("keys left: %1").arg(walletModel->getKeysLeftSinceAutoBackup())); @@ -592,12 +596,15 @@ void OverviewPage::coinJoinStatus(bool fForce) ui->labelCoinJoinEnabled->setToolTip(strKeysLeftText); QString strCoinJoinName = QString::fromStdString(gCoinJoinName); - if (!walletModel->coinJoin()->isMixing()) { - if (nBestHeight != walletModel->coinJoin()->getCachedBlocks()) { - walletModel->coinJoin()->setCachedBlocks(nBestHeight); + bool notMixing{false}; + walletModel->withCoinJoin([&](auto& client) { + notMixing = !client.isMixing(); + if (notMixing && nBestHeight != client.getCachedBlocks()) { + client.setCachedBlocks(nBestHeight); updateCoinJoinProgress(); } - + }); + if (notMixing) { setWidgetsVisible(false); ui->toggleCoinJoin->setText(tr("Start %1").arg(strCoinJoinName)); @@ -655,7 +662,8 @@ void OverviewPage::coinJoinStatus(bool fForce) } } - QString strEnabled = walletModel->coinJoin()->isMixing() ? tr("Enabled") : tr("Disabled"); + QString strEnabled; + walletModel->withCoinJoin([&](auto& client) { strEnabled = client.isMixing() ? tr("Enabled") : tr("Disabled"); }); // Show how many keys left in advanced PS UI mode only if(fShowAdvancedCJUI && !strKeysLeftText.isEmpty()) strEnabled += ", " + strKeysLeftText; ui->labelCoinJoinEnabled->setText(strEnabled); @@ -679,15 +687,16 @@ void OverviewPage::coinJoinStatus(bool fForce) } // check coinjoin status and unlock if needed - if(nBestHeight != walletModel->coinJoin()->getCachedBlocks()) { - // Balance and number of transactions might have changed - walletModel->coinJoin()->setCachedBlocks(nBestHeight); - updateCoinJoinProgress(); - } + walletModel->withCoinJoin([&](auto& client) { + if (nBestHeight != client.getCachedBlocks()) { + // Balance and number of transactions might have changed + client.setCachedBlocks(nBestHeight); + updateCoinJoinProgress(); + } + ui->labelSubmittedDenom->setText(m_privacy ? "####" : QString(client.getSessionDenoms().c_str())); + }); setWidgetsVisible(true); - - ui->labelSubmittedDenom->setText(m_privacy ? "####" : QString(walletModel->coinJoin()->getSessionDenoms().c_str())); } void OverviewPage::toggleCoinJoin(){ @@ -702,7 +711,9 @@ void OverviewPage::toggleCoinJoin(){ settings.setValue("hasMixed", "hasMixed"); } - if (!walletModel->coinJoin()->isMixing()) { + bool mixing{false}; + walletModel->withCoinJoin([&](auto& client) { mixing = client.isMixing(); }); + if (!mixing) { auto& options = walletModel->node().coinJoinOptions(); const CAmount nMinAmount = options.getSmallestDenomination() + options.getMaxCollateralAmount(); if(m_balances.balance < nMinAmount) { @@ -720,7 +731,7 @@ void OverviewPage::toggleCoinJoin(){ if(!ctx.isValid()) { //unlock was cancelled - walletModel->coinJoin()->resetCachedBlocks(); + walletModel->withCoinJoin([](auto& client) { client.resetCachedBlocks(); }); QMessageBox::warning(this, strCoinJoinName, tr("Wallet is locked and user declined to unlock. Disabling %1.").arg(strCoinJoinName), QMessageBox::Ok, QMessageBox::Ok); @@ -731,16 +742,17 @@ void OverviewPage::toggleCoinJoin(){ } - walletModel->coinJoin()->resetCachedBlocks(); - - if (walletModel->coinJoin()->isMixing()) { - ui->toggleCoinJoin->setText(tr("Start %1").arg(strCoinJoinName)); - walletModel->coinJoin()->resetPool(); - walletModel->coinJoin()->stopMixing(); - } else { - ui->toggleCoinJoin->setText(tr("Stop %1").arg(strCoinJoinName)); - walletModel->coinJoin()->startMixing(); - } + walletModel->withCoinJoin([&](auto& client) { + client.resetCachedBlocks(); + if (client.isMixing()) { + ui->toggleCoinJoin->setText(tr("Start %1").arg(strCoinJoinName)); + client.resetPool(); + client.stopMixing(); + } else { + ui->toggleCoinJoin->setText(tr("Stop %1").arg(strCoinJoinName)); + client.startMixing(); + } + }); } void OverviewPage::SetupTransactionList(int nNumItems) @@ -779,5 +791,5 @@ void OverviewPage::DisableCoinJoinCompletely() if (nWalletBackups <= 0) { ui->labelCoinJoinEnabled->setText("(" + tr("Disabled") + ")"); } - walletModel->coinJoin()->stopMixing(); + walletModel->withCoinJoin([](auto& client) { client.stopMixing(); }); } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index d92161a62605..84260faa786b 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -87,9 +87,9 @@ void WalletModel::setClientModel(ClientModel* client_model) if (!m_client_model) timer->stop(); } -interfaces::CoinJoin::Client* WalletModel::coinJoin() const +bool WalletModel::withCoinJoin(std::function func) const { - return m_node.coinJoinLoader()->GetClient(m_wallet->getWalletName()); + return m_node.coinJoinLoader()->WithClient(m_wallet->getWalletName(), std::move(func)); } void WalletModel::updateStatus() diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 081acc07e1c9..3c7445f31325 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -153,7 +154,7 @@ class WalletModel : public QObject interfaces::Node& node() const { return m_node; } interfaces::Wallet& wallet() const { return *m_wallet; } void setClientModel(ClientModel* client_model); - interfaces::CoinJoin::Client* coinJoin() const; + bool withCoinJoin(std::function func) const; QString getWalletName() const; QString getDisplayName() const; diff --git a/src/rpc/coinjoin.cpp b/src/rpc/coinjoin.cpp index 3a2cc40a6977..9509effb2e20 100644 --- a/src/rpc/coinjoin.cpp +++ b/src/rpc/coinjoin.cpp @@ -93,8 +93,9 @@ static RPCHelpMan coinjoin_reset() ValidateCoinJoinArguments(); - auto cj_clientman = CHECK_NONFATAL(node.coinjoin_loader)->GetClient(wallet->GetName()); - CHECK_NONFATAL(cj_clientman)->resetPool(); + CHECK_NONFATAL(CHECK_NONFATAL(node.coinjoin_loader)->WithClient(wallet->GetName(), [](auto& client) { + client.resetPool(); + })); return "Mixing was reset"; }, @@ -133,10 +134,11 @@ static RPCHelpMan coinjoin_start() throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please unlock wallet for mixing with walletpassphrase first."); } - auto cj_clientman = CHECK_NONFATAL(CHECK_NONFATAL(node.coinjoin_loader)->GetClient(wallet->GetName())); - if (!cj_clientman->startMixing()) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "Mixing has been started already."); - } + CHECK_NONFATAL(CHECK_NONFATAL(node.coinjoin_loader)->WithClient(wallet->GetName(), [](auto& client) { + if (!client.startMixing()) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Mixing has been started already."); + } + })); return "Mixing requested"; }, @@ -168,15 +170,15 @@ static RPCHelpMan coinjoin_status() ValidateCoinJoinArguments(); - auto cj_clientman = CHECK_NONFATAL(node.coinjoin_loader)->GetClient(wallet->GetName()); - if (!CHECK_NONFATAL(cj_clientman)->isMixing()) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "No ongoing mix session"); - } - UniValue ret(UniValue::VARR); - for (const auto& str_status : cj_clientman->getSessionStatuses()) { - ret.push_back(str_status); - } + CHECK_NONFATAL(CHECK_NONFATAL(node.coinjoin_loader)->WithClient(wallet->GetName(), [&](auto& client) { + if (!client.isMixing()) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "No ongoing mix session"); + } + for (const auto& str_status : client.getSessionStatuses()) { + ret.push_back(str_status); + } + })); return ret; }, }; @@ -207,14 +209,12 @@ static RPCHelpMan coinjoin_stop() ValidateCoinJoinArguments(); - CHECK_NONFATAL(node.coinjoin_loader); - auto cj_clientman = node.coinjoin_loader->GetClient(wallet->GetName()); - - CHECK_NONFATAL(cj_clientman); - if (!cj_clientman->isMixing()) { - throw JSONRPCError(RPC_INTERNAL_ERROR, "No mix session to stop"); - } - cj_clientman->stopMixing(); + CHECK_NONFATAL(CHECK_NONFATAL(node.coinjoin_loader)->WithClient(wallet->GetName(), [](auto& client) { + if (!client.isMixing()) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "No mix session to stop"); + } + client.stopMixing(); + })); return "Mixing was stopped"; }, @@ -278,11 +278,12 @@ static RPCHelpMan coinjoinsalt_generate() const NodeContext& node = EnsureAnyNodeContext(request.context); if (node.coinjoin_loader != nullptr) { - auto cj_clientman = node.coinjoin_loader->GetClient(wallet->GetName()); - if (cj_clientman != nullptr && cj_clientman->isMixing()) { - throw JSONRPCError(RPC_WALLET_ERROR, - strprintf("Wallet \"%s\" is currently mixing, cannot change salt!", str_wallet)); - } + node.coinjoin_loader->WithClient(wallet->GetName(), [&](auto& client) { + if (client.isMixing()) { + throw JSONRPCError(RPC_WALLET_ERROR, + strprintf("Wallet \"%s\" is currently mixing, cannot change salt!", str_wallet)); + } + }); } const auto wallet_balance{GetBalance(*wallet)}; @@ -380,11 +381,12 @@ static RPCHelpMan coinjoinsalt_set() const NodeContext& node = EnsureAnyNodeContext(request.context); if (node.coinjoin_loader != nullptr) { - auto cj_clientman = node.coinjoin_loader->GetClient(wallet->GetName()); - if (cj_clientman != nullptr && cj_clientman->isMixing()) { - throw JSONRPCError(RPC_WALLET_ERROR, - strprintf("Wallet \"%s\" is currently mixing, cannot change salt!", str_wallet)); - } + node.coinjoin_loader->WithClient(wallet->GetName(), [&](auto& client) { + if (client.isMixing()) { + throw JSONRPCError(RPC_WALLET_ERROR, + strprintf("Wallet \"%s\" is currently mixing, cannot change salt!", str_wallet)); + } + }); } const auto wallet_balance{GetBalance(*wallet)}; @@ -484,8 +486,9 @@ static RPCHelpMan getcoinjoininfo() return obj; } - auto cj_clientman = CHECK_NONFATAL(node.coinjoin_loader)->GetClient(wallet->GetName()); - CHECK_NONFATAL(cj_clientman)->getJsonInfo(obj); + CHECK_NONFATAL(CHECK_NONFATAL(node.coinjoin_loader)->WithClient(wallet->GetName(), [&](auto& client) { + client.getJsonInfo(obj); + })); std::string warning_msg; if (wallet->IsLegacy()) { diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 7bb836c40083..57a2f3bd6ed0 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -210,14 +210,15 @@ void WalletInit::InitCoinJoinSettings(interfaces::CoinJoin::Loader& coinjoin_loa } bool fAutoStart = gArgs.GetBoolArg("-coinjoinautostart", DEFAULT_COINJOIN_AUTOSTART); for (auto& wallet : wallets) { - auto manager = Assert(coinjoin_loader.GetClient(wallet->getWalletName())); - if (wallet->isLocked(/*fForMixing=*/false)) { - manager->stopMixing(); - LogPrintf("CoinJoin: Mixing stopped for locked wallet \"%s\"\n", wallet->getWalletName()); - } else if (fAutoStart) { - manager->startMixing(); - LogPrintf("CoinJoin: Automatic mixing started for wallet \"%s\"\n", wallet->getWalletName()); - } + coinjoin_loader.WithClient(wallet->getWalletName(), [&](auto& client) { + if (wallet->isLocked(/*fForMixing=*/false)) { + client.stopMixing(); + LogPrintf("CoinJoin: Mixing stopped for locked wallet \"%s\"\n", wallet->getWalletName()); + } else if (fAutoStart) { + client.startMixing(); + LogPrintf("CoinJoin: Automatic mixing started for wallet \"%s\"\n", wallet->getWalletName()); + } + }); } LogPrintf("CoinJoin: autostart=%d, multisession=%d," /* Continued */ "sessions=%d, rounds=%d, amount=%d, denoms_goal=%d, denoms_hardcap=%d\n", diff --git a/src/wallet/test/coinjoin_tests.cpp b/src/wallet/test/coinjoin_tests.cpp index 61f3c4d2ae0d..d012daf119d8 100644 --- a/src/wallet/test/coinjoin_tests.cpp +++ b/src/wallet/test/coinjoin_tests.cpp @@ -221,13 +221,14 @@ class CTransactionBuilderTestSetup : public TestChain100Setup BOOST_FIXTURE_TEST_CASE(coinjoin_manager_start_stop_tests, CTransactionBuilderTestSetup) { - auto& cj_man = *Assert(m_node.cj_walletman->getClient("")); - BOOST_CHECK_EQUAL(cj_man.IsMixing(), false); - BOOST_CHECK_EQUAL(cj_man.StartMixing(), true); - BOOST_CHECK_EQUAL(cj_man.IsMixing(), true); - BOOST_CHECK_EQUAL(cj_man.StartMixing(), false); - cj_man.StopMixing(); - BOOST_CHECK_EQUAL(cj_man.IsMixing(), false); + BOOST_CHECK(m_node.cj_walletman->doForClient("", [](auto& cj_man) { + BOOST_CHECK_EQUAL(cj_man.IsMixing(), false); + BOOST_CHECK_EQUAL(cj_man.StartMixing(), true); + BOOST_CHECK_EQUAL(cj_man.IsMixing(), true); + BOOST_CHECK_EQUAL(cj_man.StartMixing(), false); + cj_man.StopMixing(); + BOOST_CHECK_EQUAL(cj_man.IsMixing(), false); + })); } BOOST_FIXTURE_TEST_CASE(CTransactionBuilderTest, CTransactionBuilderTestSetup) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e8735483b5fe..67bce348cebc 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1652,10 +1652,10 @@ void CWallet::UnsetBlankWalletFlag(WalletBatch& batch) void CWallet::NewKeyPoolCallback() { - // Note: GetClient(*this) can return nullptr when this wallet is in the middle of its creation. + // Note: WithClient may not find this wallet when it is in the middle of its creation. // Skipping stopMixing() is fine in this case. - if (auto* coinjoin_client = coinjoin_available() ? coinjoin_loader().GetClient(GetName()) : nullptr) { - coinjoin_client->stopMixing(); + if (coinjoin_available()) { + coinjoin_loader().WithClient(GetName(), [](auto& client) { client.stopMixing(); }); } nKeysLeftSinceAutoBackup = 0; } From 87f7b2e245af3c9b9e23d2512df058a124dede1d Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sun, 29 Mar 2026 20:46:57 +0700 Subject: [PATCH 12/13] refactor: remove duplicated methods from CJWalletManagerImpl to prefer interface's --- src/coinjoin/client.cpp | 33 +++++++++++++++--------------- src/coinjoin/client.h | 26 ++++++++--------------- src/coinjoin/walletman.cpp | 4 ++-- src/interfaces/coinjoin.h | 10 ++++----- src/rpc/coinjoin.cpp | 2 +- src/wallet/test/coinjoin_tests.cpp | 12 +++++------ 6 files changed, 39 insertions(+), 48 deletions(-) diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index 6474cbd708bb..087a7d586ff0 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -61,8 +61,8 @@ void CCoinJoinClientManager::ProcessMessage(CNode& peer, CChainState& active_cha if (!m_mn_sync.IsBlockchainSynced()) return; if (!CheckDiskSpace(gArgs.GetDataDirNet())) { - ResetPool(); - StopMixing(); + resetPool(); + stopMixing(); WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::ProcessMessage -- Not enough disk space, disabling CoinJoin.\n"); return; } @@ -137,16 +137,16 @@ void CCoinJoinClientSession::ProcessMessage(CNode& peer, CChainState& active_cha } } -bool CCoinJoinClientManager::StartMixing() { +bool CCoinJoinClientManager::startMixing() { bool expected{false}; return fMixing.compare_exchange_strong(expected, true); } -void CCoinJoinClientManager::StopMixing() { +void CCoinJoinClientManager::stopMixing() { fMixing = false; } -bool CCoinJoinClientManager::IsMixing() const +bool CCoinJoinClientManager::isMixing() const { return fMixing; } @@ -159,7 +159,7 @@ void CCoinJoinClientSession::ResetPool() WITH_LOCK(cs_coinjoin, SetNull()); } -void CCoinJoinClientManager::ResetPool() +void CCoinJoinClientManager::resetPool() { nCachedLastSuccessBlock = 0; AssertLockNotHeld(cs_deqsessions); @@ -241,7 +241,7 @@ bilingual_str CCoinJoinClientSession::GetStatus(bool fWaitForBlock) const } } -std::vector CCoinJoinClientManager::GetStatuses() const +std::vector CCoinJoinClientManager::getSessionStatuses() const { AssertLockNotHeld(cs_deqsessions); @@ -255,7 +255,7 @@ std::vector CCoinJoinClientManager::GetStatuses() const return ret; } -std::string CCoinJoinClientManager::GetSessionDenoms() +std::string CCoinJoinClientManager::getSessionDenoms() const { std::string strSessionDenoms; @@ -328,7 +328,7 @@ void CCoinJoinClientManager::CheckTimeout() { AssertLockNotHeld(cs_deqsessions); - if (!CCoinJoinClientOptions::IsEnabled() || !IsMixing()) return; + if (!CCoinJoinClientOptions::IsEnabled() || !isMixing()) return; LOCK(cs_deqsessions); for (auto& session : deqSessions) { @@ -605,7 +605,7 @@ bool CCoinJoinClientManager::WaitForAnotherBlock() const bool CCoinJoinClientManager::CheckAutomaticBackup() { - if (!CCoinJoinClientOptions::IsEnabled() || !IsMixing()) return false; + if (!CCoinJoinClientOptions::IsEnabled() || !isMixing()) return false; // We don't need auto-backups for descriptor wallets if (!m_wallet->IsLegacy()) return true; @@ -614,7 +614,7 @@ bool CCoinJoinClientManager::CheckAutomaticBackup() case 0: strAutoDenomResult = _("Automatic backups disabled") + Untranslated(", ") + _("no mixing available."); WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); - StopMixing(); + stopMixing(); m_wallet->nKeysLeftSinceAutoBackup = 0; // no backup, no "keys since last backup" return false; case -1: @@ -640,7 +640,7 @@ bool CCoinJoinClientManager::CheckAutomaticBackup() m_wallet->nKeysLeftSinceAutoBackup); WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); // It's getting really dangerous, stop mixing - StopMixing(); + stopMixing(); return false; } else if (m_wallet->nKeysLeftSinceAutoBackup < COINJOIN_KEYS_THRESHOLD_WARNING) { // Low number of keys left, but it's still more or less safe to continue @@ -862,7 +862,7 @@ bool CCoinJoinClientSession::DoAutomaticDenominating(ChainstateManager& chainman bool CCoinJoinClientManager::DoAutomaticDenominating(ChainstateManager& chainman, CConnman& connman, const CTxMemPool& mempool, bool fDryRun) { - if (!CCoinJoinClientOptions::IsEnabled() || !IsMixing()) return false; + if (!CCoinJoinClientOptions::IsEnabled() || !isMixing()) return false; if (!m_mn_sync.IsBlockchainSynced()) { strAutoDenomResult = _("Can't mix while sync in progress."); @@ -1765,10 +1765,10 @@ void CCoinJoinClientSession::GetJsonInfo(UniValue& obj) const obj.pushKV("entries_count", GetEntriesCount()); } -void CCoinJoinClientManager::GetJsonInfo(UniValue& obj) const +UniValue CCoinJoinClientManager::getJsonInfo() const { - assert(obj.isObject()); - obj.pushKV("running", IsMixing()); + UniValue obj(UniValue::VOBJ); + obj.pushKV("running", isMixing()); UniValue arrSessions(UniValue::VARR); AssertLockNotHeld(cs_deqsessions); @@ -1781,5 +1781,6 @@ void CCoinJoinClientManager::GetJsonInfo(UniValue& obj) const } } obj.pushKV("sessions", arrSessions); + return obj; } diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index 6697c1d101f2..c6a3ad47cc6d 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -198,14 +198,6 @@ class CCoinJoinClientManager : public interfaces::CoinJoin::Client void ProcessMessage(CNode& peer, CChainState& active_chainstate, CConnman& connman, const CTxMemPool& mempool, std::string_view msg_type, CDataStream& vRecv) EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - bool StartMixing(); - void StopMixing(); - bool IsMixing() const; - void ResetPool() EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - - std::vector GetStatuses() const EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - std::string GetSessionDenoms() EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - bool GetMixingMasternodesInfo(std::vector& vecDmnsRet) const EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); /// Passively run mixing in the background according to the configuration in settings @@ -230,20 +222,18 @@ class CCoinJoinClientManager : public interfaces::CoinJoin::Client void DoMaintenance(ChainstateManager& chainman, CConnman& connman, const CTxMemPool& mempool) EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - void GetJsonInfo(UniValue& obj) const EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - // interfaces::CoinJoin::Client overrides void resetCachedBlocks() override { nCachedNumBlocks = std::numeric_limits::max(); } - void resetPool() override { ResetPool(); } - int getCachedBlocks() override { return nCachedNumBlocks; } - void getJsonInfo(UniValue& obj) override { GetJsonInfo(obj); } - std::vector getSessionStatuses() override { return GetStatuses(); } - std::string getSessionDenoms() override { return GetSessionDenoms(); } + int getCachedBlocks() const override { return nCachedNumBlocks; } void setCachedBlocks(int nCachedBlocks) override { nCachedNumBlocks = nCachedBlocks; } void disableAutobackups() override { fCreateAutoBackups = false; } - bool isMixing() override { return IsMixing(); } - bool startMixing() override { return StartMixing(); } - void stopMixing() override { StopMixing(); } + void resetPool() override EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); + UniValue getJsonInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); + std::vector getSessionStatuses() const override EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); + std::string getSessionDenoms() const override EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); + bool isMixing() const override; + bool startMixing() override; + void stopMixing() override; }; #endif // BITCOIN_COINJOIN_CLIENT_H diff --git a/src/coinjoin/walletman.cpp b/src/coinjoin/walletman.cpp index 69f8a924b0a7..ace7d3982259 100644 --- a/src/coinjoin/walletman.cpp +++ b/src/coinjoin/walletman.cpp @@ -183,8 +183,8 @@ void CJWalletManagerImpl::addWallet(const std::shared_ptr& wall void CJWalletManagerImpl::flushWallet(const std::string& name) { doForClient(name, [](CCoinJoinClientManager& clientman) { - clientman.ResetPool(); - clientman.StopMixing(); + clientman.resetPool(); + clientman.stopMixing(); }); } diff --git a/src/interfaces/coinjoin.h b/src/interfaces/coinjoin.h index f459b1ea41b6..0d83999d38b5 100644 --- a/src/interfaces/coinjoin.h +++ b/src/interfaces/coinjoin.h @@ -28,13 +28,13 @@ class Client virtual ~Client() {} virtual void resetCachedBlocks() = 0; virtual void resetPool() = 0; - virtual int getCachedBlocks() = 0; - virtual void getJsonInfo(UniValue& obj) = 0; - virtual std::vector getSessionStatuses() = 0; - virtual std::string getSessionDenoms() = 0; + virtual int getCachedBlocks() const = 0; + virtual UniValue getJsonInfo() const = 0; + virtual std::vector getSessionStatuses() const = 0; + virtual std::string getSessionDenoms() const = 0; virtual void setCachedBlocks(int nCachedBlocks) = 0; virtual void disableAutobackups() = 0; - virtual bool isMixing() = 0; + virtual bool isMixing() const = 0; virtual bool startMixing() = 0; virtual void stopMixing() = 0; }; diff --git a/src/rpc/coinjoin.cpp b/src/rpc/coinjoin.cpp index 9509effb2e20..b5afdb84df12 100644 --- a/src/rpc/coinjoin.cpp +++ b/src/rpc/coinjoin.cpp @@ -487,7 +487,7 @@ static RPCHelpMan getcoinjoininfo() } CHECK_NONFATAL(CHECK_NONFATAL(node.coinjoin_loader)->WithClient(wallet->GetName(), [&](auto& client) { - client.getJsonInfo(obj); + obj.pushKVs(client.getJsonInfo()); })); std::string warning_msg; diff --git a/src/wallet/test/coinjoin_tests.cpp b/src/wallet/test/coinjoin_tests.cpp index d012daf119d8..a33f68be6091 100644 --- a/src/wallet/test/coinjoin_tests.cpp +++ b/src/wallet/test/coinjoin_tests.cpp @@ -222,12 +222,12 @@ class CTransactionBuilderTestSetup : public TestChain100Setup BOOST_FIXTURE_TEST_CASE(coinjoin_manager_start_stop_tests, CTransactionBuilderTestSetup) { BOOST_CHECK(m_node.cj_walletman->doForClient("", [](auto& cj_man) { - BOOST_CHECK_EQUAL(cj_man.IsMixing(), false); - BOOST_CHECK_EQUAL(cj_man.StartMixing(), true); - BOOST_CHECK_EQUAL(cj_man.IsMixing(), true); - BOOST_CHECK_EQUAL(cj_man.StartMixing(), false); - cj_man.StopMixing(); - BOOST_CHECK_EQUAL(cj_man.IsMixing(), false); + BOOST_CHECK_EQUAL(cj_man.isMixing(), false); + BOOST_CHECK_EQUAL(cj_man.startMixing(), true); + BOOST_CHECK_EQUAL(cj_man.isMixing(), true); + BOOST_CHECK_EQUAL(cj_man.startMixing(), false); + cj_man.stopMixing(); + BOOST_CHECK_EQUAL(cj_man.isMixing(), false); })); } From 92d0f348f8ebebb267124ea88c8eea98d74330bb Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 30 Mar 2026 16:08:28 +0700 Subject: [PATCH 13/13] fix: call InitCoinJoin only wallet is added --- src/coinjoin/interfaces.cpp | 12 +++++------- src/wallet/init.cpp | 19 +++++-------------- src/walletinitinterface.h | 6 ++---- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/coinjoin/interfaces.cpp b/src/coinjoin/interfaces.cpp index 98e067b82bde..c74e17d56b69 100644 --- a/src/coinjoin/interfaces.cpp +++ b/src/coinjoin/interfaces.cpp @@ -28,11 +28,6 @@ class CoinJoinLoaderImpl : public interfaces::CoinJoin::Loader return *Assert(m_node.cj_walletman); } - interfaces::WalletLoader& wallet_loader() - { - return *Assert(m_node.wallet_loader); - } - public: explicit CoinJoinLoaderImpl(NodeContext& node) : m_node(node) @@ -44,12 +39,15 @@ class CoinJoinLoaderImpl : public interfaces::CoinJoin::Loader void AddWallet(const std::shared_ptr& wallet) override { manager().addWallet(wallet); - g_wallet_init_interface.InitCoinJoinSettings(*this, wallet_loader()); + manager().doForClient(wallet->GetName(), [&](CCoinJoinClientManager& mgr) { + if (!wallet->IsLocked()) { + g_wallet_init_interface.InitCoinJoinSettings(mgr); + } + }); } void RemoveWallet(const std::string& name) override { manager().removeWallet(name); - g_wallet_init_interface.InitCoinJoinSettings(*this, wallet_loader()); } void FlushWallet(const std::string& name) override { diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 57a2f3bd6ed0..8764cd540965 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -51,7 +51,7 @@ class WalletInit : public WalletInitInterface // Dash Specific Wallet Init void AutoLockMasternodeCollaterals(interfaces::WalletLoader& wallet_loader) const override; - void InitCoinJoinSettings(interfaces::CoinJoin::Loader& coinjoin_loader, interfaces::WalletLoader& wallet_loader) const override; + void InitCoinJoinSettings(CCoinJoinClientManager& mgr) const override; void InitAutoBackup() const override; }; @@ -201,24 +201,15 @@ void WalletInit::AutoLockMasternodeCollaterals(interfaces::WalletLoader& wallet_ } } -void WalletInit::InitCoinJoinSettings(interfaces::CoinJoin::Loader& coinjoin_loader, interfaces::WalletLoader& wallet_loader) const +void WalletInit::InitCoinJoinSettings(CCoinJoinClientManager& mgr) const { - const auto& wallets{wallet_loader.getWallets()}; - CCoinJoinClientOptions::SetEnabled(!wallets.empty() ? gArgs.GetBoolArg("-enablecoinjoin", true) : false); + CCoinJoinClientOptions::SetEnabled(gArgs.GetBoolArg("-enablecoinjoin", true)); if (!CCoinJoinClientOptions::IsEnabled()) { return; } bool fAutoStart = gArgs.GetBoolArg("-coinjoinautostart", DEFAULT_COINJOIN_AUTOSTART); - for (auto& wallet : wallets) { - coinjoin_loader.WithClient(wallet->getWalletName(), [&](auto& client) { - if (wallet->isLocked(/*fForMixing=*/false)) { - client.stopMixing(); - LogPrintf("CoinJoin: Mixing stopped for locked wallet \"%s\"\n", wallet->getWalletName()); - } else if (fAutoStart) { - client.startMixing(); - LogPrintf("CoinJoin: Automatic mixing started for wallet \"%s\"\n", wallet->getWalletName()); - } - }); + if (fAutoStart) { + mgr.startMixing(); } LogPrintf("CoinJoin: autostart=%d, multisession=%d," /* Continued */ "sessions=%d, rounds=%d, amount=%d, denoms_goal=%d, denoms_hardcap=%d\n", diff --git a/src/walletinitinterface.h b/src/walletinitinterface.h index 9222427a39b5..7fbdc4bcde98 100644 --- a/src/walletinitinterface.h +++ b/src/walletinitinterface.h @@ -6,11 +6,9 @@ #define BITCOIN_WALLETINITINTERFACE_H class ArgsManager; +class CCoinJoinClientManager; namespace interfaces { class WalletLoader; -namespace CoinJoin { -class Loader; -} // namespace CoinJoin } // namespace interfaces namespace node { struct NodeContext; @@ -29,7 +27,7 @@ class WalletInitInterface { // Dash Specific WalletInitInterface virtual void AutoLockMasternodeCollaterals(interfaces::WalletLoader& wallet_loader) const = 0; - virtual void InitCoinJoinSettings(interfaces::CoinJoin::Loader& coinjoin_loader, interfaces::WalletLoader& wallet_loader) const = 0; + virtual void InitCoinJoinSettings(CCoinJoinClientManager& mgr) const = 0; virtual void InitAutoBackup() const = 0; virtual ~WalletInitInterface() {}