Skip to content

Commit e4fa575

Browse files
Merge #6875: feat: share vecMasternodesUsed across all wallets, improve its handling
eb3be09 refactor: code style/log text adjustments (UdjinM6) f50d09a fix: update `MasternodeMetaStore::ToString()` (UdjinM6) 13a28fa perf: optimize CoinJoin masternode tracking with hybrid data structure (UdjinM6) 2c82532 feat: share `vecMasternodesUsed` across all wallets, improve its handling (UdjinM6) Pull request description: ## Issue being fixed or feature implemented `vecMasternodesUsed` has a few issues: - it's shared only across mixing sessions in a single wallet, wallets don't share this data so it's possible they are going to waste tries in `StartNewQueue()` and fail to start mixing because of that - we start with a fresh vector each time which also can result in failed mixing attempts - there are two threads (scheduler and net) where it can be accessed but it's not protected by any mutex atm - it still stores masternode outpoints while most of our codebase uses protxhash-es as masternode ids ## What was done? - moved it to `MasternodeMetaStore` and renamed it to `m_used_masternodes` - changed to `std::vector<uint256>` (using `proTxHash` now) - implemented proper encapsulation through accessor methods - `m_used_masternodes` is no longer accessed via multiple threads (because it's not cleared in `ResetPool()`) but I made it thread-safe anyway just in case (using existing `Mutex cs`) - made it persistent via serialization (with version bump 4 -> 5) ## How Has This Been Tested? Mixing in multiple wallets at once ## Breaking Changes n/a ## Checklist: - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [ ] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: knst: utACK eb3be09 kwvg: utACK eb3be09 Tree-SHA512: 68527f4edb255baf879055d622d72e3aff9807ee37c59dca3b7f4436df8bba67fddba9331b5d8af50ca849fdcfbb57ef003bf093856fc1ba210d43deecd8048c
2 parents b82450a + eb3be09 commit e4fa575

File tree

4 files changed

+77
-23
lines changed

4 files changed

+77
-23
lines changed

src/coinjoin/client.cpp

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,6 @@ void CCoinJoinClientSession::ResetPool()
248248
void CCoinJoinClientManager::ResetPool()
249249
{
250250
nCachedLastSuccessBlock = 0;
251-
vecMasternodesUsed.clear();
252251
AssertLockNotHeld(cs_deqsessions);
253252
LOCK(cs_deqsessions);
254253
for (auto& session : deqSessions) {
@@ -967,11 +966,14 @@ bool CCoinJoinClientManager::DoAutomaticDenominating(ChainstateManager& chainman
967966
// If we've used 90% of the Masternode list then drop the oldest first ~30%
968967
int nThreshold_high = nMnCountEnabled * 0.9;
969968
int nThreshold_low = nThreshold_high * 0.7;
970-
WalletCJLogPrint(m_wallet, "Checking vecMasternodesUsed: size: %d, threshold: %d\n", (int)vecMasternodesUsed.size(), nThreshold_high);
969+
size_t used_count{m_mn_metaman.GetUsedMasternodesCount()};
971970

972-
if ((int)vecMasternodesUsed.size() > nThreshold_high) {
973-
vecMasternodesUsed.erase(vecMasternodesUsed.begin(), vecMasternodesUsed.begin() + vecMasternodesUsed.size() - nThreshold_low);
974-
WalletCJLogPrint(m_wallet, " vecMasternodesUsed: new size: %d, threshold: %d\n", (int)vecMasternodesUsed.size(), nThreshold_high);
971+
WalletCJLogPrint(m_wallet, "Checking threshold - used: %d, threshold: %d\n", (int)used_count, nThreshold_high);
972+
973+
if ((int)used_count > nThreshold_high) {
974+
m_mn_metaman.RemoveUsedMasternodes(used_count - nThreshold_low);
975+
WalletCJLogPrint(m_wallet, " new used: %d, threshold: %d\n", (int)m_mn_metaman.GetUsedMasternodesCount(),
976+
nThreshold_high);
975977
}
976978

977979
bool fResult = true;
@@ -995,17 +997,17 @@ bool CCoinJoinClientManager::DoAutomaticDenominating(ChainstateManager& chainman
995997
return fResult;
996998
}
997999

998-
void CCoinJoinClientManager::AddUsedMasternode(const COutPoint& outpointMn)
1000+
void CCoinJoinClientManager::AddUsedMasternode(const uint256& proTxHash)
9991001
{
1000-
vecMasternodesUsed.push_back(outpointMn);
1002+
m_mn_metaman.AddUsedMasternode(proTxHash);
10011003
}
10021004

10031005
CDeterministicMNCPtr CCoinJoinClientManager::GetRandomNotUsedMasternode()
10041006
{
10051007
auto mnList = m_dmnman.GetListAtChainTip();
10061008

10071009
size_t nCountEnabled = mnList.GetValidMNsCount();
1008-
size_t nCountNotExcluded = nCountEnabled - vecMasternodesUsed.size();
1010+
size_t nCountNotExcluded{nCountEnabled - m_mn_metaman.GetUsedMasternodesCount()};
10091011

10101012
WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- %d enabled masternodes, %d masternodes to choose from\n", __func__, nCountEnabled, nCountNotExcluded);
10111013
if (nCountNotExcluded < 1) {
@@ -1022,15 +1024,14 @@ CDeterministicMNCPtr CCoinJoinClientManager::GetRandomNotUsedMasternode()
10221024
// shuffle pointers
10231025
Shuffle(vpMasternodesShuffled.begin(), vpMasternodesShuffled.end(), FastRandomContext());
10241026

1025-
std::set<COutPoint> excludeSet(vecMasternodesUsed.begin(), vecMasternodesUsed.end());
1026-
1027-
// loop through
1027+
// loop through - using direct O(1) lookup instead of creating a set copy
10281028
for (const auto& dmn : vpMasternodesShuffled) {
1029-
if (excludeSet.count(dmn->collateralOutpoint)) {
1029+
if (m_mn_metaman.IsUsedMasternode(dmn->proTxHash)) {
10301030
continue;
10311031
}
10321032

1033-
WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- found, masternode=%s\n", __func__, dmn->collateralOutpoint.ToStringShort());
1033+
WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- found, masternode=%s\n", __func__,
1034+
dmn->proTxHash.ToString());
10341035
return dmn;
10351036
}
10361037

@@ -1083,7 +1084,7 @@ bool CCoinJoinClientSession::JoinExistingQueue(CAmount nBalanceNeedsAnonymized,
10831084
continue;
10841085
}
10851086

1086-
m_clientman.AddUsedMasternode(dsq.masternodeOutpoint);
1087+
m_clientman.AddUsedMasternode(dmn->proTxHash);
10871088

10881089
if (connman.IsMasternodeOrDisconnectRequested(dmn->pdmnState->netInfo->GetPrimary())) {
10891090
WalletCJLogPrint(m_wallet, /* Continued */
@@ -1137,7 +1138,7 @@ bool CCoinJoinClientSession::StartNewQueue(CAmount nBalanceNeedsAnonymized, CCon
11371138
return false;
11381139
}
11391140

1140-
m_clientman.AddUsedMasternode(dmn->collateralOutpoint);
1141+
m_clientman.AddUsedMasternode(dmn->proTxHash);
11411142

11421143
// skip next mn payments winners
11431144
if (dmn->pdmnState->nLastPaidHeight + nWeightedMnCount < mnList.GetHeight() + WinnersToSkip()) {

src/coinjoin/client.h

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,9 +262,6 @@ class CCoinJoinClientManager
262262
const llmq::CInstantSendManager& m_isman;
263263
const std::unique_ptr<CCoinJoinClientQueueManager>& m_queueman;
264264

265-
// Keep track of the used Masternodes
266-
std::vector<COutPoint> vecMasternodesUsed;
267-
268265
mutable Mutex cs_deqsessions;
269266
// TODO: or map<denom, CCoinJoinClientSession> ??
270267
std::deque<CCoinJoinClientSession> deqSessions GUARDED_BY(cs_deqsessions);
@@ -327,7 +324,7 @@ class CCoinJoinClientManager
327324

328325
void ProcessPendingDsaRequest(CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_deqsessions);
329326

330-
void AddUsedMasternode(const COutPoint& outpointMn);
327+
void AddUsedMasternode(const uint256& proTxHash);
331328
CDeterministicMNCPtr GetRandomNotUsedMasternode();
332329

333330
void UpdatedSuccessBlock();

src/masternode/meta.cpp

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#include <univalue.h>
99
#include <util/time.h>
1010

11-
const std::string MasternodeMetaStore::SERIALIZATION_VERSION_STRING = "CMasternodeMetaMan-Version-4";
11+
const std::string MasternodeMetaStore::SERIALIZATION_VERSION_STRING = "CMasternodeMetaMan-Version-5";
1212

1313
CMasternodeMetaMan::CMasternodeMetaMan() :
1414
m_db{std::make_unique<db_type>("mncache.dat", "magicMasternodeCache")}
@@ -153,10 +153,44 @@ void CMasternodeMetaMan::RememberPlatformBan(const uint256& inv_hash, PlatformBa
153153
m_seen_platform_bans.insert(inv_hash, std::move(msg));
154154
}
155155

156+
void CMasternodeMetaMan::AddUsedMasternode(const uint256& proTxHash)
157+
{
158+
LOCK(cs);
159+
// Only add if not already present (prevents duplicates)
160+
if (m_used_masternodes_set.insert(proTxHash).second) {
161+
m_used_masternodes.push_back(proTxHash);
162+
}
163+
}
164+
165+
void CMasternodeMetaMan::RemoveUsedMasternodes(size_t count)
166+
{
167+
LOCK(cs);
168+
size_t removed = 0;
169+
while (removed < count && !m_used_masternodes.empty()) {
170+
// Remove from both the set and the deque
171+
m_used_masternodes_set.erase(m_used_masternodes.front());
172+
m_used_masternodes.pop_front();
173+
++removed;
174+
}
175+
}
176+
177+
size_t CMasternodeMetaMan::GetUsedMasternodesCount() const
178+
{
179+
LOCK(cs);
180+
return m_used_masternodes.size();
181+
}
182+
183+
bool CMasternodeMetaMan::IsUsedMasternode(const uint256& proTxHash) const
184+
{
185+
LOCK(cs);
186+
return m_used_masternodes_set.find(proTxHash) != m_used_masternodes_set.end();
187+
}
188+
156189
std::string MasternodeMetaStore::ToString() const
157190
{
158191
LOCK(cs);
159-
return strprintf("Masternodes: meta infos object count: %d, nDsqCount: %d", metaInfos.size(), nDsqCount);
192+
return strprintf("Masternodes: meta infos object count: %d, nDsqCount: %d, used masternodes count: %d",
193+
metaInfos.size(), nDsqCount, m_used_masternodes.size());
160194
}
161195

162196
uint256 PlatformBanMessage::GetHash() const { return ::SerializeHash(*this); }

src/masternode/meta.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <unordered_lru_cache.h>
1515

1616
#include <atomic>
17+
#include <deque>
1718
#include <map>
1819
#include <memory>
1920
#include <optional>
@@ -139,6 +140,10 @@ class MasternodeMetaStore
139140
std::map<uint256, CMasternodeMetaInfoPtr> metaInfos GUARDED_BY(cs);
140141
// keep track of dsq count to prevent masternodes from gaming coinjoin queue
141142
std::atomic<int64_t> nDsqCount{0};
143+
// keep track of the used Masternodes for CoinJoin across all wallets
144+
// Using deque for efficient FIFO removal and unordered_set for O(1) lookups
145+
std::deque<uint256> m_used_masternodes GUARDED_BY(cs);
146+
Uint256HashSet m_used_masternodes_set GUARDED_BY(cs);
142147

143148
public:
144149
template<typename Stream>
@@ -149,7 +154,9 @@ class MasternodeMetaStore
149154
for (const auto& p : metaInfos) {
150155
tmpMetaInfo.emplace_back(*p.second);
151156
}
152-
s << SERIALIZATION_VERSION_STRING << tmpMetaInfo << nDsqCount;
157+
// Convert deque to vector for serialization - unordered_set will be rebuilt on deserialization
158+
std::vector<uint256> tmpUsedMasternodes(m_used_masternodes.begin(), m_used_masternodes.end());
159+
s << SERIALIZATION_VERSION_STRING << tmpMetaInfo << nDsqCount << tmpUsedMasternodes;
153160
}
154161

155162
template<typename Stream>
@@ -164,18 +171,27 @@ class MasternodeMetaStore
164171
return;
165172
}
166173
std::vector<CMasternodeMetaInfo> tmpMetaInfo;
167-
s >> tmpMetaInfo >> nDsqCount;
174+
std::vector<uint256> tmpUsedMasternodes;
175+
s >> tmpMetaInfo >> nDsqCount >> tmpUsedMasternodes;
176+
168177
metaInfos.clear();
169178
for (auto& mm : tmpMetaInfo) {
170179
metaInfos.emplace(mm.GetProTxHash(), std::make_shared<CMasternodeMetaInfo>(std::move(mm)));
171180
}
181+
182+
// Convert vector to deque and build unordered_set for O(1) lookups
183+
m_used_masternodes.assign(tmpUsedMasternodes.begin(), tmpUsedMasternodes.end());
184+
m_used_masternodes_set.clear();
185+
m_used_masternodes_set.insert(tmpUsedMasternodes.begin(), tmpUsedMasternodes.end());
172186
}
173187

174188
void Clear() EXCLUSIVE_LOCKS_REQUIRED(!cs)
175189
{
176190
LOCK(cs);
177191

178192
metaInfos.clear();
193+
m_used_masternodes.clear();
194+
m_used_masternodes_set.clear();
179195
}
180196

181197
std::string ToString() const EXCLUSIVE_LOCKS_REQUIRED(!cs);
@@ -257,6 +273,12 @@ class CMasternodeMetaMan : public MasternodeMetaStore
257273
bool AlreadyHavePlatformBan(const uint256& inv_hash) const EXCLUSIVE_LOCKS_REQUIRED(!cs);
258274
std::optional<PlatformBanMessage> GetPlatformBan(const uint256& inv_hash) const EXCLUSIVE_LOCKS_REQUIRED(!cs);
259275
void RememberPlatformBan(const uint256& inv_hash, PlatformBanMessage&& msg) EXCLUSIVE_LOCKS_REQUIRED(!cs);
276+
277+
// CoinJoin masternode tracking
278+
void AddUsedMasternode(const uint256& proTxHash) EXCLUSIVE_LOCKS_REQUIRED(!cs);
279+
void RemoveUsedMasternodes(size_t count) EXCLUSIVE_LOCKS_REQUIRED(!cs);
280+
size_t GetUsedMasternodesCount() const EXCLUSIVE_LOCKS_REQUIRED(!cs);
281+
bool IsUsedMasternode(const uint256& proTxHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs);
260282
};
261283

262284
#endif // BITCOIN_MASTERNODE_META_H

0 commit comments

Comments
 (0)