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