Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 11 additions & 8 deletions src/Makefile.qt.include
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ 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/intro.ui \
qt/forms/masternodelist.ui \
Expand All @@ -33,6 +32,7 @@ QT_FORMS_UI = \
qt/forms/optionsdialog.ui \
qt/forms/overviewpage.ui \
qt/forms/proposalcreate.ui \
qt/forms/proposallist.ui \
qt/forms/proposalresume.ui \
qt/forms/psbtoperationsdialog.ui \
qt/forms/qrdialog.ui \
Expand All @@ -53,14 +53,14 @@ 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_editaddressdialog.cpp \
qt/moc_governancelist.cpp \
qt/moc_guiutil.cpp \
qt/moc_initexecutor.cpp \
qt/moc_intro.cpp \
Expand All @@ -78,8 +78,9 @@ 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_proposallist.cpp \
qt/moc_proposalmodel.cpp \
qt/moc_proposalresume.cpp \
qt/moc_psbtoperationsdialog.cpp \
qt/moc_qrdialog.cpp \
Expand Down Expand Up @@ -133,17 +134,17 @@ 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/editaddressdialog.h \
qt/governancelist.h \
qt/guiconstants.h \
qt/guiutil.h \
qt/guiutil_font.h \
qt/guiutil.h \
qt/initexecutor.h \
qt/intro.h \
qt/macdockiconhandler.h \
Expand All @@ -163,6 +164,7 @@ BITCOIN_QT_H = \
qt/peertablemodel.h \
qt/peertablesortproxy.h \
qt/proposalcreate.h \
qt/proposallist.h \
qt/proposalmodel.h \
qt/proposalresume.h \
qt/psbtoperationsdialog.h \
Expand Down Expand Up @@ -242,9 +244,12 @@ BITCOIN_QT_BASE_CPP = \
qt/bantablemodel.cpp \
qt/bitcoin.cpp \
qt/bitcoinaddressvalidator.cpp \
qt/masternodemodel.cpp \
qt/proposalmodel.cpp \
qt/bitcoinamountfield.cpp \
qt/bitcoingui.cpp \
qt/bitcoinunits.cpp \
qt/clientfeeds.cpp \
qt/clientmodel.cpp \
qt/csvmodelwriter.cpp \
qt/guiutil.cpp \
Expand Down Expand Up @@ -277,15 +282,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 \
Expand Down
31 changes: 16 additions & 15 deletions src/qt/bitcoingui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,31 @@

#include <qt/bitcoingui.h>

#include <chain.h>
#include <chainparams.h>
#include <interfaces/coinjoin.h>
#include <interfaces/handler.h>
#include <interfaces/node.h>
#include <node/interface_ui.h>
#include <util/system.h>
#include <util/translation.h>
#include <util/underlying.h>
#include <validation.h>

#include <qt/bitcoinunits.h>
#include <qt/clientmodel.h>
#include <qt/createwalletdialog.h>
#include <qt/guiconstants.h>
#include <qt/guiutil.h>
#include <qt/guiutil_font.h>
#include <qt/guiutil.h>
#include <qt/masternodelist.h>
#include <qt/modaloverlay.h>
#include <qt/networkstyle.h>
#include <qt/notificator.h>
#include <qt/openuridialog.h>
#include <qt/optionsdialog.h>
#include <qt/optionsmodel.h>
#include <qt/proposallist.h>
#include <qt/rpcconsole.h>
#include <qt/utilitydialog.h>

Expand All @@ -31,20 +44,6 @@
#include <qt/macdockiconhandler.h>
#endif

#include <functional>
#include <chain.h>
#include <chainparams.h>
#include <interfaces/coinjoin.h>
#include <interfaces/handler.h>
#include <interfaces/node.h>
#include <node/interface_ui.h>
#include <qt/governancelist.h>
#include <qt/masternodelist.h>
#include <util/system.h>
#include <util/translation.h>
#include <util/underlying.h>
#include <validation.h>

#include <QAction>
#include <QActionGroup>
#include <QApplication>
Expand Down Expand Up @@ -72,6 +71,8 @@
#include <QVBoxLayout>
#include <QWindow>

#include <functional>

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
Expand Down
235 changes: 235 additions & 0 deletions src/qt/clientfeeds.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
// 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 <qt/clientfeeds.h>

#include <coins.h>
#include <key_io.h>
#include <script/standard.h>
#include <util/threadnames.h>
#include <util/time.h>

#include <qt/clientmodel.h>
#include <qt/masternodemodel.h>
#include <qt/proposalmodel.h>

#include <QDebug>
#include <QThread>

namespace {
constexpr auto MASTERNODE_UPDATE_INTERVAL{3s};
constexpr auto PROPOSAL_UPDATE_INTERVAL{10s};
} // anonymous namespace

FeedBase::FeedBase(QObject* parent, const FeedBase::Config& config) :
QObject{parent},
m_config{config}
{
}

FeedBase::~FeedBase() = default;

void FeedBase::requestForceRefresh()
{
if (m_timer) {
m_timer->start(0);
}
}

void FeedBase::requestRefresh()
{
if (m_timer && !m_timer->isActive()) {
m_timer->start(m_syncing.load() ? m_config.m_throttle : m_config.m_baseline);
}
}

MasternodeFeed::MasternodeFeed(QObject* parent, ClientModel& client_model) :
Feed<MasternodeData>{parent, {/*m_baseline=*/MASTERNODE_UPDATE_INTERVAL, /*m_throttle=*/MASTERNODE_UPDATE_INTERVAL*10}},
m_client_model{client_model}
{
}

MasternodeFeed::~MasternodeFeed() = default;

void MasternodeFeed::fetch()
{
if (m_client_model.node().shutdownRequested()) {
return;
}

const auto [dmn, pindex] = m_client_model.getMasternodeList();
if (!dmn || !pindex) {
return;
}

auto projectedPayees = dmn->getProjectedMNPayees(pindex);
if (projectedPayees.empty() && dmn->getValidMNsCount() > 0) {
// GetProjectedMNPayees failed to provide results for a list with valid mns.
// Keep current list and let it try again later.
return;
}

auto ret = std::make_shared<Data>();
ret->m_list_height = dmn->getHeight();

Uint256HashMap<int> nextPayments;
for (size_t i = 0; i < projectedPayees.size(); i++) {
const auto& dmn = projectedPayees[i];
nextPayments.emplace(dmn->getProTxHash(), ret->m_list_height + (int)i + 1);
}

dmn->forEachMN(/*only_valid=*/false, [&](const auto& dmn) {
CTxDestination collateralDest;
Coin coin;
QString collateralStr = QObject::tr("UNKNOWN");
if (m_client_model.node().getUnspentOutput(dmn.getCollateralOutpoint(), coin) &&
ExtractDestination(coin.out.scriptPubKey, collateralDest)) {
collateralStr = QString::fromStdString(EncodeDestination(collateralDest));
}
int nNextPayment{0};
if (auto nextPaymentIt = nextPayments.find(dmn.getProTxHash()); nextPaymentIt != nextPayments.end()) {
nNextPayment = nextPaymentIt->second;
}
ret->m_entries.push_back(std::make_unique<MasternodeEntry>(dmn, collateralStr, nNextPayment));
});

ret->m_valid = true;
setData(std::move(ret));
}

ProposalFeed::ProposalFeed(QObject* parent, ClientModel& client_model) :
Feed<ProposalData>{parent, {/*m_baseline=*/PROPOSAL_UPDATE_INTERVAL, /*m_throttle=*/PROPOSAL_UPDATE_INTERVAL*6}},
m_client_model{client_model}
{
}

ProposalFeed::~ProposalFeed() = default;

void ProposalFeed::fetch()
{
if (m_client_model.node().shutdownRequested()) {
return;
}

const auto [dmn, pindex] = m_client_model.getMasternodeList();
if (!dmn || !pindex) {
return;
}

auto ret = std::make_shared<Data>();
// A proposal is considered passing if (YES votes - NO votes) >= (Total Weight of Masternodes / 10),
// count total valid (ENABLED) masternodes to determine passing threshold.
// Need to query number of masternodes here with access to client model.
const int nWeightedMnCount = dmn->getValidWeightedMNsCount();
ret->m_abs_vote_req = std::max(Params().GetConsensus().nGovernanceMinQuorum, nWeightedMnCount / 10);
ret->m_gov_info = m_client_model.node().gov().getGovernanceInfo();
std::vector<CGovernanceObject> govObjList;
m_client_model.getAllGovernanceObjects(govObjList);
for (const auto& govObj : govObjList) {
if (govObj.GetObjectType() != GovernanceObject::PROPOSAL) {
continue; // Skip triggers.
}
ret->m_proposals.emplace_back(std::make_shared<Proposal>(m_client_model, govObj, ret->m_gov_info, ret->m_gov_info.requiredConfs,
/*is_broadcast=*/true));
}

auto fundable{m_client_model.node().gov().getFundableProposalHashes()};
ret->m_fundable_hashes = std::move(fundable.hashes);

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

ClientFeeds::ClientFeeds(QObject* parent) :
QObject{parent},
m_thread{new QThread(this)}
{
}

ClientFeeds::~ClientFeeds()
{
stop();
}

void ClientFeeds::registerFeed(FeedBase* raw)
{
auto* timer = new QTimer(this);
timer->setSingleShot(true);
raw->m_timer = timer;

connect(timer, &QTimer::timeout, this, [this, raw] {
if (raw->m_in_progress.exchange(true)) {
raw->m_retry_pending.store(true);
return;
}
QMetaObject::invokeMethod(m_worker, [this, raw] {
try {
raw->fetch();
} catch (const std::exception& e) {
qWarning() << "ClientFeeds::fetch() exception: " << e.what();
} catch (...) {
qWarning() << "ClientFeeds::fetch() unknown exception";
}
QTimer::singleShot(0, raw, [this, raw] {
raw->m_in_progress.store(false);
if (m_stopped) return;
Q_EMIT raw->dataReady();
if (raw->m_retry_pending.exchange(false)) {
raw->requestRefresh();
}
});
});
});
}

void ClientFeeds::start()
{
m_worker = new QObject();
m_worker->moveToThread(m_thread);
m_thread->start();
QMetaObject::invokeMethod(m_worker, [] { util::ThreadRename("qt-clientfeed"); });

for (const auto& source : m_sources) {
if (source->m_timer) {
source->m_timer->start(0);
}
}
}

void ClientFeeds::stop()
{
if (m_stopped) {
return;
}

m_stopped = true;
for (const auto& source : m_sources) {
if (source->m_timer) {
source->m_timer->stop();
source->m_timer = nullptr;
}
}
if (m_thread->isRunning()) {
m_thread->quit();
m_thread->wait();
}
delete m_worker;
m_worker = nullptr;
}

void ClientFeeds::setSyncing(bool syncing)
{
if (m_stopped) {
return;
}

for (const auto& source : m_sources) {
if (source->m_syncing.load() == syncing) {
continue;
}
source->setSyncing(syncing);
if (source->m_timer && source->m_timer->isActive()) {
source->m_timer->start(syncing ? source->m_config.m_throttle : source->m_config.m_baseline);
}
}
}
Loading
Loading