Skip to content

refactor: couple simplifications for coinjoin's client implementation#7470

Open
knst wants to merge 5 commits into
dashpay:developfrom
knst:fix-cj-dangling-ptr-2
Open

refactor: couple simplifications for coinjoin's client implementation#7470
knst wants to merge 5 commits into
dashpay:developfrom
knst:fix-cj-dangling-ptr-2

Conversation

@knst

@knst knst commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Issue being fixed or feature implemented

That's a follow-up for #7259

What was done?

  • removed member variable strAutoDenomResult from CCoinJoinClientManager which is not used externally. Internal usages are replaced to longs on place. CCoinJoinClientSession still has local strAutoDenomResultfor the same purpose.
  • moved nCachedNumBlocks and related logic from coinjoin's interfaces::Coinjoin::Client directly to OverviewPage, the same as cachedNumISLocks and other relevant members
  • moved code between CCoinJoinClientManager and CCoinJoinClientSession: usages of m_mn_metaman and GetRandomNotUsedMasternode

How Has This Been Tested?

Run unit & functional tests

Breaking Changes

N/A

Checklist:

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

@knst knst added this to the 24 milestone Jul 16, 2026
@thepastaclaw

thepastaclaw commented Jul 16, 2026

Copy link
Copy Markdown

✅ Final review complete — no blockers (commit a19dee0)

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

CoinJoin masternode selection and used-node tracking now belong to CCoinJoinClientSession. Manager timeout, backup, denomination, and pending-request paths log or process actions without updating the removed automatic-denomination result state. Cache/block APIs were removed from the CoinJoin interface, and OverviewPage now owns cached block-height state for progress refreshes and toggle handling.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • dashpay/dash#7259: Refactors CoinJoin client/interface access and removes resetCachedBlocks usage in the same Qt call sites.

Suggested reviewers: pastapastapasta

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the PR's CoinJoin client refactor and simplification work, though it is a bit broad.
Description check ✅ Passed The description is clearly aligned with the CoinJoin client refactor and lists the same moved and removed pieces.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)

901-935: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Fix size_t underflow and optimize unused masternode selection.

If the number of enabled masternodes drops below the tracked used masternode count, nCountNotExcluded will underflow. This bypasses the < 1 early exit, allocates and shuffles a vector unnecessarily, and logs a massive garbage integer for the remaining node count.

Additionally, the current implementation copies and shuffles all enabled masternodes just to pick one unused node, requiring O(N) random generations and swaps. Instead, filter the unused masternodes into a smaller vector and pick one element randomly using a single GetRand call.

⚡ Proposed fix and optimization
 CDeterministicMNCPtr CCoinJoinClientSession::GetRandomNotUsedMasternode()
 {
     auto mnList = m_dmnman.GetListAtChainTip();
 
     size_t nCountEnabled = mnList.GetCounts().enabled();
-    size_t nCountNotExcluded{nCountEnabled - m_mn_metaman.GetUsedMasternodesCount()};
+    size_t nUsedCount = m_mn_metaman.GetUsedMasternodesCount();
+    size_t nCountNotExcluded = (nCountEnabled > nUsedCount) ? (nCountEnabled - nUsedCount) : 0;
 
     WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- %d enabled masternodes, %d masternodes to choose from\n", __func__, nCountEnabled, nCountNotExcluded);
     if (nCountNotExcluded < 1) {
         return nullptr;
     }
 
-    // fill a vector
-    std::vector<CDeterministicMNCPtr> vpMasternodesShuffled;
-    vpMasternodesShuffled.reserve(nCountEnabled);
-    mnList.ForEachMNShared(/*onlyValid=*/true,
-                           [&vpMasternodesShuffled](const auto& dmn) { vpMasternodesShuffled.emplace_back(dmn); });
-
-    // shuffle pointers
-    Shuffle(vpMasternodesShuffled.begin(), vpMasternodesShuffled.end(), FastRandomContext());
-
-    // loop through - using direct O(1) lookup instead of creating a set copy
-    for (const auto& dmn : vpMasternodesShuffled) {
-        if (m_mn_metaman.IsUsedMasternode(dmn->proTxHash)) {
-            continue;
-        }
-
-        WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- found, masternode=%s\n", __func__,
-                         dmn->proTxHash.ToString());
-        return dmn;
-    }
+    std::vector<CDeterministicMNCPtr> unusedMasternodes;
+    unusedMasternodes.reserve(nCountNotExcluded);
+    mnList.ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
+        if (!m_mn_metaman.IsUsedMasternode(dmn->proTxHash)) {
+            unusedMasternodes.emplace_back(dmn);
+        }
+    });
 
-    WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- failed\n", __func__);
-    return nullptr;
+    if (unusedMasternodes.empty()) {
+        WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- failed\n", __func__);
+        return nullptr;
+    }
+
+    size_t randomIndex = GetRand<size_t>(unusedMasternodes.size());
+    auto dmn = unusedMasternodes[randomIndex];
+
+    WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::%s -- found, masternode=%s\n", __func__,
+                     dmn->proTxHash.ToString());
+    return dmn;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/coinjoin/client.cpp` around lines 901 - 935, Update
CCoinJoinClientSession::GetRandomNotUsedMasternode to avoid size_t underflow by
handling used-masternode counts greater than or equal to the enabled count
before calculating the remaining count. Collect only valid, unused masternodes
into the candidate vector, return nullptr when it is empty, and select one
candidate with a single GetRand call instead of shuffling all enabled
masternodes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/coinjoin/client.cpp`:
- Around line 901-935: Update CCoinJoinClientSession::GetRandomNotUsedMasternode
to avoid size_t underflow by handling used-masternode counts greater than or
equal to the enabled count before calculating the remaining count. Collect only
valid, unused masternodes into the candidate vector, return nullptr when it is
empty, and select one candidate with a single GetRand call instead of shuffling
all enabled masternodes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d17b456f-8d7a-4bd5-b98d-36d0202dee77

📥 Commits

Reviewing files that changed from the base of the PR and between ca2912d and 6158ac1.

📒 Files selected for processing (6)
  • src/coinjoin/client.cpp
  • src/coinjoin/client.h
  • src/interfaces/coinjoin.h
  • src/qt/optionsdialog.cpp
  • src/qt/overviewpage.cpp
  • src/qt/overviewpage.h
💤 Files with no reviewable changes (2)
  • src/interfaces/coinjoin.h
  • src/qt/optionsdialog.cpp

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Preliminary review — Codex only

The CoinJoin ownership, logging, and constexpr changes are coherent, and the five-commit stack has clean permanent-history structure. However, the Qt cache move drops the prior options-dialog invalidation, so enabling Advanced CoinJoin UI during an active mix can expose a zero or stale progress bar until another block or balance change.

Validated blockers were found in the Codex precheck. Sonnet is deferred until a fresh Codex revalidation clears the blocker gate.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (failed), gpt-5.6-sol — dash-core-commit-history (failed), gpt-5.6-sol — general (completed), gpt-5.6-sol — dash-core-commit-history (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet: not run (deferred by blocker gate)

🔴 1 blocking

1 additional finding(s) omitted (not in diff).

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/qt/overviewpage.cpp`:
- [BLOCKING] src/qt/overviewpage.cpp:512-513: Invalidate progress when enabling the advanced UI
  OptionsModel emits AdvancedCJUIChanged synchronously during mapper->submit(), and this handler changes the visibility mode before calling coinJoinStatus(true). During an active mix, coinJoinStatus only recomputes progress when the block height differs from nCachedNumBlocks; after a normal timer tick those values are equal. Because updateCoinJoinProgress() returns before calculating the advanced progress while advanced mode is disabled, enabling it can reveal the UI's default 0 value or an older value. Before this PR, OptionsDialog reset the client cache to max after submitting options, forcing the next one-second timer tick to recompute; wallet->markDirty() does not invalidate the new page-local cache or emit balanceChanged when balances are unchanged. Invalidate nCachedNumBlocks here or directly refresh the progress.

@knst
knst requested review from PastaPastaPasta and UdjinM6 July 16, 2026 07:20
@knst
knst marked this pull request as draft July 16, 2026 08:01
knst added 3 commits July 16, 2026 22:46
…nomResult in some instances

  - one instance of strAutoDenomResult was never read: getSessionStatuses() reads each session's GetStatus(), not the manager's copy
  - CheckAutomaticBackup collapsed to WalletCJLogPrint
  - Manager::DoAutomaticDenominating has better logging
@knst
knst force-pushed the fix-cj-dangling-ptr-2 branch from 6158ac1 to a19dee0 Compare July 16, 2026 15:46
@knst
knst marked this pull request as ready for review July 16, 2026 18:46

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Final validation — Codex + Sonnet

Independent inspection confirms the prior Advanced CoinJoin UI cache blocker is fixed: coinJoinStatus(true) now resets nCachedNumBlocks to std::numeric_limits::max() (src/qt/overviewpage.cpp:524) before the height-throttle and progress-cache checks, and updateAdvancedCJUI() invokes this forced path synchronously, so no stale/default progress can be exposed. The five-commit CoinJoin/Qt refactor stack is clean and independently reviewable; one low-confidence commit-subject-length nitpick is retained since it is factually accurate (96 and 95 characters) though non-blocking.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (failed), gpt-5.6-sol — dash-core-commit-history (failed), gpt-5.6-sol — general (failed), gpt-5.6-sol — dash-core-commit-history (failed), gpt-5.6-sol — general (failed), gpt-5.6-sol — general (completed), gpt-5.6-sol — dash-core-commit-history (completed)
  • Verifier: claude-sonnet-5 — final-verifier
  • Sonnet reviewers: claude-sonnet-5 — general (failed), claude-sonnet-5 — dash-core-commit-history (failed), claude-sonnet-5 — general (failed), claude-sonnet-5 — dash-core-commit-history (completed), claude-sonnet-5 — general (completed)

💬 1 nitpick(s)

1 additional finding(s) omitted (not in diff).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants