diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index 88d5ee4f81870..b0b9cd34dc1e7 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -248,7 +248,6 @@ void CCoinJoinClientSession::ResetPool() void CCoinJoinClientManager::ResetPool() { nCachedLastSuccessBlock = 0; - vecMasternodesUsed.clear(); AssertLockNotHeld(cs_deqsessions); LOCK(cs_deqsessions); for (auto& session : deqSessions) { @@ -967,11 +966,14 @@ bool CCoinJoinClientManager::DoAutomaticDenominating(ChainstateManager& chainman // If we've used 90% of the Masternode list then drop the oldest first ~30% int nThreshold_high = nMnCountEnabled * 0.9; int nThreshold_low = nThreshold_high * 0.7; - WalletCJLogPrint(m_wallet, "Checking vecMasternodesUsed: size: %d, threshold: %d\n", (int)vecMasternodesUsed.size(), nThreshold_high); + size_t used_count{m_mn_metaman.GetUsedMasternodesCount()}; - if ((int)vecMasternodesUsed.size() > nThreshold_high) { - vecMasternodesUsed.erase(vecMasternodesUsed.begin(), vecMasternodesUsed.begin() + vecMasternodesUsed.size() - nThreshold_low); - WalletCJLogPrint(m_wallet, " vecMasternodesUsed: new size: %d, threshold: %d\n", (int)vecMasternodesUsed.size(), nThreshold_high); + WalletCJLogPrint(m_wallet, "Checking threshold - used: %d, threshold: %d\n", (int)used_count, nThreshold_high); + + if ((int)used_count > nThreshold_high) { + m_mn_metaman.RemoveUsedMasternodes(used_count - nThreshold_low); + WalletCJLogPrint(m_wallet, " new used: %d, threshold: %d\n", (int)m_mn_metaman.GetUsedMasternodesCount(), + nThreshold_high); } bool fResult = true; @@ -995,9 +997,9 @@ bool CCoinJoinClientManager::DoAutomaticDenominating(ChainstateManager& chainman return fResult; } -void CCoinJoinClientManager::AddUsedMasternode(const COutPoint& outpointMn) +void CCoinJoinClientManager::AddUsedMasternode(const uint256& proTxHash) { - vecMasternodesUsed.push_back(outpointMn); + m_mn_metaman.AddUsedMasternode(proTxHash); } CDeterministicMNCPtr CCoinJoinClientManager::GetRandomNotUsedMasternode() @@ -1005,7 +1007,7 @@ CDeterministicMNCPtr CCoinJoinClientManager::GetRandomNotUsedMasternode() auto mnList = m_dmnman.GetListAtChainTip(); size_t nCountEnabled = mnList.GetValidMNsCount(); - size_t nCountNotExcluded = nCountEnabled - vecMasternodesUsed.size(); + 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); if (nCountNotExcluded < 1) { @@ -1022,15 +1024,14 @@ CDeterministicMNCPtr CCoinJoinClientManager::GetRandomNotUsedMasternode() // shuffle pointers Shuffle(vpMasternodesShuffled.begin(), vpMasternodesShuffled.end(), FastRandomContext()); - std::set excludeSet(vecMasternodesUsed.begin(), vecMasternodesUsed.end()); - - // loop through + // loop through - using direct O(1) lookup instead of creating a set copy for (const auto& dmn : vpMasternodesShuffled) { - if (excludeSet.count(dmn->collateralOutpoint)) { + if (m_mn_metaman.IsUsedMasternode(dmn->proTxHash)) { continue; } - WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- found, masternode=%s\n", __func__, dmn->collateralOutpoint.ToStringShort()); + WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- found, masternode=%s\n", __func__, + dmn->proTxHash.ToString()); return dmn; } @@ -1083,7 +1084,7 @@ bool CCoinJoinClientSession::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, continue; } - m_clientman.AddUsedMasternode(dsq.masternodeOutpoint); + m_clientman.AddUsedMasternode(dmn->proTxHash); if (connman.IsMasternodeOrDisconnectRequested(dmn->pdmnState->netInfo->GetPrimary())) { WalletCJLogPrint(m_wallet, /* Continued */ @@ -1137,7 +1138,7 @@ bool CCoinJoinClientSession::StartNewQueue(CAmount nBalanceNeedsAnonymized, CCon return false; } - m_clientman.AddUsedMasternode(dmn->collateralOutpoint); + m_clientman.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 222122b0329e4..f999c9fa9c592 100644 --- a/src/coinjoin/client.h +++ b/src/coinjoin/client.h @@ -262,9 +262,6 @@ class CCoinJoinClientManager const llmq::CInstantSendManager& m_isman; const std::unique_ptr& m_queueman; - // Keep track of the used Masternodes - std::vector vecMasternodesUsed; - mutable Mutex cs_deqsessions; // TODO: or map ?? std::deque deqSessions GUARDED_BY(cs_deqsessions); @@ -327,7 +324,7 @@ class CCoinJoinClientManager void ProcessPendingDsaRequest(CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions); - void AddUsedMasternode(const COutPoint& outpointMn); + void AddUsedMasternode(const uint256& proTxHash); CDeterministicMNCPtr GetRandomNotUsedMasternode(); void UpdatedSuccessBlock(); diff --git a/src/masternode/meta.cpp b/src/masternode/meta.cpp index adb2aaa302593..3940b08700ce3 100644 --- a/src/masternode/meta.cpp +++ b/src/masternode/meta.cpp @@ -8,7 +8,7 @@ #include #include -const std::string MasternodeMetaStore::SERIALIZATION_VERSION_STRING = "CMasternodeMetaMan-Version-4"; +const std::string MasternodeMetaStore::SERIALIZATION_VERSION_STRING = "CMasternodeMetaMan-Version-5"; CMasternodeMetaMan::CMasternodeMetaMan() : m_db{std::make_unique("mncache.dat", "magicMasternodeCache")} @@ -153,10 +153,44 @@ void CMasternodeMetaMan::RememberPlatformBan(const uint256& inv_hash, PlatformBa m_seen_platform_bans.insert(inv_hash, std::move(msg)); } +void CMasternodeMetaMan::AddUsedMasternode(const uint256& proTxHash) +{ + LOCK(cs); + // Only add if not already present (prevents duplicates) + if (m_used_masternodes_set.insert(proTxHash).second) { + m_used_masternodes.push_back(proTxHash); + } +} + +void CMasternodeMetaMan::RemoveUsedMasternodes(size_t count) +{ + LOCK(cs); + size_t removed = 0; + while (removed < count && !m_used_masternodes.empty()) { + // Remove from both the set and the deque + m_used_masternodes_set.erase(m_used_masternodes.front()); + m_used_masternodes.pop_front(); + ++removed; + } +} + +size_t CMasternodeMetaMan::GetUsedMasternodesCount() const +{ + LOCK(cs); + return m_used_masternodes.size(); +} + +bool CMasternodeMetaMan::IsUsedMasternode(const uint256& proTxHash) const +{ + LOCK(cs); + return m_used_masternodes_set.find(proTxHash) != m_used_masternodes_set.end(); +} + std::string MasternodeMetaStore::ToString() const { LOCK(cs); - return strprintf("Masternodes: meta infos object count: %d, nDsqCount: %d", metaInfos.size(), nDsqCount); + return strprintf("Masternodes: meta infos object count: %d, nDsqCount: %d, used masternodes count: %d", + metaInfos.size(), nDsqCount, m_used_masternodes.size()); } uint256 PlatformBanMessage::GetHash() const { return ::SerializeHash(*this); } diff --git a/src/masternode/meta.h b/src/masternode/meta.h index 217b9d800287d..8b8f468c4841c 100644 --- a/src/masternode/meta.h +++ b/src/masternode/meta.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -139,6 +140,10 @@ class MasternodeMetaStore std::map metaInfos GUARDED_BY(cs); // keep track of dsq count to prevent masternodes from gaming coinjoin queue std::atomic nDsqCount{0}; + // keep track of the used Masternodes for CoinJoin across all wallets + // Using deque for efficient FIFO removal and unordered_set for O(1) lookups + std::deque m_used_masternodes GUARDED_BY(cs); + Uint256HashSet m_used_masternodes_set GUARDED_BY(cs); public: template @@ -149,7 +154,9 @@ class MasternodeMetaStore for (const auto& p : metaInfos) { tmpMetaInfo.emplace_back(*p.second); } - s << SERIALIZATION_VERSION_STRING << tmpMetaInfo << nDsqCount; + // Convert deque to vector for serialization - unordered_set will be rebuilt on deserialization + std::vector tmpUsedMasternodes(m_used_masternodes.begin(), m_used_masternodes.end()); + s << SERIALIZATION_VERSION_STRING << tmpMetaInfo << nDsqCount << tmpUsedMasternodes; } template @@ -164,11 +171,18 @@ class MasternodeMetaStore return; } std::vector tmpMetaInfo; - s >> tmpMetaInfo >> nDsqCount; + std::vector tmpUsedMasternodes; + s >> tmpMetaInfo >> nDsqCount >> tmpUsedMasternodes; + metaInfos.clear(); for (auto& mm : tmpMetaInfo) { metaInfos.emplace(mm.GetProTxHash(), std::make_shared(std::move(mm))); } + + // Convert vector to deque and build unordered_set for O(1) lookups + m_used_masternodes.assign(tmpUsedMasternodes.begin(), tmpUsedMasternodes.end()); + m_used_masternodes_set.clear(); + m_used_masternodes_set.insert(tmpUsedMasternodes.begin(), tmpUsedMasternodes.end()); } void Clear() EXCLUSIVE_LOCKS_REQUIRED(!cs) @@ -176,6 +190,8 @@ class MasternodeMetaStore LOCK(cs); metaInfos.clear(); + m_used_masternodes.clear(); + m_used_masternodes_set.clear(); } std::string ToString() const EXCLUSIVE_LOCKS_REQUIRED(!cs); @@ -257,6 +273,12 @@ class CMasternodeMetaMan : public MasternodeMetaStore bool AlreadyHavePlatformBan(const uint256& inv_hash) const EXCLUSIVE_LOCKS_REQUIRED(!cs); std::optional GetPlatformBan(const uint256& inv_hash) const EXCLUSIVE_LOCKS_REQUIRED(!cs); void RememberPlatformBan(const uint256& inv_hash, PlatformBanMessage&& msg) EXCLUSIVE_LOCKS_REQUIRED(!cs); + + // CoinJoin masternode tracking + void AddUsedMasternode(const uint256& proTxHash) EXCLUSIVE_LOCKS_REQUIRED(!cs); + void RemoveUsedMasternodes(size_t count) EXCLUSIVE_LOCKS_REQUIRED(!cs); + size_t GetUsedMasternodesCount() const EXCLUSIVE_LOCKS_REQUIRED(!cs); + bool IsUsedMasternode(const uint256& proTxHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs); }; #endif // BITCOIN_MASTERNODE_META_H