Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a773635
qt: move masternode list fetch logic to thread and use debounce timer
kwvg Feb 13, 2026
5af2bb9
qt: expose `NotifyGovernanceChanged` signal to UI code
kwvg Feb 13, 2026
c85e5cb
qt: move proposal list fetch to thread and use debounce timer
kwvg Feb 13, 2026
a7328e9
qt: add moon phase icons for governance cycle indicator
kwvg Feb 12, 2026
662703e
qt: add governance cycle status bar icon
kwvg Feb 13, 2026
8257eac
qt: decouple chain sync indicator from governance sync
kwvg Feb 11, 2026
9bb7b0a
qt: reorder columns in "Governance" tab, make width elastic for key cols
kwvg Jan 22, 2026
b2b9aa1
qt: change layout of "Governance" tab controls for readability
kwvg Jan 24, 2026
c0d8454
qt: report compact voting status and expanded details in tooltip
kwvg Feb 13, 2026
f45fe85
qt: use monospace font for hashes in "Governance" tab
kwvg Jan 22, 2026
4295471
qt: report proposal info using QTextEdit instead of an alert with JSON
kwvg Feb 6, 2026
552f591
qt: report more proposal information in description, calculate payments
kwvg Jan 22, 2026
af65c0e
qt: cleanup proposal context menu, add copy JSON and visit URL options
kwvg Feb 11, 2026
5f95737
qt: add voting ballot icon
kwvg Feb 12, 2026
28b67d3
qt: replace "Active" column with icons that reflect voting status
kwvg Feb 13, 2026
1a1d56e
qt: list locally recorded proposals in "Governance" tab
kwvg Feb 13, 2026
2d8cffe
qt: add blank canvas if there's no proposals to display
kwvg Jan 22, 2026
16314e7
qt: adjust layout of details page of proposal wizard to resemble DGT
kwvg Feb 6, 2026
3ca061f
qt: drop JSON/hex confirmation page, use description dialogs instead
kwvg Jan 21, 2026
90e0375
qt: use `SendConfirmationDialog` instead of `QMessageBox` for confirm
kwvg Feb 13, 2026
2883527
qt: remove remaining pages from proposal wizard
kwvg Feb 6, 2026
080c8fb
qt: add "Resume Proposal" for post-confirmation broadcast
kwvg Feb 13, 2026
a0da7de
qt: disable proposal buttons until synced, creation button w/o funds
kwvg Feb 13, 2026
9fb0c35
qt: make governance clock opt-in
kwvg Feb 13, 2026
d1e67ff
fix: disable governance UI when node runs with `--disablegovernance`
kwvg Feb 13, 2026
6cb7a40
qt: add pending status to signal unbroadcast proposals
kwvg Feb 13, 2026
a5ae10c
fix: correct tooltip display unit bug, IBD status flickering
kwvg Feb 13, 2026
3d53afa
fix: hold `cs_main` to for consistent state when fetching GovernanceInfo
kwvg Feb 13, 2026
ce94c3e
fix: improve responsiveness in update after user interaction
kwvg Feb 13, 2026
707ce77
refactor: `qt/proposalwizard.{cpp,h}` -> `qt/proposalcreate.{cpp,h}`
kwvg Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions contrib/devtools/gen_moon_icons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env python3
# Copyright (c) 2026 The Dash Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

import math
import os
import struct
import zlib

OUTPUT_PX = 64
CENTER = OUTPUT_PX / 2
RADIUS = 26
STROKE_WIDTH = 2.0
INNER_GAP_INSET = 3.0


def make_png(pixels):
"""Encode RGBA pixel data as a PNG file."""
raw = bytearray()
for y in range(OUTPUT_PX):
raw.append(0) # filter byte: None
for x in range(OUTPUT_PX):
raw.extend(pixels[y][x])

def chunk(chunk_type, data):
c = chunk_type + data
return struct.pack('>I', len(data)) + c + struct.pack('>I', zlib.crc32(c) & 0xFFFFFFFF)

ihdr = struct.pack('>IIBBBBB', OUTPUT_PX, OUTPUT_PX, 8, 6, 0, 0, 0) # 8-bit RGBA
idat = zlib.compress(bytes(raw), 9)

out = b'\x89PNG\r\n\x1a\n'
out += chunk(b'IHDR', ihdr)
out += chunk(b'IDAT', idat)
out += chunk(b'IEND', b'')
return out


def moon_frame(frame):
"""
frame 0 = new moon (filled disc + thin inner outline gap)
frame 1 = waxing crescent (small sliver on right)
frame 2 = first quarter (right half illuminated)
frame 3 = waxing gibbous (mostly illuminated)
frame 4 = full moon (outline only)
frame 5 = waning gibbous (mostly illuminated, from left)
frame 6 = last quarter (left half illuminated)
frame 7 = waning crescent (small sliver on left)
"""
if frame > 4:
# Waning: horizontal mirror of the corresponding waxing frame
pixels = moon_frame(8 - frame)
for y in range(OUTPUT_PX):
pixels[y] = pixels[y][::-1]
return pixels

# Precompute frame-specific parameters
if frame == 0:
gap_outer = RADIUS - INNER_GAP_INSET
gap_inner = gap_outer - STROKE_WIDTH
else:
frame_angle = frame * math.pi / 4

pixels = [[(0, 0, 0, 0)] * OUTPUT_PX for _ in range(OUTPUT_PX)]
for y in range(OUTPUT_PX):
for x in range(OUTPUT_PX):
dx = x - CENTER + 0.5
dy = y - CENTER + 0.5
dist = math.sqrt(dx * dx + dy * dy)
if dist > RADIUS + 1:
continue
disc_alpha = min(1.0, RADIUS + 1 - dist)
if frame == 0:
# New moon: filled disc with a thin inner outline gap
if dist < gap_inner - 0.5:
fill = 1.0
elif dist < gap_inner + 0.5:
fill = gap_inner + 0.5 - dist
elif dist < gap_outer - 0.5:
fill = 0.0
elif dist < gap_outer + 0.5:
fill = dist - (gap_outer - 0.5)
else:
fill = 1.0
else:
# Frames 1-4: terminator (shadow boundary) + outline
if abs(dy) >= RADIUS:
terminator_x = 0
else:
half_chord = math.sqrt(RADIUS * RADIUS - dy * dy)
terminator_x = math.cos(frame_angle) * half_chord
edge_dist = terminator_x - dx
if edge_dist > 1:
dark_alpha = 1.0
elif edge_dist < -1:
dark_alpha = 0.0
else:
dark_alpha = (edge_dist + 1) / 2
outline_alpha = 0.0
inner_edge = RADIUS - STROKE_WIDTH
if dist > inner_edge:
outline_alpha = min(1.0, (dist - inner_edge) / STROKE_WIDTH)
fill = max(dark_alpha, outline_alpha)
alpha = int(max(0, min(255, disc_alpha * fill * 255)))
if alpha > 0:
pixels[y][x] = (0, 0, 0, alpha)
return pixels


def main():
script_dir = os.path.dirname(os.path.abspath(__file__))
repo_root = os.path.join(script_dir, '..', '..')
icon_dir = os.path.join(repo_root, 'src', 'qt', 'res', 'icons')
for frame in range(8):
pixels = moon_frame(frame)
png_data = make_png(pixels)
filename = f'moon_{frame}.png'
filepath = os.path.join(icon_dir, filename)
with open(filepath, 'wb') as f:
f.write(png_data)
print(f' {filename} ({len(png_data)} bytes)')


if __name__ == '__main__':
main()
22 changes: 18 additions & 4 deletions src/Makefile.qt.include
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ QT_FORMS_UI = \
qt/forms/openuridialog.ui \
qt/forms/optionsdialog.ui \
qt/forms/overviewpage.ui \
qt/forms/proposalwizard.ui \
qt/forms/proposalcreate.ui \
qt/forms/proposalresume.ui \
qt/forms/psbtoperationsdialog.ui \
qt/forms/qrdialog.ui \
qt/forms/receivecoinsdialog.ui \
Expand Down Expand Up @@ -78,7 +79,8 @@ QT_MOC_CPP = \
qt/moc_peertablemodel.cpp \
qt/moc_peertablesortproxy.cpp \
qt/moc_proposalmodel.cpp \
qt/moc_proposalwizard.cpp \
qt/moc_proposalcreate.cpp \
qt/moc_proposalresume.cpp \
qt/moc_psbtoperationsdialog.cpp \
qt/moc_qrdialog.cpp \
qt/moc_qrimagewidget.cpp \
Expand Down Expand Up @@ -160,8 +162,9 @@ BITCOIN_QT_H = \
qt/paymentserver.h \
qt/peertablemodel.h \
qt/peertablesortproxy.h \
qt/proposalcreate.h \
qt/proposalmodel.h \
qt/proposalwizard.h \
qt/proposalresume.h \
qt/psbtoperationsdialog.h \
qt/qrdialog.h \
qt/qrimagewidget.h \
Expand Down Expand Up @@ -211,6 +214,14 @@ QT_RES_ICONS = \
qt/res/icons/hd_enabled.png \
qt/res/icons/lock_closed.png \
qt/res/icons/lock_open.png \
qt/res/icons/moon_0.png \
qt/res/icons/moon_1.png \
qt/res/icons/moon_2.png \
qt/res/icons/moon_3.png \
qt/res/icons/moon_4.png \
qt/res/icons/moon_5.png \
qt/res/icons/moon_6.png \
qt/res/icons/moon_7.png \
qt/res/icons/proxy.png \
qt/res/icons/remove.png \
qt/res/icons/synced.png \
Expand All @@ -220,8 +231,10 @@ QT_RES_ICONS = \
qt/res/icons/transaction3.png \
qt/res/icons/transaction4.png \
qt/res/icons/transaction5.png \
qt/res/icons/transaction6.png \
qt/res/icons/transaction_abandoned.png \
qt/res/icons/transaction_locked.png \
qt/res/icons/voting.png \
qt/res/icons/warning.png

BITCOIN_QT_BASE_CPP = \
Expand Down Expand Up @@ -271,8 +284,9 @@ BITCOIN_QT_WALLET_CPP = \
qt/openuridialog.cpp \
qt/overviewpage.cpp \
qt/paymentserver.cpp \
qt/proposalcreate.cpp \
qt/proposalmodel.cpp \
qt/proposalwizard.cpp \
qt/proposalresume.cpp \
qt/psbtoperationsdialog.cpp \
qt/qrdialog.cpp \
qt/qrimagewidget.cpp \
Expand Down
17 changes: 13 additions & 4 deletions src/governance/governance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <governance/validators.h>
#include <masternode/meta.h>
#include <masternode/sync.h>
#include <node/interface_ui.h>
#include <protocol.h>
#include <shutdown.h>
#include <spork.h>
Expand Down Expand Up @@ -318,6 +319,7 @@ void CGovernanceManager::AddGovernanceObjectInternal(CGovernanceObject& insert_o

// SEND NOTIFICATION TO SCRIPT/ZMQ
GetMainSignals().NotifyGovernanceObject(std::make_shared<const Governance::Object>(govobj->Object()), nHash.ToString());
uiInterface.NotifyGovernanceChanged();
}

void CGovernanceManager::AddGovernanceObject(CGovernanceObject& govobj, const CNode* pfrom)
Expand Down Expand Up @@ -530,19 +532,26 @@ std::vector<CGovernanceVote> CGovernanceManager::GetCurrentVotes(const uint256&
return vecResult;
}

void CGovernanceManager::GetAllNewerThan(std::vector<CGovernanceObject>& objs, int64_t nMoreThanTime) const
void CGovernanceManager::GetAllNewerThan(std::vector<CGovernanceObject>& objs, int64_t nMoreThanTime,
bool include_postponed) const
{
LOCK(cs_store);

for (const auto& [_, govobj] : mapObjects) {
// IF THIS OBJECT IS OLDER THAN TIME, CONTINUE
if (Assert(govobj)->GetCreationTime() < nMoreThanTime) {
continue;
}

// ADD GOVERNANCE OBJECT TO LIST
objs.push_back(*govobj);
}

if (include_postponed) {
for (const auto& [_, govobj] : mapPostponedObjects) {
if (Assert(govobj)->GetCreationTime() < nMoreThanTime) {
continue;
}
objs.push_back(*govobj);
}
}
Comment on lines +535 to +554
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

Fix clang-format drift in this hunk (CI failure).

The clang-format check reports differences in this file; please run clang-format/clang-format-diff for governance.cpp before merge.

🤖 Prompt for AI Agents
In `@src/governance/governance.cpp` around lines 535 - 554, Clang-format drift in
CGovernanceManager::GetAllNewerThan: reformat this function to match project
style by running clang-format (or clang-format-diff) on
src/governance/governance.cpp and committing the changes; ensure the
LOCK(cs_store) block, the ranged-for loops over mapObjects and
mapPostponedObjects, the Assert(govobj)->GetCreationTime() checks, and the
objs.push_back(*govobj) lines follow the repository's spacing/bracing
conventions so the CI formatting check passes.

}

bool CGovernanceManager::ConfirmInventoryRequest(const CInv& inv)
Expand Down
3 changes: 2 additions & 1 deletion src/governance/governance.h
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ class CGovernanceManager : public GovernanceStore, public GovernanceSignerParent
int64_t GetLastDiffTime() const { return nTimeLastDiff; }
std::vector<CGovernanceVote> GetCurrentVotes(const uint256& nParentHash, const COutPoint& mnCollateralOutpointFilter) const
EXCLUSIVE_LOCKS_REQUIRED(!cs_store);
void GetAllNewerThan(std::vector<CGovernanceObject>& objs, int64_t nMoreThanTime) const
void GetAllNewerThan(std::vector<CGovernanceObject>& objs, int64_t nMoreThanTime,
bool include_postponed = false) const
EXCLUSIVE_LOCKS_REQUIRED(!cs_store);
void UpdateLastDiffTime(int64_t nTimeIn) { nTimeLastDiff = nTimeIn; }

Expand Down
2 changes: 2 additions & 0 deletions src/governance/object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <core_io.h>
#include <index/txindex.h>
#include <logging.h>
#include <node/interface_ui.h>
#include <timedata.h>
#include <util/time.h>
#include <validation.h>
Expand Down Expand Up @@ -155,6 +156,7 @@ bool CGovernanceObject::ProcessVote(CMasternodeMetaMan& mn_metaman, CGovernanceM
// SEND NOTIFICATION TO SCRIPT/ZMQ
GetMainSignals().NotifyGovernanceVote(std::make_shared<CDeterministicMNList>(tip_mn_list),
std::make_shared<const CGovernanceVote>(vote), vote.GetHash().ToString());
uiInterface.NotifyGovernanceChanged();
return true;
}

Expand Down
23 changes: 21 additions & 2 deletions src/interfaces/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <net_types.h> // For banmap_t
#include <netaddress.h> // For Network
#include <netbase.h> // For ConnectionDirection
#include <saltedhasher.h> // For StaticSaltedHasher
#include <support/allocators/secure.h> // For SecureString
#include <uint256.h>
#include <util/settings.h> // For util::SettingsValue
Expand All @@ -23,6 +24,7 @@
#include <stdint.h>
#include <string>
#include <tuple>
#include <unordered_set>
#include <vector>

class BanMan;
Expand Down Expand Up @@ -133,9 +135,15 @@ class GOV
{
public:
virtual ~GOV() {}
virtual void getAllNewerThan(std::vector<CGovernanceObject> &objs, int64_t nMoreThanTime) = 0;
virtual int32_t getObjAbsYesCount(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) = 0;
virtual void getAllNewerThan(std::vector<CGovernanceObject> &objs, int64_t nMoreThanTime, bool include_postponed = false) = 0;
struct Votes {
int32_t m_abs{0};
int32_t m_no{0};
int32_t m_yes{0};
};
virtual Votes getObjVotes(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) = 0;
virtual bool getObjLocalValidity(const CGovernanceObject& obj, std::string& error, bool check_collateral) = 0;
virtual bool existsObj(const uint256& hash) = 0;
virtual bool isEnabled() = 0;
virtual bool processVoteAndRelay(const CGovernanceVote& vote, std::string& error) = 0;
struct GovernanceInfo {
Expand All @@ -151,6 +159,12 @@ class GOV
int requiredConfs{6};
};
virtual GovernanceInfo getGovernanceInfo() = 0;
virtual std::optional<int32_t> getProposalFundedHeight(const uint256& proposal_hash) = 0;
struct FundableResult {
std::unordered_set<uint256, StaticSaltedHasher> hashes;
CAmount allocated{0};
};
virtual FundableResult getFundableProposalHashes() = 0;
virtual std::optional<CGovernanceObject> createProposal(int32_t revision, int64_t created_time,
const std::string& data_hex, std::string& error) = 0;
virtual bool submitProposal(const uint256& parent, int32_t revision, int64_t created_time, const std::string& data_hex,
Expand All @@ -175,6 +189,7 @@ class Sync
public:
virtual ~Sync() {}
virtual bool isBlockchainSynced() = 0;
virtual bool isGovernanceSynced() = 0;
virtual bool isSynced() = 0;
virtual std::string getSyncStatus() = 0;
virtual void setContext(node::NodeContext* context) {}
Expand Down Expand Up @@ -466,6 +481,10 @@ class Node
std::function<void(SynchronizationState, interfaces::BlockTip tip, double verification_progress)>;
virtual std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) = 0;

//! Register handler for governance data messages.
using NotifyGovernanceChangedFn = std::function<void()>;
virtual std::unique_ptr<Handler> handleNotifyGovernanceChanged(NotifyGovernanceChangedFn fn) = 0;

//! Register handler for masternode list update messages.
using NotifyMasternodeListChangedFn =
std::function<void(const CDeterministicMNList& newList,
Expand Down
4 changes: 4 additions & 0 deletions src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include <consensus/amount.h> // For CAmount
#include <fs.h>
#include <governance/common.h>
#include <interfaces/chain.h> // For ChainClient
#include <pubkey.h> // For CKeyID and CScriptID (definitions needed in CTxDestination instantiation)
#include <script/standard.h> // For CTxDestination
Expand Down Expand Up @@ -351,6 +352,9 @@ class Wallet
using CanGetAddressesChangedFn = std::function<void()>;
virtual std::unique_ptr<Handler> handleCanGetAddressesChanged(CanGetAddressesChangedFn fn) = 0;

//! Get governance objects stored in the wallet.
virtual std::vector<Governance::Object> getGovernanceObjects() = 0;

//! Prepare a governance proposal (burns fee).
virtual bool prepareProposal(const uint256& govobj_hash, CAmount fee, int32_t revision, int64_t created_time,
const std::string& data_hex, const COutPoint& outpoint,
Expand Down
3 changes: 3 additions & 0 deletions src/node/interface_ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ struct UISignals {
boost::signals2::signal<CClientUIInterface::NotifyBlockTipSig> NotifyBlockTip;
boost::signals2::signal<CClientUIInterface::NotifyChainLockSig> NotifyChainLock;
boost::signals2::signal<CClientUIInterface::NotifyHeaderTipSig> NotifyHeaderTip;
boost::signals2::signal<CClientUIInterface::NotifyGovernanceChangedSig> NotifyGovernanceChanged;
boost::signals2::signal<CClientUIInterface::NotifyMasternodeListChangedSig> NotifyMasternodeListChanged;
boost::signals2::signal<CClientUIInterface::NotifyAdditionalDataSyncProgressChangedSig> NotifyAdditionalDataSyncProgressChanged;
boost::signals2::signal<CClientUIInterface::BannedListChangedSig> BannedListChanged;
Expand All @@ -46,6 +47,7 @@ ADD_SIGNALS_IMPL_WRAPPER(ShowProgress);
ADD_SIGNALS_IMPL_WRAPPER(NotifyBlockTip);
ADD_SIGNALS_IMPL_WRAPPER(NotifyChainLock);
ADD_SIGNALS_IMPL_WRAPPER(NotifyHeaderTip);
ADD_SIGNALS_IMPL_WRAPPER(NotifyGovernanceChanged);
ADD_SIGNALS_IMPL_WRAPPER(NotifyMasternodeListChanged);
ADD_SIGNALS_IMPL_WRAPPER(NotifyAdditionalDataSyncProgressChanged);
ADD_SIGNALS_IMPL_WRAPPER(BannedListChanged);
Expand All @@ -61,6 +63,7 @@ void CClientUIInterface::ShowProgress(const std::string& title, int nProgress, b
void CClientUIInterface::NotifyBlockTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyBlockTip(s, i); }
void CClientUIInterface::NotifyChainLock(const std::string& bestChainLockHash, int bestChainLockHeight) { return g_ui_signals.NotifyChainLock(bestChainLockHash, bestChainLockHeight); }
void CClientUIInterface::NotifyHeaderTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyHeaderTip(s, i); }
void CClientUIInterface::NotifyGovernanceChanged() { return g_ui_signals.NotifyGovernanceChanged(); }
void CClientUIInterface::NotifyMasternodeListChanged(const CDeterministicMNList& list, const CBlockIndex* i) { return g_ui_signals.NotifyMasternodeListChanged(list, i); }
void CClientUIInterface::NotifyAdditionalDataSyncProgressChanged(double nSyncProgress) { return g_ui_signals.NotifyAdditionalDataSyncProgressChanged(nSyncProgress); }
void CClientUIInterface::BannedListChanged() { return g_ui_signals.BannedListChanged(); }
Expand Down
3 changes: 3 additions & 0 deletions src/node/interface_ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ class CClientUIInterface
/** Masternode list has changed */
ADD_SIGNALS_DECL_WRAPPER(NotifyMasternodeListChanged, void, const CDeterministicMNList&, const CBlockIndex*);

/** Governance data changed */
ADD_SIGNALS_DECL_WRAPPER(NotifyGovernanceChanged, void);

/** Additional data sync progress changed */
ADD_SIGNALS_DECL_WRAPPER(NotifyAdditionalDataSyncProgressChanged, void, double nSyncProgress);

Expand Down
Loading
Loading