From 844519e56ea45d59625a9217e4d60ff79e0796c0 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 10 Jul 2026 18:01:00 +0700 Subject: [PATCH 1/5] refactor: inline AddUsedMasternode to CCoinJoinClientSession --- src/coinjoin/client.cpp | 9 ++------- src/coinjoin/client.h | 1 - 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index df14f62e717f..d31565d3c96b 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -909,11 +909,6 @@ bool CCoinJoinClientManager::DoAutomaticDenominating(ChainstateManager& chainman return fResult; } -void CCoinJoinClientManager::AddUsedMasternode(const uint256& proTxHash) -{ - m_mn_metaman.AddUsedMasternode(proTxHash); -} - CDeterministicMNCPtr CCoinJoinClientManager::GetRandomNotUsedMasternode() { auto mnList = m_dmnman.GetListAtChainTip(); @@ -994,7 +989,7 @@ bool CCoinJoinClientSession::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, continue; } - m_clientman.AddUsedMasternode(dmn->proTxHash); + m_mn_metaman.AddUsedMasternode(dmn->proTxHash); if (connman.IsMasternodeOrDisconnectRequested(dmn->pdmnState->netInfo->GetPrimary())) { WalletCJLogPrint(m_wallet, /* Continued */ @@ -1049,7 +1044,7 @@ bool CCoinJoinClientSession::StartNewQueue(CAmount nBalanceNeedsAnonymized, CCon return false; } - m_clientman.AddUsedMasternode(dmn->proTxHash); + m_mn_metaman.AddUsedMasternode(dmn->proTxHash); // skip next mn payments winners if (dmn->pdmnState->nLastPaidHeight + nWeightedMnCount < mnList.GetHeight() + WinnersToSkip()) { diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index 9e4c514f23b8..2bce1f72a0d3 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -209,7 +209,6 @@ class CCoinJoinClientManager : public interfaces::CoinJoin::Client void ProcessPendingDsaRequest(CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - void AddUsedMasternode(const uint256& proTxHash); CDeterministicMNCPtr GetRandomNotUsedMasternode(); void UpdatedSuccessBlock(); From ea70b345545387ae7214c9652e3659ed5aa0e37a Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 10 Jul 2026 19:28:43 +0700 Subject: [PATCH 2/5] refactor: move GetRandomNotUsedMasternode from CCoinJoinClientManager to CCoinJoinClientSession --- src/coinjoin/client.cpp | 10 +++++----- src/coinjoin/client.h | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index d31565d3c96b..a65fabed83a9 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -909,14 +909,14 @@ bool CCoinJoinClientManager::DoAutomaticDenominating(ChainstateManager& chainman return fResult; } -CDeterministicMNCPtr CCoinJoinClientManager::GetRandomNotUsedMasternode() +CDeterministicMNCPtr CCoinJoinClientSession::GetRandomNotUsedMasternode() { auto mnList = m_dmnman.GetListAtChainTip(); size_t nCountEnabled = mnList.GetCounts().enabled(); size_t nCountNotExcluded{nCountEnabled - m_mn_metaman.GetUsedMasternodesCount()}; - WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- %d enabled masternodes, %d masternodes to choose from\n", __func__, nCountEnabled, nCountNotExcluded); + WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- %d enabled masternodes, %d masternodes to choose from\n", __func__, nCountEnabled, nCountNotExcluded); if (nCountNotExcluded < 1) { return nullptr; } @@ -936,12 +936,12 @@ CDeterministicMNCPtr CCoinJoinClientManager::GetRandomNotUsedMasternode() continue; } - WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- found, masternode=%s\n", __func__, + WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- found, masternode=%s\n", __func__, dmn->proTxHash.ToString()); return dmn; } - WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- failed\n", __func__); + WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- failed\n", __func__); return nullptr; } @@ -1037,7 +1037,7 @@ bool CCoinJoinClientSession::StartNewQueue(CAmount nBalanceNeedsAnonymized, CCon // otherwise, try one randomly while (nTries < 10) { - auto dmn = m_clientman.GetRandomNotUsedMasternode(); + auto dmn = GetRandomNotUsedMasternode(); if (!dmn) { strAutoDenomResult = _("Can't find random Masternode."); WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::StartNewQueue -- %s\n", strAutoDenomResult.original); diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index 2bce1f72a0d3..02eb818c3c14 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -99,6 +99,7 @@ class CCoinJoinClientSession : public CCoinJoinBaseSession bool JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman); bool StartNewQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman); + CDeterministicMNCPtr GetRandomNotUsedMasternode(); /// step 0: select denominated inputs and txouts bool SelectDenominate(std::string& strErrorRet, std::vector& vecTxDSInRet); @@ -209,8 +210,6 @@ class CCoinJoinClientManager : public interfaces::CoinJoin::Client void ProcessPendingDsaRequest(CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - CDeterministicMNCPtr GetRandomNotUsedMasternode(); - void UpdatedSuccessBlock(); void UpdatedBlockTip(const CBlockIndex* pindex); From b968667b6553dce72d751583d06431c23ea7cf1a Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sat, 11 Jul 2026 01:20:54 +0700 Subject: [PATCH 3/5] refactor: move nCachedNumBlocks which is used by qt OverviewScreen to its implementation --- src/coinjoin/client.h | 4 ---- src/interfaces/coinjoin.h | 3 --- src/qt/optionsdialog.cpp | 1 - src/qt/overviewpage.cpp | 41 +++++++++++++++++---------------------- src/qt/overviewpage.h | 3 +++ 5 files changed, 21 insertions(+), 31 deletions(-) diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index 02eb818c3c14..4021d8a51d43 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -177,7 +177,6 @@ class CCoinJoinClientManager : public interfaces::CoinJoin::Client // Keep track of current block height int nCachedBlockHeight{0}; - int nCachedNumBlocks{std::numeric_limits::max()}; // used for the overview screen bool fCreateAutoBackups{true}; // builtin support for automatic backups bool WaitForAnotherBlock() const; @@ -218,9 +217,6 @@ class CCoinJoinClientManager : public interfaces::CoinJoin::Client EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); // interfaces::CoinJoin::Client overrides - void resetCachedBlocks() override { nCachedNumBlocks = std::numeric_limits::max(); } - int getCachedBlocks() const override { return nCachedNumBlocks; } - void setCachedBlocks(int nCachedBlocks) override { nCachedNumBlocks = nCachedBlocks; } void disableAutobackups() override { fCreateAutoBackups = false; } void resetPool() override EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); UniValue getJsonInfo() const override EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); diff --git a/src/interfaces/coinjoin.h b/src/interfaces/coinjoin.h index 16037c086fa6..171eb3fa44d5 100644 --- a/src/interfaces/coinjoin.h +++ b/src/interfaces/coinjoin.h @@ -26,13 +26,10 @@ class Client { public: virtual ~Client() {} - virtual void resetCachedBlocks() = 0; virtual void resetPool() = 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() const = 0; virtual bool startMixing() = 0; diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index c623671cea69..f27e8f8e533a 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -465,7 +465,6 @@ void OptionsDialog::on_okButton_clicked() #ifdef ENABLE_WALLET if (m_enable_wallet) { for (auto& wallet : model->node().walletLoader().getWallets()) { - 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 94c2324e363e..c09ebd86c435 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -518,6 +519,10 @@ void OverviewPage::coinJoinStatus(bool fForce) if (!fForce && (clientModel->node().shutdownRequested() || !clientModel->masternodeSync().isBlockchainSynced())) return; + // Forced updates are triggered by mode changes (advanced UI, CoinJoin or privacy toggled), + // the status has to be re-rendered even if the block height didn't change + if (fForce) nCachedNumBlocks = std::numeric_limits::max(); + // Disable any PS UI for masternode or when autobackup is disabled or failed for whatever reason if (clientModel->node().isMasternode() || nWalletBackups <= 0) { DisableCoinJoinCompletely(); @@ -577,11 +582,7 @@ void OverviewPage::coinJoinStatus(bool fForce) int nBestHeight = clientModel->node().getNumBlocks(); // We are processing more than 1 block per second, we'll just leave - bool tooFast{false}; - walletModel->withCoinJoin([&](auto& client) { - tooFast = nBestHeight > client.getCachedBlocks() && GetTime() - nLastDSProgressBlockTime <= 1; - }); - if (tooFast) return; + if (nBestHeight > nCachedNumBlocks && GetTime() - nLastDSProgressBlockTime <= 1) return; nLastDSProgressBlockTime = GetTime(); QString strKeysLeftText(tr("keys left: %1").arg(walletModel->getKeysLeftSinceAutoBackup())); @@ -596,15 +597,11 @@ void OverviewPage::coinJoinStatus(bool fForce) QString strCoinJoinName = QString::fromStdString(gCoinJoinName); bool notMixing{false}; - bool refreshProgress{false}; - walletModel->withCoinJoin([&](auto& client) { - notMixing = !client.isMixing(); - if (notMixing && nBestHeight != client.getCachedBlocks()) { - client.setCachedBlocks(nBestHeight); - refreshProgress = true; - } - }); - if (refreshProgress) updateCoinJoinProgress(); + walletModel->withCoinJoin([&](auto& client) { notMixing = !client.isMixing(); }); + if (notMixing && nBestHeight != nCachedNumBlocks) { + nCachedNumBlocks = nBestHeight; + updateCoinJoinProgress(); + } if (notMixing) { setWidgetsVisible(false); ui->toggleCoinJoin->setText(tr("Start %1").arg(strCoinJoinName)); @@ -688,16 +685,14 @@ void OverviewPage::coinJoinStatus(bool fForce) } // check coinjoin status and unlock if needed - bool refreshProgressTail{false}; + if (nBestHeight != nCachedNumBlocks) { + // Balance and number of transactions might have changed + nCachedNumBlocks = nBestHeight; + updateCoinJoinProgress(); + } walletModel->withCoinJoin([&](auto& client) { - if (nBestHeight != client.getCachedBlocks()) { - // Balance and number of transactions might have changed - client.setCachedBlocks(nBestHeight); - refreshProgressTail = true; - } ui->labelSubmittedDenom->setText(m_privacy ? "####" : QString(client.getSessionDenoms().c_str())); }); - if (refreshProgressTail) updateCoinJoinProgress(); setWidgetsVisible(true); } @@ -734,7 +729,7 @@ void OverviewPage::toggleCoinJoin(){ if(!ctx.isValid()) { //unlock was cancelled - walletModel->withCoinJoin([](auto& client) { client.resetCachedBlocks(); }); + nCachedNumBlocks = std::numeric_limits::max(); QMessageBox::warning(this, strCoinJoinName, tr("Wallet is locked and user declined to unlock. Disabling %1.").arg(strCoinJoinName), QMessageBox::Ok, QMessageBox::Ok); @@ -745,8 +740,8 @@ void OverviewPage::toggleCoinJoin(){ } + nCachedNumBlocks = std::numeric_limits::max(); walletModel->withCoinJoin([&](auto& client) { - client.resetCachedBlocks(); if (client.isMixing()) { ui->toggleCoinJoin->setText(tr("Start %1").arg(strCoinJoinName)); client.resetPool(); diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index 7bbfae77c35b..b1a5c2f047ea 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -11,6 +11,7 @@ #include #include +#include #include class ClientModel; @@ -58,6 +59,8 @@ public Q_SLOTS: BitcoinUnit m_display_bitcoin_unit; bool fShowAdvancedCJUI; int cachedNumISLocks{-1}; + //! Block height at which the CoinJoin status was last rendered + int nCachedNumBlocks{std::numeric_limits::max()}; TxViewDelegate *txdelegate; std::unique_ptr filter; From d68d8c4f597af3d2a13a8e587b094d36fc937166 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 13 Jul 2026 18:48:36 +0700 Subject: [PATCH 4/5] refactor: simplify logging for cj client and removes unused strAutoDenomResult in some instances - one instance of strAutoDenomResult was never read: getSessionStatuses() reads each session's GetStatus(), not the manager's copy - CheckAutomaticBackup collapsed to WalletCJLogPrint - Manager::DoAutomaticDenominating has better logging --- src/coinjoin/client.cpp | 39 +++++++++++++-------------------------- src/coinjoin/client.h | 1 - 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index a65fabed83a9..35747e2ec50c 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -331,9 +331,7 @@ void CCoinJoinClientManager::CheckTimeout() LOCK(cs_deqsessions); for (auto& session : deqSessions) { - if (session.CheckTimeout()) { - strAutoDenomResult = _("Session timed out."); - } + session.CheckTimeout(); } } @@ -611,40 +609,33 @@ bool CCoinJoinClientManager::CheckAutomaticBackup() switch (nWalletBackups) { case 0: - strAutoDenomResult = _("Automatic backups disabled") + Untranslated(", ") + _("no mixing available."); - WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); + WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- Automatic backups disabled, no mixing available.\n"); stopMixing(); m_wallet->nKeysLeftSinceAutoBackup = 0; // no backup, no "keys since last backup" return false; case -1: // Automatic backup failed, nothing else we can do until user fixes the issue manually. - // There is no way to bring user attention in daemon mode, so we just update status and + // There is no way to bring user attention in daemon mode, so we just // keep spamming if debug is on. - strAutoDenomResult = _("ERROR! Failed to create automatic backup") + Untranslated(", ") + _("see debug.log for details."); - WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); + WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- ERROR! Failed to create automatic backup, see debug.log for details.\n"); return false; case -2: // We were able to create automatic backup but keypool was not replenished because wallet is locked. - // There is no way to bring user attention in daemon mode, so we just update status and + // There is no way to bring user attention in daemon mode, so we just // keep spamming if debug is on. - strAutoDenomResult = _("WARNING! Failed to replenish keypool, please unlock your wallet to do so.") + Untranslated(", ") + _("see debug.log for details."); - WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); + WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- WARNING! Failed to replenish keypool, please unlock your wallet to do so, see debug.log for details.\n"); return false; } if (m_wallet->nKeysLeftSinceAutoBackup < COINJOIN_KEYS_THRESHOLD_STOP) { // We should never get here via mixing itself but probably something else is still actively using keypool - strAutoDenomResult = strprintf(_("Very low number of keys left: %d") + Untranslated(", ") + - _("no mixing available."), - m_wallet->nKeysLeftSinceAutoBackup); - WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); + WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- Very low number of keys left: %d, no mixing available.\n", m_wallet->nKeysLeftSinceAutoBackup); // It's getting really dangerous, stop mixing 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 - strAutoDenomResult = strprintf(_("Very low number of keys left: %d"), m_wallet->nKeysLeftSinceAutoBackup); - WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); + WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- Very low number of keys left: %d\n", m_wallet->nKeysLeftSinceAutoBackup); if (fCreateAutoBackups) { WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- Trying to create new backup.\n"); @@ -658,8 +649,7 @@ bool CCoinJoinClientManager::CheckAutomaticBackup() } if (!errorString.original.empty()) { // Things are really broken - strAutoDenomResult = _("ERROR! Failed to create automatic backup") + Untranslated(": ") + errorString; - WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- %s\n", strAutoDenomResult.original); + WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::CheckAutomaticBackup -- ERROR! Failed to create automatic backup: %s\n", errorString.original); return false; } } @@ -864,12 +854,12 @@ bool CCoinJoinClientManager::DoAutomaticDenominating(ChainstateManager& chainman if (!CCoinJoinClientOptions::IsEnabled() || !isMixing()) return false; if (!m_mn_sync.IsBlockchainSynced()) { - strAutoDenomResult = _("Can't mix while sync in progress."); + WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::DoAutomaticDenominating -- Can't mix while sync in progress.\n"); return false; } if (!fDryRun && m_wallet->IsLocked(true)) { - strAutoDenomResult = _("Wallet is locked."); + WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::DoAutomaticDenominating -- Wallet is locked.\n"); return false; } @@ -898,8 +888,7 @@ bool CCoinJoinClientManager::DoAutomaticDenominating(ChainstateManager& chainman if (!CheckAutomaticBackup()) return false; if (WaitForAnotherBlock()) { - strAutoDenomResult = _("Last successful action was too recent."); - WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::DoAutomaticDenominating -- %s\n", strAutoDenomResult.original); + WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::DoAutomaticDenominating -- Last successful action was too recent.\n"); return false; } @@ -1133,9 +1122,7 @@ void CCoinJoinClientManager::ProcessPendingDsaRequest(CConnman& connman) AssertLockNotHeld(cs_deqsessions); LOCK(cs_deqsessions); for (auto& session : deqSessions) { - if (session.ProcessPendingDsaRequest(connman)) { - strAutoDenomResult = _("Mixing in progress…"); - } + session.ProcessPendingDsaRequest(connman); } } diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index 4021d8a51d43..3da980319d7d 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -172,7 +172,6 @@ class CCoinJoinClientManager : public interfaces::CoinJoin::Client int nCachedLastSuccessBlock{0}; int nMinBlocksToWait{1}; // how many blocks to wait for after one successful mixing tx in non-multisession mode - bilingual_str strAutoDenomResult; // Keep track of current block height int nCachedBlockHeight{0}; From a19dee00cdc11d9bc1f7ffd5a4ef0af82a52443e Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 14 Jul 2026 00:42:56 +0700 Subject: [PATCH 5/5] refactor: make nMinBlocksToWait as constexpr static instead member variable --- src/coinjoin/client.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/coinjoin/client.h b/src/coinjoin/client.h index 3da980319d7d..90fd6928783c 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -171,7 +171,8 @@ class CCoinJoinClientManager : public interfaces::CoinJoin::Client std::deque deqSessions GUARDED_BY(cs_deqsessions); int nCachedLastSuccessBlock{0}; - int nMinBlocksToWait{1}; // how many blocks to wait for after one successful mixing tx in non-multisession mode + // how many blocks to wait for after one successful mixing tx in non-multisession mode + static constexpr int nMinBlocksToWait{1}; // Keep track of current block height int nCachedBlockHeight{0};