Skip to content

Conversation

@UdjinM6
Copy link

@UdjinM6 UdjinM6 commented Oct 5, 2025

Issue being fixed or feature implemented

vecMasternodesUsed has a few issues:

  • it's shared only across mixing sessions in a single wallet, wallets don't share this data so it's possible they are going to waste tries in StartNewQueue() and fail to start mixing because of that
  • we start with a fresh vector each time which also can result in failed mixing attempts
  • there are two threads (scheduler and net) where it can be accessed but it's not protected by any mutex atm
  • it still stores masternode outpoints while most of our codebase uses protxhash-es as masternode ids

What was done?

  • moved it to MasternodeMetaStore and renamed it to m_used_masternodes
  • changed to std::vector<uint256> (using proTxHash now)
  • implemented proper encapsulation through accessor methods
  • m_used_masternodes is no longer accessed via multiple threads (because it's not cleared in ResetPool()) but I made it thread-safe anyway just in case (using existing Mutex cs)
  • made it persistent via serialization (with version bump 4 -> 5)

How Has This Been Tested?

Mixing in multiple wallets at once

Breaking Changes

n/a

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

@UdjinM6 UdjinM6 added this to the 23 milestone Oct 5, 2025
@github-actions
Copy link

github-actions bot commented Oct 5, 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.

@coderabbitai
Copy link

coderabbitai bot commented Oct 5, 2025

Walkthrough

Replaces 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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Files needing attention:
    • src/coinjoin/client.cpp / src/coinjoin/client.h — signature changes, updated call sites, logging, queue interactions.
    • src/masternode/meta.h / src/masternode/meta.cpp — new containers, serialization/deserialization changes, set↔deque consistency, concurrency (locking) semantics.
    • Areas: DoAutomaticDenominating, GetRandomNotUsedMasternode, StartNewQueue, JoinExistingQueue — verify exclusion logic, identity usage, and logging updates.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 pull request title "feat: share vecMasternodesUsed across all wallets, improve its handling" directly and accurately describes the primary objective of the changeset. The changes across all modified files (client.cpp, client.h, meta.cpp, and meta.h) work together to accomplish exactly what the title promises: moving vecMasternodesUsed from per-wallet storage to a shared location in MasternodeMetaStore, and improving its handling through thread-safety, persistence, and better identity management using proTxHash. The title is concise, specific, and clearly conveys the main architectural improvement without unnecessary detail.
Description Check ✅ Passed The pull request description is well-related to the changeset and provides meaningful context. It clearly articulates the problems with the existing implementation (per-wallet scoping, reinitialization, lack of thread-safety, and use of outpoints instead of proTxHash), describes the specific solutions implemented (moving to MasternodeMetaStore, renaming, type change, encapsulation, thread-safety, and persistence via serialization), and mentions the testing approach. The description demonstrates a clear understanding of what was changed and why, which is sufficient for this lenient check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55f723e and de30f58.

📒 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.h
  • src/masternode/meta.h
  • src/coinjoin/client.cpp
  • 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.h
  • src/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 -- $(git ls-files -- $(cat test/util/data/non-backported.txt)) | ./contrib/devtools/clang-format-diff.py -p1 > diff_output.txt


[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

knst
knst previously approved these changes Oct 6, 2025
Copy link
Collaborator

@knst knst left a comment

Choose a reason for hiding this comment

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

LGTM de30f58

// 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);
Copy link
Member

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?

Copy link
Author

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 :)

UdjinM6 added a commit to UdjinM6/dash that referenced this pull request Oct 6, 2025
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.
UdjinM6 added a commit to UdjinM6/dash that referenced this pull request Oct 6, 2025
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.
@UdjinM6 UdjinM6 force-pushed the fix_share_vecMasternodesUsed branch from 9eadb70 to 7ad1724 Compare October 6, 2025 16:31
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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9eadb70 and 7ad1724.

📒 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.cpp
  • src/coinjoin/client.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
🧬 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 -- $(git ls-files -- $(cat test/util/data/non-backported.txt)) | ./contrib/devtools/clang-format-diff.py -p1 > diff_output.txt

⏰ 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::deque for FIFO ordering and Uint256HashSet for 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 in meta.cpp correctly 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) and m_used_masternodes_set from 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_masternodes field 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() and IsUsedMasternode() 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 proTxHash instead of COutPoint and properly delegates to the centralized tracking in CMasternodeMetaMan.


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 JoinExistingQueue and StartNewQueue correctly use proTxHash for masternode tracking, ensuring consistency across the codebase.

Also applies to: 1165-1165

@PastaPastaPasta
Copy link
Member

"""
Performance improvements at 1800 used masternodes:

  • Masternode selection: 2.5ms → 0.1ms (25x faster)
  • Batch removal: 100µs → 27µs (4x faster)
    """

Was this actually benched? Or a guess by claude?

@UdjinM6
Copy link
Author

UdjinM6 commented Oct 6, 2025

It's a guess:

estimations

Performance Metrics

Before (Vector):

Masternode Selection @ 1800 used:
├─ GetUsedMasternodesSet()  500 µs  (construct set from vector)
├─ Loop lookups             2000 µs (worst case)
└─ Total                    2500 µs = 2.5 ms

Batch Removal (540 items):
└─ vector.erase()           100 µs  (shift 1260 elements)

After (Hybrid):

Masternode Selection @ 1800 used:
├─ Loop with IsUsedMasternode() 100 µs (2000 × 50 ns)
└─ Total                        100 µs = 0.1 ms

Batch Removal (540 items):
└─ 540× pop_front()         27 µs   (O(1) per element)

Improvement:

  • Selection: 25x faster (2.5ms → 0.1ms)
  • Removal: 4x faster (100µs → 27µs)

but it also created a benchmark

performance_test.cpp
// Performance comparison test for CoinJoin masternode tracking optimization
// Compile with: g++ -std=c++17 -O2 performance_test.cpp -o performance_test
// Run with: ./performance_test

#include <chrono>
#include <deque>
#include <iostream>
#include <random>
#include <set>
#include <unordered_set>
#include <vector>

// Simulate uint256 with a simple hash-able type
using uint256 = uint64_t;

namespace {
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<uint64_t> dis;
}

// Original vector-based approach
class VectorApproach {
    std::vector<uint256> m_used;
public:
    void Add(uint256 hash) { m_used.push_back(hash); }

    void RemoveBatch(size_t count) {
        if (count > m_used.size()) {
            m_used.clear();
        } else {
            m_used.erase(m_used.begin(), m_used.begin() + count);
        }
    }

    std::set<uint256> GetSet() const {
        return {m_used.begin(), m_used.end()};
    }

    size_t Size() const { return m_used.size(); }
};

// Optimized hybrid approach
class HybridApproach {
    std::deque<uint256> m_used;
    std::unordered_set<uint256> m_used_set;
public:
    void Add(uint256 hash) {
        if (m_used_set.insert(hash).second) {
            m_used.push_back(hash);
        }
    }

    void RemoveBatch(size_t count) {
        size_t removed = 0;
        while (removed < count && !m_used.empty()) {
            m_used_set.erase(m_used.front());
            m_used.pop_front();
            ++removed;
        }
    }

    bool IsUsed(uint256 hash) const {
        return m_used_set.count(hash) > 0;
    }

    size_t Size() const { return m_used.size(); }
};

template<typename Approach>
double BenchmarkSelection(Approach& tracker, const std::vector<uint256>& candidates, int iterations) {
    auto start = std::chrono::high_resolution_clock::now();

    for (int iter = 0; iter < iterations; ++iter) {
        // Simulate GetUsedMasternodesSet() approach
        if constexpr (std::is_same_v<Approach, VectorApproach>) {
            auto excludeSet = tracker.GetSet();
            for (const auto& candidate : candidates) {
                if (excludeSet.count(candidate)) {
                    continue; // skip
                }
            }
        } else {
            // Simulate IsUsedMasternode() approach
            for (const auto& candidate : candidates) {
                if (tracker.IsUsed(candidate)) {
                    continue; // skip
                }
            }
        }
    }

    auto end = std::chrono::high_resolution_clock::now();
    return std::chrono::duration<double, std::micro>(end - start).count() / iterations;
}

template<typename Approach>
double BenchmarkRemoval(Approach& tracker, size_t count) {
    auto start = std::chrono::high_resolution_clock::now();
    tracker.RemoveBatch(count);
    auto end = std::chrono::high_resolution_clock::now();
    return std::chrono::duration<double, std::micro>(end - start).count();
}

int main() {
    std::cout << "=== CoinJoin Masternode Tracking Performance Test ===\n\n";

    // Test configurations
    const std::vector<int> mn_counts = {500, 1000, 1500, 2000};
    const int num_candidates = 2000;  // Shuffled masternode list
    const int selection_iterations = 100;

    for (int mn_count : mn_counts) {
        int used_count = static_cast<int>(mn_count * 0.9);  // 90% threshold
        int remove_count = static_cast<int>(used_count * 0.3);  // Remove 30%

        std::cout << "Testing with " << mn_count << " masternodes ("
                  << used_count << " used, removing " << remove_count << "):\n";
        std::cout << std::string(70, '-') << "\n";

        // Generate test data
        std::vector<uint256> used_hashes;
        std::vector<uint256> candidates;

        for (int i = 0; i < used_count; ++i) {
            used_hashes.push_back(dis(gen));
        }

        for (int i = 0; i < num_candidates; ++i) {
            candidates.push_back(dis(gen));
        }

        // Test Vector Approach
        VectorApproach vector_tracker;
        for (const auto& hash : used_hashes) {
            vector_tracker.Add(hash);
        }

        double vector_selection_time = BenchmarkSelection(vector_tracker, candidates, selection_iterations);
        double vector_removal_time = BenchmarkRemoval(vector_tracker, remove_count);

        // Test Hybrid Approach
        HybridApproach hybrid_tracker;
        for (const auto& hash : used_hashes) {
            hybrid_tracker.Add(hash);
        }

        double hybrid_selection_time = BenchmarkSelection(hybrid_tracker, candidates, selection_iterations);
        double hybrid_removal_time = BenchmarkRemoval(hybrid_tracker, remove_count);

        // Results
        std::cout << "Masternode Selection (avg over " << selection_iterations << " iterations):\n";
        std::cout << "  Vector:  " << vector_selection_time << " µs\n";
        std::cout << "  Hybrid:  " << hybrid_selection_time << " µs\n";
        std::cout << "  Speedup: " << (vector_selection_time / hybrid_selection_time) << "x\n\n";

        std::cout << "Batch Removal (" << remove_count << " items):\n";
        std::cout << "  Vector:  " << vector_removal_time << " µs\n";
        std::cout << "  Hybrid:  " << hybrid_removal_time << " µs\n";
        std::cout << "  Speedup: " << (vector_removal_time / hybrid_removal_time) << "x\n\n";

        double vector_total = vector_selection_time + vector_removal_time;
        double hybrid_total = hybrid_selection_time + hybrid_removal_time;

        std::cout << "Total Time per CoinJoin cycle:\n";
        std::cout << "  Vector:  " << vector_total << " µs\n";
        std::cout << "  Hybrid:  " << hybrid_total << " µs\n";
        std::cout << "  Speedup: " << (vector_total / hybrid_total) << "x\n\n";

        std::cout << "Memory Usage Estimate:\n";
        std::cout << "  Vector:  ~" << (used_count * 32) / 1024 << " KB\n";
        std::cout << "  Hybrid:  ~" << (used_count * 72) / 1024 << " KB (deque + set)\n\n";

        std::cout << std::string(70, '=') << "\n\n";
    }

    std::cout << "Note: Actual performance may vary based on:\n";
    std::cout << "  - CPU cache effects\n";
    std::cout << "  - Memory allocation patterns\n";
    std::cout << "  - Compiler optimizations\n";
    std::cout << "  - Real uint256 hash function overhead\n\n";

    return 0;
}

and I'm getting numbers like this

Testing with 2000 masternodes (1800 used, removing 540):
----------------------------------------------------------------------
Masternode Selection (avg over 100 iterations):
  Vector:  196.568 µs
  Hybrid:  0.00042 µs
  Speedup: 468019x

Batch Removal (540 items):
  Vector:  5.208 µs
  Hybrid:  23.5 µs
  Speedup: 0.221617x

Total Time per CoinJoin cycle:
  Vector:  201.776 µs
  Hybrid:  23.5004 µs
  Speedup: 8.58606x

Memory Usage Estimate:
  Vector:  ~56 KB
  Hybrid:  ~126 KB (deque + set)

(NOTE: Total Time per CoinJoin cycle stats are inaccurate because we select non-used masternode all the time and remove them only once in a while, can't just add the two together)

@github-actions
Copy link

github-actions bot commented Oct 8, 2025

This pull request has conflicts, please rebase.

UdjinM6 added a commit to UdjinM6/dash that referenced this pull request Oct 8, 2025
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.
@UdjinM6 UdjinM6 force-pushed the fix_share_vecMasternodesUsed branch from 7ad1724 to 3b0b595 Compare October 8, 2025 21:48
@UdjinM6
Copy link
Author

UdjinM6 commented Oct 8, 2025

Rebased after #6868

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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7ad1724 and 3b0b595.

📒 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.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/masternode/meta.h
  • src/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 -- $(git ls-files -- $(cat test/util/data/non-backported.txt)) | ./contrib/devtools/clang-format-diff.py -p1 > diff_output.txt'. Process completed with exit code 1.

⏰ 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 but clang-format isn’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.

@UdjinM6 UdjinM6 force-pushed the fix_share_vecMasternodesUsed branch from 3b0b595 to a797abe Compare October 10, 2025 14:22
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.cpp (1)

183-187: Consider using C++20 contains() for idiomatic code.

The logic is correct. For C++20 codebases, contains() is more idiomatic than find() != 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3b0b595 and a797abe.

📒 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_masternodes data structures.


156-163: LGTM! Duplicate prevention and consistency maintained.

The method correctly prevents duplicates by checking insert().second before 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.

@github-actions
Copy link

This pull request has conflicts, please rebase.

@UdjinM6 UdjinM6 force-pushed the fix_share_vecMasternodesUsed branch from a797abe to dd040b5 Compare October 14, 2025 15:31
UdjinM6 added a commit to UdjinM6/dash that referenced this pull request Oct 14, 2025
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.
@github-actions
Copy link

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.
@UdjinM6 UdjinM6 force-pushed the fix_share_vecMasternodesUsed branch from dd040b5 to f50d09a Compare October 25, 2025 08:30
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

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

📥 Commits

Reviewing files that changed from the base of the PR and between dd040b5 and f50d09a.

📒 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.cpp
  • src/coinjoin/client.h
  • 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/masternode/meta.h
  • src/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() coherence

Clearing 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.cpp

This 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.

knst
knst previously approved these changes Oct 26, 2025
Copy link
Collaborator

@knst knst left a 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

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);
Copy link
Collaborator

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):

Suggested change
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);

}
}

void CMasternodeMetaMan::RemoveUsedMasternodes(size_t nCount)
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: nCount -> count

Suggested change
void CMasternodeMetaMan::RemoveUsedMasternodes(size_t nCount)
void CMasternodeMetaMan::RemoveUsedMasternodes(size_t count)

{
LOCK(cs);
size_t removed = 0;
while (removed < nCount && !m_used_masternodes.empty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
while (removed < nCount && !m_used_masternodes.empty()) {
while (removed < count && !m_used_masternodes.empty()) {

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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between f50d09a and eb3be09.

📒 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 AddUsedMasternode use proTxHash and no lingering collateralOutpoint patterns detected. Thread-safe via lock mechanism in CMasternodeMetaMan.

Comment on lines +1010 to 1011
size_t nCountNotExcluded{nCountEnabled - m_mn_metaman.GetUsedMasternodesCount()};

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

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.

Copy link
Collaborator

@knst knst left a comment

Choose a reason for hiding this comment

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

utACK eb3be09

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.

3 participants