Skip to content

Commit df1ca87

Browse files
PastaPastaPastathepastaclaw
authored andcommitted
Merge dashpay#7159: feat(qt): UI refresh (5/n, add proposal information widget to information, donut chart for proposal allocation)
c8f0c2a feat(qt): add lifecycle and vote-aware sorting in proposal list (Kittywhiskers Van Gogh) c1f97d6 feat(qt): add budget donut chart to proposal info view (Kittywhiskers Van Gogh) 8936b5a feat(qt): add interactive DonutChart widget (Kittywhiskers Van Gogh) cb5bcd5 feat(qt): add proposal info button to proposal list (Kittywhiskers Van Gogh) e7de61d feat(qt): add wallet-specific section to proposal widget (Kittywhiskers Van Gogh) 0005209 feat(qt): report proposal information (Kittywhiskers Van Gogh) 37b16f8 interfaces: add unique voter count reporting (Kittywhiskers Van Gogh) Pull request description: ## Additional Information * Depends on dashpay#7146 * Depends on dashpay#7118 | | | | -- | -- | | ![](https://github.com/user-attachments/assets/4c32a587-4adf-46a5-b0f6-2c5aa5e730dc) | ![](https://github.com/user-attachments/assets/275c1a58-6b08-46fb-846b-91a415f1d54d) | | ![](https://github.com/user-attachments/assets/b550ffc9-2b06-4d62-b115-c47dbf2d2bc6) | ![](https://github.com/user-attachments/assets/6ad4bf7d-aea2-4527-9ece-83c1047b84a0) | ## Breaking Changes None expected. ## Checklist - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests - [x] I have made corresponding changes to the documentation **(note: N/A)** - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: UdjinM6: LGTM light ACK c8f0c2a Tree-SHA512: db714a9845605e059909efc1102d3f176db293f716d08efcd3f00ab48596aa0820aa5871bea81cb8099a9abca23cf17b0709d5d60e3127bb2eb022a78bf8c2e2
1 parent 9061ad0 commit df1ca87

29 files changed

+1199
-40
lines changed

src/Makefile.qt.include

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ QT_FORMS_UI = \
3434
qt/forms/optionsdialog.ui \
3535
qt/forms/overviewpage.ui \
3636
qt/forms/proposalcreate.ui \
37+
qt/forms/proposalinfo.ui \
3738
qt/forms/proposallist.ui \
3839
qt/forms/proposalresume.ui \
3940
qt/forms/psbtoperationsdialog.ui \
@@ -62,6 +63,7 @@ QT_MOC_CPP = \
6263
qt/moc_createwalletdialog.cpp \
6364
qt/moc_csvmodelwriter.cpp \
6465
qt/moc_descriptiondialog.cpp \
66+
qt/moc_donutchart.cpp \
6567
qt/moc_editaddressdialog.cpp \
6668
qt/moc_guiutil.cpp \
6769
qt/moc_informationwidget.cpp \
@@ -83,6 +85,7 @@ QT_MOC_CPP = \
8385
qt/moc_peertablemodel.cpp \
8486
qt/moc_peertablesortproxy.cpp \
8587
qt/moc_proposalcreate.cpp \
88+
qt/moc_proposalinfo.cpp \
8689
qt/moc_proposallist.cpp \
8790
qt/moc_proposalmodel.cpp \
8891
qt/moc_proposalresume.cpp \
@@ -145,6 +148,7 @@ BITCOIN_QT_H = \
145148
qt/createwalletdialog.h \
146149
qt/csvmodelwriter.h \
147150
qt/descriptiondialog.h \
151+
qt/donutchart.h \
148152
qt/editaddressdialog.h \
149153
qt/guiconstants.h \
150154
qt/guiutil_font.h \
@@ -170,6 +174,7 @@ BITCOIN_QT_H = \
170174
qt/peertablemodel.h \
171175
qt/peertablesortproxy.h \
172176
qt/proposalcreate.h \
177+
qt/proposalinfo.h \
173178
qt/proposallist.h \
174179
qt/proposalmodel.h \
175180
qt/proposalresume.h \
@@ -251,19 +256,19 @@ BITCOIN_QT_BASE_CPP = \
251256
qt/bantablemodel.cpp \
252257
qt/bitcoin.cpp \
253258
qt/bitcoinaddressvalidator.cpp \
254-
qt/masternodemodel.cpp \
255-
qt/proposalmodel.cpp \
256259
qt/bitcoinamountfield.cpp \
257260
qt/bitcoingui.cpp \
258261
qt/bitcoinunits.cpp \
259262
qt/clientfeeds.cpp \
260263
qt/clientmodel.cpp \
261264
qt/csvmodelwriter.cpp \
265+
qt/donutchart.cpp \
262266
qt/guiutil.cpp \
263267
qt/guiutil_font.cpp \
264268
qt/informationwidget.cpp \
265269
qt/initexecutor.cpp \
266270
qt/intro.cpp \
271+
qt/masternodemodel.cpp \
267272
qt/modaloverlay.cpp \
268273
qt/networkstyle.cpp \
269274
qt/networkwidget.cpp \
@@ -272,6 +277,8 @@ BITCOIN_QT_BASE_CPP = \
272277
qt/optionsmodel.cpp \
273278
qt/peertablemodel.cpp \
274279
qt/peertablesortproxy.cpp \
280+
qt/proposalinfo.cpp \
281+
qt/proposalmodel.cpp \
275282
qt/qvalidatedlineedit.cpp \
276283
qt/qvaluecombobox.cpp \
277284
qt/rpcconsole.cpp \

src/governance/object.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,27 @@ int CGovernanceObject::GetAbstainCount(const CDeterministicMNList& tip_mn_list,
592592
return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_ABSTAIN);
593593
}
594594

595+
CGovernanceObject::UniqueVoterCount CGovernanceObject::GetUniqueVoterCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
596+
{
597+
LOCK(cs);
598+
UniqueVoterCount result;
599+
for (const auto& [outpoint, recVote] : mapCurrentMNVotes) {
600+
if (recVote.mapInstances.count(eVoteSignalIn) == 0) {
601+
continue;
602+
}
603+
auto dmn = tip_mn_list.GetMNByCollateral(outpoint);
604+
if (!dmn) {
605+
continue;
606+
}
607+
if (dmn->nType == MnType::Evo) {
608+
++result.m_evo;
609+
} else {
610+
++result.m_regular;
611+
}
612+
}
613+
return result;
614+
}
615+
595616
bool CGovernanceObject::GetCurrentMNVotes(const COutPoint& mnCollateralOutpoint, vote_rec_t& voteRecord) const
596617
{
597618
LOCK(cs);

src/governance/object.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,13 @@ class CGovernanceObject
227227
int GetAbstainCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
228228
EXCLUSIVE_LOCKS_REQUIRED(!cs);
229229

230+
struct UniqueVoterCount {
231+
uint16_t m_regular{0};
232+
uint16_t m_evo{0};
233+
};
234+
UniqueVoterCount GetUniqueVoterCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const
235+
EXCLUSIVE_LOCKS_REQUIRED(!cs);
236+
230237
bool GetCurrentMNVotes(const COutPoint& mnCollateralOutpoint, vote_rec_t& voteRecord) const
231238
EXCLUSIVE_LOCKS_REQUIRED(!cs);
232239

src/interfaces/node.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ class GOV
143143
int32_t m_yes{0};
144144
};
145145
virtual Votes getObjVotes(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) = 0;
146+
struct UniqueVoters {
147+
uint16_t m_regular{0};
148+
uint16_t m_evo{0};
149+
};
150+
virtual UniqueVoters getObjUniqueVoters(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) = 0;
146151
virtual bool getObjLocalValidity(const CGovernanceObject& obj, std::string& error, bool check_collateral) = 0;
147152
virtual bool existsObj(const uint256& hash) = 0;
148153
virtual bool isEnabled() = 0;

src/node/interfaces.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,20 @@ class GOVImpl : public GOV
242242
}
243243
return ret;
244244
}
245+
UniqueVoters getObjUniqueVoters(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) override
246+
{
247+
if (context().govman != nullptr && context().dmnman != nullptr) {
248+
const auto& tip_mn_list{context().dmnman->GetListAtChainTip()};
249+
if (auto govobj{context().govman->FindGovernanceObject(obj.GetHash())}) {
250+
const auto count = govobj->GetUniqueVoterCount(tip_mn_list, vote_signal);
251+
return {.m_regular = count.m_regular, .m_evo = count.m_evo};
252+
} else {
253+
const auto count = obj.GetUniqueVoterCount(tip_mn_list, vote_signal);
254+
return {.m_regular = count.m_regular, .m_evo = count.m_evo};
255+
}
256+
}
257+
return {0, 0};
258+
}
245259
bool existsObj(const uint256& hash) override
246260
{
247261
if (context().govman != nullptr) {

src/qt/bitcoingui.cpp

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const NetworkStyle* networkStyle,
135135
this->message(title, message, style);
136136
});
137137
connect(walletFrame, &WalletFrame::currentWalletSet, [this] { updateWalletStatus(); });
138+
connect(walletFrame, &WalletFrame::showProposalInfo, this, [this] {
139+
rpcConsole->setInfoView(RPCConsole::InfoView::Governance);
140+
showDebugWindow();
141+
});
138142
} else
139143
#endif // ENABLE_WALLET
140144
{
@@ -1009,6 +1013,10 @@ void BitcoinGUI::addWallet(WalletModel* walletModel)
10091013
});
10101014
connect(wallet_view, &WalletView::encryptionStatusChanged, this, &BitcoinGUI::updateWalletStatus);
10111015
connect(wallet_view, &WalletView::incomingTransaction, this, &BitcoinGUI::incomingTransaction);
1016+
connect(wallet_view, &WalletView::showProposalInfo, this, [this] {
1017+
rpcConsole->setInfoView(RPCConsole::InfoView::Governance);
1018+
showDebugWindow();
1019+
});
10121020
connect(this, &BitcoinGUI::setPrivacy, wallet_view, &WalletView::setPrivacy);
10131021
wallet_view->setPrivacy(isPrivacyModeActivated());
10141022
const QString display_name = walletModel->getDisplayName();
@@ -1817,26 +1825,35 @@ void BitcoinGUI::updateGovernanceCycleIcon()
18171825
const auto remaining_blocks{std::max<int>(0, gov_info.nextsuperblock - current_height)};
18181826
const auto remaining_str{GUIUtil::formatBlockDuration(remaining_blocks, gov_info.targetSpacing)};
18191827
const bool awaiting_superblock{current_height % gov_info.superblockcycle >= gov_info.superblockcycle - gov_info.superblockmaturitywindow};
1828+
// Voting closes superblockmaturitywindow blocks before the superblock
1829+
const auto voting_remaining{std::max<int>(0, remaining_blocks - gov_info.superblockmaturitywindow)};
1830+
const auto voting_str{GUIUtil::formatBlockDuration(voting_remaining, gov_info.targetSpacing)};
18201831

1821-
QString tooltip1{};
18221832
if (awaiting_superblock) {
18231833
labelGovernanceCycleIcon->setPixmap(m_gov_cycle_pixmaps.at({ToUnderlying(GUIUtil::ThemedColor::BLUE), 0}));
1824-
tooltip1 = tr("~%1 (%2 blocks) left for superblock").arg(remaining_str).arg(remaining_blocks);
18251834
} else {
18261835
const auto cycle_blocks{gov_info.superblockcycle - gov_info.superblockmaturitywindow};
18271836
const auto blocks_elapsed{gov_info.superblockcycle - remaining_blocks - gov_info.superblockmaturitywindow};
18281837
const auto progress{static_cast<double>(std::max(0, blocks_elapsed)) / static_cast<double>(std::max(1, cycle_blocks))};
18291838
const auto frame{std::clamp<int>(static_cast<int>(progress * (GOV_CYCLE_FRAME_COUNT - 1)), 0, GOV_CYCLE_FRAME_COUNT - 2) + 1};
18301839
labelGovernanceCycleIcon->setPixmap(m_gov_cycle_pixmaps.at({ToUnderlying(GUIUtil::ThemedColor::GREEN), frame}));
1831-
tooltip1 = tr("~%1 (%2 blocks) left for voting").arg(remaining_str).arg(remaining_blocks);
18321840
}
18331841

1834-
const auto allocated_budget{m_node.gov().getFundableProposalHashes().allocated};
1835-
const auto budget_pct{gov_info.governancebudget > 0
1836-
? static_cast<int>(static_cast<double>(allocated_budget) / static_cast<double>(gov_info.governancebudget) * 100.0)
1837-
: 0};
1838-
const auto unit{options_model.getDisplayUnit()};
1839-
const auto tooltip2{tr("~%1% of budget committed (%2 %3).").arg(budget_pct).arg(allocated_budget / BitcoinUnits::factor(unit)).arg(BitcoinUnits::name(unit))};
1842+
const auto tooltip1{remaining_blocks == 0
1843+
? (awaiting_superblock ? tr("Superblock imminent") : tr("Voting period ended"))
1844+
: (awaiting_superblock
1845+
? tr("~%1 (%2 blocks) left for superblock").arg(remaining_str).arg(remaining_blocks)
1846+
: tr("~%1 (%2 blocks) left for voting").arg(voting_str).arg(voting_remaining))};
1847+
const auto tooltip2{[&]() {
1848+
const auto allocated_budget{m_node.gov().getFundableProposalHashes().allocated};
1849+
const auto budget_pct{gov_info.governancebudget > 0
1850+
? static_cast<int>(static_cast<double>(allocated_budget) / static_cast<double>(gov_info.governancebudget) * 100.0)
1851+
: 0};
1852+
const auto unit{options_model.getDisplayUnit()};
1853+
return tr("~%1% of budget committed (%2 / %3)").arg(budget_pct)
1854+
.arg(GUIUtil::formatAmount(unit, allocated_budget, /*is_signed=*/false, /*truncate=*/2))
1855+
.arg(GUIUtil::formatAmount(unit, gov_info.governancebudget, /*is_signed=*/false, /*truncate=*/2));
1856+
}()};
18401857
labelGovernanceCycleIcon->setToolTip(QString("<nobr>%1<br>%2</nobr>").arg(tooltip1).arg(tooltip2));
18411858
labelGovernanceCycleIcon->show();
18421859
}

src/qt/clientfeeds.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,14 @@ void ProposalFeed::fetch()
231231
}
232232
ret->m_proposals.emplace_back(std::make_shared<Proposal>(m_client_model, govObj, ret->m_gov_info, ret->m_gov_info.requiredConfs,
233233
/*is_broadcast=*/true));
234+
const auto voters = m_client_model.node().gov().getObjUniqueVoters(govObj, VOTE_SIGNAL_FUNDING);
235+
ret->m_max_regular_voters = std::max(ret->m_max_regular_voters, voters.m_regular);
236+
ret->m_max_evo_voters = std::max(ret->m_max_evo_voters, voters.m_evo);
234237
}
235238

236239
auto fundable{m_client_model.node().gov().getFundableProposalHashes()};
237240
ret->m_fundable_hashes = std::move(fundable.hashes);
241+
ret->m_allocated = fundable.allocated;
238242

239243
setData(std::move(ret));
240244
}

src/qt/clientfeeds.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,12 @@ class QuorumFeed : public Feed<QuorumData> {
190190
using Proposals = std::vector<std::shared_ptr<Proposal>>;
191191

192192
struct ProposalData {
193+
CAmount m_allocated{0};
193194
int m_abs_vote_req{0};
194195
interfaces::GOV::GovernanceInfo m_gov_info;
195196
Proposals m_proposals;
197+
uint16_t m_max_evo_voters{0};
198+
uint16_t m_max_regular_voters{0};
196199
Uint256HashSet m_fundable_hashes;
197200
};
198201

0 commit comments

Comments
 (0)