Skip to content

perf(qt): reduce consequent calls of updateVotingCapability for each block#6835

Merged
PastaPastaPasta merged 2 commits into
dashpay:developfrom
knst:perf-qt-masternodelist
Sep 23, 2025
Merged

perf(qt): reduce consequent calls of updateVotingCapability for each block#6835
PastaPastaPasta merged 2 commits into
dashpay:developfrom
knst:perf-qt-masternodelist

Conversation

@knst
Copy link
Copy Markdown
Collaborator

@knst knst commented Sep 2, 2025

Issue being fixed or feature implemented

Currently, there are 2 handlers for governance in Qt app: updateProposalList and updateVotingCapability.
updateVotingCapability is calling for each block; updateProposalList is calling once in 10 seconds.
Calling updateVotingCapability for each block is very inefficient because during reindex this handler takes uses one full CPU core on my laptop and make Qt app very irresponsible and lagging; it could take up to 10-60 seconds to see an input text in RPC console or react to click.

What was done?

Call updateVotingCapability for each call of updateProposalList, no more often.

Minor improvement: use unique_ptr instead shared_ptr in ClientModel: it returns a copy of object anyway currently instead shared_ptr.

How Has This Been Tested?

This PR makes updateVotingCapability disappear from the top of profiler.

Breaking Changes

N/A

Checklist:

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

@knst knst added this to the 23 milestone Sep 2, 2025
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Sep 2, 2025

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 2, 2025

Walkthrough

  • ClientModel changes: mnListCached ownership switches from std::shared_ptr to std::unique_ptr; allocations updated to std::make_unique. The mutex guarding it is renamed from cs_mnlinst to cs_mnlist, with corresponding updates in constructor, setMasternodeList, getMasternodeList, and refreshMasternodeList. getMasternodeList now dereferences a unique_ptr. The public typedef CDeterministicMNListPtr is removed. mnListCached gains GUARDED_BY(cs_mnlist).
  • GovernanceList changes: added include <chain.h>. In setClientModel, updateProposalList() is called only when model != nullptr and previous wiring is removed. updateProposalList now conditionally calls updateVotingCapability when walletModel exists. updateVotingCapability uses mn_list variable, clears votableMasternodes before iteration, and calls mn_list.ForEachMN.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Ownership model change (shared_ptr → unique_ptr) with mutex rename across header/implementation.
  • Removal of a public typedef and concurrent-guard annotation update.
  • Adjusted access patterns and locking in multiple methods.
  • GovernanceList refactor with include addition, call-site relocation, minor variable renames, and small logic reordering.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@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 (1)
src/qt/clientmodel.h (1)

56-78: Remove lingering CDeterministicMNListPtr usages in governance.h
The alias using CDeterministicMNListPtr = std::shared_ptr<CDeterministicMNList>; (line 40) and its field CDeterministicMNListPtr lastMNListForVotingKeys; (line 180) need updating now that the public typedef was removed—replace with the appropriate type or inline declaration.

🧹 Nitpick comments (6)
src/qt/clientmodel.h (1)

118-121: Prefer non-recursive mutex; annotate mnListTip; simplify locking path

Use a plain Mutex here and avoid re-entrant locking; also add thread-safety annotation for mnListTip. This makes misuse harder and TSAN hints clearer.

-    mutable RecursiveMutex cs_mnlist; // protects mnListCached
-    std::unique_ptr<CDeterministicMNList> mnListCached GUARDED_BY(cs_mnlist){};
-    const CBlockIndex* mnListTip{nullptr};
+    mutable Mutex cs_mnlist; // protects mnListCached/mnListTip
+    std::unique_ptr<CDeterministicMNList> mnListCached GUARDED_BY(cs_mnlist){};
+    const CBlockIndex* mnListTip GUARDED_BY(cs_mnlist){nullptr};

Follow-up in clientmodel.cpp provided below to remove the nested LOCK.

src/qt/governancelist.cpp (2)

10-10: Drop heavy include if unused

chain.h seems unnecessary in this TU; types accessed via clientModel are opaque. Remove to cut compile time if nothing here needs it.

-#include <chain.h>

409-412: Trigger voting capability updates with proposal refresh (throttled)

This achieves the PR goal of avoiding per-block recomputation. Consider, optionally, reusing the already-fetched masternode list from above to avoid a second copy inside updateVotingCapability.

src/qt/clientmodel.cpp (3)

102-111: Update tip even if list hash unchanged

If the BlockIndex pointer instance changes while the block hash stays the same (reindex/IBD), mnListTip should still be refreshed. Move the assignment before the equality short-circuit.

-    LOCK(cs_mnlist);
-    if (mnListCached->GetBlockHash() == mnList.GetBlockHash()) {
-        return;
-    }
-    mnListCached = std::make_unique<CDeterministicMNList>(mnList);
-    mnListTip = tip;
+    LOCK(cs_mnlist);
+    mnListTip = tip;
+    if (mnListCached->GetBlockHash() == mnList.GetBlockHash()) {
+        return;
+    }
+    mnListCached = std::make_unique<CDeterministicMNList>(mnList);

119-125: Remove redundant nested LOCK and the need for RecursiveMutex

refreshMasternodeList locks cs_mnlist then calls setMasternodeList(), which also locks it. Drop the outer LOCK and keep all mutation inside setMasternodeList(). This enables using a plain Mutex (see header diff).

 void ClientModel::refreshMasternodeList()
 {
     auto [mnList, tip] = m_node.evo().getListAtChainTip();
-
-    LOCK(cs_mnlist);
-    setMasternodeList(mnList, tip);
+    setMasternodeList(mnList, tip);
 }

113-117: Minor: assert non-null cache (defensive)

Optional: add an assertion to document the invariant that mnListCached is initialized in the ctor.

-    LOCK(cs_mnlist);
-    return {*mnListCached, mnListTip};
+    LOCK(cs_mnlist);
+    assert(mnListCached != nullptr);
+    return {*mnListCached, mnListTip};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e23a658 and dacbacc.

📒 Files selected for processing (3)
  • src/qt/clientmodel.cpp (2 hunks)
  • src/qt/clientmodel.h (1 hunks)
  • src/qt/governancelist.cpp (4 hunks)
🧰 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/qt/governancelist.cpp
  • src/qt/clientmodel.cpp
  • src/qt/clientmodel.h
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
PR: dashpay/dash#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T18:42:49.794Z
Learning: Applies to src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp} : Masternode lists must use immutable data structures (Immer library) for thread safety
📚 Learning: 2025-07-20T18:42:49.794Z
Learnt from: CR
PR: dashpay/dash#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-20T18:42:49.794Z
Learning: Applies to src/{masternode,evo}/**/*.{cpp,h,cc,cxx,hpp} : Masternode lists must use immutable data structures (Immer library) for thread safety

Applied to files:

  • src/qt/clientmodel.cpp
🧬 Code graph analysis (2)
src/qt/clientmodel.cpp (2)
src/node/interfaces.cpp (9)
  • LOCK (533-537)
  • LOCK (543-550)
  • LOCK (551-558)
  • LOCK (820-829)
  • LOCK (863-867)
  • LOCK (1047-1051)
  • tip (96-104)
  • tip (538-542)
  • tip (559-567)
src/qt/clientmodel.h (1)
  • cs_mnlist (119-119)
src/qt/clientmodel.h (1)
src/evo/deterministicmns.h (1)
  • CDeterministicMNList (127-188)
⏰ 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). (7)
  • GitHub Check: Lint / Run linters
  • GitHub Check: x86_64-pc-linux-gnu_multiprocess / Build depends
  • GitHub Check: x86_64-w64-mingw32 / Build depends
  • GitHub Check: x86_64-apple-darwin / Build depends
  • GitHub Check: x86_64-pc-linux-gnu_nowallet / Build depends
  • GitHub Check: x86_64-pc-linux-gnu / Build depends
  • GitHub Check: arm-linux-gnueabihf / Build depends
🔇 Additional comments (3)
src/qt/governancelist.cpp (2)

367-368: Good: initialize data flow only once clientModel is set

Moving the initial refresh behind the nullptr check is correct and prevents premature calls.


492-503: Robust iteration and state reset look good

Clearing votableMasternodes before ForEachMN(true, ...) is correct; using wallet.isSpendable(script) matches RPC logic.

src/qt/clientmodel.cpp (1)

53-53: Switch to unique_ptr init: LGTM

Local ownership here is appropriate; copies are still returned by value to callers.

Copy link
Copy Markdown

@UdjinM6 UdjinM6 left a comment

Choose a reason for hiding this comment

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

This makes sense as an optimisation for reindex but not for regular usage when block time is 15 times larger than GOVERNANCELIST_UPDATE_SECONDS (156 > 10). Can we use one logic while we are still syncing and then switch to another one once we are up to date?

Also, I don't like having all the unrelated refactoring here, let's move that to another PR.

@knst
Copy link
Copy Markdown
Collaborator Author

knst commented Sep 3, 2025

This makes sense as an optimisation for reindex but not for regular usage when block time is 15 times larger than GOVERNANCELIST_UPDATE_SECONDS (156 > 10)

@UdjinM6

NOTE_1: not every new block changes Deterministic MN List. And ability of vote is triggered even more rare, because it is relevant to only masternodes that you have private key.
NOTE_2: you may import private key to your wallet and you maybe able to vote for masternode even if there was no new block.

IMO, 10seconds is a good balance between wasting CPU resources and how long masternode owner is ready to wait once he added a private key to wallet or once is protx transaction is mined.

Just to avoid possible misunderstanding...
Do you mean, that updating ability of vote every 10seconds is still too big waste of cpu?
Or do you mean, that 10seconds is too big delay for "non-reindex" regular mode and it's better to update ability to vote faster?

@knst knst requested a review from UdjinM6 September 3, 2025 19:16
Copy link
Copy Markdown

@UdjinM6 UdjinM6 left a comment

Choose a reason for hiding this comment

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

My concern was that we might update it too often but I tried it and it seems to be working just fine.

utACK dacbacc

(still don't like having unrelated refactoring here :P )

Copy link
Copy Markdown
Collaborator

@kwvg kwvg left a comment

Choose a reason for hiding this comment

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

utACK dacbacc

Comment thread src/qt/clientmodel.cpp

void ClientModel::setMasternodeList(const CDeterministicMNList& mnList, const CBlockIndex* tip)
{
LOCK(cs_mnlinst);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: should be done in a separate commit

@PastaPastaPasta PastaPastaPasta merged commit 24064da into dashpay:develop Sep 23, 2025
34 of 38 checks passed
@knst knst deleted the perf-qt-masternodelist branch September 23, 2025 14:12
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.

4 participants