Skip to content

Conversation

knst
Copy link
Collaborator

@knst knst commented Oct 15, 2025

Issue being fixed or feature implemented

Current implementation:

  1. every access to CMasternodeMetaInfo is done by accessing GetMetaInfo under mutex CMasternodeMetaMan::cs
  2. GetMetaInfo returns shared_ptr to object
  3. every read or write of GetMetaInfo is protected by atomic, second mutex CMasternodeMetaInfo::cs; access to object is done indirectly by one more memory hop (because shared_ptr)

It is not efficient, because GetMetaInfo is spread all over code base and not a single case need shared_ptr here; all accesses are brief and short (single read or single write; except RPC where all structure is serialized).

What was done?

This PR is follow-up for PR #6868 with further improvements and simplification of code.

Instead returning shared_ptr with CMasternodeMetaInfo directly access to members of CMasternodeMetaMan under its mutex; all mutexes, atomic and shared_ptr itself are removed from CMasternodeMetaInfo accesses.

It simplified implementation significantly, removed big amount of thread-safety annotations.
Performance is probably improved, but improvement is unmeasurable, because CMasternodeMetaInfo and CMasternodeMetaMan has not been spotted as hot spot in profiler neither hot spot in lock contentions logs.

Also this PR removes duplicated code between coinjoin/client and coinjoin/server by creating a helper in CMasternodeMetaMan

How Has This Been Tested?

Run unit & functional tests

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

@knst knst added this to the 23 milestone Oct 15, 2025
@knst knst changed the title refactor: drop mutex and atomic from CMasternodeMetaInfo, keep object instead shared_ptr refactor: drop mutex and atomic from CMasternodeMetaInfo, access to object directly without shared_ptr Oct 15, 2025
Copy link

coderabbitai bot commented Oct 15, 2025

Walkthrough

The PR refactors masternode metadata into a value-based CMasternodeMetaInfo stored and managed by CMasternodeMetaMan. It replaces many direct per-entry pointer accesses (GetMetaInfo(...)->...) with new manager-level APIs (IsDsqOver, Allow/DisallowMixing, IsValidForMixingTxes, Set/GetLastOutboundAttempt/Success, OutboundFailedTooManyTimes, AddGovernanceVote, Set/Reset/GetPlatformBan, etc.). Call sites across coinjoin, evo (deterministicmns, mnauth), llmq, governance, net, net_processing, rpc, and related files are updated to use the new interfaces; DSQ, mixing, outbound, governance vote, and platform-ban logic are centralized and simplified.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.81% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly conveys the primary refactor of removing mutex and atomic usage from CMasternodeMetaInfo and shifting to direct member access without shared_ptr, clearly reflecting the main change and avoiding unnecessary details.
Description Check ✅ Passed The description clearly outlines the inefficiencies of the current implementation, details the refactoring steps taken to remove mutexes, atomics, and shared_ptr overhead, and notes testing and non-breaking status, making it directly relevant to the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

github-actions bot commented Oct 15, 2025

⚠️ Potential Merge Conflicts Detected

This PR has potential conflicts with the following open PRs:

Please coordinate with the authors of these PRs to avoid merge conflicts.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/net_processing.cpp (1)

3480-3485: Still need the mixing meta lock

IsValidForMixingTxes assumes CMasternodeMetaMan::cs is held; the previous code performed the meta check inside the manager’s critical section. We call it here without locking, so the method’s internal AssertLockHeld will trip (or worse, we race). Acquire WITH_LOCK(mn_metaman.GetCs(), return mn_metaman.IsValidForMixingTxes(...)) (or similar helper) before invoking the new API.

🧹 Nitpick comments (5)
src/masternode/meta.h (4)

56-61: Serialization format change: confirm backward compatibility and handle duplicates

You now serialize a vector of CMasternnodeMetaInfo by value. Ensure this matches prior on-disk format for the same SERIALIZATION_VERSION_STRING. Also, if the vector contains duplicate m_protx_hash entries, later ones are silently ignored by emplace.

Consider using insert_or_assign to make last-wins explicit.

Apply this small change to make behavior explicit:

-        for (auto& mm : tmpMetaInfo) {
-            metaInfos.emplace(mm.m_protx_hash, CMasternodeMetaInfo{std::move(mm)});
-        }
+        for (auto& mm : tmpMetaInfo) {
+            metaInfos.insert_or_assign(mm.m_protx_hash, std::move(mm));
+        }

71-73: Add thread-safety annotation to outbound setters

SetLastOutboundAttempt/Success mutate shared state. For consistency with the new locking model, add the same EXCLUSIVE_LOCKS_REQUIRED(!cs) (or appropriate annotation) used on other mutators.


39-41: Consider guarding against attempt counter overflow

outboundAttemptCount is int and is unbounded; in pathological cases it could overflow. Either use a wider unsigned type or saturate at a reasonable max in SetLastOutboundAttempt.

Example:

-    void SetLastOutboundAttempt(int64_t t) { lastOutboundAttempt = t; ++outboundAttemptCount; }
+    void SetLastOutboundAttempt(int64_t t) {
+        lastOutboundAttempt = t;
+        if (outboundAttemptCount < std::numeric_limits<int>::max()) ++outboundAttemptCount;
+    }

92-106: Serialize/Unserialize: minor efficiency/readability nits

  • Reserve tmpMetaInfo to avoid reallocations (metaInfos.size()).
  • Use insert_or_assign during Unserialize as suggested above.
-        std::vector<CMasternodeMetaInfo> tmpMetaInfo;
-        for (const auto& p : metaInfos) {
-            tmpMetaInfo.emplace_back(p.second);
-        }
+        std::vector<CMasternnodeMetaInfo> tmpMetaInfo;
+        tmpMetaInfo.reserve(metaInfos.size());
+        for (const auto& [_, mi] : metaInfos) {
+            tmpMetaInfo.emplace_back(mi);
+        }

Also applies to: 114-125

src/governance/object.cpp (1)

151-152: Behavioral change: dropped failure handling from AddGovernanceVote

Previously, failure from AddGovernanceVote aborted processing. The new API is void; processing now proceeds unconditionally. Confirm this is intentional and that rate/duplicate checks are fully covered earlier (fileVotes/rate checks).

If you still need a guard, consider having AddGovernanceVote return a bool (no-op vs. insert) and log on false.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f170aed and b57740d.

📒 Files selected for processing (12)
  • src/coinjoin/client.cpp (2 hunks)
  • src/coinjoin/server.cpp (2 hunks)
  • src/evo/deterministicmns.cpp (1 hunks)
  • src/evo/mnauth.cpp (1 hunks)
  • src/governance/object.cpp (1 hunks)
  • src/llmq/dkgsession.cpp (1 hunks)
  • src/llmq/utils.cpp (1 hunks)
  • src/masternode/meta.cpp (4 hunks)
  • src/masternode/meta.h (5 hunks)
  • src/net.cpp (4 hunks)
  • src/net_processing.cpp (2 hunks)
  • src/rpc/evo.cpp (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/llmq/dkgsession.cpp
  • src/llmq/utils.cpp
  • src/net_processing.cpp
  • src/net.cpp
  • src/governance/object.cpp
  • src/coinjoin/server.cpp
  • src/coinjoin/client.cpp
  • src/rpc/evo.cpp
  • src/evo/deterministicmns.cpp
  • src/evo/mnauth.cpp
  • src/masternode/meta.h
  • src/masternode/meta.cpp
src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Masternode lists must use immutable data structures (Immer library) for thread safety

Files:

  • src/evo/deterministicmns.cpp
  • src/evo/mnauth.cpp
  • src/masternode/meta.h
  • src/masternode/meta.cpp
🔇 Additional comments (13)
src/llmq/utils.cpp (1)

915-916: Good API migration

Switched to mn_metaman.GetLastOutboundSuccess(...) keeps logic unchanged and centralizes access.

src/rpc/evo.cpp (1)

1382-1383: Avoids shared_ptr/meta indirection

Using GetInfo(...).ToJson() is simpler and matches value-based storage. Minor perf note: this copies the meta before JSON, which is fine here.

src/llmq/dkgsession.cpp (1)

495-502: Correctly moved to manager-level checks

OutboundFailedTooManyTimes/IsPlatformBanned calls on m_mn_metaman are consistent with the refactor and preserve behavior.

src/coinjoin/server.cpp (2)

81-91: Centralized DSQ gating via IsDsqOver

Good consolidation; logs and ERR_RECENT behavior preserved.


173-179: Consistent DSQ abuse prevention

Using IsDsqOver(...) with GetValidMNsCount() matches the new API and intent to avoid domination.

src/masternode/meta.h (1)

200-206: Public API surface looks coherent

The value-returning GetInfo and centralized IsDsqOver/readers align with the new locking model and reduce unsafe exposure.

Optionally, verify no old pointer-style usages remain:

src/coinjoin/client.cpp (2)

119-124: Centralized DSQ rate-limit — looks good; confirm locking

Using m_mn_metaman.IsDsqOver(...) simplifies and unifies the guard. Please confirm these new manager-level APIs perform internal locking of CMasternodeMetaMan::cs (since CMasternodeMetaInfo dropped its own mutex/atomic) so call sites don’t need to hold the lock explicitly.


1166-1169: Unified check in StartNewQueue — good; verify count semantics

The IsDsqOver(dmn->proTxHash, nMnCount) gate is consistent with ProcessMessage. Please double-check IsDsqOver expects non-weighted MN count (GetValidMNsCount), not weighted, to avoid threshold drift.

src/net.cpp (5)

3445-3449: Swap to GetLastOutboundAttempt — verify time units and locking

nANow is seconds (GetTimestd::chrono::seconds().count()). Please confirm GetLastOutboundAttempt returns the same unit and that internal locking of CMasternodeMetaMan::cs is handled inside the method.


3469-3471: SetLastOutboundSuccess on existing outbound — ensure backoff reset

The success mark here is appropriate. Confirm it also clears/decays failure/backoff state for this MN so future attempts aren’t unnecessarily delayed.


3476-3482: Second use of GetLastOutboundAttempt — same unit/lock check

Same ask as above: confirm return unit matches nANow and that manager-level methods encapsulate required locking.


3527-3528: SetLastOutboundAttempt before dialing — semantics check

Marking the attempt pre-connection is fine for backoff. Please verify failure paths always advance failure counters (or similar) so OutboundFailedTooManyTimes reflects this attempt if connect fails immediately.


3541-3543: OutboundFailedTooManyTimes usage — confirm increment path

Ensure the code path that led here increments the failure count used by OutboundFailedTooManyTimes() within CMasternodeMetaMan, and that concurrency with list updates is internally synchronized.

Comment on lines +671 to 674
if (m_mn_metaman.ResetPlatformBan(opt_proTx->proTxHash, nHeight)) {
LogPrint(BCLog::LLMQ, "%s -- MN %s is failed to Platform revived at height %d\n", __func__,
opt_proTx->proTxHash.ToString(), nHeight);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clarify the log message for platform ban revival.

The log message "is failed to Platform revived" is grammatically incorrect and confusing. Since this block now executes when the revival succeeds (not when it fails, as in the previous implementation), consider revising the message to something clearer like:

LogPrint(BCLog::LLMQ, "%s -- MN %s platform ban revived at height %d\n", __func__,
         opt_proTx->proTxHash.ToString(), nHeight);
🤖 Prompt for AI Agents
In src/evo/deterministicmns.cpp around lines 671-674, the LogPrint message is
grammatically incorrect and misleading ("is failed to Platform revived") and
should reflect that ResetPlatformBan succeeded; update the log string to a
clear, concise message such as "MN %s platform ban revived at height %d"
(preserve __func__ and the proTxHash.ToString() and nHeight parameters) so the
log accurately represents a successful revival.

Comment on lines 105 to 108
if (!peer.IsInboundConn()) {
mn_metaman.GetMetaInfo(mnauth.proRegTxHash)->SetLastOutboundSuccess(GetTime<std::chrono::seconds>().count());
mn_metaman.SetLastOutboundSuccess(mnauth.proRegTxHash, GetTime<std::chrono::seconds>().count());
if (peer.m_masternode_probe_connection) {
LogPrint(BCLog::NET_NETCONN, "CMNAuth::ProcessMessage -- Masternode probe successful for %s, disconnecting. peer=%d\n",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard SetLastOutboundSuccess with mn meta lock

SetLastOutboundSuccess requires the manager mutex to be held (the old code locked via GetMetaInfo). We’re now calling it bare, which violates the contract and will assert. Wrap the call in the manager’s lock (e.g. WITH_LOCK(mn_metaman.GetCs(), mn_metaman.SetLastOutboundSuccess(...));).

🤖 Prompt for AI Agents
In src/evo/mnauth.cpp around lines 105 to 108, SetLastOutboundSuccess is called
without holding mn_metaman's mutex which violates the manager's locking contract
and will assert; wrap the call with the manager lock, e.g. use
WITH_LOCK(mn_metaman.GetCs(),
mn_metaman.SetLastOutboundSuccess(mnauth.proRegTxHash,
GetTime<std::chrono::seconds>().count())); and keep the rest of the logic
unchanged so the call is protected by the manager's mutex.

Comment on lines +89 to 104
bool CMasternodeMetaMan::IsDsqOver(const uint256& protx_hash, int mn_count) const
{
auto metaInfo = GetMetaInfo(proTxHash);
if (metaInfo == nullptr) {
// return a threshold which is slightly above nDsqCount i.e. a no-go
return nDsqCount + 1;
LOCK(cs);
auto it = metaInfos.find(protx_hash);
if (it != metaInfos.end()) {
LogPrint(BCLog::COINJOIN, "DSQUEUE -- node %s is logged\n", protx_hash.ToString());
return false;
}
return metaInfo->GetLastDsq() + nMnCount / 5;
const auto& meta_info = it->second;
int64_t last_dsq = meta_info.m_last_dsq;
int64_t threshold = last_dsq + mn_count / 5;

LogPrint(BCLog::COINJOIN, "DSQUEUE -- mn: %s last_dsq: %d dsq_threshold: %d nDsqCount: %d\n",
protx_hash.ToString(), last_dsq, threshold, nDsqCount);
return last_dsq != 0 && threshold > nDsqCount;
}
Copy link

@coderabbitai coderabbitai bot Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix the inverted logic in IsDsqOver.

There is a critical logic error in this method. Line 93 checks if (it != metaInfos.end()) and returns false when the entry is found. However, line 97 then attempts to access it->second, which would be invalid if the iterator points to end() (i.e., when the entry is not found).

The condition on line 93 should be inverted to check for the "not found" case:

Apply this diff to fix the logic error:

 bool CMasternodeMetaMan::IsDsqOver(const uint256& protx_hash, int mn_count) const
 {
     LOCK(cs);
     auto it = metaInfos.find(protx_hash);
-    if (it != metaInfos.end()) {
+    if (it == metaInfos.end()) {
         LogPrint(BCLog::COINJOIN, "DSQUEUE -- node %s is logged\n", protx_hash.ToString());
         return false;
     }
     const auto& meta_info = it->second;
     int64_t last_dsq = meta_info.m_last_dsq;
     int64_t threshold = last_dsq + mn_count / 5;
     
     LogPrint(BCLog::COINJOIN, "DSQUEUE -- mn: %s last_dsq: %d  dsq_threshold: %d  nDsqCount: %d\n",
              protx_hash.ToString(), last_dsq, threshold, nDsqCount);
     return last_dsq != 0 && threshold > nDsqCount;
 }

Additionally, the log message on line 94 appears incorrect. It says "node %s is logged" when the node is not found. Consider updating it to: "DSQUEUE -- node %s not found in metaInfos\n".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bool CMasternodeMetaMan::IsDsqOver(const uint256& protx_hash, int mn_count) const
{
auto metaInfo = GetMetaInfo(proTxHash);
if (metaInfo == nullptr) {
// return a threshold which is slightly above nDsqCount i.e. a no-go
return nDsqCount + 1;
LOCK(cs);
auto it = metaInfos.find(protx_hash);
if (it != metaInfos.end()) {
LogPrint(BCLog::COINJOIN, "DSQUEUE -- node %s is logged\n", protx_hash.ToString());
return false;
}
return metaInfo->GetLastDsq() + nMnCount / 5;
const auto& meta_info = it->second;
int64_t last_dsq = meta_info.m_last_dsq;
int64_t threshold = last_dsq + mn_count / 5;
LogPrint(BCLog::COINJOIN, "DSQUEUE -- mn: %s last_dsq: %d dsq_threshold: %d nDsqCount: %d\n",
protx_hash.ToString(), last_dsq, threshold, nDsqCount);
return last_dsq != 0 && threshold > nDsqCount;
}
bool CMasternodeMetaMan::IsDsqOver(const uint256& protx_hash, int mn_count) const
{
LOCK(cs);
auto it = metaInfos.find(protx_hash);
if (it == metaInfos.end()) {
LogPrint(BCLog::COINJOIN, "DSQUEUE -- node %s is logged\n", protx_hash.ToString());
return false;
}
const auto& meta_info = it->second;
int64_t last_dsq = meta_info.m_last_dsq;
int64_t threshold = last_dsq + mn_count / 5;
LogPrint(BCLog::COINJOIN, "DSQUEUE -- mn: %s last_dsq: %d dsq_threshold: %d nDsqCount: %d\n",
protx_hash.ToString(), last_dsq, threshold, nDsqCount);
return last_dsq != 0 && threshold > nDsqCount;
}
🤖 Prompt for AI Agents
In src/masternode/meta.cpp around lines 89 to 104, the iterator check is
inverted and leads to invalid access: change the if condition to check for "not
found" (it == metaInfos.end()), log "DSQUEUE -- node %s not found in
metaInfos\n" in that branch and return false there, and otherwise proceed to use
it->second; ensure the rest of the function uses the found meta_info to compute
last_dsq, threshold, and return the correct boolean expression.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@UdjinM6 Thank you for confirming! The fix will prevent the potential crash from dereferencing an invalid iterator. 🎉


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Comment on lines 3607 to 3611
// At this point, the outgoing message serialization version can't change.
const auto meta_info = m_mn_metaman.GetMetaInfo(ban_msg.m_protx_hash);
if (meta_info->SetPlatformBan(true, ban_msg.m_requested_height)) {
LogPrintf("PLATFORMBAN -- forward message to other nodes\n");
m_mn_metaman.RememberPlatformBan(hash, std::move(ban_msg));
if (m_mn_metaman.SetPlatformBan(hash, std::move(ban_msg))) {
LogPrintf("PLATFORMBAN -- hash: %s forward message to other nodes\n", hash.ToString());
ret.m_inventory.emplace_back(MSG_PLATFORM_BAN, hash);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing ban propagation after failed SetPlatformBan

SetPlatformBan now returns false when the entry was already present. Previously we always forwarded by calling RememberPlatformBan regardless. Dropping the relay in this branch breaks propagation of matching quorum signatures (peers stop seeing refreshed bans). Keep forwarding when the manager reports “already had it”—only skip if the call throws/invalidates.

🤖 Prompt for AI Agents
In src/net_processing.cpp around lines 3607 to 3611, the current logic only
forwards the platform-ban message when m_mn_metaman.SetPlatformBan returns true;
change it so that a false return that indicates “already present” still results
in forwarding the ban inventory (i.e., still emplace MSG_PLATFORM_BAN) so
matching quorum signature refreshes propagate — only avoid forwarding if
SetPlatformBan signals an error/invalidation (exception or explicit invalid
status); in practice, treat a normal false (already-existed) the same as true
for relay, and only suppress relay on thrown/invalid cases.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/masternode/meta.h (1)

54-54: Copy constructor may be expensive for large governance maps.

The defaulted copy constructor will perform a deep copy of mapGovernanceObjectsVotedOn, which could be expensive if a masternode has voted on many governance objects. Since CMasternodeMetaInfo is now returned by value from GetInfo(), this copy cost will be incurred on every call.

Consider whether the call sites need a full copy, or if they could work with const references where appropriate. If copies are necessary, document the performance implications or consider optimizing the storage structure if governance maps become large.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b57740d and 6834d68.

📒 Files selected for processing (1)
  • src/masternode/meta.h (5 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/masternode/meta.h
src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Masternode lists must use immutable data structures (Immer library) for thread safety

Files:

  • src/masternode/meta.h
🔇 Additional comments (6)
src/masternode/meta.h (6)

98-106: LGTM!

The serialization correctly creates a vector from the value-based map entries. The use of p.second at line 103 properly extracts the CMasternodeMetaInfo value from the map.


109-124: LGTM!

The unserialization correctly reconstructs the map using mm.m_protx_hash as the key and move semantics for the value. This aligns well with the value-based storage model.


188-190: LGTM!

The private helpers correctly return references (GetMetaInfo) and const references (GetMetaInfoOrDefault) while requiring the lock to be held by the caller. This is an efficient pattern for internal use within the class.


199-199: LGTM!

Returning CMasternodeMetaInfo by value ensures thread safety by providing a copy to the caller, eliminating the need for the caller to hold the lock after the method returns. This is the correct approach for the public API.


205-227: LGTM!

The public API methods are consistently annotated with EXCLUSIVE_LOCKS_REQUIRED(!cs), indicating they acquire the lock internally. This provides a clean, thread-safe interface for callers who don't need to manage locking manually.


72-83: Verify platform ban idempotency at the same height.

The logic at lines 77-78 prevents unsetting a ban at the same height, but allows setting a ban multiple times at the same height (when is_banned == true). This might allow duplicate processing of the same ban message.

Verify whether callers deduplicate ban messages before calling SetPlatformBan, or if this method should enforce stricter idempotency:

@knst knst force-pushed the fix-drop-meta-cs branch from 6834d68 to cdc9825 Compare October 15, 2025 19:28
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/masternode/meta.cpp (1)

93-108: Critical logic error in IsDsqOver already flagged in past review.

This issue was previously identified: Line 97 checks if (it != metaInfos.end()) (meaning "found"), but then returns false and does not use the iterator. Subsequently, line 101 attempts to access it->second, which is undefined behavior when the iterator is at end() (not found).

The condition must be inverted to check for "not found" before returning false.

Apply this diff to fix the logic error:

 bool CMasternodeMetaMan::IsDsqOver(const uint256& protx_hash, int mn_count) const
 {
     LOCK(cs);
     auto it = metaInfos.find(protx_hash);
-    if (it != metaInfos.end()) {
-        LogPrint(BCLog::COINJOIN, "DSQUEUE -- node %s is logged\n", protx_hash.ToString());
+    if (it == metaInfos.end()) {
+        LogPrint(BCLog::COINJOIN, "DSQUEUE -- node %s not found in metaInfos\n", protx_hash.ToString());
         return false;
     }
     const auto& meta_info = it->second;
     int64_t last_dsq = meta_info.m_last_dsq;
     int64_t threshold = last_dsq + mn_count / 5;
     
     LogPrint(BCLog::COINJOIN, "DSQUEUE -- mn: %s last_dsq: %d  dsq_threshold: %d  nDsqCount: %d\n",
              protx_hash.ToString(), last_dsq, threshold, nDsqCount);
     return last_dsq != 0 && threshold > nDsqCount;
 }
🧹 Nitpick comments (1)
src/masternode/meta.cpp (1)

16-18: Minor: Consider renaming the default instance for clarity.

The variable name default_meta_info_meta_info contains redundant repetition. Consider simplifying to default_meta_info or default_metainfo.

Apply this diff to improve naming:

 namespace {
-static const CMasternodeMetaInfo default_meta_info_meta_info{};
+static const CMasternodeMetaInfo default_meta_info{};
 } // anonymous namespace

Then update line 73:

-    if (it == metaInfos.end()) return default_meta_info_meta_info;
+    if (it == metaInfos.end()) return default_meta_info;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6834d68 and cdc9825.

📒 Files selected for processing (2)
  • src/masternode/meta.cpp (4 hunks)
  • src/masternode/meta.h (5 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/masternode/meta.cpp
  • src/masternode/meta.h
src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Masternode lists must use immutable data structures (Immer library) for thread safety

Files:

  • src/masternode/meta.cpp
  • src/masternode/meta.h
🔇 Additional comments (13)
src/masternode/meta.cpp (4)

110-118: Verify atomic increment ordering is correct.

nDsqCount++ (line 112) increments the atomic counter before acquiring the lock. While nDsqCount is atomic and this is safe for the increment itself, confirm that reading nDsqCount.load() on line 116 after incrementing but potentially from a different thread's perspective won't cause any DSQ sequencing issues.

The current pattern appears intentional—incrementing the global counter first, then recording that value under lock—but please verify this ordering satisfies the DSQ queue semantics across concurrent AllowMixing calls.


126-130: Confirm mixing transaction threshold semantics.

Line 129 uses m_mixing_tx_count <= MASTERNODE_MAX_MIXING_TXES, allowing up to 5 mixing transactions inclusive. Ensure this matches the intended policy (i.e., 5 is the maximum allowed, not 4).


189-197: LGTM: ResetPlatformBan correctly handles missing entries.

Returning false when the masternode entry does not exist (line 194) is appropriate—you cannot reset a ban on a nonexistent entry.


199-210: LGTM: SetPlatformBan creates entry if needed and caches ban message.

The method correctly uses GetMetaInfo (which creates an entry if absent), attempts the ban update, and on success caches the ban message. The move semantics for ban_msg are efficient.

src/masternode/meta.h (9)

29-46: LGTM: Public member variables align with value-based refactor.

Exposing CMasternodeMetaInfo fields publicly is appropriate given the refactor removes per-entry locking and centralizes synchronization at the manager level (CMasternodeMetaMan::cs).


56-61: LGTM: Serialization includes all necessary fields.

All members—including m_protx_hash, DSQ/mixing state, governance votes, outbound tracking, and platform ban state—are serialized. This preserves state across restarts.


69-70: LGTM: Outbound attempt/success setters correctly update counters.

SetLastOutboundAttempt increments the attempt counter, while SetLastOutboundSuccess resets it. This logic supports the "too many failed attempts" check.


72-83: Verify platform ban state transition semantics.

The SetPlatformBan logic rejects older heights (line 74-76) and rejects unbanning at the same height as a ban (line 77-79). Confirm this aligns with the intended platform ban state machine—specifically, that unbanning at the same height as banning should indeed fail.

If the semantics are correct, consider adding a brief comment explaining why same-height unbans are rejected, as this is subtle.


92-92: LGTM: Value-based map storage aligns with refactor objectives.

Switching from std::map<uint256, std::shared_ptr<CMasternodeMetaInfo>> to std::map<uint256, CMasternodeMetaInfo> removes pointer indirection and simplifies memory management, as intended by this refactor.


102-103: LGTM: Serialization correctly copies value-based entries.

Iterating metaInfos and emplacing p.second into the temporary vector is correct for the value-based map.


122-122: LGTM: Unserialization efficiently moves entries into map.

Using CMasternodeMetaInfo{std::move(mm)} avoids copying the unserialized object. The key mm.m_protx_hash is extracted before the move, which is correct.


188-189: LGTM: Private accessor lock annotations match implementations.

The EXCLUSIVE_LOCKS_REQUIRED(cs) annotations correctly document that callers must hold cs when invoking these private helpers.


199-227: LGTM: Public API lock annotations correctly specify external requirements.

All public methods use EXCLUSIVE_LOCKS_REQUIRED(!cs), documenting that callers must not hold the lock (the methods acquire it internally). This prevents deadlocks and centralizes locking at the manager level.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants