2626
2727#include < QAbstractItemView>
2828#include < QMessageBox>
29+ #include < QMetaObject>
2930
3031//
3132// Governance Tab main widget.
3233//
33-
3434GovernanceList::GovernanceList (QWidget* parent) :
3535 QWidget(parent),
36- ui(std::make_unique<Ui::GovernanceList>()),
37- proposalModel(new ProposalModel(this )),
38- proposalModelProxy(new QSortFilterProxyModel(this )),
39- proposalContextMenu(new QMenu(this )),
40- timer(new QTimer(this ))
36+ ui{new Ui::GovernanceList},
37+ proposalModel{new ProposalModel (this )},
38+ proposalContextMenu{new QMenu (this )},
39+ m_worker (new QObject),
40+ proposalModelProxy{new QSortFilterProxyModel (this )},
41+ m_thread{new QThread (this )},
42+ m_timer{new QTimer (this )}
4143{
4244 ui->setupUi (this );
4345
@@ -77,31 +79,49 @@ GovernanceList::GovernanceList(QWidget* parent) :
7779 connect (ui->btnCreateProposal , &QPushButton::clicked, this , &GovernanceList::showCreateProposalDialog);
7880 connect (ui->govTableView , &QTableView::doubleClicked, this , &GovernanceList::showAdditionalInfo);
7981
80- connect (timer, &QTimer::timeout, this , &GovernanceList::updateProposalList);
81-
8282 // Initialize masternode count to 0
8383 ui->mnCountLabel ->setText (" 0" );
8484
8585 GUIUtil::updateFonts ();
86+
87+ // Background thread for calculating proposal list
88+ m_worker->moveToThread (m_thread);
89+ // Make sure executor object is deleted in its own thread
90+ connect (m_thread, &QThread::finished, m_worker, &QObject::deleteLater);
91+ m_thread->start ();
92+
93+ // Debounce timer to apply proposal list changes
94+ m_timer->setSingleShot (true );
95+ connect (m_timer, &QTimer::timeout, this , &GovernanceList::updateProposalList);
8696}
8797
88- GovernanceList::~GovernanceList () = default ;
98+ GovernanceList::~GovernanceList ()
99+ {
100+ m_timer->stop ();
101+ m_thread->quit ();
102+ m_thread->wait ();
103+ delete ui;
104+ }
89105
90106void GovernanceList::setClientModel (ClientModel* model)
91107{
92108 this ->clientModel = model;
93- if (model != nullptr ) {
94- connect (model->getOptionsModel (), &OptionsModel::displayUnitChanged, this , &GovernanceList::updateDisplayUnit);
95-
96- updateProposalList ();
109+ if (clientModel) {
110+ connect (clientModel, &ClientModel::governanceChanged, this , &GovernanceList::handleProposalListChanged);
111+ connect (clientModel->getOptionsModel (), &OptionsModel::displayUnitChanged, this , &GovernanceList::updateDisplayUnit);
112+ m_timer->start (0 );
113+ } else {
114+ m_timer->stop ();
97115 }
98116}
99117
100118void GovernanceList::setWalletModel (WalletModel* model)
101119{
102120 this ->walletModel = model;
103- if (model && clientModel) {
104- updateVotingCapability ();
121+ if (walletModel && clientModel) {
122+ m_timer->start (0 );
123+ } else {
124+ m_timer->stop ();
105125 }
106126}
107127
@@ -113,35 +133,88 @@ void GovernanceList::updateDisplayUnit()
113133 }
114134}
115135
136+ void GovernanceList::handleProposalListChanged ()
137+ {
138+ if (!clientModel || m_timer->isActive ()) {
139+ // Too early or already processing, nothing to do
140+ return ;
141+ }
142+ int delay{GOVERNANCELIST_UPDATE_SECONDS * 1000 };
143+ if (!clientModel->masternodeSync ().isBlockchainSynced ()) {
144+ // Currently syncing, reduce refreshes
145+ delay *= 6 ;
146+ }
147+ m_timer->start (delay);
148+ }
149+
116150void GovernanceList::updateProposalList ()
117151{
118- if (this ->clientModel ) {
119- // A proposal is considered passing if (YES votes - NO votes) >= (Total Weight of Masternodes / 10),
120- // count total valid (ENABLED) masternodes to determine passing threshold.
121- // Need to query number of masternodes here with access to clientModel.
122- const int nWeightedMnCount = clientModel->getMasternodeList ().first ->getValidWeightedMNsCount ();
123- const int nAbsVoteReq = std::max (Params ().GetConsensus ().nGovernanceMinQuorum , nWeightedMnCount / 10 );
124- proposalModel->setVotingParams (nAbsVoteReq);
125-
126- std::vector<CGovernanceObject> govObjList;
127- clientModel->getAllGovernanceObjects (govObjList);
128- ProposalList newProposals;
129- for (const auto & govObj : govObjList) {
130- if (govObj.GetObjectType () != GovernanceObject::PROPOSAL) {
131- continue ; // Skip triggers.
132- }
133- newProposals.emplace_back (std::make_unique<Proposal>(this ->clientModel , govObj));
134- }
135- proposalModel->reconcile (std::move (newProposals));
152+ if (!clientModel || clientModel->node ().shutdownRequested ()) {
153+ return ;
154+ }
136155
137- // Update voting capability if we now have both client and wallet models
138- if (walletModel) {
139- updateVotingCapability ();
156+ if (m_in_progress.exchange (true )) {
157+ // Already applying, re-arm for next attempt
158+ handleProposalListChanged ();
159+ return ;
160+ }
161+
162+ QMetaObject::invokeMethod (m_worker, [this ] {
163+ auto result = std::make_shared<CalcProposalList>(calcProposalList ());
164+ m_in_progress.store (false );
165+ QTimer::singleShot (0 , this , [this , result] {
166+ setProposalList (std::move (*result));
167+ });
168+ });
169+ }
170+
171+ GovernanceList::CalcProposalList GovernanceList::calcProposalList () const
172+ {
173+ CalcProposalList ret;
174+ if (!clientModel || clientModel->node ().shutdownRequested ()) {
175+ return ret;
176+ }
177+
178+ const auto [dmn, pindex] = clientModel->getMasternodeList ();
179+ if (!dmn || !pindex) {
180+ return ret;
181+ }
182+
183+ // A proposal is considered passing if (YES votes - NO votes) >= (Total Weight of Masternodes / 10),
184+ // count total valid (ENABLED) masternodes to determine passing threshold.
185+ // Need to query number of masternodes here with access to clientModel.
186+ const int nWeightedMnCount = dmn->getValidWeightedMNsCount ();
187+ ret.m_abs_vote_req = std::max (Params ().GetConsensus ().nGovernanceMinQuorum , nWeightedMnCount / 10 );
188+
189+ std::vector<CGovernanceObject> govObjList;
190+ clientModel->getAllGovernanceObjects (govObjList);
191+ for (const auto & govObj : govObjList) {
192+ if (govObj.GetObjectType () != GovernanceObject::PROPOSAL) {
193+ continue ; // Skip triggers.
140194 }
195+ ret.m_proposals .emplace_back (std::make_unique<Proposal>(this ->clientModel , govObj));
141196 }
142197
143- // Schedule next update.
144- timer->start (GOVERNANCELIST_UPDATE_SECONDS * 1000 );
198+ // Discover voting capability if we now have both client and wallet models
199+ if (walletModel) {
200+ dmn->forEachMN (/* only_valid=*/ true , [&](const auto & dmn) {
201+ // Check if wallet owns the voting key using the same logic as RPC
202+ const auto script = GetScriptForDestination (PKHash (dmn.getKeyIdVoting ()));
203+ if (walletModel->wallet ().isSpendable (script)) {
204+ ret.m_votable_masternodes [dmn.getProTxHash ()] = dmn.getKeyIdVoting ();
205+ }
206+ });
207+ }
208+
209+ return ret;
210+ }
211+
212+ void GovernanceList::setProposalList (CalcProposalList&& data)
213+ {
214+ proposalModel->setVotingParams (data.m_abs_vote_req );
215+ proposalModel->reconcile (std::move (data.m_proposals ));
216+ votableMasternodes = std::move (data.m_votable_masternodes );
217+ updateMasternodeCount ();
145218}
146219
147220void GovernanceList::updateProposalCount () const
@@ -213,26 +286,6 @@ void GovernanceList::showAdditionalInfo(const QModelIndex& index)
213286 QMessageBox::information (this , windowTitle, json);
214287}
215288
216- void GovernanceList::updateVotingCapability ()
217- {
218- if (!walletModel || !clientModel) return ;
219-
220- auto [mn_list, pindex] = clientModel->getMasternodeList ();
221- if (!pindex) return ;
222-
223- votableMasternodes.clear ();
224- mn_list->forEachMN (/* only_valid=*/ true , [&](const auto & dmn) {
225- // Check if wallet owns the voting key using the same logic as RPC
226- const CScript script = GetScriptForDestination (PKHash (dmn.getKeyIdVoting ()));
227- if (walletModel->wallet ().isSpendable (script)) {
228- votableMasternodes[dmn.getProTxHash ()] = dmn.getKeyIdVoting ();
229- }
230- });
231-
232- // Update masternode count display
233- updateMasternodeCount ();
234- }
235-
236289void GovernanceList::updateMasternodeCount () const
237290{
238291 if (ui && ui->mnCountLabel ) {
@@ -340,5 +393,5 @@ void GovernanceList::voteForProposal(vote_outcome_enum_t outcome)
340393 QMessageBox::information (this , tr (" Voting Results" ), message);
341394
342395 // Update proposal list to show new vote counts
343- updateProposalList ();
396+ handleProposalListChanged ();
344397}
0 commit comments