diff --git a/contrib/devtools/gen_moon_icons.py b/contrib/devtools/gen_moon_icons.py new file mode 100755 index 000000000000..cc3ef499a361 --- /dev/null +++ b/contrib/devtools/gen_moon_icons.py @@ -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() diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 3dc81d225e5b..095cf7ce2353 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -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 \ @@ -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 \ @@ -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 \ @@ -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 \ @@ -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 = \ @@ -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 \ diff --git a/src/governance/governance.cpp b/src/governance/governance.cpp index 43741675c9a4..6eaa1ed71c89 100644 --- a/src/governance/governance.cpp +++ b/src/governance/governance.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -318,6 +319,7 @@ void CGovernanceManager::AddGovernanceObjectInternal(CGovernanceObject& insert_o // SEND NOTIFICATION TO SCRIPT/ZMQ GetMainSignals().NotifyGovernanceObject(std::make_shared(govobj->Object()), nHash.ToString()); + uiInterface.NotifyGovernanceChanged(); } void CGovernanceManager::AddGovernanceObject(CGovernanceObject& govobj, const CNode* pfrom) @@ -530,19 +532,26 @@ std::vector CGovernanceManager::GetCurrentVotes(const uint256& return vecResult; } -void CGovernanceManager::GetAllNewerThan(std::vector& objs, int64_t nMoreThanTime) const +void CGovernanceManager::GetAllNewerThan(std::vector& 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); + } + } } bool CGovernanceManager::ConfirmInventoryRequest(const CInv& inv) diff --git a/src/governance/governance.h b/src/governance/governance.h index 96485c317d03..fa048b40d9d2 100644 --- a/src/governance/governance.h +++ b/src/governance/governance.h @@ -291,7 +291,8 @@ class CGovernanceManager : public GovernanceStore, public GovernanceSignerParent int64_t GetLastDiffTime() const { return nTimeLastDiff; } std::vector GetCurrentVotes(const uint256& nParentHash, const COutPoint& mnCollateralOutpointFilter) const EXCLUSIVE_LOCKS_REQUIRED(!cs_store); - void GetAllNewerThan(std::vector& objs, int64_t nMoreThanTime) const + void GetAllNewerThan(std::vector& objs, int64_t nMoreThanTime, + bool include_postponed = false) const EXCLUSIVE_LOCKS_REQUIRED(!cs_store); void UpdateLastDiffTime(int64_t nTimeIn) { nTimeLastDiff = nTimeIn; } diff --git a/src/governance/object.cpp b/src/governance/object.cpp index fb4f75548d05..956f209b4292 100644 --- a/src/governance/object.cpp +++ b/src/governance/object.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -155,6 +156,7 @@ bool CGovernanceObject::ProcessVote(CMasternodeMetaMan& mn_metaman, CGovernanceM // SEND NOTIFICATION TO SCRIPT/ZMQ GetMainSignals().NotifyGovernanceVote(std::make_shared(tip_mn_list), std::make_shared(vote), vote.GetHash().ToString()); + uiInterface.NotifyGovernanceChanged(); return true; } diff --git a/src/interfaces/node.h b/src/interfaces/node.h index b6fbef3de525..13cab2f924ca 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -10,6 +10,7 @@ #include // For banmap_t #include // For Network #include // For ConnectionDirection +#include // For StaticSaltedHasher #include // For SecureString #include #include // For util::SettingsValue @@ -23,6 +24,7 @@ #include #include #include +#include #include class BanMan; @@ -133,9 +135,15 @@ class GOV { public: virtual ~GOV() {} - virtual void getAllNewerThan(std::vector &objs, int64_t nMoreThanTime) = 0; - virtual int32_t getObjAbsYesCount(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) = 0; + virtual void getAllNewerThan(std::vector &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 { @@ -151,6 +159,12 @@ class GOV int requiredConfs{6}; }; virtual GovernanceInfo getGovernanceInfo() = 0; + virtual std::optional getProposalFundedHeight(const uint256& proposal_hash) = 0; + struct FundableResult { + std::unordered_set hashes; + CAmount allocated{0}; + }; + virtual FundableResult getFundableProposalHashes() = 0; virtual std::optional 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, @@ -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) {} @@ -466,6 +481,10 @@ class Node std::function; virtual std::unique_ptr handleNotifyHeaderTip(NotifyHeaderTipFn fn) = 0; + //! Register handler for governance data messages. + using NotifyGovernanceChangedFn = std::function; + virtual std::unique_ptr handleNotifyGovernanceChanged(NotifyGovernanceChangedFn fn) = 0; + //! Register handler for masternode list update messages. using NotifyMasternodeListChangedFn = std::function // For CAmount #include +#include #include // For ChainClient #include // For CKeyID and CScriptID (definitions needed in CTxDestination instantiation) #include