Skip to content

Comments

perf: do linear lookup instead building 2 heavy Hash-Maps#7176

Open
knst wants to merge 3 commits intodashpay:developfrom
knst:fix-follow-up-2026-02
Open

perf: do linear lookup instead building 2 heavy Hash-Maps#7176
knst wants to merge 3 commits intodashpay:developfrom
knst:fix-follow-up-2026-02

Conversation

@knst
Copy link
Collaborator

@knst knst commented Feb 24, 2026

Issue being fixed or feature implemented

Currently, for each update of masternode list 3 list are generated: list of shared_pointers, hash-map by service, hash-map by protx.

Creating these hash-maps creates overhead for every user for every masternode update. It causes excessive battery use, excessive RAM usage while these lookups happens in rare conditions (only for masternode owners during voting; only when "Peer Detail" is open).

It's better to do linear lookup once over all masternode in the specific scenario, rather than build these heavy objects for every update of list.

Lookup during voting may looks scary due to potential O(N^2). Though, it's not N^2, but N*M (where M is amount of masternodes that belongs to single Owner, M<<N). Secondly, N is relatively small and voting is not assumed to be rapid-fast action (milliseconds); it's okay if user will wait extra 10ms in the worst case scenario for each its vote.

What was done?

Code is updated to do linear lookup instead building heavy hash-maps for every update.

Current implementation (in develop) has a bug with dangling pointer and potentially could cause UB: #7118 (comment)
Fixed by this PR

How Has This Been Tested?

N/A

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 added 2 commits February 24, 2026 15:10
…-Maps which used almost never

It also fixes a bug with possible UB and dangling pointer to masternode if list has been re-created
@github-actions
Copy link

github-actions bot commented Feb 24, 2026

⚠️ 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 Feb 24, 2026

Walkthrough

This pull request removes two MasternodeData index maps (m_by_service and m_by_protx) and updates callers to iterate m_entries (linear lookup). It deletes src/qt/util.h and adds GUIUtil::MakeQByteArray in src/qt/guiutil.h, replacing usages of the removed util::make_array. It also removes the GOV::getObjLocalValidity declaration and its GOVImpl implementation. Several Qt UI files (clientfeeds, proposallist, rpcconsole) were adapted to use shared_ptr-based MasternodeEntry lookups and QByteArray construction via the new helper.

Sequence Diagram(s)

(Skipped — changes are refactoring and API removal without a new multi-component sequential control flow.)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: replacing two heavy hash-map structures with linear lookups during specific operations.
Description check ✅ Passed The pull request description clearly explains the performance optimization being implemented: replacing expensive hash-map generation on every masternode list update with linear lookups performed only when needed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/qt/proposallist.cpp (1)

552-557: ⚠️ Potential issue | 🟡 Minor

Fix clang-format diff in the failedMessages block

CI flagged formatting differences for Lines 552-559. Please run clang-format (or the repo formatting script) on this block.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/qt/proposallist.cpp` around lines 552 - 557, The failedMessages.append
block in proposallist.cpp (inside the "Sign vote via wallet interface" branch
where walletModel->wallet().signGovernanceVote is called) is misformatted; run
the project's clang-format or formatting script on that block (the
failedMessages.append(...) and surrounding braces/continue) so indentation, line
breaks and brace placement match the repo style and resolve the CI clang-format
diff.
🧹 Nitpick comments (1)
src/qt/guiutil.h (1)

534-538: Add safeguards to MakeQByteArray template to prevent misuse with non-byte containers

The template accepts any type with .data() and .size(), but QByteArray interprets the size as a byte count. While the current call site in rpcconsole.cpp safely passes std::vector<unsigned char> (where element count equals byte count), adding a static assertion and explicit narrowing cast would prevent future misuse with containers like std::vector<int>.

♻️ Suggested hardening
 template <typename T1>
 inline QByteArray MakeQByteArray(const T1& data)
 {
-    return QByteArray(reinterpret_cast<const char*>(data.data()), data.size());
+    static_assert(sizeof(*data.data()) == 1, "MakeQByteArray expects byte-sized elements");
+    return QByteArray(reinterpret_cast<const char*>(data.data()),
+                      static_cast<int>(data.size()));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/qt/guiutil.h` around lines 534 - 538, MakeQByteArray currently accepts
any type with .data() and .size(), but QByteArray expects a byte count; add a
compile-time guard and an explicit narrowing cast to avoid misuse with non-byte
element types. In MakeQByteArray add a static_assert that sizeof(typename
T1::value_type) == 1 (or that value_type is unsigned char/char/uint8_t) to
ensure element size is one byte, and change the size argument to QByteArray to
use an explicit static_cast<int>(data.size()) so you don't pass a size_t
directly; keep the reinterpret_cast<const char*>(data.data()) for the pointer.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/qt/clientfeeds.h`:
- Around line 151-155: MasternodeData's m_counts member (type
interfaces::MnList::Counts) lacks the {} initializer for consistency with
similar structs; update the MasternodeData definition to initialize m_counts
with {} (matching CreditPoolData and InstantSendData) so the line declaring
m_counts uses m_counts{} to explicitly value-initialize it.

---

Outside diff comments:
In `@src/qt/proposallist.cpp`:
- Around line 552-557: The failedMessages.append block in proposallist.cpp
(inside the "Sign vote via wallet interface" branch where
walletModel->wallet().signGovernanceVote is called) is misformatted; run the
project's clang-format or formatting script on that block (the
failedMessages.append(...) and surrounding braces/continue) so indentation, line
breaks and brace placement match the repo style and resolve the CI clang-format
diff.

---

Nitpick comments:
In `@src/qt/guiutil.h`:
- Around line 534-538: MakeQByteArray currently accepts any type with .data()
and .size(), but QByteArray expects a byte count; add a compile-time guard and
an explicit narrowing cast to avoid misuse with non-byte element types. In
MakeQByteArray add a static_assert that sizeof(typename T1::value_type) == 1 (or
that value_type is unsigned char/char/uint8_t) to ensure element size is one
byte, and change the size argument to QByteArray to use an explicit
static_cast<int>(data.size()) so you don't pass a size_t directly; keep the
reinterpret_cast<const char*>(data.data()) for the pointer.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d71b89c and eb5eb40.

📒 Files selected for processing (9)
  • src/Makefile.qt.include
  • src/interfaces/node.h
  • src/node/interfaces.cpp
  • src/qt/clientfeeds.cpp
  • src/qt/clientfeeds.h
  • src/qt/guiutil.h
  • src/qt/proposallist.cpp
  • src/qt/rpcconsole.cpp
  • src/qt/util.h
💤 Files with no reviewable changes (4)
  • src/interfaces/node.h
  • src/qt/util.h
  • src/Makefile.qt.include
  • src/node/interfaces.cpp

Comment on lines 151 to +155
struct MasternodeData {
bool m_valid{false};
int m_list_height{0};
interfaces::MnList::Counts m_counts{};
QHash<QByteArray, const MasternodeEntry*> m_by_service{};
std::vector<std::shared_ptr<MasternodeEntry>> m_entries{};
Uint256HashMap<const MasternodeEntry*> m_by_protx{};
interfaces::MnList::Counts m_counts;
std::vector<std::shared_ptr<MasternodeEntry>> m_entries;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/qt/clientfeeds.h | sed -n '145,160p'

Repository: dashpay/dash

Length of output: 489


🏁 Script executed:

rg -n -A5 'struct MasternodeData' src

Repository: dashpay/dash

Length of output: 694


🏁 Script executed:

rg -n 'MasternodeData\s*[({]' src --type=cpp --type=cc --type=h --type=hpp

Repository: dashpay/dash

Length of output: 82


🏁 Script executed:

rg -n 'interfaces::MnList::Counts' src --type=cpp --type=cc --type=h --type=hpp -B2 -A2

Repository: dashpay/dash

Length of output: 82


🏁 Script executed:

rg -n 'MasternodeData' src --type=cpp --type=h -B2 -A5 | head -80

Repository: dashpay/dash

Length of output: 3045


🏁 Script executed:

fd . src -name '*.h' -o -name '*.cpp' | xargs grep -l 'MnList::Counts' | head -5

Repository: dashpay/dash

Length of output: 286


🏁 Script executed:

rg -n 'namespace.*MnList\|struct.*Counts\|class.*Counts' src --type=cpp --type=h -A3

Repository: dashpay/dash

Length of output: 38


🏁 Script executed:

find src -name '*.h' -o -name '*.cpp' | xargs grep -l 'Counts\s*[{=:]' | head -10

Repository: dashpay/dash

Length of output: 158


🏁 Script executed:

rg 'struct Counts|class Counts' src --type=cpp --type=h -A10

Repository: dashpay/dash

Length of output: 4075


🏁 Script executed:

rg 'MnList::Counts' src --type=cpp --type=h -B3 -A3

Repository: dashpay/dash

Length of output: 383


🏁 Script executed:

rg -n 'm_counts' src/qt --type=cpp --type=h -B2 -A2

Repository: dashpay/dash

Length of output: 3934


🏁 Script executed:

cat -n src/qt/masternodelist.cpp | sed -n '200,225p'

Repository: dashpay/dash

Length of output: 1095


🏁 Script executed:

rg -n 'ret\.' src/qt/masternodelist.cpp | grep -A2 -B2 'MasternodeData ret'

Repository: dashpay/dash

Length of output: 38


🏁 Script executed:

cat -n src/interfaces/node.h | sed -n '1,50p' | grep -A20 'struct Counts'

Repository: dashpay/dash

Length of output: 38


🏁 Script executed:

rg -n 'struct Counts' src/interfaces/node.h -A12

Repository: dashpay/dash

Length of output: 576


Add initializer for consistency with similar structures in the same file.

Line 154 omits the {} initializer on m_counts. While the interfaces::MnList::Counts type safely zero-initializes all its members (defined in src/interfaces/node.h), other similar data structures in src/qt/clientfeeds.h (CreditPoolData at line 117 and InstantSendData at line 135) explicitly initialize their m_counts members with {}. For consistency and clarity:

Suggested fix
-    interfaces::MnList::Counts m_counts;
+    interfaces::MnList::Counts m_counts{};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
struct MasternodeData {
bool m_valid{false};
int m_list_height{0};
interfaces::MnList::Counts m_counts{};
QHash<QByteArray, const MasternodeEntry*> m_by_service{};
std::vector<std::shared_ptr<MasternodeEntry>> m_entries{};
Uint256HashMap<const MasternodeEntry*> m_by_protx{};
interfaces::MnList::Counts m_counts;
std::vector<std::shared_ptr<MasternodeEntry>> m_entries;
struct MasternodeData {
bool m_valid{false};
int m_list_height{0};
interfaces::MnList::Counts m_counts{};
std::vector<std::shared_ptr<MasternodeEntry>> m_entries;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/qt/clientfeeds.h` around lines 151 - 155, MasternodeData's m_counts
member (type interfaces::MnList::Counts) lacks the {} initializer for
consistency with similar structs; update the MasternodeData definition to
initialize m_counts with {} (matching CreditPoolData and InstantSendData) so the
line declaring m_counts uses m_counts{} to explicitly value-initialize it.

Copy link
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.

minor nit and CodeRabbit suggestion (source), otherwise LGTM

const auto addr_key{util::make_array(stats->nodeStats.addr.GetKey())};
const MasternodeEntry* dmn = [&]() -> const MasternodeEntry* {
const auto addr_key{GUIUtil::MakeQByteArray(stats->nodeStats.addr.GetKey())};
const std::shared_ptr<MasternodeEntry> dmn = [&]() -> const std::shared_ptr<MasternodeEntry> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Consider using MasternodeEntryList alias

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