-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: share vecMasternodesUsed across all wallets, improve its handling
#6875
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
|
WalkthroughReplaces CCoinJoinClientManager's in-memory vecMasternodesUsed with CMasternodeMetaMan-based tracking keyed by masternode proTxHash. AddUsedMasternode signature changed to accept a uint256 proTxHash and all call sites updated. Client selection, exclusion, queue-joining, and logging now use proTxHash and delegate counting/removal to m_mn_metaman. CMasternodeMetaMan adds m_used_masternodes (deque) and m_used_masternodes_set, thread-safe APIs (AddUsedMasternode, RemoveUsedMasternodes, GetUsedMasternodesCount, IsUsedMasternode), updates serialization version to "CMasternodeMetaMan-Version-5", and MasternodeMetaStore::ToString() now performs internal locking. Sequence Diagram(s)sequenceDiagram
participant Client as CCoinJoinClientManager
participant MNMeta as CMasternodeMetaMan
participant MN as Masternode (dmn)
participant Queue as CoinJoinQueue
Client->>MNMeta: GetUsedMasternodesCount()
Note right of MNMeta #BFD7EA: determine exclusion count
Client->>MN: Request candidate masternode(s)
MN->>MNMeta: IsUsedMasternode(proTxHash)?
alt not used
MN-->Client: return candidate (proTxHash)
else used
MN-->Client: skip candidate
end
Client->>Queue: JoinExistingQueue(proTxHash) / StartNewQueue(proTxHash)
Note right of Client #E8F5E9: use proTxHash as identity for queue/pending DSA
alt denom completed successfully
Client->>MNMeta: AddUsedMasternode(proTxHash)
end
Client->>MNMeta: RemoveUsedMasternodes(n)
Note right of MNMeta #FFF3E0: evict oldest n proTxHashes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
-
src/coinjoin/client.cpp(5 hunks) -
src/coinjoin/client.h(1 hunks) -
src/masternode/meta.cpp(2 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/coinjoin/client.hsrc/masternode/meta.hsrc/coinjoin/client.cppsrc/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/masternode/meta.hsrc/masternode/meta.cpp
🧬 Code graph analysis (4)
src/coinjoin/client.h (2)
src/coinjoin/client.cpp (2)
AddUsedMasternode(1024-1027)AddUsedMasternode(1024-1024)src/masternode/meta.cpp (2)
AddUsedMasternode(156-160)AddUsedMasternode(156-156)
src/masternode/meta.h (2)
src/coinjoin/client.cpp (2)
AddUsedMasternode(1024-1027)AddUsedMasternode(1024-1024)src/masternode/meta.cpp (8)
AddUsedMasternode(156-160)AddUsedMasternode(156-156)RemoveUsedMasternodes(162-170)RemoveUsedMasternodes(162-162)GetUsedMasternodesCount(172-176)GetUsedMasternodesCount(172-172)GetUsedMasternodesSet(178-182)GetUsedMasternodesSet(178-178)
src/coinjoin/client.cpp (1)
src/masternode/meta.cpp (2)
AddUsedMasternode(156-160)AddUsedMasternode(156-156)
src/masternode/meta.cpp (1)
src/coinjoin/client.cpp (2)
AddUsedMasternode(1024-1027)AddUsedMasternode(1024-1024)
🪛 GitHub Actions: Clang Diff Format Check
src/coinjoin/client.cpp
[error] 1021-1021: Clang format differences detected. Differences shown between 'before formatting' and 'after formatting'. Command: git diff -U0 origin/develop --
[error] 1021-1027: Clang-format-diff reported formatting changes that need to be applied. Run the suggested formatting diff or execute the formatting tool to fix.
⏰ 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). (1)
- GitHub Check: Build container / Build container
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM de30f58
src/masternode/meta.h
Outdated
| // keep track of dsq count to prevent masternodes from gaming coinjoin queue | ||
| std::atomic<int64_t> nDsqCount{0}; | ||
| // keep track of the used Masternodes for CoinJoin | ||
| std::vector<uint256> m_used_masternodes GUARDED_BY(cs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It really seems like bad design to use a vector here; The eviction policy isn't super clear as to the current behavior; but it seems an LRU cache or a std::set together with a std::list would better accomplish our goals here.
Granted this isn't like it's really in a hot loop; but it's still wasteful, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claude made this - 9eadb70, seems reasonable :)
Improves performance by implementing a dual data structure approach for tracking used masternodes in CoinJoin sessions: - Use std::deque<uint256> for maintaining FIFO insertion order - Use std::unordered_set<uint256> for O(1) lookup performance - Replace GetUsedMasternodesSet() with IsUsedMasternode() to avoid expensive set construction on every masternode selection Performance improvements at 1800 used masternodes: - Masternode selection: 2.5ms → 0.1ms (25x faster) - Batch removal: 100µs → 27µs (4x faster) The optimization becomes increasingly important at scale (>1000 MNs) and in multi-wallet scenarios with concurrent CoinJoin sessions. Key design decisions: - Deque provides O(1) front removal (vs O(n) for vector) - Unordered_set provides O(1) lookup (vs O(n log n) set construction) - Only deque is serialized; set is rebuilt on load (no version bump) - Both structures stay synchronized through all operations - Automatic duplicate prevention in AddUsedMasternode() Memory cost: ~130 KB for 1800 entries (negligible) Code complexity: Minimal, well-encapsulated This builds on PR dashpay#6875 which moved masternode tracking from per-wallet to shared global storage. That PR solved the multi-wallet coordination problem; this patch addresses the performance bottleneck at scale.
Improves performance by implementing a dual data structure approach for tracking used masternodes in CoinJoin sessions: - Use std::deque<uint256> for maintaining FIFO insertion order - Use std::unordered_set<uint256> for O(1) lookup performance - Replace GetUsedMasternodesSet() with IsUsedMasternode() to avoid expensive set construction on every masternode selection Performance improvements at 1800 used masternodes: - Masternode selection: 2.5ms → 0.1ms (25x faster) - Batch removal: 100µs → 27µs (4x faster) The optimization becomes increasingly important at scale (>1000 MNs) and in multi-wallet scenarios with concurrent CoinJoin sessions. Key design decisions: - Deque provides O(1) front removal (vs O(n) for vector) - Unordered_set provides O(1) lookup (vs O(n log n) set construction) - Only deque is serialized; set is rebuilt on load (no version bump) - Both structures stay synchronized through all operations - Automatic duplicate prevention in AddUsedMasternode() Memory cost: ~130 KB for 1800 entries (negligible) Code complexity: Minimal, well-encapsulated This builds on PR dashpay#6875 which moved masternode tracking from per-wallet to shared global storage. That PR solved the multi-wallet coordination problem; this patch addresses the performance bottleneck at scale.
9eadb70 to
7ad1724
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
-
src/coinjoin/client.cpp(5 hunks) -
src/masternode/meta.cpp(2 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.cppsrc/coinjoin/client.cppsrc/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.cppsrc/masternode/meta.h
🧬 Code graph analysis (3)
src/masternode/meta.cpp (1)
src/coinjoin/client.cpp (2)
AddUsedMasternode(1024-1027)AddUsedMasternode(1024-1024)
src/coinjoin/client.cpp (1)
src/masternode/meta.cpp (2)
AddUsedMasternode(156-163)AddUsedMasternode(156-156)
src/masternode/meta.h (2)
src/coinjoin/client.cpp (2)
AddUsedMasternode(1024-1027)AddUsedMasternode(1024-1024)src/masternode/meta.cpp (8)
AddUsedMasternode(156-163)AddUsedMasternode(156-156)RemoveUsedMasternodes(165-175)RemoveUsedMasternodes(165-165)GetUsedMasternodesCount(177-181)GetUsedMasternodesCount(177-177)IsUsedMasternode(183-187)IsUsedMasternode(183-183)
🪛 GitHub Actions: Clang Diff Format Check
src/coinjoin/client.cpp
[error] 1021-1021: Clang format differences detected. Run the clang-format-diff.py step or clang-format to fix formatting in src/coinjoin/client.cpp. Command that produced issue: git diff -U0 origin/develop --
⏰ 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). (10)
- GitHub Check: linux64_multiprocess-build / Build source
- GitHub Check: linux64_nowallet-build / Build source
- GitHub Check: mac-build / Build source
- GitHub Check: arm-linux-build / Build source
- GitHub Check: linux64_tsan-build / Build source
- GitHub Check: win64-build / Build source
- GitHub Check: linux64_sqlite-build / Build source
- GitHub Check: linux64-build / Build source
- GitHub Check: linux64_ubsan-build / Build source
- GitHub Check: linux64_fuzz-build / Build source
🔇 Additional comments (12)
src/masternode/meta.h (5)
135-138: LGTM: Dual data structure is appropriate for FIFO eviction.The combination of
std::dequefor FIFO ordering andUint256HashSetfor O(1) lookups is a reasonable design. While the past review suggested an LRU cache, that would be inappropriate here since the eviction policy is FIFO (oldest entries removed first), not LRU. The implementation inmeta.cppcorrectly maintains synchronization between these two structures.
149-151: LGTM: Serialization approach is sound.Converting the deque to a vector for serialization is correct, and the unordered_set is appropriately omitted since it can be reconstructed during deserialization.
166-177: LGTM: Deserialization correctly reconstructs both structures.The deserialization properly rebuilds both
m_used_masternodes(deque) andm_used_masternodes_setfrom the serialized vector, maintaining consistency.
185-186: LGTM: Clear() maintains structure consistency.Both data structures are properly cleared, maintaining synchronization.
269-273: LGTM: Public API provides proper encapsulation.The new methods provide clean, encapsulated access to the used masternodes tracking functionality.
src/masternode/meta.cpp (4)
11-11: LGTM: Serialization version correctly bumped.The version increment from 4 to 5 is appropriate given the addition of the new
m_used_masternodesfield to the serialized data.
156-163: LGTM: Duplicate prevention is correctly implemented.The method correctly prevents duplicates by checking the set insertion result before appending to the deque, and thread safety is ensured via the lock.
165-175: LGTM: FIFO removal correctly maintains structure synchronization.The method properly removes the oldest entries (FIFO) and keeps both the set and deque in sync by removing from both structures.
177-187: LGTM: Accessor methods are correctly implemented.Both
GetUsedMasternodesCount()andIsUsedMasternode()properly leverage the appropriate data structure (deque for size, set for lookup) with correct thread safety.src/coinjoin/client.cpp (3)
1024-1026: LGTM: Signature change aligns with proTxHash migration.The method now correctly accepts
proTxHashinstead ofCOutPointand properly delegates to the centralized tracking inCMasternodeMetaMan.
1034-1059: LGTM: Efficient O(1) lookup with centralized tracking.The changes correctly leverage
IsUsedMasternode()for O(1) lookups via the unordered_set, and the migration from outpoint-based to proTxHash-based identification is complete and consistent.
1111-1111: LGTM: Consistent proTxHash-based tracking across all call sites.Both
JoinExistingQueueandStartNewQueuecorrectly useproTxHashfor masternode tracking, ensuring consistency across the codebase.Also applies to: 1165-1165
|
"""
Was this actually benched? Or a guess by claude? |
|
It's a guess: estimationsPerformance MetricsBefore (Vector):After (Hybrid):Improvement:
but it also created a benchmark performance_test.cppand I'm getting numbers like this (NOTE: |
|
This pull request has conflicts, please rebase. |
Improves performance by implementing a dual data structure approach for tracking used masternodes in CoinJoin sessions: - Use std::deque<uint256> for maintaining FIFO insertion order - Use std::unordered_set<uint256> for O(1) lookup performance - Replace GetUsedMasternodesSet() with IsUsedMasternode() to avoid expensive set construction on every masternode selection Performance improvements at 1800 used masternodes: - Masternode selection: 2.5ms → 0.1ms (25x faster) - Batch removal: 100µs → 27µs (4x faster) The optimization becomes increasingly important at scale (>1000 MNs) and in multi-wallet scenarios with concurrent CoinJoin sessions. Key design decisions: - Deque provides O(1) front removal (vs O(n) for vector) - Unordered_set provides O(1) lookup (vs O(n log n) set construction) - Only deque is serialized; set is rebuilt on load (no version bump) - Both structures stay synchronized through all operations - Automatic duplicate prevention in AddUsedMasternode() Memory cost: ~130 KB for 1800 entries (negligible) Code complexity: Minimal, well-encapsulated This builds on PR dashpay#6875 which moved masternode tracking from per-wallet to shared global storage. That PR solved the multi-wallet coordination problem; this patch addresses the performance bottleneck at scale.
7ad1724 to
3b0b595
Compare
|
Rebased after #6868 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/coinjoin/client.cpp (1)
1033-1038: Clamp “not excluded” count to avoid size_t underflow.If used > enabled (possible across wallets/persistence), current subtraction underflows.
Apply:
- size_t nCountNotExcluded{nCountEnabled - m_mn_metaman.GetUsedMasternodesCount()}; + const size_t used = m_mn_metaman.GetUsedMasternodesCount(); + const size_t nCountNotExcluded = (used >= nCountEnabled) ? 0 : (nCountEnabled - used);
🧹 Nitpick comments (4)
src/masternode/meta.h (2)
135-139: Add explicit invariant/capacity to prevent unbounded growth.Deque+unordered_set is fine, but this structure can grow indefinitely across restarts. Consider:
- Enforcing a hard cap (e.g., 2× valid MNs) and evicting from front on insert beyond cap.
- Or, use unordered_lru_cache<uint256, bool, StaticSaltedHasher> to get built‑in LRU eviction and O(1) membership, per project practice.
As per coding guidelines
166-178: Deserialization rebuilds both containers correctly.Assigning deque and rebuilding the set is correct and keeps O(1) checks. Consider asserting sizes match in debug to catch divergence.
src/coinjoin/client.cpp (1)
1111-1112: Confirm intent: marking MN as “used” before connection/eligibility checks.You add to “used” prior to winner/connection/dsq checks. This prevents reattempts across wallets, but can also exclude nodes never actually mixed. Confirm this is desired; otherwise, add conditionally after successful queue join/start.
Also applies to: 1165-1166
src/masternode/meta.cpp (1)
165-175: FIFO removal keeps structures in sync.Implementation is correct. Optionally assert sizes remain equal in debug.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
-
src/coinjoin/client.cpp(5 hunks) -
src/coinjoin/client.h(1 hunks) -
src/masternode/meta.cpp(2 hunks) -
src/masternode/meta.h(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/coinjoin/client.h
🧰 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/coinjoin/client.cppsrc/masternode/meta.hsrc/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/masternode/meta.hsrc/masternode/meta.cpp
🧬 Code graph analysis (3)
src/coinjoin/client.cpp (1)
src/masternode/meta.cpp (2)
AddUsedMasternode(156-163)AddUsedMasternode(156-156)
src/masternode/meta.h (2)
src/masternode/meta.cpp (8)
AddUsedMasternode(156-163)AddUsedMasternode(156-156)RemoveUsedMasternodes(165-175)RemoveUsedMasternodes(165-165)GetUsedMasternodesCount(177-181)GetUsedMasternodesCount(177-177)IsUsedMasternode(183-187)IsUsedMasternode(183-183)src/coinjoin/client.cpp (2)
AddUsedMasternode(1024-1027)AddUsedMasternode(1024-1024)
src/masternode/meta.cpp (2)
src/coinjoin/client.cpp (2)
AddUsedMasternode(1024-1027)AddUsedMasternode(1024-1024)src/masternode/meta.h (2)
cs(55-80)cs(57-57)
🪛 GitHub Actions: Clang Diff Format Check
src/coinjoin/client.cpp
[error] 1021-1021: Clang-format differences detected for the file. The diff shows formatting changes between before/after formatting. Command: 'git diff -U0 origin/develop --
⏰ 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). (10)
- GitHub Check: linux64_multiprocess-build / Build source
- GitHub Check: mac-build / Build source
- GitHub Check: linux64_tsan-build / Build source
- GitHub Check: linux64_nowallet-build / Build source
- GitHub Check: arm-linux-build / Build source
- GitHub Check: win64-build / Build source
- GitHub Check: linux64_ubsan-build / Build source
- GitHub Check: linux64_sqlite-build / Build source
- GitHub Check: linux64_fuzz-build / Build source
- GitHub Check: linux64-build / Build source
🔇 Additional comments (12)
src/masternode/meta.h (4)
17-17: Header include looks good.Adding is appropriate for the new FIFO container.
149-152: Serialization order/version OK; consider forward-compat note.Converting deque→vector for wire is fine. Since version bumped to 5 in meta.cpp, older caches (v4) will be ignored. If that’s intentional, fine; otherwise, add backward handling to accept v4 (and default m_used_masternodes to empty).
185-187: Clear() now clears new state as well.Good coverage.
269-274: API surface is clear and thread-safe.Annotations indicate “do not hold cs” externally; methods lock internally. Consistent with usage in meta.cpp.
src/coinjoin/client.cpp (3)
1051-1059: LGTM: O(1) membership check instead of copying set.Direct IsUsedMasternode lookup avoids building a temporary set. Good.
1024-1027: Forwarder to metaman is clean.
994-1000: Run clang-format on src/coinjoin/client.cpp
CI is reporting formatting mismatches butclang-formatisn’t available here; please install/run clang-format locally on this file and commit the updated formatting.src/masternode/meta.cpp (5)
11-11: Version bump to V5.Matches new serialized field. Ensure release notes mention dropping V4 cache on mismatch.
156-163: Duplicate-safe insert with O(1) membership.Good use of set+deque to avoid duplicates and keep order for eviction.
177-181: Count via deque size OK.
183-187: O(1) lookup via set OK.
189-194: ToString locks internally; adds used count.Good observability improvement.
3b0b595 to
a797abe
Compare
There was a problem hiding this 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.cpp (1)
183-187: Consider using C++20contains()for idiomatic code.The logic is correct. For C++20 codebases,
contains()is more idiomatic thanfind() != end().Apply this diff for a minor readability improvement:
bool CMasternodeMetaMan::IsUsedMasternode(const uint256& proTxHash) const { LOCK(cs); - return m_used_masternodes_set.find(proTxHash) != m_used_masternodes_set.end(); + return m_used_masternodes_set.contains(proTxHash); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/masternode/meta.cpp(2 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,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
🔇 Additional comments (5)
src/masternode/meta.cpp (5)
11-11: LGTM! Serialization version correctly bumped.The version increment from 4 to 5 is appropriate given the addition of persistent
m_used_masternodesdata structures.
156-163: LGTM! Duplicate prevention and consistency maintained.The method correctly prevents duplicates by checking
insert().secondbefore adding to the deque, ensuring both data structures remain consistent.
165-175: LGTM! FIFO removal correctly implemented.The method safely removes the oldest entries while maintaining consistency between the set and deque. Edge cases (empty deque, excessive count) are properly handled.
177-181: LGTM! Thread-safe getter.
189-194: LGTM! Improved observability.The addition of used masternodes count to the string output enhances debugging and monitoring capabilities.
|
This pull request has conflicts, please rebase. |
a797abe to
dd040b5
Compare
Improves performance by implementing a dual data structure approach for tracking used masternodes in CoinJoin sessions: - Use std::deque<uint256> for maintaining FIFO insertion order - Use std::unordered_set<uint256> for O(1) lookup performance - Replace GetUsedMasternodesSet() with IsUsedMasternode() to avoid expensive set construction on every masternode selection Performance improvements at 1800 used masternodes: - Masternode selection: 2.5ms → 0.1ms (25x faster) - Batch removal: 100µs → 27µs (4x faster) The optimization becomes increasingly important at scale (>1000 MNs) and in multi-wallet scenarios with concurrent CoinJoin sessions. Key design decisions: - Deque provides O(1) front removal (vs O(n) for vector) - Unordered_set provides O(1) lookup (vs O(n log n) set construction) - Only deque is serialized; set is rebuilt on load (no version bump) - Both structures stay synchronized through all operations - Automatic duplicate prevention in AddUsedMasternode() Memory cost: ~130 KB for 1800 entries (negligible) Code complexity: Minimal, well-encapsulated This builds on PR dashpay#6875 which moved masternode tracking from per-wallet to shared global storage. That PR solved the multi-wallet coordination problem; this patch addresses the performance bottleneck at scale.
|
This pull request has conflicts, please rebase. |
Improves performance by implementing a dual data structure approach for tracking used masternodes in CoinJoin sessions: - Use std::deque<uint256> for maintaining FIFO insertion order - Use std::unordered_set<uint256> for O(1) lookup performance - Replace GetUsedMasternodesSet() with IsUsedMasternode() to avoid expensive set construction on every masternode selection Performance improvements at 1800 used masternodes: - Masternode selection: 2.5ms → 0.1ms (25x faster) - Batch removal: 100µs → 27µs (4x faster) The optimization becomes increasingly important at scale (>1000 MNs) and in multi-wallet scenarios with concurrent CoinJoin sessions. Key design decisions: - Deque provides O(1) front removal (vs O(n) for vector) - Unordered_set provides O(1) lookup (vs O(n log n) set construction) - Only deque is serialized; set is rebuilt on load (no version bump) - Both structures stay synchronized through all operations - Automatic duplicate prevention in AddUsedMasternode() Memory cost: ~130 KB for 1800 entries (negligible) Code complexity: Minimal, well-encapsulated This builds on PR dashpay#6875 which moved masternode tracking from per-wallet to shared global storage. That PR solved the multi-wallet coordination problem; this patch addresses the performance bottleneck at scale.
dd040b5 to
f50d09a
Compare
There was a problem hiding this 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
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/coinjoin/client.cpp (2)
964-979: Use integer math and consistent types; avoid FP and signed/unsigned mixing.Compute thresholds in size_t with integer ops and log with %zu to avoid truncation and wrap risks.
Apply:
- int nMnCountEnabled = m_dmnman.GetListAtChainTip().GetValidMNsCount(); - - // 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; - size_t nUsedMasternodes{m_mn_metaman.GetUsedMasternodesCount()}; - - WalletCJLogPrint(m_wallet, "Checking nUsedMasternodes: %d, threshold: %d\n", (int)nUsedMasternodes, nThreshold_high); - if ((int)nUsedMasternodes > nThreshold_high) { - size_t nToRemove{nUsedMasternodes - nThreshold_low}; + const int nMnCountEnabled = m_dmnman.GetListAtChainTip().GetValidMNsCount(); + const size_t nThreshold_high = (static_cast<size_t>(nMnCountEnabled) * 9) / 10; + const size_t nThreshold_low = (nThreshold_high * 7) / 10; + const size_t nUsedMasternodes = m_mn_metaman.GetUsedMasternodesCount(); + + WalletCJLogPrint(m_wallet, "Checking nUsedMasternodes: %zu, threshold: %zu\n", + nUsedMasternodes, nThreshold_high); + if (nUsedMasternodes > nThreshold_high) { + const size_t nToRemove = nUsedMasternodes - nThreshold_low; m_mn_metaman.RemoveUsedMasternodes(nToRemove); - WalletCJLogPrint(m_wallet, " new nUsedMasternodes: %d, threshold: %d\n", - (int)m_mn_metaman.GetUsedMasternodesCount(), nThreshold_high); + WalletCJLogPrint(m_wallet, " new nUsedMasternodes: %zu, threshold: %zu\n", + m_mn_metaman.GetUsedMasternodesCount(), nThreshold_high); }
1010-1015: Fix potential size_t underflow when used > enabled.Saturate subtraction to 0; otherwise nCountNotExcluded underflows to a huge value. Also log with %zu.
Apply:
- size_t nCountEnabled = mnList.GetValidMNsCount(); - size_t nCountNotExcluded{nCountEnabled - m_mn_metaman.GetUsedMasternodesCount()}; + const size_t nCountEnabled = mnList.GetValidMNsCount(); + const size_t nUsed = std::min(m_mn_metaman.GetUsedMasternodesCount(), nCountEnabled); + const size_t nCountNotExcluded = nCountEnabled - nUsed; @@ - WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- %d enabled masternodes, %d masternodes to choose from\n", __func__, nCountEnabled, nCountNotExcluded); + WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- %zu enabled masternodes, %zu masternodes to choose from\n", + __func__, nCountEnabled, nCountNotExcluded);
🧹 Nitpick comments (6)
src/coinjoin/client.h (1)
327-327: API change looks good; consider brief doc note.Signature switch to proTxHash aligns with meta store and centralizes locking. Consider adding a short comment that the tracking is global across wallets to set expectations for callers.
src/masternode/meta.h (2)
143-147: Global used-masternodes container: add an upper bound.m_used_masternodes and m_used_masternodes_set can grow with historical proTxHashes. Add a soft cap or eviction policy beyond the runtime thresholding to bound memory in long-lived nodes. A fixed cap (e.g., k ≈ 2× active MNs) with FIFO eviction would suffice.
277-282: Public CoinJoin tracking API: naming and comments.Interfaces are clear. Minor: RemoveUsedMasternodes could be renamed RemoveOldestUsedMasternodes for precision, and document FIFO semantics.
src/coinjoin/client.cpp (2)
1088-1107: Defer marking masternode as used until we commit to join.Marking used before connectivity/winner checks can exclude a node across wallets without being actually used. Move AddUsedMasternode to the success path just before returning true.
Apply:
- m_clientman.AddUsedMasternode(dmn->proTxHash); - if (connman.IsMasternodeOrDisconnectRequested(dmn->pdmnState->netInfo->GetPrimary())) { @@ - strAutoDenomResult = _("Trying to connect…"); + m_clientman.AddUsedMasternode(dmn->proTxHash); + strAutoDenomResult = _("Trying to connect…"); return true;
1142-1191: Also defer marking in StartNewQueue.Same reasoning: add only after winner/threshold/connectivity checks pass and we’re about to proceed.
Apply:
- m_clientman.AddUsedMasternode(dmn->proTxHash); - // skip next mn payments winners if (dmn->pdmnState->nLastPaidHeight + nWeightedMnCount < mnList.GetHeight() + WinnersToSkip()) { @@ - WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::StartNewQueue -- attempting connection, masternode=%s, tries=%s\n", + WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::StartNewQueue -- attempting connection, masternode=%s, tries=%s\n", dmn->proTxHash.ToString(), nTries); @@ - mixingMasternode = dmn; + m_clientman.AddUsedMasternode(dmn->proTxHash); + mixingMasternode = dmn;src/masternode/meta.cpp (1)
192-194: Format specifiers: avoid narrowing.Use %zu for container sizes and a 64-bit specifier for nDsqCount.
Apply:
- return strprintf("Masternodes: meta infos object count: %d, nDsqCount: %d, used masternodes count: %d", - metaInfos.size(), nDsqCount, m_used_masternodes.size()); + return strprintf("Masternodes: meta infos object count: %zu, nDsqCount: %lld, used masternodes count: %zu", + metaInfos.size(), static_cast<long long>(nDsqCount.load()), m_used_masternodes.size());
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/coinjoin/client.cpp(5 hunks)src/coinjoin/client.h(1 hunks)src/masternode/meta.cpp(2 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/coinjoin/client.cppsrc/coinjoin/client.hsrc/masternode/meta.hsrc/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/masternode/meta.hsrc/masternode/meta.cpp
🧬 Code graph analysis (4)
src/coinjoin/client.cpp (1)
src/masternode/meta.cpp (2)
AddUsedMasternode(156-163)AddUsedMasternode(156-156)
src/coinjoin/client.h (2)
src/masternode/meta.cpp (2)
AddUsedMasternode(156-163)AddUsedMasternode(156-156)src/coinjoin/client.cpp (2)
AddUsedMasternode(1001-1004)AddUsedMasternode(1001-1001)
src/masternode/meta.h (2)
src/masternode/meta.cpp (8)
AddUsedMasternode(156-163)AddUsedMasternode(156-156)RemoveUsedMasternodes(165-175)RemoveUsedMasternodes(165-165)GetUsedMasternodesCount(177-181)GetUsedMasternodesCount(177-177)IsUsedMasternode(183-187)IsUsedMasternode(183-183)src/coinjoin/client.cpp (2)
AddUsedMasternode(1001-1004)AddUsedMasternode(1001-1001)
src/masternode/meta.cpp (2)
src/coinjoin/client.cpp (2)
AddUsedMasternode(1001-1004)AddUsedMasternode(1001-1001)src/masternode/meta.h (1)
cs(57-57)
🪛 GitHub Actions: Clang Diff Format Check
src/coinjoin/client.cpp
[error] 998-1000: Clang format differences detected. The patch shows formatting changes; run 'clang-format' (or the project's formatting tool) to fix code style issues in this file.
⏰ 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). (3)
- GitHub Check: linux64_tsan-test / Test source
- GitHub Check: linux64_ubsan-test / Test source
- GitHub Check: linux64_multiprocess-build / Build source
🔇 Additional comments (7)
src/masternode/meta.h (2)
157-160: Serialization path OK; confirm version‑mismatch semantics.Deque→vector round‑trip and set rebuild are fine. Clear() before reading then early return on version mismatch drops prior cache. Confirm that discarding the cache is intended for unknown versions; otherwise consider delaying Clear() until after version check.
Also applies to: 174-176, 182-186
193-195: Clear() coherenceClearing both deque and set maintains invariants. Looks good.
src/coinjoin/client.cpp (3)
1028-1036: O(1) exclusion via set is solid.Good switch to direct set lookup.
1001-1004: Wrapper OK; relies on internal locking.Forwarding to CMasternodeMetaMan centralizes state and synchronization.
998-1000: Manual verification required: Run clang-format locally.clang-format is unavailable in the sandbox environment, preventing programmatic verification of the formatting issue. You must run the verification command locally as specified in your review comment:
#!/bin/bash clang-format -i src/coinjoin/client.cpp git diff -- src/coinjoin/client.cppThis will confirm whether formatting changes are needed and apply them if required.
src/masternode/meta.cpp (2)
11-11: Version bump acknowledged; ensure upgrade notes.SERIALIZATION_VERSION_STRING → "…-Version-5" is fine; ensure any release/upgrade notes mention the new on-disk format and that older nodes will rebuild cache if needed.
156-187: Deque+set operations look correct and lock-scoped.Dedup on insert and FIFO pop with set erasure are correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM f50d09a
see nits about code-style and logs
src/coinjoin/client.cpp
Outdated
| 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 nUsedMasternodes: %d, threshold: %d\n", (int)nUsedMasternodes, nThreshold_high); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit(code-style):
nUsedMasternodes is not matched with our code style.
prefix n should not be used.
case of variable should be snake-case, such as used_masternodes or used_masternodes_count or something similar.
See doc/developer-notes.md:
- Variable (including function arguments) and namespace names are all lowercase and may use
_to
separate words (snake_case).
I'd suggest to change log record to be a bit more human rather than use variable name in logs (even if it's debug):
| WalletCJLogPrint(m_wallet, "Checking nUsedMasternodes: %d, threshold: %d\n", (int)nUsedMasternodes, nThreshold_high); | |
| WalletCJLogPrint(m_wallet, "Checking threshold of used masternodes for coinjoin: amount=%d, threshold=%d\n", (int)nUsedMasternodes, nThreshold_high); |
src/masternode/meta.cpp
Outdated
| } | ||
| } | ||
|
|
||
| void CMasternodeMetaMan::RemoveUsedMasternodes(size_t nCount) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: nCount -> count
| void CMasternodeMetaMan::RemoveUsedMasternodes(size_t nCount) | |
| void CMasternodeMetaMan::RemoveUsedMasternodes(size_t count) |
src/masternode/meta.cpp
Outdated
| { | ||
| LOCK(cs); | ||
| size_t removed = 0; | ||
| while (removed < nCount && !m_used_masternodes.empty()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| while (removed < nCount && !m_used_masternodes.empty()) { | |
| while (removed < count && !m_used_masternodes.empty()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/coinjoin/client.cpp (2)
997-999: Fix clang-format CI failure for this file.CI reports clang-format differences at these lines. Re-run the formatter and commit the result.
#!/bin/bash clang-format -i src/coinjoin/client.cpp git diff -- src/coinjoin/client.cpp
969-976: Use integer math in size_t, clamp removals, and keep logging types consistent.Avoid FP truncation and signed/unsigned mixing; compute thresholds in size_t, clamp to_remove, and cast in logs.
- int nThreshold_high = nMnCountEnabled * 0.9; - int nThreshold_low = nThreshold_high * 0.7; - size_t used_count{m_mn_metaman.GetUsedMasternodesCount()}; - - 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); - } + const size_t threshold_high = (static_cast<size_t>(nMnCountEnabled) * 9) / 10; // 90% + const size_t threshold_low = (threshold_high * 7) / 10; // ~63% of total + const size_t used_count = m_mn_metaman.GetUsedMasternodesCount(); + + WalletCJLogPrint(m_wallet, "Checking threshold - used: %d, threshold: %d\n", + static_cast<int>(used_count), static_cast<int>(threshold_high)); + + if (used_count > threshold_high) { + const size_t to_remove = used_count > threshold_low ? (used_count - threshold_low) : 0; + if (to_remove != 0) { + m_mn_metaman.RemoveUsedMasternodes(to_remove); + WalletCJLogPrint(m_wallet, " new used: %d, threshold: %d\n", + static_cast<int>(m_mn_metaman.GetUsedMasternodesCount()), + static_cast<int>(threshold_high)); + } + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/coinjoin/client.cpp(5 hunks)src/masternode/meta.cpp(2 hunks)src/masternode/meta.h(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/masternode/meta.cpp
- src/masternode/meta.h
🧰 Additional context used
📓 Path-based instructions (1)
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/coinjoin/client.cpp
🧬 Code graph analysis (1)
src/coinjoin/client.cpp (1)
src/masternode/meta.cpp (2)
AddUsedMasternode(156-163)AddUsedMasternode(156-156)
🪛 GitHub Actions: Clang Diff Format Check
src/coinjoin/client.cpp
[error] 997-999: Clang-format differences detected between before/after formatting. Apply formatting changes to this file (e.g., run clang-format or the project's formatting script).
⏰ 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). (10)
- GitHub Check: mac-build / Build source
- GitHub Check: win64-build / Build source
- GitHub Check: linux64_tsan-build / Build source
- GitHub Check: linux64_sqlite-build / Build source
- GitHub Check: linux64_ubsan-build / Build source
- GitHub Check: linux64-build / Build source
- GitHub Check: linux64_fuzz-build / Build source
- GitHub Check: arm-linux-build / Build source
- GitHub Check: linux64_nowallet-build / Build source
- GitHub Check: Lint / Run linters
🔇 Additional comments (4)
src/coinjoin/client.cpp (4)
1027-1035: LGTM: O(1) membership check via metaman set; clearer logging.
1087-1087: Confirm intent: mark masternode as “used” before connection attempt.This prevents retrying the same MN even if the subsequent connection is skipped/fails. If that’s the new policy, fine; otherwise move the call after the connection/eligibility checks.
1141-1141: Double-check early “used” marking before winner/DSQ checks.Here we mark as used before skipping winners and DSQ rate-limits. If we’d prefer “used” to mean “actually mixed with,” consider moving this call after those checks.
1000-1003: LGTM: API shift to proTxHash verified across all call-sites.Verification complete—all calls to
AddUsedMasternodeuseproTxHashand no lingeringcollateralOutpointpatterns detected. Thread-safe via lock mechanism inCMasternodeMetaMan.
| size_t nCountNotExcluded{nCountEnabled - m_mn_metaman.GetUsedMasternodesCount()}; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix potential size_t underflow and printf specifiers.
If used_count > enabled, subtraction underflows; also %d mismatches size_t. Clamp and cast.
- 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);
+ const size_t used_count = m_mn_metaman.GetUsedMasternodesCount();
+ const size_t nCountNotExcluded = nCountEnabled > used_count ? (nCountEnabled - used_count) : 0;
+
+ WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- %d enabled masternodes, %d masternodes to choose from\n",
+ __func__, static_cast<int>(nCountEnabled), static_cast<int>(nCountNotExcluded));Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/coinjoin/client.cpp around lines 1010-1011, the subtraction size_t
nCountNotExcluded{nCountEnabled - m_mn_metaman.GetUsedMasternodesCount()}; can
underflow if GetUsedMasternodesCount() > nCountEnabled and the printf formatting
uses wrong specifier; change to compute a non-negative value by clamping to zero
(e.g. compute used = m_mn_metaman.GetUsedMasternodesCount(); nCountNotExcluded =
(used >= nCountEnabled) ? 0 : (nCountEnabled - used);), and update any
printf/format calls to use the correct size_t specifier (%zu) or cast to
unsigned/uint64_t and use the matching specifier to avoid mismatches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK eb3be09
Issue being fixed or feature implemented
vecMasternodesUsedhas a few issues:StartNewQueue()and fail to start mixing because of thatWhat was done?
MasternodeMetaStoreand renamed it tom_used_masternodesstd::vector<uint256>(usingproTxHashnow)m_used_masternodesis no longer accessed via multiple threads (because it's not cleared inResetPool()) but I made it thread-safe anyway just in case (using existingMutex cs)How Has This Been Tested?
Mixing in multiple wallets at once
Breaking Changes
n/a
Checklist: