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