diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml index 28975eae73bf..3ad1d1a4d93d 100644 --- a/.github/workflows/semantic-pull-request.yml +++ b/.github/workflows/semantic-pull-request.yml @@ -38,6 +38,7 @@ jobs: # Configure which scopes are allowed (newline delimited). scopes: | consensus + interfaces log mining net @@ -45,7 +46,7 @@ jobs: rest rpc scripts + stats utils wallet zmq - stats diff --git a/contrib/devtools/gen_macos_icons.py b/contrib/devtools/gen_macos_icons.py new file mode 100755 index 000000000000..dd0758874c3b --- /dev/null +++ b/contrib/devtools/gen_macos_icons.py @@ -0,0 +1,118 @@ +#!/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 os +import platform +import shutil +import subprocess +import sys +import tempfile + +# Assuming 1024x1024 canvas, the squircle content area is ~864x864 with +# ~80px transparent padding on each side +CONTENT_RATIO = 864 / 1024 + +DIR_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..")) +DIR_SRC = os.path.join(DIR_ROOT, "src", "qt", "res", "src") +DIR_OUT = os.path.join(DIR_ROOT, "src", "qt", "res", "icons") + +# Icon Composer exports to runtime icon map +ICONS = [ + ("macos_devnet.png", "dash_macos_devnet.png"), + ("macos_mainnet.png", "dash_macos_mainnet.png"), + ("macos_regtest.png", "dash_macos_regtest.png"), + ("macos_testnet.png", "dash_macos_testnet.png"), +] +TRAY = os.path.join(DIR_SRC, "tray.svg") + +# Canvas to filename mapping for bundle icon +ICNS_MAP = [ + (16, "icon_16x16.png"), + (32, "icon_16x16@2x.png"), + (32, "icon_32x32.png"), + (64, "icon_32x32@2x.png"), + (128, "icon_128x128.png"), + (256, "icon_128x128@2x.png"), + (256, "icon_256x256.png"), + (512, "icon_256x256@2x.png"), + (512, "icon_512x512.png"), + (1024, "icon_512x512@2x.png"), +] + +# Maximum height of canvas is 22pt, we use max height instead of recommended +# 16pt canvas to prevent the icon from looking undersized due to icon width. +# See https://bjango.com/articles/designingmenubarextras/ +TRAY_MAP = [ + (22, "dash_macos_tray.png"), + (44, "dash_macos_tray@2x.png") +] + + +def sips_resample_padded(src, dst, canvas_size): + content_size = max(round(canvas_size * CONTENT_RATIO), 1) + subprocess.check_call( + ["sips", "-z", str(content_size), str(content_size), "-p", str(canvas_size), str(canvas_size), src, "--out", dst], + stdout=subprocess.DEVNULL, + ) + + +def sips_svg_to_png(svg_path, png_path, height): + subprocess.check_call( + ["sips", "-s", "format", "png", "--resampleHeight", str(height), svg_path, "--out", png_path], + stdout=subprocess.DEVNULL, + ) + + +def generate_icns(tmpdir): + iconset = os.path.join(tmpdir, "dash.iconset") + os.makedirs(iconset) + + src_main = os.path.join(DIR_SRC, ICONS[1][0]) + for canvas_px, filename in ICNS_MAP: + sips_resample_padded(src_main, os.path.join(iconset, filename), canvas_px) + + icns_out = os.path.join(DIR_OUT, "dash.icns") + subprocess.check_call(["iconutil", "-c", "icns", iconset, "-o", icns_out]) + print(f"Created: {icns_out}") + + +def check_source(path): + if not os.path.isfile(path): + sys.exit(f"Error: Source image not found: {path}") + + +def main(): + if platform.system() != "Darwin": + sys.exit("Error: This script requires macOS (needs sips, iconutil).") + + for tool in ("sips", "iconutil"): + if shutil.which(tool) is None: + sys.exit(f"Error: '{tool}' not found. Install Xcode command-line tools.") + + check_source(TRAY) + for src_name, _ in ICONS: + check_source(os.path.join(DIR_SRC, src_name)) + + os.makedirs(DIR_OUT, exist_ok=True) + + # Generate bundle icon + with tempfile.TemporaryDirectory(prefix="dash_icons_") as tmpdir: + generate_icns(tmpdir) + + # Generate runtime icons + for src_name, dst_name in ICONS: + src = os.path.join(DIR_SRC, src_name) + dst = os.path.join(DIR_OUT, dst_name) + sips_resample_padded(src, dst, 256) + print(f"Created: {dst}") + + # Generate tray icons + for canvas_px, filename in TRAY_MAP: + dst = os.path.join(DIR_OUT, filename) + sips_svg_to_png(TRAY, dst, canvas_px) + print(f"Created: {dst}") + +if __name__ == "__main__": + main() diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 095cf7ce2353..04652688dbef 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -23,16 +23,19 @@ QT_FORMS_UI = \ qt/forms/debugwindow.ui \ qt/forms/descriptiondialog.ui \ qt/forms/editaddressdialog.ui \ - qt/forms/governancelist.ui \ qt/forms/helpmessagedialog.ui \ + qt/forms/informationwidget.ui \ qt/forms/intro.ui \ qt/forms/masternodelist.ui \ qt/forms/mnemonicverificationdialog.ui \ qt/forms/modaloverlay.ui \ + qt/forms/networkwidget.ui \ qt/forms/openuridialog.ui \ qt/forms/optionsdialog.ui \ qt/forms/overviewpage.ui \ qt/forms/proposalcreate.ui \ + qt/forms/proposalinfo.ui \ + qt/forms/proposallist.ui \ qt/forms/proposalresume.ui \ qt/forms/psbtoperationsdialog.ui \ qt/forms/qrdialog.ui \ @@ -53,15 +56,17 @@ QT_MOC_CPP = \ qt/moc_bitcoinamountfield.cpp \ qt/moc_bitcoingui.cpp \ qt/moc_bitcoinunits.cpp \ + qt/moc_clientfeeds.cpp \ qt/moc_clientmodel.cpp \ qt/moc_coincontroldialog.cpp \ qt/moc_coincontroltreewidget.cpp \ qt/moc_createwalletdialog.cpp \ qt/moc_csvmodelwriter.cpp \ qt/moc_descriptiondialog.cpp \ + qt/moc_donutchart.cpp \ qt/moc_editaddressdialog.cpp \ - qt/moc_governancelist.cpp \ qt/moc_guiutil.cpp \ + qt/moc_informationwidget.cpp \ qt/moc_initexecutor.cpp \ qt/moc_intro.cpp \ qt/moc_macdockiconhandler.cpp \ @@ -70,6 +75,7 @@ QT_MOC_CPP = \ qt/moc_masternodemodel.cpp \ qt/moc_mnemonicverificationdialog.cpp \ qt/moc_modaloverlay.cpp \ + qt/moc_networkwidget.cpp \ qt/moc_notificator.cpp \ qt/moc_openuridialog.cpp \ qt/moc_optionsdialog.cpp \ @@ -78,8 +84,10 @@ QT_MOC_CPP = \ qt/moc_paymentserver.cpp \ qt/moc_peertablemodel.cpp \ qt/moc_peertablesortproxy.cpp \ - qt/moc_proposalmodel.cpp \ qt/moc_proposalcreate.cpp \ + qt/moc_proposalinfo.cpp \ + qt/moc_proposallist.cpp \ + qt/moc_proposalmodel.cpp \ qt/moc_proposalresume.cpp \ qt/moc_psbtoperationsdialog.cpp \ qt/moc_qrdialog.cpp \ @@ -133,17 +141,19 @@ BITCOIN_QT_H = \ qt/bitcoinamountfield.h \ qt/bitcoingui.h \ qt/bitcoinunits.h \ + qt/clientfeeds.h \ qt/clientmodel.h \ qt/coincontroldialog.h \ qt/coincontroltreewidget.h \ qt/createwalletdialog.h \ qt/csvmodelwriter.h \ qt/descriptiondialog.h \ + qt/donutchart.h \ qt/editaddressdialog.h \ - qt/governancelist.h \ qt/guiconstants.h \ - qt/guiutil.h \ qt/guiutil_font.h \ + qt/guiutil.h \ + qt/informationwidget.h \ qt/initexecutor.h \ qt/intro.h \ qt/macdockiconhandler.h \ @@ -154,6 +164,7 @@ BITCOIN_QT_H = \ qt/mnemonicverificationdialog.h \ qt/modaloverlay.h \ qt/networkstyle.h \ + qt/networkwidget.h \ qt/notificator.h \ qt/openuridialog.h \ qt/optionsdialog.h \ @@ -163,6 +174,8 @@ BITCOIN_QT_H = \ qt/peertablemodel.h \ qt/peertablesortproxy.h \ qt/proposalcreate.h \ + qt/proposalinfo.h \ + qt/proposallist.h \ qt/proposalmodel.h \ qt/proposalresume.h \ qt/psbtoperationsdialog.h \ @@ -203,6 +216,12 @@ QT_RES_ICONS = \ qt/res/icons/connect4_16.png \ qt/res/icons/dash.ico \ qt/res/icons/dash.png \ + qt/res/icons/dash_macos_devnet.png \ + qt/res/icons/dash_macos_mainnet.png \ + qt/res/icons/dash_macos_regtest.png \ + qt/res/icons/dash_macos_testnet.png \ + qt/res/icons/dash_macos_tray.png \ + qt/res/icons/dash_macos_tray@2x.png \ qt/res/icons/dash_testnet.ico \ qt/res/icons/editcopy.png \ qt/res/icons/editpaste.png \ @@ -245,19 +264,26 @@ BITCOIN_QT_BASE_CPP = \ qt/bitcoinamountfield.cpp \ qt/bitcoingui.cpp \ qt/bitcoinunits.cpp \ + qt/clientfeeds.cpp \ qt/clientmodel.cpp \ qt/csvmodelwriter.cpp \ + qt/donutchart.cpp \ qt/guiutil.cpp \ qt/guiutil_font.cpp \ + qt/informationwidget.cpp \ qt/initexecutor.cpp \ qt/intro.cpp \ + qt/masternodemodel.cpp \ qt/modaloverlay.cpp \ qt/networkstyle.cpp \ + qt/networkwidget.cpp \ qt/notificator.cpp \ qt/optionsdialog.cpp \ qt/optionsmodel.cpp \ qt/peertablemodel.cpp \ qt/peertablesortproxy.cpp \ + qt/proposalinfo.cpp \ + qt/proposalmodel.cpp \ qt/qvalidatedlineedit.cpp \ qt/qvaluecombobox.cpp \ qt/rpcconsole.cpp \ @@ -277,15 +303,13 @@ BITCOIN_QT_WALLET_CPP = \ qt/createwalletdialog.cpp \ qt/descriptiondialog.cpp \ qt/editaddressdialog.cpp \ - qt/governancelist.cpp \ qt/masternodelist.cpp \ - qt/masternodemodel.cpp \ qt/mnemonicverificationdialog.cpp \ qt/openuridialog.cpp \ qt/overviewpage.cpp \ qt/paymentserver.cpp \ qt/proposalcreate.cpp \ - qt/proposalmodel.cpp \ + qt/proposallist.cpp \ qt/proposalresume.cpp \ qt/psbtoperationsdialog.cpp \ qt/qrdialog.cpp \ diff --git a/src/active/quorums.cpp b/src/active/quorums.cpp index 79c5c892846c..c6c82f219bef 100644 --- a/src/active/quorums.cpp +++ b/src/active/quorums.cpp @@ -89,7 +89,7 @@ size_t QuorumParticipant::GetQuorumRecoveryStartOffset(const CQuorum& quorum, gs { auto mns = m_dmnman.GetListForBlock(pIndex); std::vector vecProTxHashes; - vecProTxHashes.reserve(mns.GetValidMNsCount()); + vecProTxHashes.reserve(mns.GetCounts().enabled()); mns.ForEachMN(/*onlyValid=*/true, [&](const auto& pMasternode) { vecProTxHashes.emplace_back(pMasternode.proTxHash); }); std::sort(vecProTxHashes.begin(), vecProTxHashes.end()); diff --git a/src/coinjoin/client.cpp b/src/coinjoin/client.cpp index 8299cd9d86cb..be6e7b458115 100644 --- a/src/coinjoin/client.cpp +++ b/src/coinjoin/client.cpp @@ -124,7 +124,7 @@ MessageProcessingResult CCoinJoinClientQueueManager::ProcessMessage(NodeId from, LogPrint(BCLog::COINJOIN, "DSQUEUE -- CoinJoin queue is ready, masternode=%s, queue=%s\n", dmn->proTxHash.ToString(), dsq.ToString()); return ret; } else { - if (m_mn_metaman.IsMixingThresholdExceeded(dmn->proTxHash, tip_mn_list.GetValidMNsCount())) { + if (m_mn_metaman.IsMixingThresholdExceeded(dmn->proTxHash, tip_mn_list.GetCounts().enabled())) { LogPrint(BCLog::COINJOIN, "DSQUEUE -- Masternode %s is sending too many dsq messages\n", dmn->proTxHash.ToString()); return ret; @@ -826,7 +826,7 @@ bool CCoinJoinClientSession::DoAutomaticDenominating(ChainstateManager& chainman return false; } - if (m_dmnman.GetListAtChainTip().GetValidMNsCount() == 0 && + if (m_dmnman.GetListAtChainTip().GetCounts().enabled() == 0 && Params().NetworkIDString() != CBaseChainParams::REGTEST) { strAutoDenomResult = _("No Masternodes detected."); WalletCJLogPrint(m_wallet, "CCoinJoinClientSession::DoAutomaticDenominating -- %s\n", strAutoDenomResult.original); @@ -988,7 +988,7 @@ bool CCoinJoinClientManager::DoAutomaticDenominating(ChainstateManager& chainman return false; } - int nMnCountEnabled = m_dmnman.GetListAtChainTip().GetValidMNsCount(); + int nMnCountEnabled = m_dmnman.GetListAtChainTip().GetCounts().enabled(); // If we've used 90% of the Masternode list then drop the oldest first ~30% int nThreshold_high = nMnCountEnabled * 0.9; @@ -1033,7 +1033,7 @@ CDeterministicMNCPtr CCoinJoinClientManager::GetRandomNotUsedMasternode() { auto mnList = m_dmnman.GetListAtChainTip(); - size_t nCountEnabled = mnList.GetValidMNsCount(); + size_t nCountEnabled = mnList.GetCounts().enabled(); size_t nCountNotExcluded{nCountEnabled - m_mn_metaman.GetUsedMasternodesCount()}; WalletCJLogPrint(m_wallet, "CCoinJoinClientManager::%s -- %d enabled masternodes, %d masternodes to choose from\n", __func__, nCountEnabled, nCountNotExcluded); @@ -1078,7 +1078,7 @@ bool CCoinJoinClientSession::JoinExistingQueue(CAmount nBalanceNeedsAnonymized, if (m_queueman == nullptr) return false; const auto mnList = m_dmnman.GetListAtChainTip(); - const int nWeightedMnCount = mnList.GetValidWeightedMNsCount(); + const int nWeightedMnCount = mnList.GetCounts().m_valid_weighted; // Look through the queues and see if anything matches CCoinJoinQueue dsq; @@ -1143,8 +1143,9 @@ bool CCoinJoinClientSession::StartNewQueue(CAmount nBalanceNeedsAnonymized, CCon int nTries = 0; const auto mnList = m_dmnman.GetListAtChainTip(); - const int nMnCount = mnList.GetValidMNsCount(); - const int nWeightedMnCount = mnList.GetValidWeightedMNsCount(); + const auto mnCounts = mnList.GetCounts(); + const int nMnCount = mnCounts.enabled(); + const int nWeightedMnCount = mnCounts.m_valid_weighted; // find available denominated amounts std::set setAmounts; diff --git a/src/coinjoin/server.cpp b/src/coinjoin/server.cpp index c68e33224a56..e431e1d9632b 100644 --- a/src/coinjoin/server.cpp +++ b/src/coinjoin/server.cpp @@ -100,7 +100,7 @@ void CCoinJoinServer::ProcessDSACCEPT(CNode& peer, CDataStream& vRecv) } } - if (m_mn_metaman.IsMixingThresholdExceeded(dmn->proTxHash, mnList.GetValidMNsCount())) { + if (m_mn_metaman.IsMixingThresholdExceeded(dmn->proTxHash, mnList.GetCounts().enabled())) { if (fLogIPs) { LogPrint(BCLog::COINJOIN, "DSACCEPT -- last dsq too recent, must wait: peer=%d, addr=%s\n", peer.GetId(), peer.addr.ToStringAddrPort()); @@ -193,7 +193,7 @@ void CCoinJoinServer::ProcessDSQUEUE(NodeId from, CDataStream& vRecv) if (!dsq.fReady) { //don't allow a few nodes to dominate the queuing process - if (m_mn_metaman.IsMixingThresholdExceeded(dmn->proTxHash, tip_mn_list.GetValidMNsCount())) { + if (m_mn_metaman.IsMixingThresholdExceeded(dmn->proTxHash, tip_mn_list.GetCounts().enabled())) { LogPrint(BCLog::COINJOIN, "DSQUEUE -- node sending too many dsq messages, masternode=%s\n", dmn->proTxHash.ToString()); return; } diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index 5ad3b2ee23ee..14eb342d596e 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -18,7 +18,7 @@ class Loader; } // namespace CoinJoin } // namespace interfaces namespace node { -class NodeContext; +struct NodeContext; } // namespace node namespace wallet { class CWallet; diff --git a/src/evo/chainhelper.cpp b/src/evo/chainhelper.cpp index 511d08549587..5c1f19a508d0 100644 --- a/src/evo/chainhelper.cpp +++ b/src/evo/chainhelper.cpp @@ -21,9 +21,9 @@ CChainstateHelper::CChainstateHelper(CEvoDB& evodb, CDeterministicMNManager& dmn const CSporkManager& sporkman, const chainlock::Chainlocks& chainlocks, const llmq::CQuorumManager& qman) : isman{isman}, + credit_pool_manager{std::make_unique(evodb, chainman)}, m_chainlocks{chainlocks}, ehf_manager{std::make_unique(evodb, chainman, qman)}, - credit_pool_manager{std::make_unique(evodb, chainman)}, mn_payments{std::make_unique(dmnman, govman, chainman, consensus_params, mn_sync, sporkman)}, special_tx{std::make_unique(*credit_pool_manager, dmnman, *ehf_manager, qblockman, qsnapman, chainman, consensus_params, chainlocks, qman)} @@ -44,6 +44,12 @@ bool CChainstateHelper::HasChainLock(int nHeight, const uint256& blockHash) cons int32_t CChainstateHelper::GetBestChainLockHeight() const { return m_chainlocks.GetBestChainLockHeight(); } +/** Passthrough functions to CCreditPoolManager */ +CCreditPool CChainstateHelper::GetCreditPool(const CBlockIndex* const pindex) +{ + return credit_pool_manager->GetCreditPool(pindex); +} + /** Passthrough functions to CInstantSendManager */ std::optional> CChainstateHelper::ConflictingISLockIfAny( const CTransaction& tx) const diff --git a/src/evo/chainhelper.h b/src/evo/chainhelper.h index 5b5478d077a7..69a229d93cad 100644 --- a/src/evo/chainhelper.h +++ b/src/evo/chainhelper.h @@ -13,34 +13,45 @@ class CBlockIndex; class CCreditPoolManager; class CDeterministicMNManager; class CEvoDB; +class CGovernanceManager; class ChainstateManager; +class CMasternodeSync; class CMNHFManager; class CMNPaymentsProcessor; -class CMasternodeSync; -class CGovernanceManager; class CSpecialTxProcessor; class CSporkManager; class CTransaction; class uint256; - +struct CCreditPool; namespace chainlock { class Chainlocks; -} -namespace Consensus { struct Params; } +} // namespace chainlock +namespace Consensus { +struct Params; +} // namespace Consensus namespace llmq { class CInstantSendManager; class CQuorumBlockProcessor; class CQuorumManager; class CQuorumSnapshotManager; -} +} // namespace llmq +namespace node { +class BlockAssembler; +} // namespace node class CChainstateHelper { + friend class node::BlockAssembler; + private: llmq::CInstantSendManager& isman; + const std::unique_ptr credit_pool_manager; public: const chainlock::Chainlocks& m_chainlocks; + const std::unique_ptr ehf_manager; + const std::unique_ptr mn_payments; + const std::unique_ptr special_tx; public: CChainstateHelper() = delete; @@ -54,11 +65,14 @@ class CChainstateHelper const llmq::CQuorumManager& qman); ~CChainstateHelper(); - /** Passthrough functions to chainlock::Chainlocks*/ + /** Passthrough functions to chainlock::Chainlocks */ bool HasConflictingChainLock(int nHeight, const uint256& blockHash) const; bool HasChainLock(int nHeight, const uint256& blockHash) const; int32_t GetBestChainLockHeight() const; + /** Passthrough functions to CCreditPoolManager */ + CCreditPool GetCreditPool(const CBlockIndex* const pindex); + /** Passthrough functions to CInstantSendManager */ std::optional> ConflictingISLockIfAny(const CTransaction& tx) const; bool IsInstantSendWaitingForTx(const uint256& hash) const; @@ -66,12 +80,6 @@ class CChainstateHelper bool ShouldInstantSendRejectConflicts() const; std::unordered_map GetSignalsStage(const CBlockIndex* const pindexPrev); - -public: - const std::unique_ptr ehf_manager; - const std::unique_ptr credit_pool_manager; - const std::unique_ptr mn_payments; - const std::unique_ptr special_tx; }; #endif // BITCOIN_EVO_CHAINHELPER_H diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index ac89530dc6ee..cb963d53a808 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -225,7 +225,8 @@ std::vector CDeterministicMNList::GetProjectedMNPayees(gsl } const bool isMNRewardReallocation = DeploymentActiveAfter(pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_MN_RR); - const auto weighted_count = isMNRewardReallocation ? GetValidMNsCount() : GetValidWeightedMNsCount(); + const auto counts = GetCounts(); + const auto weighted_count = isMNRewardReallocation ? counts.enabled() : counts.m_valid_weighted; nCount = std::min(nCount, int(weighted_count)); std::vector result; @@ -293,7 +294,7 @@ int CDeterministicMNList::CalcMaxPoSePenalty() const // Maximum PoSe penalty is dynamic and equals the number of registered MNs // It's however at least 100. // This means that the max penalty is usually equal to a full payment cycle - return std::max(100, (int)GetAllMNsCount()); + return std::max(100, (int)GetCounts().total()); } int CDeterministicMNList::CalcPenalty(int percent) const @@ -333,7 +334,7 @@ void CDeterministicMNList::PoSePunish(const uint256& proTxHash, int penalty, boo void CDeterministicMNList::DecreaseScores() { std::vector toDecrease; - toDecrease.reserve(GetAllMNsCount() / 10); + toDecrease.reserve(GetCounts().total() / 10); // only iterate and decrease for valid ones (not PoSe banned yet) // if a MN ever reaches the maximum, it stays in PoSe banned state until revived ForEachMNShared(/*onlyValid=*/true, [&toDecrease](const auto& dmn) { @@ -690,7 +691,7 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_nullnHeight; @@ -702,18 +703,19 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_nullactive()) { - ::g_stats_client->gauge("masternodes.count", newList.GetAllMNsCount()); - ::g_stats_client->gauge("masternodes.weighted_count", newList.GetValidWeightedMNsCount()); - ::g_stats_client->gauge("masternodes.enabled", newList.GetValidMNsCount()); - ::g_stats_client->gauge("masternodes.weighted_enabled", newList.GetValidWeightedMNsCount()); - ::g_stats_client->gauge("masternodes.evo.count", newList.GetAllEvoCount()); - ::g_stats_client->gauge("masternodes.evo.enabled", newList.GetValidEvoCount()); - ::g_stats_client->gauge("masternodes.mn.count", newList.GetAllMNsCount() - newList.GetAllEvoCount()); - ::g_stats_client->gauge("masternodes.mn.enabled", newList.GetValidMNsCount() - newList.GetValidEvoCount()); + const auto counts{newList.GetCounts()}; + ::g_stats_client->gauge("masternodes.count", counts.total()); + ::g_stats_client->gauge("masternodes.weighted_count", counts.m_total_weighted); + ::g_stats_client->gauge("masternodes.enabled", counts.enabled()); + ::g_stats_client->gauge("masternodes.weighted_enabled", counts.m_valid_weighted); + ::g_stats_client->gauge("masternodes.evo.count", counts.m_total_evo); + ::g_stats_client->gauge("masternodes.evo.enabled", counts.m_valid_evo); + ::g_stats_client->gauge("masternodes.mn.count", counts.m_total_mn); + ::g_stats_client->gauge("masternodes.mn.enabled", counts.m_valid_mn); } if (nHeight == consensusParams.DIP0003EnforcementHeight) { @@ -755,7 +757,7 @@ bool CDeterministicMNManager::UndoBlock(gsl::not_null pindex curList.ApplyDiff(pindex, diff); auto inversedDiff{curList.BuildDiff(prevList)}; - updatesRet = {curList, prevList, inversedDiff}; + updatesRet = {.old_list = curList, .new_list = prevList, .diff = inversedDiff}; } const auto& consensusParams = Params().GetConsensus(); diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 51a2bf987bfc..3a684ffa78df 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -142,6 +142,18 @@ class CDeterministicMNList using MnInternalIdMap = immer::map; using MnUniquePropertyMap = immer::map, ImmerHasher>; + struct Counts { + size_t m_total_evo{0}; + size_t m_total_mn{0}; + size_t m_total_weighted{0}; + size_t m_valid_evo{0}; + size_t m_valid_mn{0}; + size_t m_valid_weighted{0}; + + [[nodiscard]] size_t total() const { return m_total_mn + m_total_evo; } + [[nodiscard]] size_t enabled() const { return m_valid_mn + m_valid_evo; } + }; + private: uint256 blockHash; int nHeight{-1}; @@ -253,34 +265,30 @@ class CDeterministicMNList InvalidateSMLCache(); } - [[nodiscard]] size_t GetAllMNsCount() const - { - return mnMap.size(); - } - - [[nodiscard]] size_t GetValidMNsCount() const + [[nodiscard]] Counts GetCounts() const { - return ranges::count_if(mnMap, [](const auto& p) { return !p.second->pdmnState->IsBanned(); }); - } - - [[nodiscard]] size_t GetAllEvoCount() const - { - return ranges::count_if(mnMap, [](const auto& p) { return p.second->nType == MnType::Evo; }); - } - - [[nodiscard]] size_t GetValidEvoCount() const - { - return ranges::count_if(mnMap, [](const auto& p) { - return p.second->nType == MnType::Evo && !p.second->pdmnState->IsBanned(); - }); - } - - [[nodiscard]] size_t GetValidWeightedMNsCount() const - { - return std::accumulate(mnMap.begin(), mnMap.end(), 0, [](auto res, const auto& p) { - if (p.second->pdmnState->IsBanned()) return res; - return res + GetMnType(p.second->nType).voting_weight; - }); + Counts ret; + for (const auto& [_, dmn] : mnMap) { + const bool is_evo = dmn->nType == MnType::Evo; + const bool is_valid = !dmn->pdmnState->IsBanned(); + const auto weight = GetMnType(dmn->nType).voting_weight; + if (is_evo) { + ret.m_total_evo++; + if (is_valid) { + ret.m_valid_evo++; + } + } else { + ret.m_total_mn++; + if (is_valid) { + ret.m_valid_mn++; + } + } + if (is_valid) { + ret.m_valid_weighted += weight; + } + ret.m_total_weighted += weight; + } + return ret; } /** @@ -364,7 +372,7 @@ class CDeterministicMNList /** * Calculates the projected MN payees for the next *count* blocks. The result is not guaranteed to be correct * as PoSe banning might occur later - * @param nCount the number of payees to return. "nCount = max()"" means "all", use it to avoid calling GetValidWeightedMNsCount twice. + * @param nCount the number of payees to return. "nCount = max()"" means "all", use it to avoid calling GetCounts twice. */ [[nodiscard]] std::vector GetProjectedMNPayees(gsl::not_null pindexPrev, int nCount = std::numeric_limits::max()) const; diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 4a7af81ee2b6..c1434cc9acc7 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -273,7 +273,7 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul LogPrintf("%s -- MN %s removed from list because collateral was used for " /* Continued */ "a new ProRegTx. collateralOutpoint=%s, nHeight=%d, mapCurMNs.allMNsCount=%d\n", __func__, replacedDmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort(), - nHeight, newList.GetAllMNsCount()); + nHeight, newList.GetCounts().total()); } } @@ -467,7 +467,7 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul LogPrintf("%s -- MN %s removed from list because collateral was spent. " /* Continued */ "collateralOutpoint=%s, nHeight=%d, mapCurMNs.allMNsCount=%d\n", __func__, dmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort(), nHeight, - newList.GetAllMNsCount()); + newList.GetCounts().total()); } } } diff --git a/src/governance/governance.cpp b/src/governance/governance.cpp index 6eaa1ed71c89..9fb476a7333a 100644 --- a/src/governance/governance.cpp +++ b/src/governance/governance.cpp @@ -1466,7 +1466,7 @@ std::vector> CGovernanceManager::GetApp // A proposal is considered passing if (YES votes) >= (Total Weight of Masternodes / 10), // count total valid (ENABLED) masternodes to determine passing threshold. - const int nWeightedMnCount = tip_mn_list.GetValidWeightedMNsCount(); + const int nWeightedMnCount = tip_mn_list.GetCounts().m_valid_weighted; const int nAbsVoteReq = std::max(Params().GetConsensus().nGovernanceMinQuorum, nWeightedMnCount / 10); LOCK(cs_store); diff --git a/src/governance/object.cpp b/src/governance/object.cpp index 956f209b4292..fc99fa4d559e 100644 --- a/src/governance/object.cpp +++ b/src/governance/object.cpp @@ -592,6 +592,27 @@ int CGovernanceObject::GetAbstainCount(const CDeterministicMNList& tip_mn_list, return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_ABSTAIN); } +CGovernanceObject::UniqueVoterCount CGovernanceObject::GetUniqueVoterCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const +{ + LOCK(cs); + UniqueVoterCount result; + for (const auto& [outpoint, recVote] : mapCurrentMNVotes) { + if (recVote.mapInstances.count(eVoteSignalIn) == 0) { + continue; + } + auto dmn = tip_mn_list.GetMNByCollateral(outpoint); + if (!dmn) { + continue; + } + if (dmn->nType == MnType::Evo) { + ++result.m_evo; + } else { + ++result.m_regular; + } + } + return result; +} + bool CGovernanceObject::GetCurrentMNVotes(const COutPoint& mnCollateralOutpoint, vote_rec_t& voteRecord) const { LOCK(cs); @@ -610,7 +631,7 @@ void CGovernanceObject::UpdateSentinelVariables(const CDeterministicMNList& tip_ // CALCULATE MINIMUM SUPPORT LEVELS REQUIRED - int nWeightedMnCount = (int)tip_mn_list.GetValidWeightedMNsCount(); + int nWeightedMnCount = (int)tip_mn_list.GetCounts().m_valid_weighted; if (nWeightedMnCount == 0) return; // CALCULATE THE MINIMUM VOTE COUNT REQUIRED FOR FULL SIGNAL diff --git a/src/governance/object.h b/src/governance/object.h index 05eb940bf275..2f2443915835 100644 --- a/src/governance/object.h +++ b/src/governance/object.h @@ -227,6 +227,13 @@ class CGovernanceObject int GetAbstainCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const EXCLUSIVE_LOCKS_REQUIRED(!cs); + struct UniqueVoterCount { + uint16_t m_regular{0}; + uint16_t m_evo{0}; + }; + UniqueVoterCount GetUniqueVoterCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const + EXCLUSIVE_LOCKS_REQUIRED(!cs); + bool GetCurrentMNVotes(const COutPoint& mnCollateralOutpoint, vote_rec_t& voteRecord) const EXCLUSIVE_LOCKS_REQUIRED(!cs); diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 449ceeaf5458..0726da6911e3 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -721,6 +721,22 @@ size_t CInstantSendManager::GetInstantSendLockCount() const return db.GetInstantSendLockCount(); } +CInstantSendManager::Counts CInstantSendManager::GetCounts() const +{ + Counts ret; + ret.m_verified = db.GetInstantSendLockCount(); + { + LOCK(cs_pendingLocks); + ret.m_unverified = pendingInstantSendLocks.size(); + ret.m_awaiting_tx = pendingNoTxInstantSendLocks.size(); + } + { + LOCK(cs_nonLocked); + ret.m_unprotected_tx = nonLockedTxs.size(); + } + return ret; +} + void CInstantSendManager::CacheBlockHeightInternal(const CBlockIndex* const block_index) const { AssertLockHeld(cs_height_cache); diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index 8c3ec763f05d..dde3739d6030 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -186,6 +186,14 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent size_t GetInstantSendLockCount() const; + struct Counts { + size_t m_verified{0}; + size_t m_unverified{0}; + size_t m_awaiting_tx{0}; + size_t m_unprotected_tx{0}; + }; + Counts GetCounts() const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks, !cs_nonLocked); + void CacheBlockHeight(const CBlockIndex* const block_index) const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); std::optional GetBlockHeight(const uint256& hash) const override EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); void CacheTipHeight(const CBlockIndex* const tip) const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index b5676bdc9388..64fcfd0fd8ee 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -307,6 +308,7 @@ void NetInstantSend::ProcessPendingISLocks(std::vector; +using MnEntryCPtr = std::shared_ptr; //! Interface for a list of masternode entries class MnList @@ -99,28 +99,29 @@ class MnList MnList() = delete; + struct Counts { + size_t m_total_evo{0}; + size_t m_total_mn{0}; + size_t m_total_weighted{0}; + size_t m_valid_evo{0}; + size_t m_valid_mn{0}; + size_t m_valid_weighted{0}; + + [[nodiscard]] size_t total() const { return m_total_mn + m_total_evo; } + [[nodiscard]] size_t enabled() const { return m_valid_mn + m_valid_evo; } + }; + virtual Counts getCounts() const = 0; virtual int32_t getHeight() const = 0; - virtual size_t getAllEvoCount() const = 0; - virtual size_t getAllMNsCount() const = 0; - virtual size_t getValidEvoCount() const = 0; - virtual size_t getValidMNsCount() const = 0; - virtual size_t getValidWeightedMNsCount() const = 0; virtual uint256 getBlockHash() const = 0; - virtual void forEachMN(bool only_valid, std::function cb) const = 0; - virtual MnEntryCPtr getMN(const uint256& hash) const = 0; - virtual MnEntryCPtr getMNByService(const CService& service) const = 0; - virtual MnEntryCPtr getValidMN(const uint256& hash) const = 0; + virtual void forEachMN(bool only_valid, std::function cb) const = 0; virtual std::vector getProjectedMNPayees(const CBlockIndex* pindex) const = 0; - virtual void copyContextTo(MnList& mn_list) const = 0; virtual void setContext(node::NodeContext* context) = 0; }; using MnListPtr = std::shared_ptr; -MnListPtr MakeMNList(const CDeterministicMNList& mn_list); - //! Interface for the src/evo part of a dash node (dashd process). class EVO { @@ -142,7 +143,11 @@ class GOV 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; + struct UniqueVoters { + uint16_t m_regular{0}; + uint16_t m_evo{0}; + }; + virtual UniqueVoters getObjUniqueVoters(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) = 0; virtual bool existsObj(const uint256& hash) = 0; virtual bool isEnabled() = 0; virtual bool processVoteAndRelay(const CGovernanceVote& vote, std::string& error) = 0; @@ -177,7 +182,36 @@ class LLMQ { public: virtual ~LLMQ() {} - virtual size_t getInstantSentLockCount() = 0; + struct ChainLockInfo { + int32_t m_height{0}; + int64_t m_block_time{0}; + uint256 m_hash{}; + }; + virtual ChainLockInfo getBestChainLock() = 0; + struct CreditPoolCounts { + CAmount m_diff{0}; + CAmount m_limit{0}; + CAmount m_locked{0}; + }; + virtual CreditPoolCounts getCreditPoolCounts() = 0; + struct InstantSendCounts { + size_t m_verified{0}; + size_t m_unverified{0}; + size_t m_awaiting_tx{0}; + size_t m_unprotected_tx{0}; + }; + virtual InstantSendCounts getInstantSendCounts() = 0; + virtual size_t getPendingAssetUnlocks() = 0; + struct QuorumInfo { + std::string m_name; + size_t m_count{0}; + double m_health{0.0}; + bool m_rotates{false}; + int32_t m_data_retention_blocks{0}; + int32_t m_newest_height{0}; + int32_t m_expiry_height{0}; + }; + virtual std::vector getQuorumStats() = 0; virtual void setContext(node::NodeContext* context) {} }; @@ -481,6 +515,10 @@ class Node std::function; virtual std::unique_ptr handleNotifyHeaderTip(NotifyHeaderTipFn fn) = 0; + //! Register handler for InstantSend data messages. + using NotifyInstantSendChangedFn = std::function; + virtual std::unique_ptr handleNotifyInstantSendChanged(NotifyInstantSendChangedFn fn) = 0; + //! Register handler for governance data messages. using NotifyGovernanceChangedFn = std::function; virtual std::unique_ptr handleNotifyGovernanceChanged(NotifyGovernanceChangedFn fn) = 0; diff --git a/src/llmq/utils.cpp b/src/llmq/utils.cpp index 71720b6a3ba1..8106d893fbeb 100644 --- a/src/llmq/utils.cpp +++ b/src/llmq/utils.cpp @@ -134,7 +134,7 @@ std::vector CalculateScoresForQuorum(const CDeterministicMNList const bool onlyEvoNodes) { std::vector scores; - scores.reserve(mn_list.GetAllMNsCount()); + scores.reserve(mn_list.GetCounts().total()); mn_list.ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) { if (dmn->pdmnState->confirmedHash.IsNull()) { @@ -310,12 +310,13 @@ void BuildQuorumSnapshot(const Consensus::LLMQParams& llmqParams, const Consensu return; } - quorumSnapshot.activeQuorumMembers.resize(allMns.GetAllMNsCount()); + const auto allMnsTotal = allMns.GetCounts().total(); + quorumSnapshot.activeQuorumMembers.resize(allMnsTotal); const auto modifier = GetHashModifier(llmqParams, consensus_params, pCycleQuorumBaseBlockIndex); auto sortedAllMns = CalculateQuorum(allMns, modifier); LogPrint(BCLog::LLMQ, "BuildQuorumSnapshot h[%d] numMns[%d]\n", pCycleQuorumBaseBlockIndex->nHeight, - allMns.GetAllMNsCount()); + allMnsTotal); std::fill(quorumSnapshot.activeQuorumMembers.begin(), quorumSnapshot.activeQuorumMembers.end(), false); size_t index = {}; @@ -352,7 +353,7 @@ std::vector BuildNewQuorumQuarterMembers(const Consensus::LLMQPar auto quarterSize{quorumSize / 4}; const auto modifier = GetHashModifier(llmqParams, util_params.m_chainman.GetConsensus(), util_params.m_base_index); - if (allMns.GetValidMNsCount() < quarterSize) { + if (allMns.GetCounts().enabled() < quarterSize) { return quarterQuorumMembers; } @@ -408,7 +409,7 @@ std::vector BuildNewQuorumQuarterMembers(const Consensus::LLMQPar size_t firstSkippedIndex = 0; size_t idx{0}; for (const size_t i : irange::range(nQuorums)) { - auto usedMNsCount = MnsUsedAtHIndexed[i].GetAllMNsCount(); + auto usedMNsCount = MnsUsedAtHIndexed[i].GetCounts().total(); bool updated{false}; size_t initial_loop_idx = idx; while (quarterQuorumMembers[i].size() < quarterSize && (usedMNsCount + quarterQuorumMembers[i].size() < sortedCombinedMnsList.size())) { @@ -468,7 +469,7 @@ std::vector ComputeQuorumMembersByQuarterRotation(const Consensus llmq::WORK_DIFF_DEPTH); CDeterministicMNList allMns = util_params.m_dmnman.GetListForBlock(pWorkBlockIndex); LogPrint(BCLog::LLMQ, "ComputeQuorumMembersByQuarterRotation llmqType[%d] nHeight[%d] allMns[%d]\n", - ToUnderlying(llmqParams.type), util_params.m_base_index->nHeight, allMns.GetValidMNsCount()); + ToUnderlying(llmqParams.type), util_params.m_base_index->nHeight, allMns.GetCounts().enabled()); PreviousQuorumQuarters previousQuarters(nQuorums); auto prev_cycles{previousQuarters.GetCycles()}; diff --git a/src/masternode/payments.cpp b/src/masternode/payments.cpp index 06c5691b064f..29b1edb44a39 100644 --- a/src/masternode/payments.cpp +++ b/src/masternode/payments.cpp @@ -53,7 +53,7 @@ CAmount PlatformShare(const CAmount reward) voutMasternodePaymentsRet.emplace_back(platformReward, CScript() << OP_RETURN); } const auto mnList = m_dmnman.GetListForBlock(pindexPrev); - if (mnList.GetAllMNsCount() == 0) { + if (mnList.GetCounts().total() == 0) { LogPrint(BCLog::MNPAYMENTS, "CMNPaymentsProcessor::%s -- no masternode registered to receive a payment\n", __func__); return true; } diff --git a/src/node/interface_ui.cpp b/src/node/interface_ui.cpp index 1faf4512207f..6fa66ba5f770 100644 --- a/src/node/interface_ui.cpp +++ b/src/node/interface_ui.cpp @@ -24,6 +24,7 @@ struct UISignals { boost::signals2::signal NotifyChainLock; boost::signals2::signal NotifyHeaderTip; boost::signals2::signal NotifyGovernanceChanged; + boost::signals2::signal NotifyInstantSendChanged; boost::signals2::signal NotifyMasternodeListChanged; boost::signals2::signal NotifyAdditionalDataSyncProgressChanged; boost::signals2::signal BannedListChanged; @@ -48,6 +49,7 @@ ADD_SIGNALS_IMPL_WRAPPER(NotifyBlockTip); ADD_SIGNALS_IMPL_WRAPPER(NotifyChainLock); ADD_SIGNALS_IMPL_WRAPPER(NotifyHeaderTip); ADD_SIGNALS_IMPL_WRAPPER(NotifyGovernanceChanged); +ADD_SIGNALS_IMPL_WRAPPER(NotifyInstantSendChanged); ADD_SIGNALS_IMPL_WRAPPER(NotifyMasternodeListChanged); ADD_SIGNALS_IMPL_WRAPPER(NotifyAdditionalDataSyncProgressChanged); ADD_SIGNALS_IMPL_WRAPPER(BannedListChanged); @@ -64,6 +66,7 @@ void CClientUIInterface::NotifyBlockTip(SynchronizationState s, const CBlockInde 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::NotifyInstantSendChanged() { return g_ui_signals.NotifyInstantSendChanged(); } 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(); } diff --git a/src/node/interface_ui.h b/src/node/interface_ui.h index 7d7856eef9dc..67a6d0f5b1b0 100644 --- a/src/node/interface_ui.h +++ b/src/node/interface_ui.h @@ -115,6 +115,9 @@ class CClientUIInterface /** Masternode list has changed */ ADD_SIGNALS_DECL_WRAPPER(NotifyMasternodeListChanged, void, const CDeterministicMNList&, const CBlockIndex*); + /** InstantSend data changed */ + ADD_SIGNALS_DECL_WRAPPER(NotifyInstantSendChanged, void); + /** Governance data changed */ ADD_SIGNALS_DECL_WRAPPER(NotifyGovernanceChanged, void); diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index d805bf8494ae..15bb513e0bd2 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include #include @@ -24,7 +26,11 @@ #include #include #include +#include #include +#include +#include +#include #include #include #include @@ -135,49 +141,36 @@ class MnListImpl : public MnList } ~MnListImpl() = default; + Counts getCounts() const override + { + const auto counts{m_list.GetCounts()}; + return { + .m_total_evo = counts.m_total_evo, + .m_total_mn = counts.m_total_mn, + .m_total_weighted = counts.m_total_weighted, + .m_valid_evo = counts.m_valid_evo, + .m_valid_mn = counts.m_valid_mn, + .m_valid_weighted = counts.m_valid_weighted, + }; + } int32_t getHeight() const override { return m_list.GetHeight(); } - size_t getAllEvoCount() const override { return m_list.GetAllEvoCount(); } - size_t getAllMNsCount() const override { return m_list.GetAllMNsCount(); } - size_t getValidEvoCount() const override { return m_list.GetValidEvoCount(); } - size_t getValidMNsCount() const override { return m_list.GetValidMNsCount(); } - size_t getValidWeightedMNsCount() const override { return m_list.GetValidWeightedMNsCount(); } uint256 getBlockHash() const override { return m_list.GetBlockHash(); } - void forEachMN(bool only_valid, std::function cb) const override + void forEachMN(bool only_valid, std::function cb) const override { m_list.ForEachMNShared(only_valid, [&cb](const auto& dmn) { - cb(MnEntryImpl{dmn}); + cb(std::make_shared(dmn)); }); } - MnEntryCPtr getMN(const uint256& hash) const override - { - const auto dmn{m_list.GetMN(hash)}; - return dmn ? std::make_unique(dmn) : nullptr; - } - MnEntryCPtr getMNByService(const CService& service) const override - { - const auto dmn{m_list.GetMNByService(service)}; - return dmn ? std::make_unique(dmn) : nullptr; - } - MnEntryCPtr getValidMN(const uint256& hash) const override - { - const auto dmn{m_list.GetValidMN(hash)}; - return dmn ? std::make_unique(dmn) : nullptr; - } std::vector getProjectedMNPayees(const CBlockIndex* pindex) const override { std::vector ret; for (const auto& payee : m_list.GetProjectedMNPayees(pindex)) { - ret.emplace_back(std::make_unique(payee)); + ret.emplace_back(std::make_shared(payee)); } return ret; } - void copyContextTo(MnList& mn_list) const override - { - if (!m_context) return; - mn_list.setContext(m_context); - } void setContext(NodeContext* context) override { m_context = context; @@ -200,13 +193,15 @@ class EVOImpl : public EVO public: std::pair getListAtChainTip() override { - const CBlockIndex *tip = WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip()); - MnListImpl mnList{CDeterministicMNList{}}; - if (tip != nullptr && context().dmnman != nullptr) { - mnList = context().dmnman->GetListForBlock(tip); + const auto *tip = WITH_LOCK(::cs_main, return chainman().ActiveChain().Tip()); + if (tip && context().dmnman) { + MnListImpl mnList = context().dmnman->GetListForBlock(tip); + if (!mnList.getBlockHash().IsNull()) { + mnList.setContext(m_context); + return {std::make_shared(mnList), tip}; + } } - mnList.setContext(m_context); - return {std::make_shared(mnList), tip}; + return {nullptr, nullptr}; } void setContext(NodeContext* context) override { @@ -247,18 +242,24 @@ class GOVImpl : public GOV } return ret; } - bool existsObj(const uint256& hash) override + UniqueVoters getObjUniqueVoters(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) override { - if (context().govman != nullptr) { - return context().govman->HaveObjectForHash(hash); + if (context().govman != nullptr && context().dmnman != nullptr) { + const auto& tip_mn_list{context().dmnman->GetListAtChainTip()}; + if (auto govobj{context().govman->FindGovernanceObject(obj.GetHash())}) { + const auto count = govobj->GetUniqueVoterCount(tip_mn_list, vote_signal); + return {.m_regular = count.m_regular, .m_evo = count.m_evo}; + } else { + const auto count = obj.GetUniqueVoterCount(tip_mn_list, vote_signal); + return {.m_regular = count.m_regular, .m_evo = count.m_evo}; + } } - return false; + return {0, 0}; } - bool getObjLocalValidity(const CGovernanceObject& obj, std::string& error, bool check_collateral) override + bool existsObj(const uint256& hash) override { - if (context().govman != nullptr && context().chainman != nullptr && context().dmnman != nullptr) { - LOCK(cs_main); - return obj.IsValidLocally(context().dmnman->GetListAtChainTip(), *(context().chainman), error, check_collateral); + if (context().govman != nullptr) { + return context().govman->HaveObjectForHash(hash); } return false; } @@ -291,7 +292,7 @@ class GOVImpl : public GOV CSuperblock::GetNearestSuperblocksHeights(context().chainman->ActiveHeight(), info.lastsuperblock, info.nextsuperblock); info.governancebudget = CSuperblock::GetPaymentsLimit(context().chainman->ActiveChain(), info.nextsuperblock); if (context().dmnman) { - info.fundingthreshold = context().dmnman->GetListAtChainTip().GetValidWeightedMNsCount() / 10; + info.fundingthreshold = static_cast(context().dmnman->GetListAtChainTip().GetCounts().m_valid_weighted / 10); } } info.proposalfee = GOVERNANCE_PROPOSAL_FEE_TX; @@ -418,12 +419,100 @@ class LLMQImpl : public LLMQ NodeContext& context() { return *Assert(m_context); } public: - size_t getInstantSentLockCount() override + CreditPoolCounts getCreditPoolCounts() override + { + CreditPoolCounts ret{}; + if (!context().chainman) { + return ret; + } + const auto* pindex{WITH_LOCK(::cs_main, return context().chainman->ActiveChain().Tip())}; + if (!pindex || !pindex->pprev) { + return ret; + } + auto& chain_helper{context().chainman->ActiveChainstate().ChainHelper()}; + const auto pool{chain_helper.GetCreditPool(pindex)}; + ret.m_locked = pool.locked; + ret.m_limit = pool.currentLimit; + ret.m_diff = pool.locked - chain_helper.GetCreditPool(pindex->pprev).locked; + return ret; + } + ChainLockInfo getBestChainLock() override + { + if (!context().chainlocks) { + return {}; + } + const auto [clsig, pindex] = context().chainlocks->GetBestChainlockWithPindex(); + if (!pindex) { + return {}; + } + return { + .m_height = clsig.getHeight(), + .m_block_time = pindex->GetBlockTime(), + .m_hash = clsig.getBlockHash(), + }; + } + InstantSendCounts getInstantSendCounts() override + { + if (!context().llmq_ctx || !context().llmq_ctx->isman) { + return {}; + } + const auto counts{context().llmq_ctx->isman->GetCounts()}; + return { + .m_verified = counts.m_verified, + .m_unverified = counts.m_unverified, + .m_awaiting_tx = counts.m_awaiting_tx, + .m_unprotected_tx = counts.m_unprotected_tx, + }; + } + size_t getPendingAssetUnlocks() override { - if (context().llmq_ctx && context().llmq_ctx->isman != nullptr) { - return context().llmq_ctx->isman->GetInstantSendLockCount(); + if (!context().mempool) { + return 0; } - return 0; + LOCK(context().mempool->cs); + return static_cast(ranges::count_if(context().mempool->mapTx, [](const auto& entry) { + return entry.GetTx().IsPlatformTransfer(); + })); + } + std::vector getQuorumStats() override + { + std::vector stats{}; + if (!context().llmq_ctx || !context().llmq_ctx->qman || !context().chainman) { + return stats; + } + const auto* pindex{WITH_LOCK(::cs_main, return context().chainman->ActiveChain().Tip())}; + if (!pindex) { + return stats; + } + for (const auto& type : llmq::GetEnabledQuorumTypes(*context().chainman, pindex)) { + const auto llmq_params{Params().GetLLMQ(type)}; + if (!llmq_params.has_value()) { + continue; + } + const auto quorums{context().llmq_ctx->qman->ScanQuorums(type, pindex, llmq_params->signingActiveQuorumCount)}; + double health{0.0}; + for (const auto& q : quorums) { + size_t numMembers = q->members.size(); + size_t numValidMembers = q->qc->CountValidMembers(); + health += (numMembers > 0) ? (double(numValidMembers) / double(numMembers)) : 0.0; + } + health = quorums.empty() ? 0.0 : (health / quorums.size()); + const int32_t newest_height{(!quorums.empty() && quorums[0]->m_quorum_base_block_index) + ? quorums[0]->m_quorum_base_block_index->nHeight : 0}; + const int32_t expiry_height{(newest_height > 0) + ? newest_height + llmq_params->signingActiveQuorumCount * llmq_params->dkgInterval + : 0}; + stats.emplace_back(QuorumInfo{ + .m_name = std::string(llmq_params->name), + .m_count = quorums.size(), + .m_health = health, + .m_rotates = llmq_params->useRotation, + .m_data_retention_blocks = llmq_params->max_store_depth(), + .m_newest_height = newest_height, + .m_expiry_height = expiry_height, + }); + } + return stats; } void setContext(NodeContext* context) override { @@ -918,6 +1007,10 @@ class NodeImpl : public Node /* verification progress is unused when a header was received */ 0); })); } + std::unique_ptr handleNotifyInstantSendChanged(NotifyInstantSendChangedFn fn) override + { + return MakeHandler(::uiInterface.NotifyInstantSendChanged_connect(fn)); + } std::unique_ptr handleNotifyGovernanceChanged(NotifyGovernanceChangedFn fn) override { return MakeHandler(::uiInterface.NotifyGovernanceChanged_connect(fn)); @@ -1384,5 +1477,4 @@ class ChainImpl : public Chain namespace interfaces { std::unique_ptr MakeNode(node::NodeContext& context) { return std::make_unique(context); } std::unique_ptr MakeChain(node::NodeContext& node) { return std::make_unique(node); } -MnListPtr MakeMNList(const CDeterministicMNList& mn_list) { return std::make_shared(mn_list); } } // namespace interfaces diff --git a/src/node/miner.cpp b/src/node/miner.cpp index e5ee48a78293..919db864a008 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -468,7 +468,7 @@ void BlockAssembler::addPackageTxs(const CTxMemPool& mempool, int& nPackagesSele // duplicates of indexes. There's used `BlockSubsidy` equaled to 0 std::optional creditPoolDiff; if (DeploymentActiveAfter(pindexPrev, chainparams.GetConsensus(), Consensus::DEPLOYMENT_V20)) { - CCreditPool creditPool = m_chain_helper.credit_pool_manager->GetCreditPool(pindexPrev); + CCreditPool creditPool = m_chain_helper.GetCreditPool(pindexPrev); creditPoolDiff.emplace(std::move(creditPool), pindexPrev, chainparams.GetConsensus(), 0); } diff --git a/src/node/sync_manager.cpp b/src/node/sync_manager.cpp index 8acf829afd67..6d6d53259e36 100644 --- a/src/node/sync_manager.cpp +++ b/src/node/sync_manager.cpp @@ -76,7 +76,7 @@ int SyncManager::RequestGovernanceObjectVotes(const std::vector& vNodesC if (Params().NetworkIDString() != CBaseChainParams::MAIN) { nMaxObjRequestsPerNode = std::max(1, int(nProjectedVotes / - std::max(1, (int)m_gov_manager.GetMNManager().GetListAtChainTip().GetValidMNsCount()))); + std::max(1, (int)m_gov_manager.GetMNManager().GetListAtChainTip().GetCounts().enabled()))); } static Mutex cs_recently; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 166e19155bd8..d0d6ed9f64ec 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -5,18 +5,31 @@ #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include #include -#include #include +#include +#include #include #include #include #include #include #include +#include #include #include @@ -31,20 +44,6 @@ #include #endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include #include #include @@ -72,6 +71,8 @@ #include #include +#include + namespace { // Total governance clock frames. Frame 0 is reserved for the superblock // maturity window; frames 1 through GOV_CYCLE_FRAME_COUNT-1 are used for the @@ -115,8 +116,16 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const NetworkStyle* networkStyle, #ifdef ENABLE_WALLET enableWallet = WalletModel::isWalletEnabled(); #endif // ENABLE_WALLET - QApplication::setWindowIcon(m_network_style->getTrayAndWindowIcon()); - setWindowIcon(m_network_style->getTrayAndWindowIcon()); + + QIcon icon{m_network_style->getTrayAndWindowIcon()}; +#ifdef Q_OS_MACOS + if (auto macos_icon{m_network_style->getMacIcon()}) { + icon = macos_icon.value(); + } +#endif // Q_OS_MACOS + QApplication::setWindowIcon(icon); + setWindowIcon(icon); + updateWindowTitle(); rpcConsole = new RPCConsole(node, this, enableWallet ? Qt::Window : Qt::Widget); @@ -134,6 +143,10 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const NetworkStyle* networkStyle, this->message(title, message, style); }); connect(walletFrame, &WalletFrame::currentWalletSet, [this] { updateWalletStatus(); }); + connect(walletFrame, &WalletFrame::showProposalInfo, this, [this] { + rpcConsole->setInfoView(RPCConsole::InfoView::Governance); + showDebugWindow(); + }); } else #endif // ENABLE_WALLET { @@ -446,6 +459,8 @@ void BitcoinGUI::createActions() openPeersAction->setStatusTip(tr("Show peers info")); openRepairAction = new QAction(tr("Wallet &Repair"), this); openRepairAction->setStatusTip(tr("Show wallet repair options")); + openDebugLogAction = new QAction(tr("Open &debug log file"), this); + openDebugLogAction->setStatusTip(tr("Open the debug log file from the current data directory")); openConfEditorAction = new QAction(tr("Open &wallet configuration file"), this); openConfEditorAction->setStatusTip(tr("Open configuration file")); // override TextHeuristicRole set by default which confuses this action with application settings @@ -516,7 +531,8 @@ void BitcoinGUI::createActions() connect(openPeersAction, &QAction::triggered, this, &BitcoinGUI::showPeers); connect(openRepairAction, &QAction::triggered, this, &BitcoinGUI::showRepair); - // Open configs and backup folder from menu + // Open logs, configs, and backup folder from menu + connect(openDebugLogAction, &QAction::triggered, GUIUtil::openDebugLogfile); connect(openConfEditorAction, &QAction::triggered, this, &BitcoinGUI::showConfEditor); connect(showBackupsAction, &QAction::triggered, this, &BitcoinGUI::showBackups); @@ -639,6 +655,7 @@ void BitcoinGUI::createMenuBar() file->addAction(m_load_psbt_clipboard_action); file->addSeparator(); } + file->addAction(openDebugLogAction); file->addAction(openConfEditorAction); if(walletFrame) { file->addAction(showBackupsAction); @@ -1004,6 +1021,10 @@ void BitcoinGUI::addWallet(WalletModel* walletModel) }); connect(wallet_view, &WalletView::encryptionStatusChanged, this, &BitcoinGUI::updateWalletStatus); connect(wallet_view, &WalletView::incomingTransaction, this, &BitcoinGUI::incomingTransaction); + connect(wallet_view, &WalletView::showProposalInfo, this, [this] { + rpcConsole->setInfoView(RPCConsole::InfoView::Governance); + showDebugWindow(); + }); connect(this, &BitcoinGUI::setPrivacy, wallet_view, &WalletView::setPrivacy); wallet_view->setPrivacy(isPrivacyModeActivated()); const QString display_name = walletModel->getDisplayName(); @@ -1091,6 +1112,8 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) openAction->setEnabled(enabled); m_close_wallet_action->setEnabled(enabled); m_close_all_wallets_action->setEnabled(enabled); + + updateWidth(); } void BitcoinGUI::createTrayIcon() @@ -1098,7 +1121,13 @@ void BitcoinGUI::createTrayIcon() assert(QSystemTrayIcon::isSystemTrayAvailable()); if (QSystemTrayIcon::isSystemTrayAvailable()) { - trayIcon = new QSystemTrayIcon(m_network_style->getTrayAndWindowIcon(), this); + QIcon icon{m_network_style->getTrayAndWindowIcon()}; +#ifdef Q_OS_MACOS + if (auto macos_tray{m_network_style->getMacTray()}) { + icon = macos_tray.value(); + } +#endif // Q_OS_MACOS + trayIcon = new QSystemTrayIcon(icon, this); QString toolTip = tr("%1 client").arg(PACKAGE_NAME) + " " + m_network_style->getTitleAddText(); trayIcon->setToolTip(toolTip); } @@ -1139,6 +1168,7 @@ void BitcoinGUI::createIconMenu(QMenu *pmenu) repair_action = pmenu->addAction(openRepairAction->text(), openRepairAction, &QAction::trigger); } pmenu->addSeparator(); + QAction* debuglog_action = pmenu->addAction(openDebugLogAction->text(), openDebugLogAction, &QAction::trigger); QAction* conf_action = pmenu->addAction(openConfEditorAction->text(), openConfEditorAction, &QAction::trigger); QAction* backups_action{nullptr}; if (enableWallet) { @@ -1155,7 +1185,7 @@ void BitcoinGUI::createIconMenu(QMenu *pmenu) // Using QSystemTrayIcon::Context is not reliable. // See https://bugreports.qt.io/browse/QTBUG-91697 pmenu, &QMenu::aboutToShow, - [this, show_hide_action, send_action, cj_send_action, receive_action, sign_action, verify_action, options_action, node_window_action, quit_action, repair_action, backups_action, info_action, graph_action, peer_action, conf_action] { + [this, show_hide_action, send_action, cj_send_action, receive_action, sign_action, verify_action, options_action, node_window_action, quit_action, repair_action, backups_action, info_action, graph_action, peer_action, debuglog_action, conf_action] { if (m_node.shutdownRequested()) return; // nothing to do, node is shutting down. if (show_hide_action) show_hide_action->setText( @@ -1182,6 +1212,7 @@ void BitcoinGUI::createIconMenu(QMenu *pmenu) node_window_action->setEnabled(openRPCConsoleAction->isEnabled()); graph_action->setEnabled(openGraphAction->isEnabled()); peer_action->setEnabled(openPeersAction->isEnabled()); + debuglog_action->setEnabled(openDebugLogAction->isEnabled()); conf_action->setEnabled(openConfEditorAction->isEnabled()); if (quit_action) quit_action->setEnabled(true); } @@ -1806,28 +1837,37 @@ void BitcoinGUI::updateGovernanceCycleIcon() const auto gov_info{m_node.gov().getGovernanceInfo()}; const auto remaining_blocks{std::max(0, gov_info.nextsuperblock - current_height)}; - const auto days{static_cast(static_cast(remaining_blocks) * gov_info.targetSpacing / (24*60*60))}; + const auto remaining_str{GUIUtil::formatBlockDuration(remaining_blocks, gov_info.targetSpacing)}; const bool awaiting_superblock{current_height % gov_info.superblockcycle >= gov_info.superblockcycle - gov_info.superblockmaturitywindow}; + // Voting closes superblockmaturitywindow blocks before the superblock + const auto voting_remaining{std::max(0, remaining_blocks - gov_info.superblockmaturitywindow)}; + const auto voting_str{GUIUtil::formatBlockDuration(voting_remaining, gov_info.targetSpacing)}; - QString tooltip1{}; if (awaiting_superblock) { labelGovernanceCycleIcon->setPixmap(m_gov_cycle_pixmaps.at({ToUnderlying(GUIUtil::ThemedColor::BLUE), 0})); - tooltip1 = tr("~%n day(s) (%1 blocks) left for superblock", "", days).arg(remaining_blocks); } else { const auto cycle_blocks{gov_info.superblockcycle - gov_info.superblockmaturitywindow}; const auto blocks_elapsed{gov_info.superblockcycle - remaining_blocks - gov_info.superblockmaturitywindow}; const auto progress{static_cast(std::max(0, blocks_elapsed)) / static_cast(std::max(1, cycle_blocks))}; const auto frame{std::clamp(static_cast(progress * (GOV_CYCLE_FRAME_COUNT - 1)), 0, GOV_CYCLE_FRAME_COUNT - 2) + 1}; labelGovernanceCycleIcon->setPixmap(m_gov_cycle_pixmaps.at({ToUnderlying(GUIUtil::ThemedColor::GREEN), frame})); - tooltip1 = tr("~%n day(s) (%1 blocks) left for voting", "", days).arg(remaining_blocks); } - const auto allocated_budget{m_node.gov().getFundableProposalHashes().allocated}; - const auto budget_pct{gov_info.governancebudget > 0 - ? static_cast(static_cast(allocated_budget) / static_cast(gov_info.governancebudget) * 100.0) - : 0}; - const auto unit{options_model.getDisplayUnit()}; - const auto tooltip2{tr("~%1% of budget committed (%2 %3).").arg(budget_pct).arg(allocated_budget / BitcoinUnits::factor(unit)).arg(BitcoinUnits::name(unit))}; + const auto tooltip1{remaining_blocks == 0 + ? (awaiting_superblock ? tr("Superblock imminent") : tr("Voting period ended")) + : (awaiting_superblock + ? tr("~%1 (%2 blocks) left for superblock").arg(remaining_str).arg(remaining_blocks) + : tr("~%1 (%2 blocks) left for voting").arg(voting_str).arg(voting_remaining))}; + const auto tooltip2{[&]() { + const auto allocated_budget{m_node.gov().getFundableProposalHashes().allocated}; + const auto budget_pct{gov_info.governancebudget > 0 + ? static_cast(static_cast(allocated_budget) / static_cast(gov_info.governancebudget) * 100.0) + : 0}; + const auto unit{options_model.getDisplayUnit()}; + return tr("~%1% of budget committed (%2 / %3)").arg(budget_pct) + .arg(GUIUtil::formatAmount(unit, allocated_budget, /*is_signed=*/false, /*truncate=*/2)) + .arg(GUIUtil::formatAmount(unit, gov_info.governancebudget, /*is_signed=*/false, /*truncate=*/2)); + }()}; labelGovernanceCycleIcon->setToolTip(QString("%1
%2
").arg(tooltip1).arg(tooltip2)); labelGovernanceCycleIcon->show(); } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index db820e46b1f3..670ce48f084b 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -173,6 +173,7 @@ class BitcoinGUI : public QMainWindow QAction* openGraphAction = nullptr; QAction* openPeersAction = nullptr; QAction* openRepairAction = nullptr; + QAction* openDebugLogAction = nullptr; QAction* openConfEditorAction = nullptr; QAction* showBackupsAction = nullptr; QAction* openAction = nullptr; diff --git a/src/qt/clientfeeds.cpp b/src/qt/clientfeeds.cpp new file mode 100644 index 000000000000..c277d85f45dc --- /dev/null +++ b/src/qt/clientfeeds.cpp @@ -0,0 +1,335 @@ +// 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. + +#include + +#include +#include +#include +#include