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
@coderabbitai
Copy link

coderabbitai bot commented Oct 15, 2025

Walkthrough

The PR refactors masternode metadata to value-based CMasternodeMetaInfo entries and expands CMasternodeMetaMan with manager-level APIs. Call sites that previously accessed per-entry pointers (GetMetaInfo(...)->...) now call new CMasternodeMetaMan methods (e.g., IsDsqOver, AllowMixing/DisallowMixing, IsValidForMixingTxes, Set/GetLastOutboundAttempt/Success, OutboundFailedTooManyTimes, AddGovernanceVote, Set/Reset/GetPlatformBan). DSQ/mixing guards, outbound tracking, governance vote recording, and platform-ban handling were updated across coinjoin, evo, llmq, governance, net, net_processing, rpc, and related modules. Several CMasternodeMetaInfo internals and serialization/lifecycles were changed.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Peer as Peer
    participant Server as CoinJoin Server
    participant MNMetaMan as CMasternodeMetaMan

    Note right of Peer: DSACCEPT / DSQUEUE request

    Peer->>Server: Send DSQ request (proTxHash, mnCount)
    Server->>MNMetaMan: IsDsqOver(proTxHash, mnCount)?
    alt IsDsqOver == true
        MNMetaMan-->>Server: true
        Server->>Peer: Reject (ERR_RECENT) / log concise message
    else IsDsqOver == false
        MNMetaMan-->>Server: false
        Server->>MNMetaMan: AllowMixing(proTxHash) / initialize queue/session
        Server->>Peer: Proceed with DSQ handling
    end
Loading
sequenceDiagram
    autonumber
    participant Detector as Node/NetProcessing
    participant MNMetaMan as CMasternodeMetaMan
    participant Prop as PlatformBanPropagator

    Note right of Detector: Platform ban detected/forwarded

    Detector->>MNMetaMan: SetPlatformBan(inv_hash, ban_msg)
    MNMetaMan-->>Detector: bool applied?
    alt applied == true
        Detector->>Prop: Enqueue ban for forwarding
        Prop->>Network: Forward ban to peers
    else applied == false
        Detector->>Logs: No-op / already known
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Files/areas needing extra attention:

  • src/masternode/meta.h/.cpp — large API, data-model, serialization, and locking changes.
  • src/coinjoin/client.cpp and src/coinjoin/server.cpp — DSQ guard and mixing lifecycle adjustments.
  • src/net.cpp and src/net_processing.cpp — outbound tracking, connection decisions, and platform-ban forwarding.
  • src/evo/deterministicmns.cpp and src/evo/mnauth.cpp — platform-ban reset and outbound timestamp updates.
  • src/governance/object.cpp — change in governance vote handling (removed early exit on AddGovernanceVote failure).
  • Concurrency: verify new value-based meta store and manager-level locking annotations are correct across call sites.

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main refactoring change: removing mutex and atomic from CMasternodeMetaInfo and switching to direct object access without shared_ptr.
Description check ✅ Passed The description is well-structured and directly related to the changeset, explaining the current inefficient implementation, what was changed, why, and how it was tested.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a657aff and fe3966d.

📒 Files selected for processing (11)
  • 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 (7 hunks)
  • src/net.cpp (4 hunks)
  • src/net_processing.cpp (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/llmq/dkgsession.cpp
  • src/net_processing.cpp
  • src/llmq/utils.cpp
  • src/coinjoin/server.cpp
🧰 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/evo/deterministicmns.cpp
  • src/coinjoin/client.cpp
  • src/net.cpp
  • src/masternode/meta.cpp
  • src/governance/object.cpp
  • src/masternode/meta.h
  • src/evo/mnauth.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/masternode/meta.cpp
  • src/masternode/meta.h
  • src/evo/mnauth.cpp
🧠 Learnings (10)
📓 Common learnings
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/blockprocessor.cpp:217-224
Timestamp: 2025-08-19T14:57:31.801Z
Learning: In PR #6692, knst acknowledged a null pointer dereference issue in ProcessBlock() method where LookupBlockIndex may return nullptr but is passed to gsl::not_null, and created follow-up PR #6789 to address it, consistent with avoiding scope creep in performance-focused PRs.
Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.
Learnt from: kwvg
Repo: dashpay/dash PR: 6718
File: test/functional/test_framework/test_framework.py:2102-2102
Timestamp: 2025-06-09T16:43:20.996Z
Learning: In the test framework consolidation PR (#6718), user kwvg prefers to limit functional changes to those directly related to MasternodeInfo, avoiding scope creep even for minor improvements like error handling consistency.
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T18:42:49.794Z
Learning: Applies to src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp} : Masternode lists must use immutable data structures (Immer library) for thread safety
📚 Learning: 2025-01-07T18:50:44.838Z
Learnt from: knst
Repo: dashpay/dash PR: 6511
File: src/evo/deterministicmns.cpp:1369-1373
Timestamp: 2025-01-07T18:50:44.838Z
Learning: The functions `MigrateDBIfNeeded` and `MigrateDBIfNeeded2` in `src/evo/deterministicmns.cpp` are temporary and will be removed in a future version. Refactoring suggestions for these functions should be avoided.

Applied to files:

  • src/evo/deterministicmns.cpp
📚 Learning: 2025-07-09T15:02:26.899Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6729
File: src/evo/deterministicmns.cpp:1313-1316
Timestamp: 2025-07-09T15:02:26.899Z
Learning: In Dash's masternode transaction validation, `IsVersionChangeValid()` is only called by transaction types that update existing masternode entries (like `ProUpServTx`, `ProUpRegTx`, `ProUpRevTx`), not by `ProRegTx` which creates new entries. This means validation logic in `IsVersionChangeValid()` only applies to the subset of transaction types that actually call it, not all masternode transaction types.

Applied to files:

  • src/coinjoin/client.cpp
📚 Learning: 2025-07-20T18:42:49.794Z
Learnt from: CR
Repo: dashpay/dash PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T18:42:49.794Z
Learning: Applies to src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp} : Masternode lists must use immutable data structures (Immer library) for thread safety

Applied to files:

  • src/masternode/meta.cpp
  • src/masternode/meta.h
📚 Learning: 2025-06-09T16:43:20.996Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6718
File: test/functional/test_framework/test_framework.py:2102-2102
Timestamp: 2025-06-09T16:43:20.996Z
Learning: In the test framework consolidation PR (#6718), user kwvg prefers to limit functional changes to those directly related to MasternodeInfo, avoiding scope creep even for minor improvements like error handling consistency.

Applied to files:

  • src/masternode/meta.cpp
📚 Learning: 2025-11-04T18:24:27.205Z
Learnt from: UdjinM6
Repo: dashpay/dash PR: 6933
File: src/llmq/utils.cpp:284-298
Timestamp: 2025-11-04T18:24:27.205Z
Learning: In consensus-critical code (such as quorum formation, block validation, or deployment activation logic), do not suggest changes to the logic itself even if the implementation appears theoretically incorrect or off-by-one. Consensus rules, once deployed on the Dash network, must be preserved exactly to avoid network forks. Refactoring PRs should maintain perfect behavioral equivalence. Only suggest logic changes if explicitly accompanied by a DIP (Dash Improvement Proposal) or if the maintainer indicates the consensus rule needs to be changed with appropriate activation logic.

Applied to files:

  • src/masternode/meta.cpp
📚 Learning: 2025-08-19T14:57:31.801Z
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/blockprocessor.cpp:217-224
Timestamp: 2025-08-19T14:57:31.801Z
Learning: In PR #6692, knst acknowledged a null pointer dereference issue in ProcessBlock() method where LookupBlockIndex may return nullptr but is passed to gsl::not_null, and created follow-up PR #6789 to address it, consistent with avoiding scope creep in performance-focused PRs.

Applied to files:

  • src/masternode/meta.cpp
📚 Learning: 2025-10-21T11:09:34.688Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6849
File: src/governance/governance.cpp:1339-1343
Timestamp: 2025-10-21T11:09:34.688Z
Learning: In PR #6849, `cmapVoteToObject` was intentionally moved from `GovernanceStore` to `CGovernanceManager` as a memory-only variable and is NOT guarded by `cs_store`. It relies on `CacheMap`'s internal thread-safety instead.

Applied to files:

  • src/governance/object.cpp
📚 Learning: 2025-10-21T11:09:34.688Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6849
File: src/governance/governance.cpp:1339-1343
Timestamp: 2025-10-21T11:09:34.688Z
Learning: In the Dash Core codebase, `CacheMap` (defined in src/cachemap.h) is internally thread-safe and uses its own `mutable CCriticalSection cs` to protect access to its members. Methods like `GetSize()`, `Insert()`, `Get()`, `HasKey()`, etc., can be called without holding external locks.

Applied to files:

  • src/masternode/meta.h
📚 Learning: 2025-09-09T21:36:58.969Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6837
File: src/stats/rawsender.cpp:223-232
Timestamp: 2025-09-09T21:36:58.969Z
Learning: In RawSender class (src/stats/rawsender.cpp), socket access operations (m_sock and m_server) should be protected by the cs_net mutex, not avoiding synchronization entirely. While lock overhead concerns are valid in general, socket operations require proper synchronization via cs_net.

Applied to files:

  • src/evo/mnauth.cpp
🧬 Code graph analysis (3)
src/masternode/meta.cpp (1)
src/masternode/meta.h (9)
  • m_last_dsq (35-35)
  • m_mixing_tx_count (36-36)
  • outboundAttemptCount (41-41)
  • lastOutboundAttempt (42-42)
  • lastOutboundSuccess (43-43)
  • m_platform_ban (46-86)
  • m_platform_ban_updated (48-48)
  • SetLastOutboundSuccess (72-72)
  • SetPlatformBan (74-85)
src/governance/object.cpp (2)
src/rpc/governance.cpp (1)
  • vote (1013-1013)
src/node/interfaces.cpp (2)
  • vote (149-161)
  • vote (149-149)
src/masternode/meta.h (1)
src/masternode/meta.cpp (42)
  • ToJson (38-54)
  • ToJson (38-38)
  • AddGovernanceVote (56-62)
  • AddGovernanceVote (56-56)
  • AddGovernanceVote (132-136)
  • AddGovernanceVote (132-132)
  • RemoveGovernanceObject (64-68)
  • RemoveGovernanceObject (64-64)
  • RemoveGovernanceObject (138-144)
  • RemoveGovernanceObject (138-138)
  • SetLastOutboundAttempt (153-157)
  • SetLastOutboundAttempt (153-153)
  • SetLastOutboundSuccess (159-163)
  • SetLastOutboundSuccess (159-159)
  • SetPlatformBan (199-210)
  • SetPlatformBan (199-199)
  • GetMetaInfo (83-91)
  • GetMetaInfo (83-83)
  • GetMetaInfoOrDefault (70-75)
  • GetMetaInfoOrDefault (70-70)
  • GetInfo (77-81)
  • GetInfo (77-77)
  • IsDsqOver (93-108)
  • IsDsqOver (93-93)
  • AllowMixing (110-118)
  • AllowMixing (110-110)
  • DisallowMixing (120-124)
  • DisallowMixing (120-120)
  • IsValidForMixingTxes (126-130)
  • IsValidForMixingTxes (126-126)
  • GetAndClearDirtyGovernanceObjectHashes (146-151)
  • GetAndClearDirtyGovernanceObjectHashes (146-146)
  • GetLastOutboundAttempt (165-169)
  • GetLastOutboundAttempt (165-165)
  • GetLastOutboundSuccess (171-175)
  • GetLastOutboundSuccess (171-171)
  • OutboundFailedTooManyTimes (177-181)
  • OutboundFailedTooManyTimes (177-177)
  • IsPlatformBanned (183-187)
  • IsPlatformBanned (183-183)
  • ResetPlatformBan (189-197)
  • ResetPlatformBan (189-189)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: check_merge
  • GitHub Check: predict_conflicts
  • GitHub Check: mac-build / Build source
  • GitHub Check: linux64_nowallet-build / Build source
  • GitHub Check: arm-linux-build / Build source
  • GitHub Check: linux64_ubsan-build / Build source
  • GitHub Check: linux64_sqlite-build / Build source
  • GitHub Check: linux64-build / Build source
  • GitHub Check: linux64_fuzz-build / Build source
  • GitHub Check: win64-build / Build source
  • GitHub Check: linux64_tsan-build / Build source
  • GitHub Check: Lint / Run linters
🔇 Additional comments (18)
src/coinjoin/client.cpp (2)

127-132: LGTM! Centralized DSQ rate limiting.

The refactor to use IsDsqOver consolidates DSQ threshold checking into a single API, replacing duplicated last-dsq/threshold calculations. The logic correctly guards AllowMixing and queue operations, skipping masternodes that exceed their DSQ message limit.


177-183: LGTM! Consistent DSQ rate limiting in queue startup.

The IsDsqOver check here mirrors the logic in ProcessMessage, ensuring consistent DSQ rate limiting across both DSQUEUE processing and new queue initiation paths.

src/governance/object.cpp (1)

150-150: Review comment is incorrect—AddGovernanceVote returns void and cannot fail.

The function signature in both CMasternodeMetaInfo::AddGovernanceVote (src/masternode/meta.cpp:56) and CMasternodeMetaMan::AddGovernanceVote (src/masternode/meta.cpp:132) returns void, not bool. The implementation performs a simple map operation (mapGovernanceObjectsVotedOn.emplace() and increment) that always succeeds and has no error paths. The call at line 150 correctly ignores the return value because there is no return value. The reviewer's concern about error handling and inconsistent state is based on an incorrect premise about the function's behavior.

Likely an incorrect or invalid review comment.

src/net.cpp (1)

3406-3406: LGTM: Clean API migration to CMasternodeMetaMan methods.

The changes successfully replace inline metadata access patterns with the new public CMasternodeMetaMan API. The new approach centralizes locking and eliminates shared_ptr indirection, aligning with the PR objectives.

Also applies to: 3429-3429, 3436-3436, 3496-3496, 3509-3509

src/masternode/meta.cpp (9)

13-18: Good use of default instance pattern.

The compile-time constants and default meta-info instance enable clean fallback semantics in GetMetaInfoOrDefault, avoiding optional or pointer returns.


38-54: LGTM: ToJson correctly adapted for value-based semantics.

Direct member access is appropriate now that CMasternodeMetaInfo is accessed by value under the manager's lock.


56-68: LGTM: Governance vote tracking correctly adapted.

Per-call locking removed as these methods are now invoked under CMasternodeMetaMan::cs.


70-91: LGTM: Accessor methods correctly implement value-based semantics.

The three-tier accessor pattern (GetMetaInfoOrDefault, GetInfo, GetMetaInfo) cleanly separates concerns: read-only default fallback, thread-safe copy, and internal mutable access.


110-124: LGTM: Mixing lifecycle methods correctly refactored.

Both methods properly acquire the manager lock before calling GetMetaInfo. The atomic increment of nDsqCount in AllowMixing is safe outside the lock.


126-136: LGTM: Mixing validation and governance vote tracking correctly implemented.

Both methods properly handle locking and delegate to the appropriate accessors.


138-151: LGTM: Governance object cleanup correctly handles all entries.

Single lock acquisition for iteration over all entries is efficient and correct.


153-181: LGTM: Outbound connection tracking correctly consolidated.

All methods properly acquire the manager lock and use appropriate accessors. The threshold check in OutboundFailedTooManyTimes correctly uses the constant defined at the top of the file.


183-227: LGTM: Platform ban management correctly implemented.

The three-method pattern (IsPlatformBanned, ResetPlatformBan, SetPlatformBan) properly handles ban state with appropriate locking. ResetPlatformBan correctly returns false when the entry doesn't exist.

src/masternode/meta.h (5)

31-86: LGTM: CMasternodeMetaInfo correctly refactored to value-based struct.

The public member access, explicit constructor, and defaulted copy constructor are appropriate for value semantics. The SetPlatformBan method correctly handles height-based update ordering to prevent stale updates.


88-150: LGTM: MasternodeMetaStore correctly adapted for value-based storage.

The serialization correctly handles value-based CMasternodeMetaInfo entries, using move semantics during unserialization to avoid unnecessary copies.


204-206: LGTM: Private accessors correctly encapsulate internal access patterns.

The distinction between mutable GetMetaInfo and const GetMetaInfoOrDefault with proper locking annotations ensures type-safe access to metadata entries.


217-238: LGTM: Public API correctly declares DSQ, mixing, and outbound tracking methods.

All locking annotations are correct (EXCLUSIVE_LOCKS_REQUIRED(!cs) for methods that acquire the lock internally). Method signatures match the implementations in meta.cpp.


240-244: LGTM: Platform ban API correctly declared with efficient move semantics.

The use of rvalue reference in SetPlatformBan and optional return in GetPlatformBan follow modern C++ best practices. Locking annotations are correct.


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.

@github-actions
Copy link

github-actions bot commented Oct 15, 2025

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

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 682
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 3607 to 3608
// 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.

@github-actions
Copy link

This pull request has conflicts, please rebase.

@github-actions
Copy link

github-actions bot commented Nov 4, 2025

This pull request has conflicts, please rebase.

@knst knst force-pushed the fix-drop-meta-cs branch from a657aff to fe3966d Compare November 4, 2025 19:24

if (auto meta_info = m_mn_metaman.GetMetaInfo(opt_proTx->proTxHash, false);
!meta_info || !meta_info->SetPlatformBan(false, nHeight)) {
if (m_mn_metaman.ResetPlatformBan(opt_proTx->proTxHash, nHeight)) {
Copy link

Choose a reason for hiding this comment

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

shouldn't this be

Suggested change
if (m_mn_metaman.ResetPlatformBan(opt_proTx->proTxHash, nHeight)) {
if (!m_mn_metaman.ResetPlatformBan(opt_proTx->proTxHash, nHeight)) {

?

static constexpr int MASTERNODE_MAX_MIXING_TXES{5};

namespace {
static const CMasternodeMetaInfo default_meta_info_meta_info{};
Copy link

Choose a reason for hiding this comment

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

that's a weird name... maybe

Suggested change
static const CMasternodeMetaInfo default_meta_info_meta_info{};
static const CMasternodeMetaInfo default_meta_info{};

?


LOCK(cs);
auto& mm = GetMetaInfo(proTxHash);
mm.m_last_dsq = nDsqCount.load();
Copy link

Choose a reason for hiding this comment

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

Could also drop nDsqCount++; above, do

Suggested change
mm.m_last_dsq = nDsqCount.load();
mm.m_last_dsq = ++nDsqCount;

and make it pure int64_t nDsqCount GUARDED_BY(cs) {0}; since it's protected by cs everywhere else anyway now

Comment on lines +141 to +142
for (auto& p : metaInfos) {
p.second.RemoveGovernanceObject(nGovernanceObjectHash);
Copy link

Choose a reason for hiding this comment

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

Suggested change
for (auto& p : metaInfos) {
p.second.RemoveGovernanceObject(nGovernanceObjectHash);
for (auto& [_, meta_info] : metaInfos) {
meta_info.RemoveGovernanceObject(nGovernanceObjectHash);

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