Skip to content

feat(qt): introduce framework for sourcing and applying data, use for {Masternode,Proposal}Lists #7146

Open
kwvg wants to merge 9 commits intodashpay:developfrom
kwvg:client_feeds
Open

feat(qt): introduce framework for sourcing and applying data, use for {Masternode,Proposal}Lists #7146
kwvg wants to merge 9 commits intodashpay:developfrom
kwvg:client_feeds

Conversation

@kwvg
Copy link
Collaborator

@kwvg kwvg commented Feb 15, 2026

Additional Information

  • Dependency for feat(qt): UI refresh (4/n, introduce distinct widgets for Dash-specific reporting in debug window) #7118

  • The redesigned "Masternode" and "Governance" tabs (see dash#7110 and dash#7116) were migrated to a worker-based update mechanism with a773635 and others, this proved to deal with main thread contention and the debounce mechanism prevented frequent refreshes of infrequently updated data and was a step towards throttled event-driven updates.

    • This implementation though had a problematic side effect, a worker thread is spawned per tab per wallet and while per tab threads were by design, per wallet threads meant redundant work happening all at once fighting over the same locks, which cause noticeable performance degradation.

    • This pull request therefore introduces the concept of "feeds", that perform the expensive fetch operation that are either triggered by events (and debounced) or user feedback (and executed immediately) to ensure snappiness. The IBD throttling logic is respected.

  • The result is having a central worker thread fetching (fetch()) and dispatching refresh signals (dataReady()) which are then used by the consumer (e.g. updateMasternodeList()) to fetch wallet-specific information (which is relatively inexpensive) and then apply those changes (e.g. setMasternodeList()).

    • Note that wallet-specific fetching is done on the main thread, to prevent thread explosion (even if they are doing now non-redundant work) as they'd still be per tab per wallet.
  • Note that masternodes and proposal views use different refresh intervals, masternode data uses 3 seconds and proposal data uses 10 seconds, scaled up to 30s and 60s respectively during IBD.

Breaking Changes

None expected.

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 (note: N/A)
  • I have made corresponding changes to the documentation (note: N/A)
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

@kwvg kwvg added this to the 24 milestone Feb 15, 2026
@github-actions
Copy link

github-actions bot commented Feb 15, 2026

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

@kwvg kwvg requested a review from UdjinM6 February 15, 2026 20:43
@kwvg
Copy link
Collaborator Author

kwvg commented Feb 15, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Feb 15, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)

Walkthrough

This PR adds a Qt client-side feed system (ClientFeeds, FeedBase, templated Feed, MasternodeFeed, ProposalFeed) and integrates it into ClientModel. Masternode and proposal UI, models, and related headers are refactored to consume MasternodeData / ProposalData from feeds. Ownership semantics change from unique_ptr to shared_ptr for entries and proposals. GovernanceList is replaced by ProposalList across UI and build visibility; build/moc/public-header lists updated accordingly. Feed scheduling runs on a worker thread with QTimer-driven fetches and dataReady signaling.

Sequence Diagram(s)

sequenceDiagram
    participant UI as ProposalList / MasternodeList
    participant CM as ClientModel
    participant CF as ClientFeeds
    participant Feed as MasternodeFeed / ProposalFeed
    participant Timer as QTimer
    participant Worker as WorkerThread
    participant Data as FeedData

    UI->>CM: setClientModel(...)
    CM->>CF: create/register feeds, start()
    CF->>Feed: registerFeed(feed)
    CF->>Timer: create/start(baseline/throttle)
    Timer->>CF: timeout
    CF->>Worker: schedule(fetch task)
    Worker->>Feed: fetch()
    Feed->>Data: gather/compute data
    Feed->>Worker: setData(shared_ptr)
    Worker->>CF: notify dataReady
    CF->>UI: emit dataReady
    UI->>UI: updateMasternodeList / updateProposalList
    alt syncing state change
        UI->>CF: setSyncing(true/false)
        CF->>Timer: adjust/backoff intervals
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • UdjinM6
  • knst
  • PastaPastaPasta
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.04% 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 clearly summarizes the main objective: introducing a feeds framework for data sourcing and applying it to Masternode and Proposal lists, which is the primary focus of the changeset.
Description check ✅ Passed The description provides comprehensive context about the motivation (per-wallet thread redundancy), the solution (centralized feeds framework), and implementation details, all directly related to the changeset.

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

✨ Finishing touches
🧪 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.

🧹 Nitpick comments (11)
test/lint/lint-circular-dependencies.py (1)

60-66: New circular dependency entries align with the refactored module structure.

Seven new cycles added, all rooted in qt/clientmodelqt/masternodelist/qt/proposallist and their transitive dependencies. These replace the prior qt/governancelist-based entries. Based on learnings, when refactoring changes module dependencies, update the allowlist entries to reflect the new dependency paths.

As an architectural note: the number of cycles through qt/clientmodel is growing. Consider whether a forward-declaration or interface-based decoupling could reduce this surface in a follow-up.

src/qt/clientfeeds.h (1)

96-103: add() has no guard against being called after stop().

If add() is called after stop(), the feed will be registered (including registerFeed which creates a timer and connects it) but m_thread may already be finished and m_worker set to nullptr. The invokeMethod in registerFeed's lambda would then invoke on a null object.

Consider adding an assert or early return:

🛡️ Suggested defensive guard
     template<typename Source, typename... Args>
     Source* add(Args&&... args) {
+        assert(!m_stopped);
         auto source = std::make_unique<Source>(std::forward<Args>(args)...);

Based on learnings, prefer assert() for programmer errors that should be caught in development, rather than defensive runtime checks with fallbacks.

src/qt/clientmodel.cpp (1)

84-89: Proposal feed refreshes on every block tip but not masternode feed.

m_feed_proposal->requestRefresh() is triggered on every non-header numBlocksChanged, while the masternode feed only refreshes when masternodeListChanged fires. If the intent is that the masternode feed should also refresh periodically with blocks (e.g., to update "next payment height" or similar block-height-dependent data), this may be a gap. If the masternode list signal is sufficient, this is fine as-is.

src/qt/walletview.h (1)

12-12: Consider forward-declaring ProposalList instead of including the full header.

Only a ProposalList* member is declared here (line 72). A forward declaration would reduce header coupling and compilation dependencies, consistent with the existing forward declarations in this file (e.g., class ClientModel;, class OverviewPage;).

♻️ Suggested change
-#include <qt/proposallist.h>
+class ProposalList;

(Move the #include to walletview.cpp where ProposalList methods are actually called.)

src/qt/clientfeeds.cpp (1)

46-76: Solid feed registration with proper thread marshalling and retry logic.

The concurrency model is correct: timer fires on main thread → fetch dispatched to worker → completion marshalled back to main thread via QTimer::singleShot. The m_in_progress atomic prevents overlapping fetches, and m_retry_pending ensures no updates are lost.

One minor point: util::ThreadRename("qt-clientfeed") on Line 58 is called on every fetch invocation. Since the worker thread is reused, this syscall is redundant after the first call.

♻️ Move thread rename to thread start

Consider renaming the thread once at start time instead of on every fetch:

 void ClientFeeds::start()
 {
     m_worker->moveToThread(m_thread);
     connect(m_thread, &QThread::finished, m_worker, &QObject::deleteLater);
     m_thread->start();
+    QMetaObject::invokeMethod(m_worker, [] { util::ThreadRename("qt-clientfeed"); });

     for (const auto& source : m_sources) {

And remove it from registerFeed:

         QMetaObject::invokeMethod(m_worker, [this, raw] {
-            util::ThreadRename("qt-clientfeed");
             try {
src/qt/masternodelist.cpp (3)

184-235: Feed fetch logic is correct; minor style nit on C-style cast.

The fetch logic properly handles edge cases (shutdown, empty MN list, failed projected payees). Data is correctly shared via std::make_shared<Data>().

Line 208: (int)i uses a C-style cast. Prefer static_cast<int>(i) for consistency with the rest of the codebase (e.g., Line 203 already uses the more explicit style indirectly).

♻️ Use C++ cast
-        nextPayments.emplace(dmn->getProTxHash(), ret->m_list_height + (int)i + 1);
+        nextPayments.emplace(dmn->getProTxHash(), ret->m_list_height + static_cast<int>(i) + 1);

237-285: updateMasternodeList() re-fetches MN list for wallet ownership — intentional but worth documenting.

Lines 271-283 call clientModel->getMasternodeList() again (the feed already fetched it in MasternodeFeed::fetch()). This is necessary because the feed's MasternodeData contains pre-built MasternodeEntry objects and doesn't expose raw DMN references needed for isSpendable key/script checks. A brief comment explaining this would help future readers.

📝 Suggested comment
+    // Re-fetch raw MN list from clientModel since MasternodeData contains
+    // pre-built UI entries without raw key/script data needed for ownership checks.
     const auto [dmn, pindex] = clientModel->getMasternodeList();

254-257: Field-by-field copy could be simplified.

The copy from feed to ret copies each field individually. Since MasternodeData is a simple struct, a direct copy would be more concise and less error-prone if new fields are added later.

♻️ Simplify to struct copy
-    MasternodeFeed::Data ret;
-    ret.m_list_height = feed->m_list_height;
-    ret.m_entries = feed->m_entries;
-    ret.m_valid = feed->m_valid;
+    MasternodeFeed::Data ret = *feed;
src/qt/proposallist.cpp (3)

130-146: No cleanup of prior feed connection if setClientModel is called more than once.

If setClientModel is ever called a second time with a different ClientModel*, the old m_feed signal connection (line 138) will remain active alongside the new one, potentially invoking updateProposalList twice per feed update — once from the stale feed and once from the new one.

If setClientModel is guaranteed to be called only once, this is fine. Otherwise, disconnect the previous feed before connecting the new one.

Suggested guard
 void ProposalList::setClientModel(ClientModel* model)
 {
+    if (m_feed) {
+        disconnect(m_feed, &ProposalFeed::dataReady, this, &ProposalList::updateProposalList);
+        m_feed = nullptr;
+    }
     this->clientModel = model;
     if (!clientModel) {
         return;
     }

282-289: Clearing m_proposals discards all pre-fetched feed data for Local source — intentional but worth a brief inline comment.

Line 284 clears the proposals vector that was just copied from the feed. For the Local source path this is expected (only wallet proposals should be shown), but a reader might wonder why the feed data was copied at all for this branch. A one-liner comment at line 263 (e.g., "Copy feed data as baseline; source-specific paths may override") would help.


632-671: Reentrancy guard uses a plain bool — adequate here but not exception-safe.

m_col_refresh is set to true on line 639 and reset on line 671. If any code between those lines threw, the flag would remain set permanently, silently disabling future resizes. In practice, Qt widget calls don't throw, so this is fine. A scope-guard pattern (auto guard = qScopeGuard([&]{ m_col_refresh = false; });) would be slightly more robust but is not essential.

@kwvg kwvg force-pushed the client_feeds branch 2 times, most recently from 6f0d15e to fd12e06 Compare February 16, 2026 15:04
@kwvg kwvg marked this pull request as ready for review February 16, 2026 16:16
@kwvg kwvg requested review from PastaPastaPasta, UdjinM6 and knst and removed request for UdjinM6 February 16, 2026 16:17
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: 2

Caution

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

⚠️ Outside diff range comments (1)
src/qt/proposalmodel.h (1)

1-1: ⚠️ Potential issue | 🟡 Minor

CI failure: clang-format differences detected.

The pipeline reports clang-format violations for this file. Run ./contrib/devtools/clang-format-diff.py -p1 or apply clang-format to resolve.

🤖 Fix all issues with AI agents
In `@src/qt/clientmodel.cpp`:
- Around line 90-95: The masternode feed's next-payment heights become stale
because m_feed_masternode is only refreshed on masternodeListChanged while its
nNextPayment depends on current block height; update m_feed_masternode on each
new block like m_feed_proposal does by calling
m_feed_masternode->requestRefresh() inside the numBlocksChanged lambda (the same
lambda that sets m_feeds->setSyncing and calls
m_feed_proposal->requestRefresh()), so height-dependent calculations using
getMasternodeList() are re-evaluated per block.

In `@test/util/data/non-backported.txt`:
- Around line 31-38: Replace the broad file globs ending with ".*" (e.g.,
src/qt/clientfeeds.*, src/qt/descriptiondialog.*, src/qt/guiutil_font.*,
src/qt/masternodelist.*, src/qt/masternodelodel.*,
src/qt/mnemonicverificationdialog.*, src/qt/proposalcreate.*,
src/qt/proposallist.*) with C++-only patterns restricting to .cpp and .h (for
example src/qt/clientfeeds.{cpp,h}) so the list only matches C++ source and
header files used by clang-format; update each listed entry to the {cpp,h}
pattern.
🧹 Nitpick comments (5)
src/qt/masternodelist.cpp (2)

189-192: Shallow copy of feed data is intentional but worth noting.

ret.m_entries = feed->m_entries copies the shared_ptr vector (bumping ref counts). This is correct for the feed pattern where the snapshot is shared, but it means the UI holds references to the same entry objects the feed produced. Ensure MasternodeEntry objects are treated as immutable after construction (they appear to be, from the read-only accessor pattern).


206-218: Potential snapshot mismatch between feed data and getMasternodeList().

The masternode entries displayed come from m_feed->data() (Line 178), but the owned-MN check at Line 206 calls clientModel->getMasternodeList() separately. These could reference different chain tips momentarily, causing a mismatch between displayed entries and ownership highlights. This self-corrects on the next feed cycle, but if strict consistency is needed, consider deriving owned_mns from the same snapshot the feed uses.

src/qt/masternodelist.h (1)

34-35: Stale forward declaration of QThread.

With the removal of the old worker thread infrastructure, QThread is no longer referenced in this header. The forward declaration can be removed.

🧹 Suggested cleanup
 QT_BEGIN_NAMESPACE
 class QModelIndex;
-class QThread;
 QT_END_NAMESPACE
src/qt/proposallist.h (1)

64-64: std::atomic<bool> for single-threaded guard.

m_col_refresh is only accessed from the main thread (via showEvent, resizeEvent, and sectionResized signal handler). A plain bool would suffice, but this is harmless.

src/qt/clientfeeds.cpp (1)

79-98: Variable dmn is shadowed three times in nested scopes.

Line 64 binds dmn from getMasternodeList(), Line 81 reuses dmn for projectedPayees[i], and Line 85 uses dmn as the lambda parameter. While valid C++, this triple-shadowing is confusing and could hide bugs if the wrong dmn is accidentally referenced.

♻️ Rename to avoid shadowing
-    for (size_t i = 0; i < projectedPayees.size(); i++) {
-        const auto& dmn = projectedPayees[i];
-        nextPayments.emplace(dmn->getProTxHash(), ret->m_list_height + (int)i + 1);
+    for (size_t i = 0; i < projectedPayees.size(); i++) {
+        const auto& payee = projectedPayees[i];
+        nextPayments.emplace(payee->getProTxHash(), ret->m_list_height + (int)i + 1);
     }

-    dmn->forEachMN(/*only_valid=*/false, [&](const auto& dmn) {
+    dmn->forEachMN(/*only_valid=*/false, [&](const auto& mn) {

Comment on lines 90 to 95
connect(this, &ClientModel::numBlocksChanged, this,
[this](int, const QDateTime&, const QString&, double, bool header, SynchronizationState sync_state) {
if (header) return;
m_feeds->setSyncing(sync_state != SynchronizationState::POST_INIT);
if (m_feed_proposal) m_feed_proposal->requestRefresh();
});
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what triggers masternodeListChanged and if height-dependent data is handled
rg -n "masternodeListChanged" --type cpp --type-add 'header:*.h' --type header -C 2

echo ""
echo "=== MasternodeFeed fetch implementation ==="
rg -n "MasternodeFeed::fetch" --type cpp -A 30

Repository: dashpay/dash

Length of output: 3422


🏁 Script executed:

#!/bin/bash
# Check getMasternodeList implementation to see if pindex is current or cached
echo "=== getMasternodeList implementation ==="
rg -n "getMasternodeList" --type cpp --type-add 'header:*.h' --type header -B 2 -A 10 | head -80

echo ""
echo "=== How nextPayments is used in MasternodeFeed ==="
rg -n "nextPayments" src/qt/clientfeeds.cpp -B 2 -A 2

echo ""
echo "=== Check if m_list_height or nextPayments update on block changes ==="
rg -n "m_list_height\|next.*payment" src/qt/clientfeeds.cpp -B 1 -A 1

Repository: dashpay/dash

Length of output: 5814


🏁 Script executed:

#!/bin/bash
# Compare ProposalFeed::fetch to understand why it updates on every block
echo "=== ProposalFeed::fetch implementation ==="
rg -n "class ProposalFeed" --type cpp --type-add 'header:*.h' --type header -A 5

echo ""
echo "=== ProposalFeed::fetch method ==="
rg -n "void ProposalFeed::fetch" src/qt/clientfeeds.cpp -A 50 | head -80

echo ""
echo "=== Check where block changes trigger proposal refresh ==="
rg -n "m_feed_proposal.*requestRefresh\|numBlocksChanged" src/qt/clientmodel.cpp -B 2 -A 2

Repository: dashpay/dash

Length of output: 3036


🏁 Script executed:

#!/bin/bash
# Search for where nNextPayment is used after being calculated
echo "=== Usage of nNextPayment in MasternodeFeed ==="
rg -n "nNextPayment" src/qt/clientfeeds.cpp -B 2 -A 10

echo ""
echo "=== Check how MasternodeFeed data is displayed ==="
rg -n "MasternodeFeed\|m_feed_masternode" src/qt/ -t cpp --type-add 'header:*.h' --type header | grep -v "Binary" | head -30

echo ""
echo "=== Check the Data structure to see what's stored ==="
rg -n "struct.*MasternodeData\|struct MasternodeFeed::Data" src/qt/clientfeeds.cpp -A 20 | head -60

Repository: dashpay/dash

Length of output: 961


🏁 Script executed:

#!/bin/bash
# Search for MasternodeEntry class definition and usage
echo "=== MasternodeEntry class definition ==="
rg -n "struct MasternodeEntry\|class MasternodeEntry" --type cpp --type-add 'header:*.h' --type header -A 15 | head -80

echo ""
echo "=== Find MasternodeModel or display classes ==="
find src/qt -name "*.h" -o -name "*.cpp" | xargs grep -l "MasternodeEntry\|MasternodeFeed" | head -10

echo ""
echo "=== Check what calls getMasternodeList to see how it's used in UI ==="
rg -n "getMasternodeList\(\)" src/qt/ -B 3 -A 8 | head -100

Repository: dashpay/dash

Length of output: 6762


Masternode feed next payment heights go stale between masternode list changes.

m_feed_masternode is only refreshed on masternodeListChanged (line 81), but the calculated nNextPayment values depend on the current block height. Since getMasternodeList() returns cached data that only updates when the masternode list changes, the next_payment_height displayed to users becomes increasingly inaccurate as new blocks arrive. This is inconsistent with m_feed_proposal, which refreshes on every block despite using the same cached masternode list, suggesting that height-dependent calculations require real-time updates.

🤖 Prompt for AI Agents
In `@src/qt/clientmodel.cpp` around lines 90 - 95, The masternode feed's
next-payment heights become stale because m_feed_masternode is only refreshed on
masternodeListChanged while its nNextPayment depends on current block height;
update m_feed_masternode on each new block like m_feed_proposal does by calling
m_feed_masternode->requestRefresh() inside the numBlocksChanged lambda (the same
lambda that sets m_feeds->setSyncing and calls
m_feed_proposal->requestRefresh()), so height-dependent calculations using
getMasternodeList() are re-evaluated per block.

Comment on lines +31 to +38
src/qt/clientfeeds.*
src/qt/descriptiondialog.*
src/qt/governancelist.*
src/qt/guiutil_font.*
src/qt/masternodelist.*
src/qt/masternodemodel.*
src/qt/mnemonicverificationdialog.*
src/qt/proposalcreate.*
src/qt/proposallist.*
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

Restrict new entries to .cpp/.h patterns.

This file should only list C++ sources/headers. Using .* can accidentally include other extensions if added later. Consider narrowing to {cpp,h} for the new entries.

✅ Suggested update
-src/qt/clientfeeds.*
+src/qt/clientfeeds.{cpp,h}-src/qt/proposallist.*
+src/qt/proposallist.{cpp,h}

Based on learnings: “test/util/data/non-backported.txt should only include C++ files (.cpp or .h) because it is used for running clang-format. Other file types … should not be added.”

🤖 Prompt for AI Agents
In `@test/util/data/non-backported.txt` around lines 31 - 38, Replace the broad
file globs ending with ".*" (e.g., src/qt/clientfeeds.*,
src/qt/descriptiondialog.*, src/qt/guiutil_font.*, src/qt/masternodelist.*,
src/qt/masternodelodel.*, src/qt/mnemonicverificationdialog.*,
src/qt/proposalcreate.*, src/qt/proposallist.*) with C++-only patterns
restricting to .cpp and .h (for example src/qt/clientfeeds.{cpp,h}) so the list
only matches C++ source and header files used by clang-format; update each
listed entry to the {cpp,h} pattern.

@kwvg kwvg requested a review from UdjinM6 February 16, 2026 17:39
@kwvg kwvg force-pushed the client_feeds branch 2 times, most recently from d353007 to 763e1c0 Compare February 16, 2026 18:32
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

🤖 Fix all issues with AI agents
In `@src/qt/clientfeeds.h`:
- Around line 137-166: The m_worker QObject is orphaned because deleteLater() is
queued to the worker thread's event loop and may never run once stop() calls
m_thread->quit() and m_thread->wait(); fix by ensuring deterministic cleanup:
either set m_worker->setParent(this) (ClientFeeds) when creating/assigning it so
it will be deleted with the ClientFeeds instance, or explicitly delete m_worker
after m_thread->wait() in stop() (e.g., if (m_worker) { m_worker->deleteLater();
delete m_worker; m_worker = nullptr; } or simply delete m_worker and set to
nullptr) and remove reliance on the QThread::finished -> deleteLater connection;
update start() and stop() code paths accordingly to use the chosen approach and
reference m_worker, start(), stop(), and the QThread::finished/deleteLater usage
when making the change.
🧹 Nitpick comments (5)
src/qt/clientfeeds.cpp (1)

55-99: Variable name dmn shadows outer scope on Line 78.

Line 61 binds dmn from the structured binding, then Line 78 declares const auto& dmn = projectedPayees[i] inside the loop, and Line 82's lambda parameter is also named dmn. While legal C++, this triple-shadowing is confusing and could mask bugs during maintenance.

🔧 Rename inner variables to avoid confusion
     for (size_t i = 0; i < projectedPayees.size(); i++) {
-        const auto& dmn = projectedPayees[i];
-        nextPayments.emplace(dmn->getProTxHash(), ret->m_list_height + (int)i + 1);
+        const auto& payee = projectedPayees[i];
+        nextPayments.emplace(payee->getProTxHash(), ret->m_list_height + (int)i + 1);
     }
 
-    dmn->forEachMN(/*only_valid=*/false, [&](const auto& dmn) {
+    dmn->forEachMN(/*only_valid=*/false, [&](const auto& mn) {
src/qt/proposallist.cpp (2)

295-313: showCreateProposalDialog: three separate connect calls to accepted could be consolidated.

Lines 309-311 connect three separate slots to the same QDialog::accepted signal. While functionally correct, a single lambda could handle all three actions.

♻️ Optional consolidation
-    connect(proposalCreate, &QDialog::accepted, this, [this] { if (m_feed) m_feed->requestForceRefresh(); });
-    connect(proposalCreate, &QDialog::accepted, this, &ProposalList::updateProposalButtons);
-    connect(proposalCreate, &QDialog::accepted, this, &ProposalList::showResumeProposalDialog);
+    connect(proposalCreate, &QDialog::accepted, this, [this] {
+        if (m_feed) m_feed->requestForceRefresh();
+        updateProposalButtons();
+        showResumeProposalDialog();
+    });

589-628: refreshColumnWidths uses m_col_refresh as a reentrancy guard — std::atomic is overkill here.

Since refreshColumnWidths is only invoked from the main thread (via showEvent, resizeEvent, and sectionResized signal), a plain bool would suffice. The atomic adds unnecessary overhead for cross-core visibility guarantees that aren't needed.

This is declared in the header (Line 64 of proposallist.h). Minor nit — not worth changing if consistency with other patterns is preferred.

src/qt/proposallist.h (1)

23-36: Remove unused CDeterministicMNList forward declaration (line 23).

The forward declaration is not referenced in this header or the corresponding .cpp file and can be safely removed.

src/qt/masternodelist.h (1)

20-35: Remove unused QThread forward declaration (line 34).

The QThread forward declaration is not used anywhere in the class definition and can be removed. Additionally, the #include <QThread> in the implementation file may also be unnecessary.

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

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@src/Makefile.qt.include`:
- Around line 247-248: The list in BITCOIN_QT_BASE_CPP is out of alphabetical
order: move the entries "qt/masternodemodel.cpp" and "qt/proposalmodel.cpp" from
their current location between bitcoinaddressvalidator.cpp and
bitcoinamountfield.cpp into their correct alphabetical positions — place
"qt/masternodemodel.cpp" near "modaloverlay.cpp" (M-section) and
"qt/proposalmodel.cpp" near "qvalidatedlineedit.cpp" (P/Q-section) so the entire
list remains sorted; update only the ordering in the Makefile fragment,
preserving existing whitespace and line endings.

In `@src/qt/clientfeeds.cpp`:
- Around line 109-141: ProposalFeed::fetch() uses a structured binding named dmn
from m_client_model.getMasternodeList(), which shadows/duplicates the naming
pattern criticized elsewhere; rename the binding (e.g., mn_list or mnList) and
update all subsequent uses (currently dmn->getValidWeightedMNsCount() and any
other dmn references) to the new name to match the MasternodeFeed::fetch()
rename for clarity and consistency.
- Around line 55-99: The fetch function currently shadows the outer variable
named dmn (from auto [dmn, pindex] = m_client_model.getMasternodeList()) with
inner uses named dmn in the projectedPayees loop and the forEachMN lambda;
rename one side to avoid shadowing (e.g. rename the structured binding dmn to
mnList or mnManager, or rename the lambda parameters to payee and mn) and update
all references in MasternodeFeed::fetch (uses in getProjectedMNPayees,
projectedPayees loop, nextPayments.emplace, and the forEachMN callback where
getCollateralOutpoint(), getProTxHash(), etc. are called) so identifiers are
unique and readable.
- Around line 187-198: ClientFeeds::start currently restarts every source timer
with m_timer->start(0), which forces an immediate timeout and makes prior
requestForceRefresh() calls redundant; change the logic to only start timers
that are not already active (check QTimer::isActive() on each source->m_timer
and call start() only when isActive() is false) so that pre-start
requestForceRefresh() results are preserved and you avoid forcing an immediate
0ms restart.

In `@src/qt/clientfeeds.h`:
- Around line 145-152: The add<Source>(...) template must only be called before
the feed system is started; add an assert at the top of add() to fail-fast when
called after start() (use the existing startup flag used by start()/m_started or
the lifecycle indicator checked by registerFeed/start). Modify the add()
implementation (referencing add, registerFeed, m_sources and the start()
lifecycle flag) to Q_ASSERT or assert(!startedFlag) so new feeds can't be added
once start() has run, keeping registerFeed and m_sources behavior unchanged.
- Around line 96-100: MasternodeData includes an m_valid flag used by
MasternodeList::updateMasternodeList() to schedule retries, while ProposalData
currently lacks this flag; add a brief inline comment inside the ProposalData
struct explaining why validity tracking is unnecessary (or that
ProposalFeed::fetch() only publishes complete data and handles failures via
early return) so future readers know the asymmetry is intentional and not a
missing field.

In `@src/qt/proposallist.h`:
- Around line 43-44: The doc comment above the ProposalList class is stale
(mentions "Governance Manager page widget" and the old class name
GovernanceList); update the comment to accurately describe ProposalList (e.g.,
"Proposal list page widget" or similar) so it matches the renamed class
ProposalList in proposallist.h and any related documentation references.
🧹 Nitpick comments (6)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In `@src/Makefile.qt.include`:
- Around line 247-248: The list in BITCOIN_QT_BASE_CPP is out of alphabetical
order: move the entries "qt/masternodemodel.cpp" and "qt/proposalmodel.cpp" from
their current location between bitcoinaddressvalidator.cpp and
bitcoinamountfield.cpp into their correct alphabetical positions — place
"qt/masternodemodel.cpp" near "modaloverlay.cpp" (M-section) and
"qt/proposalmodel.cpp" near "qvalidatedlineedit.cpp" (P/Q-section) so the entire
list remains sorted; update only the ordering in the Makefile fragment,
preserving existing whitespace and line endings.

In `@src/qt/clientfeeds.cpp`:
- Around line 109-141: ProposalFeed::fetch() uses a structured binding named dmn
from m_client_model.getMasternodeList(), which shadows/duplicates the naming
pattern criticized elsewhere; rename the binding (e.g., mn_list or mnList) and
update all subsequent uses (currently dmn->getValidWeightedMNsCount() and any
other dmn references) to the new name to match the MasternodeFeed::fetch()
rename for clarity and consistency.
- Around line 55-99: The fetch function currently shadows the outer variable
named dmn (from auto [dmn, pindex] = m_client_model.getMasternodeList()) with
inner uses named dmn in the projectedPayees loop and the forEachMN lambda;
rename one side to avoid shadowing (e.g. rename the structured binding dmn to
mnList or mnManager, or rename the lambda parameters to payee and mn) and update
all references in MasternodeFeed::fetch (uses in getProjectedMNPayees,
projectedPayees loop, nextPayments.emplace, and the forEachMN callback where
getCollateralOutpoint(), getProTxHash(), etc. are called) so identifiers are
unique and readable.
- Around line 187-198: ClientFeeds::start currently restarts every source timer
with m_timer->start(0), which forces an immediate timeout and makes prior
requestForceRefresh() calls redundant; change the logic to only start timers
that are not already active (check QTimer::isActive() on each source->m_timer
and call start() only when isActive() is false) so that pre-start
requestForceRefresh() results are preserved and you avoid forcing an immediate
0ms restart.

In `@src/qt/clientfeeds.h`:
- Around line 145-152: The add<Source>(...) template must only be called before
the feed system is started; add an assert at the top of add() to fail-fast when
called after start() (use the existing startup flag used by start()/m_started or
the lifecycle indicator checked by registerFeed/start). Modify the add()
implementation (referencing add, registerFeed, m_sources and the start()
lifecycle flag) to Q_ASSERT or assert(!startedFlag) so new feeds can't be added
once start() has run, keeping registerFeed and m_sources behavior unchanged.
- Around line 96-100: MasternodeData includes an m_valid flag used by
MasternodeList::updateMasternodeList() to schedule retries, while ProposalData
currently lacks this flag; add a brief inline comment inside the ProposalData
struct explaining why validity tracking is unnecessary (or that
ProposalFeed::fetch() only publishes complete data and handles failures via
early return) so future readers know the asymmetry is intentional and not a
missing field.
src/Makefile.qt.include (1)

247-248: masternodemodel.cpp and proposalmodel.cpp break alphabetical order in BITCOIN_QT_BASE_CPP.

The rest of the list is sorted alphabetically, but these two entries are wedged between bitcoinaddressvalidator.cpp and bitcoinamountfield.cpp. They should be placed in their correct positions (masternodemodel.cpp near modaloverlay.cpp, proposalmodel.cpp near qvalidatedlineedit.cpp).

Proposed fix
 BITCOIN_QT_BASE_CPP = \
   qt/appearancewidget.cpp \
   qt/bantablemodel.cpp \
   qt/bitcoin.cpp \
   qt/bitcoinaddressvalidator.cpp \
-  qt/masternodemodel.cpp \
-  qt/proposalmodel.cpp \
   qt/bitcoinamountfield.cpp \
   qt/bitcoingui.cpp \
   qt/bitcoinunits.cpp \
   qt/clientfeeds.cpp \
   qt/clientmodel.cpp \
   qt/csvmodelwriter.cpp \
   qt/guiutil.cpp \
   qt/guiutil_font.cpp \
   qt/initexecutor.cpp \
   qt/intro.cpp \
+  qt/masternodemodel.cpp \
   qt/modaloverlay.cpp \
   qt/networkstyle.cpp \
   qt/notificator.cpp \
   qt/optionsdialog.cpp \
   qt/optionsmodel.cpp \
   qt/peertablemodel.cpp \
   qt/peertablesortproxy.cpp \
+  qt/proposalmodel.cpp \
   qt/qvalidatedlineedit.cpp \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Makefile.qt.include` around lines 247 - 248, The list in
BITCOIN_QT_BASE_CPP is out of alphabetical order: move the entries
"qt/masternodemodel.cpp" and "qt/proposalmodel.cpp" from their current location
between bitcoinaddressvalidator.cpp and bitcoinamountfield.cpp into their
correct alphabetical positions — place "qt/masternodemodel.cpp" near
"modaloverlay.cpp" (M-section) and "qt/proposalmodel.cpp" near
"qvalidatedlineedit.cpp" (P/Q-section) so the entire list remains sorted; update
only the ordering in the Makefile fragment, preserving existing whitespace and
line endings.
src/qt/clientfeeds.cpp (3)

109-141: Same dmn shadowing pattern in ProposalFeed::fetch().

Line 115 binds dmn from structured binding, and line 124 uses dmn->getValidWeightedMNsCount(). While there's no lambda shadowing here, maintaining consistency with the suggested rename in MasternodeFeed::fetch() would improve clarity (e.g., mn_list).

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

In `@src/qt/clientfeeds.cpp` around lines 109 - 141, ProposalFeed::fetch() uses a
structured binding named dmn from m_client_model.getMasternodeList(), which
shadows/duplicates the naming pattern criticized elsewhere; rename the binding
(e.g., mn_list or mnList) and update all subsequent uses (currently
dmn->getValidWeightedMNsCount() and any other dmn references) to the new name to
match the MasternodeFeed::fetch() rename for clarity and consistency.

55-99: Variable shadowing: dmn used for both the mn-list and the lambda parameter.

On line 61, dmn is bound to the masternode list from the structured binding. On lines 78 and 82, dmn is reused as the lambda parameter (projected payee and forEachMN callback respectively). While this compiles, it shadows the outer dmn and harms readability.

Suggested rename
-    const auto [dmn, pindex] = m_client_model.getMasternodeList();
+    const auto [mn_list, pindex] = m_client_model.getMasternodeList();
-    if (!dmn || !pindex) {
+    if (!mn_list || !pindex) {
         return;
     }

-    auto projectedPayees = dmn->getProjectedMNPayees(pindex);
-    if (projectedPayees.empty() && dmn->getValidMNsCount() > 0) {
+    auto projectedPayees = mn_list->getProjectedMNPayees(pindex);
+    if (projectedPayees.empty() && mn_list->getValidMNsCount() > 0) {
         return;
     }

     auto ret = std::make_shared<Data>();
-    ret->m_list_height = dmn->getHeight();
+    ret->m_list_height = mn_list->getHeight();
     ...
-    dmn->forEachMN(/*only_valid=*/false, [&](const auto& dmn) {
+    mn_list->forEachMN(/*only_valid=*/false, [&](const auto& dmn) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/qt/clientfeeds.cpp` around lines 55 - 99, The fetch function currently
shadows the outer variable named dmn (from auto [dmn, pindex] =
m_client_model.getMasternodeList()) with inner uses named dmn in the
projectedPayees loop and the forEachMN lambda; rename one side to avoid
shadowing (e.g. rename the structured binding dmn to mnList or mnManager, or
rename the lambda parameters to payee and mn) and update all references in
MasternodeFeed::fetch (uses in getProjectedMNPayees, projectedPayees loop,
nextPayments.emplace, and the forEachMN callback where getCollateralOutpoint(),
getProTxHash(), etc. are called) so identifiers are unique and readable.

187-198: start() re-starts all timers at 0ms, making prior requestForceRefresh() calls redundant.

In clientmodel.cpp, requestForceRefresh() is called on each feed right after add(), but then start() (line 193-196) restarts all timers with 0ms anyway. The pre-start() force-refresh calls are effectively no-ops since the timer gets reset. Not a bug, but the redundancy may be confusing to future readers.

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

In `@src/qt/clientfeeds.cpp` around lines 187 - 198, ClientFeeds::start currently
restarts every source timer with m_timer->start(0), which forces an immediate
timeout and makes prior requestForceRefresh() calls redundant; change the logic
to only start timers that are not already active (check QTimer::isActive() on
each source->m_timer and call start() only when isActive() is false) so that
pre-start requestForceRefresh() results are preserved and you avoid forcing an
immediate 0ms restart.
src/qt/clientfeeds.h (2)

145-152: add() must only be called before start() — consider an assert.

The add<Source>() template creates a feed, registers it (creating a timer), and adds it to m_sources. If called after start(), the new feed's timer won't be initially started, and the feed won't participate in the worker thread lifecycle correctly. An assert would catch misuse during development.

Suggested safeguard
     template<typename Source, typename... Args>
     Source* add(Args&&... args) {
+        assert(!m_thread->isRunning() && "feeds must be added before start()");
         auto source = std::make_unique<Source>(std::forward<Args>(args)...);

Based on learnings, in C++/Qt codebases, use fail-fast asserts in setters to enforce invariants. Prefer assert() for programmer errors that should be caught in development and debugging.

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

In `@src/qt/clientfeeds.h` around lines 145 - 152, The add<Source>(...) template
must only be called before the feed system is started; add an assert at the top
of add() to fail-fast when called after start() (use the existing startup flag
used by start()/m_started or the lifecycle indicator checked by
registerFeed/start). Modify the add() implementation (referencing add,
registerFeed, m_sources and the start() lifecycle flag) to Q_ASSERT or
assert(!startedFlag) so new feeds can't be added once start() has run, keeping
registerFeed and m_sources behavior unchanged.

96-100: MasternodeData has m_valid but ProposalData does not — intentional asymmetry?

MasternodeData has an m_valid field (defaulting to false) that is checked in MasternodeList::updateMasternodeList() to schedule retries. ProposalData (lines 117-122) lacks this field, so ProposalFeed::fetch() either publishes complete data or publishes nothing (early return). The asymmetry is fine if intentional, but consider adding a brief comment on ProposalData explaining why validity tracking isn't needed there.

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

In `@src/qt/clientfeeds.h` around lines 96 - 100, MasternodeData includes an
m_valid flag used by MasternodeList::updateMasternodeList() to schedule retries,
while ProposalData currently lacks this flag; add a brief inline comment inside
the ProposalData struct explaining why validity tracking is unnecessary (or that
ProposalFeed::fetch() only publishes complete data and handles failures via
early return) so future readers know the asymmetry is intentional and not a
missing field.

@github-actions
Copy link

This pull request has conflicts, please rebase.

1 similar comment
@github-actions
Copy link

This pull request has conflicts, please rebase.

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

🧹 Nitpick comments (6)
src/qt/clientfeeds.cpp (1)

76-79: Variable dmn on line 78 shadows the structured binding on line 61.

The inner dmn (the projected payee) shadows the outer dmn (the masternode list). It works because the inner scope ends before line 82 uses the outer one, but this is confusing and fragile if the loop body is ever extended.

♻️ Suggested rename
     for (size_t i = 0; i < projectedPayees.size(); i++) {
-        const auto& dmn = projectedPayees[i];
-        nextPayments.emplace(dmn->getProTxHash(), ret->m_list_height + (int)i + 1);
+        const auto& payee = projectedPayees[i];
+        nextPayments.emplace(payee->getProTxHash(), ret->m_list_height + (int)i + 1);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/qt/clientfeeds.cpp` around lines 76 - 79, The inner loop variable dmn in
the block that iterates projectedPayees shadows the outer structured-binding
dmn; rename the inner variable to a clearer, non-conflicting name (e.g.,
projectedPayee or payee) in the for-loop that builds nextPayments so you still
call projectedPayee->getProTxHash() and compute ret->m_list_height + (int)i + 1
without changing logic; update all uses inside that loop (the
nextPayments.emplace call) to the new identifier to remove the shadowing and
avoid future confusion.
src/qt/clientfeeds.h (1)

94-100: Consider consolidating MasternodeEntryList alias near MasternodeEntry.

The MasternodeEntryList alias is defined here in the feed header. For discoverability, it may be better placed alongside the MasternodeEntry class definition. However, this is a minor organizational preference.

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

In `@src/qt/clientfeeds.h` around lines 94 - 100, Move the MasternodeEntryList
type alias next to the MasternodeEntry class definition to improve
discoverability: locate the current alias MasternodeEntryList
(std::vector<std::shared_ptr<MasternodeEntry>>) and cut it from
src/qt/clientfeeds.h where MasternodeData is declared, then paste it adjacent to
the MasternodeEntry class/struct declaration so references (MasternodeEntryList,
MasternodeEntry) are colocated; ensure MasternodeData continues to include
m_entries of type MasternodeEntryList and update any includes or forward
declarations if necessary.
src/qt/proposallist.cpp (1)

308-312: Three separate connections from QDialog::accepted — consider consolidating.

Lines 309–311 connect three separate lambdas/slots to proposalCreate's accepted signal. While functionally correct (all will fire), a single slot that performs all three actions would be slightly cleaner and make the execution order explicit.

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

In `@src/qt/proposallist.cpp` around lines 308 - 312, Consolidate the three
separate connections to QDialog::accepted into a single slot so the actions run
in a defined order: replace the three connect calls that reference
proposalCreate, QDialog::accepted, m_feed->requestForceRefresh(),
ProposalList::updateProposalButtons, and ProposalList::showResumeProposalDialog
with one connect that calls a single lambda or a new ProposalList method which
(1) calls m_feed->requestForceRefresh() if m_feed, (2) calls
updateProposalButtons(), and (3) calls showResumeProposalDialog(), then keep
proposalCreate->show() as-is.
src/qt/proposallist.h (1)

23-23: Remove unused CDeterministicMNList forward declaration.

The forward declaration at line 23 is not used in any member declarations, method signatures, or in the corresponding .cpp file and can be safely removed.

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

In `@src/qt/proposallist.h` at line 23, Remove the unused forward declaration of
CDeterministicMNList in proposallist.h: locate the line declaring "class
CDeterministicMNList;" and delete it since it is not referenced by any member
declarations, method signatures, or in the corresponding .cpp; ensure no other
code relies on that forward declaration (e.g., check for usages of
CDeterministicMNList in proposallist.cpp) and run a build to verify no missing
symbols.
src/qt/proposalmodel.h (1)

95-95: Consolidate duplicate Proposals type alias to a single header.

This type alias is defined identically in both proposalmodel.h (line 95) and clientfeeds.h (line 115). Define it once in a shared header and include from both files to avoid maintenance issues if one definition diverges from the other.

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

In `@src/qt/proposalmodel.h` at line 95, The Proposals type alias (using Proposals
= std::vector<std::shared_ptr<Proposal>>;) is duplicated; remove the duplicate
and centralize it: create a single shared header (e.g., a new proposals_types.h)
that defines the Proposals alias and include that header from both
proposalmodel.h and clientfeeds.h, then delete the local alias definitions in
ProposalModel and ClientFeeds headers so both use the same centralized Proposals
type; ensure the shared header forward-declares Proposal or includes the
Proposal definition as needed to compile.
src/qt/masternodelist.h (1)

34-34: Remove unused QThread forward declaration.

The QThread forward declaration is no longer referenced in this header after the removal of m_thread. The class has no members or methods that use it. Additionally, the #include <QThread> in masternodelist.cpp appears to be dead code and should also be removed.

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

In `@src/qt/masternodelist.h` at line 34, Remove the now-unused QThread forward
declaration from the header: delete the line declaring "class QThread;" in
masternodelist.h and also remove the corresponding "#include <QThread>" from
masternodelist.cpp since m_thread was removed and no code references QThread;
ensure no other symbols in MasternodeList or functions reference QThread before
committing.
🤖 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 28-59: m_syncing is a plain bool causing a data race between
setSyncing() (main thread) and reads in requestRefresh() (worker thread); change
m_syncing to std::atomic<bool> in class FeedBase and update its initialization
and usage so setSyncing() writes atomically and reads in requestRefresh()/retry
handlers use the atomic value (leave method names setSyncing(),
requestRefresh(), and the FeedBase::m_syncing identifier intact so callers
remain unchanged).

---

Duplicate comments:
In `@src/qt/clientfeeds.h`:
- Around line 137-166: The stop() implementation must explicitly free the
heap-allocated m_worker because deleteLater() tied to QThread::finished may
never run after quit()+wait(); modify stop() to, after calling m_thread->quit()
and m_thread->wait(), check m_worker and delete it (delete m_worker; m_worker =
nullptr) and likewise delete or cleanup m_thread, ensuring no orphaned pointers
remain; reference m_worker, m_thread, and stop() when making this change.

In `@src/qt/clientmodel.cpp`:
- Around line 88-94: The masternode feed (m_feed_masternode) is not being
refreshed on block height updates so its nNextPayment values (computed in
MasternodeFeed::fetch()) grow stale; update the numBlocksChanged handler (the
same lambda that calls m_feed_proposal->requestRefresh()) to also call
m_feed_masternode->requestRefresh() when header is false so the masternode feed
is refreshed on each block change and keeps next-payment heights accurate.
Ensure you reference m_feed_masternode and MasternodeFeed::fetch() behavior when
adding the call and guard it the same way you guard the proposal refresh.

In `@src/qt/proposallist.h`:
- Around line 43-44: The doc comment above the ProposalList class is stale
("Governance Manager page widget"); update the comment for the ProposalList
class to accurately describe its current purpose and behavior (e.g., "Proposal
list view widget" or a brief summary of what ProposalList does), ensuring the
comment directly precedes the class declaration for ProposalList so future
reviewers see the correct description.

---

Nitpick comments:
In `@src/qt/clientfeeds.cpp`:
- Around line 76-79: The inner loop variable dmn in the block that iterates
projectedPayees shadows the outer structured-binding dmn; rename the inner
variable to a clearer, non-conflicting name (e.g., projectedPayee or payee) in
the for-loop that builds nextPayments so you still call
projectedPayee->getProTxHash() and compute ret->m_list_height + (int)i + 1
without changing logic; update all uses inside that loop (the
nextPayments.emplace call) to the new identifier to remove the shadowing and
avoid future confusion.

In `@src/qt/clientfeeds.h`:
- Around line 94-100: Move the MasternodeEntryList type alias next to the
MasternodeEntry class definition to improve discoverability: locate the current
alias MasternodeEntryList (std::vector<std::shared_ptr<MasternodeEntry>>) and
cut it from src/qt/clientfeeds.h where MasternodeData is declared, then paste it
adjacent to the MasternodeEntry class/struct declaration so references
(MasternodeEntryList, MasternodeEntry) are colocated; ensure MasternodeData
continues to include m_entries of type MasternodeEntryList and update any
includes or forward declarations if necessary.

In `@src/qt/masternodelist.h`:
- Line 34: Remove the now-unused QThread forward declaration from the header:
delete the line declaring "class QThread;" in masternodelist.h and also remove
the corresponding "#include <QThread>" from masternodelist.cpp since m_thread
was removed and no code references QThread; ensure no other symbols in
MasternodeList or functions reference QThread before committing.

In `@src/qt/proposallist.cpp`:
- Around line 308-312: Consolidate the three separate connections to
QDialog::accepted into a single slot so the actions run in a defined order:
replace the three connect calls that reference proposalCreate,
QDialog::accepted, m_feed->requestForceRefresh(),
ProposalList::updateProposalButtons, and ProposalList::showResumeProposalDialog
with one connect that calls a single lambda or a new ProposalList method which
(1) calls m_feed->requestForceRefresh() if m_feed, (2) calls
updateProposalButtons(), and (3) calls showResumeProposalDialog(), then keep
proposalCreate->show() as-is.

In `@src/qt/proposallist.h`:
- Line 23: Remove the unused forward declaration of CDeterministicMNList in
proposallist.h: locate the line declaring "class CDeterministicMNList;" and
delete it since it is not referenced by any member declarations, method
signatures, or in the corresponding .cpp; ensure no other code relies on that
forward declaration (e.g., check for usages of CDeterministicMNList in
proposallist.cpp) and run a build to verify no missing symbols.

In `@src/qt/proposalmodel.h`:
- Line 95: The Proposals type alias (using Proposals =
std::vector<std::shared_ptr<Proposal>>;) is duplicated; remove the duplicate and
centralize it: create a single shared header (e.g., a new proposals_types.h)
that defines the Proposals alias and include that header from both
proposalmodel.h and clientfeeds.h, then delete the local alias definitions in
ProposalModel and ClientFeeds headers so both use the same centralized Proposals
type; ensure the shared header forward-declares Proposal or includes the
Proposal definition as needed to compile.

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)

480-575: ⚠️ Potential issue | 🟠 Major

Guard against null mnList before dereference.

getMasternodeList() can return a null list even when pindex is non-null (as indicated by other call sites). The current code only checks pindex and then dereferences mnList, which can crash.

🐛 Proposed fix
-    auto [mnList, pindex] = clientModel->getMasternodeList();
-    if (!pindex) {
+    auto [mnList, pindex] = clientModel->getMasternodeList();
+    if (!mnList || !pindex) {
         QMessageBox::warning(this, tr("Voting Failed"), tr("Unable to get masternode list. Please try again later."));
         return;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/qt/proposallist.cpp` around lines 480 - 575, The code calls
clientModel->getMasternodeList() and only checks pindex but then dereferences
mnList (mnList->getValidMN), which can be null; update the block after auto
[mnList, pindex] to also check if (!mnList) and handle it the same as a missing
pindex (QMessageBox::warning with "Unable to get masternode list. Please try
again later." then return) so you never dereference a null mnList; reference
getMasternodeList, mnList, pindex, and mnList->getValidMN when making the
change.
🤖 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/proposallist.cpp`:
- Around line 5-23: The code uses std::max and std::clamp in
refreshColumnWidths() but doesn't include <algorithm>, which is brittle; fix by
adding a direct `#include` <algorithm> to the top of proposallist.cpp (near the
other standard includes) so that std::max and std::clamp are available; update
any include ordering if needed to keep style consistent and ensure
refreshColumnWidths() compiles across toolchains.

---

Outside diff comments:
In `@src/qt/proposallist.cpp`:
- Around line 480-575: The code calls clientModel->getMasternodeList() and only
checks pindex but then dereferences mnList (mnList->getValidMN), which can be
null; update the block after auto [mnList, pindex] to also check if (!mnList)
and handle it the same as a missing pindex (QMessageBox::warning with "Unable to
get masternode list. Please try again later." then return) so you never
dereference a null mnList; reference getMasternodeList, mnList, pindex, and
mnList->getValidMN when making the change.

---

Duplicate comments:
In `@src/qt/clientfeeds.cpp`:
- Around line 143-154: The destructor may double-delete m_worker because start()
can reparent it for deletion via deleteLater(); fix by transferring ownership
when starting: in ClientFeeds::start() call m_worker->setParent(nullptr) (or
otherwise remove this as parent) before moving it to the QThread and connecting
QThread::finished to m_worker->deleteLater(), and keep the explicit delete in
~ClientFeeds() so that if start() was never called the QObject parent will still
safely delete m_worker; refer to ClientFeeds, start(), stop(), m_worker, and
~ClientFeeds().

In `@src/qt/clientmodel.cpp`:
- Around line 78-80: The masternode next payment heights can go stale because
m_feed_masternode is only refreshed on masternodeListChanged; update the
numBlocksChanged handler (the lambda connected to ClientModel::numBlocksChanged
where ProposalFeed is refreshed) to also call
m_feed_masternode->requestRefresh() so the MasternodeFeed recalculates
height-dependent fields on every new block; ensure you reference the existing
m_feed_masternode and its requestRefresh() method inside that same
numBlocksChanged lambda.

@PastaPastaPasta
Copy link
Member

Issues and Suggestions

  1. Redundant util::ThreadRename on every fetch (Minor)

clientfeeds.cpp:58 — util::ThreadRename("qt-clientfeed") is called inside the registerFeed
lambda, which runs on every timer fire. Since the worker thread is reused, this syscall is
redundant after the first call. Move it to start():
void ClientFeeds::start() {
m_worker->moveToThread(m_thread);
connect(m_thread, &QThread::finished, m_worker, &QObject::deleteLater);
m_thread->start();
QMetaObject::invokeMethod(m_worker, [] { util::ThreadRename("qt-clientfeed"); });
// ...
}

  1. No guard on add() after stop() (Minor)

clientfeeds.h:97-103 — If add() were called after stop(), the feed would be registered but
m_worker is already nullptr, causing the invokeMethod in registerFeed to target a null object. An
assert(!m_stopped) would catch this programmer error:
template<typename Source, typename... Args>
Source* add(Args&&... args) {
assert(!m_stopped);
// ...
}

@kwvg kwvg force-pushed the client_feeds branch 3 times, most recently from adaad00 to 7b927bd Compare February 17, 2026 15:36
PastaPastaPasta added a commit that referenced this pull request Feb 19, 2026
5e5aaec fix: MN update notifications had old_list/new_list swapped (UdjinM6)

Pull request description:

  ## Issue being fixed or feature implemented
  - UI: Masternode list tab displayed state at block X-1 while node was at block X
  - Net: mnauth disconnect handling could miss removals, keeping removed masternode peers connected longer than intended

  Noticed while reviewing #7146

  ## What was done?

  ## How Has This Been Tested?
  Run qt, open masternode list tab, sort by last paid (desc). Open Info dialog to see block height, wait for another block after full sync and compare.

  ## 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)_

ACKs for top commit:
  PastaPastaPasta:
    utACK 5e5aaec
  kwvg:
    utACK 5e5aaec

Tree-SHA512: 00cd0abd17a46e025f4419b8c06ca0ac5a5edbb07a372da75f1b40866b9b218116fa803f4f903b4010dd878aa05e6377d3ca7e8df99aa184d6b24a0f76a00325
kwvg added 9 commits February 19, 2026 21:09
Will be needed as we start consolidating non-wallet data sourcing into a
central thread to prevent duplicated fetching.

Review with `git log -p -n1 --color-moved=dimmed_zebra --ignore-space-change`.
The current system spins up threads for each wallet instance, this is
not sustainable for clients that load a lot of wallets and is
computationally expensive for redundant fetches
Same reasoning as previous commit and we consolidate two loops while
we're at it.

Review with `git log -p -n1 --color-moved=dimmed_zebra --ignore-space-change`.
Copy link

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

utACK 3d64056

@UdjinM6 UdjinM6 modified the milestones: 24, 23.1.1 Feb 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments