From 72ed0ed5505b77d1fd1a62af0631c3492674668e Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:52:44 +0530 Subject: [PATCH 01/14] fix: remove unused `m_node` in `ProposalWizard` --- src/qt/governancelist.cpp | 2 +- src/qt/proposalwizard.cpp | 3 +-- src/qt/proposalwizard.h | 6 +----- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/qt/governancelist.cpp b/src/qt/governancelist.cpp index f7b64ba1b2ee..bba2f2ebcadf 100644 --- a/src/qt/governancelist.cpp +++ b/src/qt/governancelist.cpp @@ -156,7 +156,7 @@ void GovernanceList::showCreateProposalDialog() QMessageBox::warning(this, tr("Unavailable"), tr("A synced node and an unlocked wallet are required.")); return; } - ProposalWizard* proposalWizard = new ProposalWizard(this->clientModel->node(), this->walletModel, this); + ProposalWizard* proposalWizard = new ProposalWizard(this->walletModel, this); // Ensure closing the dialog actually destroys it so a fresh flow starts next time proposalWizard->setAttribute(Qt::WA_DeleteOnClose, true); // Modeless window that does not block the parent diff --git a/src/qt/proposalwizard.cpp b/src/qt/proposalwizard.cpp index 7e5c01d15378..4e7733d52ed9 100644 --- a/src/qt/proposalwizard.cpp +++ b/src/qt/proposalwizard.cpp @@ -36,9 +36,8 @@ namespace { static QString toHex(const QByteArray& bytes) { return QString(bytes.toHex()); } } // namespace -ProposalWizard::ProposalWizard(interfaces::Node& node, WalletModel* walletModel, QWidget* parent) : +ProposalWizard::ProposalWizard(WalletModel* walletModel, QWidget* parent) : QDialog(parent), - m_node(node), m_walletModel(walletModel), m_ui(new Ui::ProposalWizard) { diff --git a/src/qt/proposalwizard.h b/src/qt/proposalwizard.h index 02b4f6d841bf..e24a6390db77 100644 --- a/src/qt/proposalwizard.h +++ b/src/qt/proposalwizard.h @@ -14,9 +14,6 @@ class QTimer; -namespace interfaces { -class Node; -} namespace Ui { class SendCoinsEntry; } @@ -28,7 +25,7 @@ class ProposalWizard : public QDialog { Q_OBJECT public: - explicit ProposalWizard(interfaces::Node& node, WalletModel* walletModel, QWidget* parent = nullptr); + explicit ProposalWizard(WalletModel* walletModel, QWidget* parent = nullptr); ~ProposalWizard(); private Q_SLOTS: @@ -46,7 +43,6 @@ private Q_SLOTS: void updateDisplayUnit(); private: - interfaces::Node& m_node; WalletModel* m_walletModel; Ui::ProposalWizard* m_ui; From 99fd1181971b06477e25b358c95b7a2c82e8d502 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 12 Feb 2026 00:03:30 +0530 Subject: [PATCH 02/14] fix: avoid precision loss, storing as `CAmount`, fix potential overflow --- src/qt/proposalmodel.cpp | 7 ++++--- src/qt/proposalmodel.h | 4 ++-- src/qt/proposalwizard.cpp | 18 +++++++++++------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/qt/proposalmodel.cpp b/src/qt/proposalmodel.cpp index ffdd59f2076b..d02c665b6c59 100644 --- a/src/qt/proposalmodel.cpp +++ b/src/qt/proposalmodel.cpp @@ -16,6 +16,7 @@ #include #include +#include Proposal::Proposal(ClientModel* _clientModel, const CGovernanceObject& _govObj) : clientModel{_clientModel}, @@ -40,7 +41,7 @@ Proposal::Proposal(ClientModel* _clientModel, const CGovernanceObject& _govObj) } if (const UniValue& amountValue = prop_data.find_value("payment_amount"); amountValue.isNum()) { - m_paymentAmount = amountValue.get_real(); + m_paymentAmount = llround(amountValue.get_real() * COIN); } if (const UniValue& urlValue = prop_data.find_value("url"); urlValue.isStr()) { @@ -121,7 +122,7 @@ QVariant ProposalModel::data(const QModelIndex& index, int role) const case Column::END_DATE: return proposal->endDate().date(); case Column::PAYMENT_AMOUNT: { - return BitcoinUnits::floorWithUnit(m_display_unit, proposal->paymentAmount() * COIN, false, + return BitcoinUnits::floorWithUnit(m_display_unit, proposal->paymentAmount(), false, BitcoinUnits::SeparatorStyle::ALWAYS); } case Column::IS_ACTIVE: @@ -146,7 +147,7 @@ QVariant ProposalModel::data(const QModelIndex& index, int role) const case Column::END_DATE: return proposal->endDate(); case Column::PAYMENT_AMOUNT: - return proposal->paymentAmount(); + return qlonglong(proposal->paymentAmount()); case Column::IS_ACTIVE: return proposal->isActive(); case Column::VOTING_STATUS: diff --git a/src/qt/proposalmodel.h b/src/qt/proposalmodel.h index 6d09c3f7fa65..d7e7ac7af2b0 100644 --- a/src/qt/proposalmodel.h +++ b/src/qt/proposalmodel.h @@ -23,7 +23,7 @@ class Proposal ClientModel* clientModel; const CGovernanceObject govObj; - double m_paymentAmount{0.0}; + CAmount m_paymentAmount{0}; QDateTime m_endDate{}; QDateTime m_startDate{}; QString m_hash{}; @@ -34,7 +34,7 @@ class Proposal explicit Proposal(ClientModel* _clientModel, const CGovernanceObject& _govObj); bool isActive() const; - double paymentAmount() const { return m_paymentAmount; } + CAmount paymentAmount() const { return m_paymentAmount; } int GetAbsoluteYesCount() const; QDateTime endDate() const { return m_endDate; } QDateTime startDate() const { return m_startDate; } diff --git a/src/qt/proposalwizard.cpp b/src/qt/proposalwizard.cpp index 4e7733d52ed9..ab5888b95513 100644 --- a/src/qt/proposalwizard.cpp +++ b/src/qt/proposalwizard.cpp @@ -169,9 +169,7 @@ void ProposalWizard::buildJsonAndHex() QJsonObject o; o.insert("name", m_ui->editName->text()); o.insert("payment_address", m_ui->editPayAddr->text()); - const auto formatted = BitcoinUnits::format(BitcoinUnits::Unit::DASH, m_ui->paymentAmount->value(), false, - BitcoinUnits::SeparatorStyle::NEVER); - o.insert("payment_amount", formatted.toDouble()); + o.insert("payment_amount", m_ui->paymentAmount->value() / static_cast(COIN)); o.insert("url", m_ui->editUrl->text()); if (start_epoch > 0) o.insert("start_epoch", start_epoch); if (end_epoch > 0) o.insert("end_epoch", end_epoch); @@ -346,11 +344,17 @@ void ProposalWizard::updateLabels() { if (m_walletModel && m_walletModel->getOptionsModel()) { const auto unit = m_walletModel->getOptionsModel()->getDisplayUnit(); - const CAmount totalAmount = static_cast(m_ui->paymentAmount->value() * - m_ui->comboPayments->currentData().toInt()); + const CAmount per_payment = m_ui->paymentAmount->value(); + const int payments = m_ui->comboPayments->currentData().toInt(); + CAmount total{0}; + if (payments > 0 && per_payment > 0 && per_payment <= MAX_MONEY / payments) { + total = per_payment * payments; + } else if (payments > 0 && per_payment > 0) { + total = MAX_MONEY; + } m_ui->labelTotalValue->setText( - BitcoinUnits::formatWithUnit(unit, totalAmount, false, BitcoinUnits::SeparatorStyle::ALWAYS)); - m_fee_formatted = BitcoinUnits::formatWithUnit(unit, GOVERNANCE_PROPOSAL_FEE_TX, false, + BitcoinUnits::formatWithUnit(unit, total, /*plussign=*/false, BitcoinUnits::SeparatorStyle::ALWAYS)); + m_fee_formatted = BitcoinUnits::formatWithUnit(unit, GOVERNANCE_PROPOSAL_FEE_TX, /*plussign=*/false, BitcoinUnits::SeparatorStyle::ALWAYS); m_ui->labelFeeValue->setText(m_fee_formatted.isEmpty() ? QString("-") : m_fee_formatted); // Dynamic header/subheader and prepare text From 8ab5c0c3a4cdad674991a93211725ddc061ce626 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:20:17 +0530 Subject: [PATCH 03/14] fix: don't include invisible/hidden tabs in width calculations --- src/qt/bitcoingui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index b1acc667ba32..989c4442a472 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1507,7 +1507,7 @@ void BitcoinGUI::updateWidth() int nWidthWidestButton{0}; int nButtonsVisible{0}; for (QAbstractButton* button : tabGroup->buttons()) { - if (!button->isEnabled()) { + if (!button->isEnabled() || !button->isVisible()) { continue; } QFontMetrics fm(button->font()); From 3b68a2de8dd14c8d7959f0900a5ea143a71b4933 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:20:52 +0530 Subject: [PATCH 04/14] fix: emit `dataChanged()` when the display unit changes. --- src/qt/proposalmodel.cpp | 8 ++++++++ src/qt/proposalmodel.h | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/qt/proposalmodel.cpp b/src/qt/proposalmodel.cpp index d02c665b6c59..ea0ccbff362a 100644 --- a/src/qt/proposalmodel.cpp +++ b/src/qt/proposalmodel.cpp @@ -258,6 +258,14 @@ void ProposalModel::reconcile(ProposalList&& proposals) } } +void ProposalModel::setDisplayUnit(const BitcoinUnit& display_unit) +{ + m_display_unit = display_unit; + if (!m_data.empty()) { + Q_EMIT dataChanged(createIndex(0, Column::PAYMENT_AMOUNT), createIndex(rowCount() - 1, Column::PAYMENT_AMOUNT)); + } +} + void ProposalModel::setVotingParams(int newAbsVoteReq) { if (this->nAbsVoteReq == newAbsVoteReq) { diff --git a/src/qt/proposalmodel.h b/src/qt/proposalmodel.h index d7e7ac7af2b0..e2918298d9bf 100644 --- a/src/qt/proposalmodel.h +++ b/src/qt/proposalmodel.h @@ -82,7 +82,7 @@ class ProposalModel : public QAbstractTableModel void append(std::unique_ptr&& proposal); void remove(int row); void reconcile(ProposalList&& proposals); - void setDisplayUnit(const BitcoinUnit& display_unit) { m_display_unit = display_unit; } + void setDisplayUnit(const BitcoinUnit& display_unit); void setVotingParams(int nAbsVoteReq); const Proposal* getProposalAt(const QModelIndex& index) const; From 14b494cbfee678605cfa230ed1d1695da90b0095 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 12 Feb 2026 07:30:12 +0530 Subject: [PATCH 05/14] fix: avoid potential reassignment of atomic `m_mn_list_changed` --- src/qt/masternodelist.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/qt/masternodelist.cpp b/src/qt/masternodelist.cpp index ec293e8ce526..a4b37f6b1ac2 100644 --- a/src/qt/masternodelist.cpp +++ b/src/qt/masternodelist.cpp @@ -181,13 +181,14 @@ void MasternodeList::updateDIP3ListScheduled() return; } - if (m_mn_list_changed.load(std::memory_order_relaxed)) { + if (m_mn_list_changed.exchange(false)) { int64_t nMnListUpdateSecods = clientModel->masternodeSync().isBlockchainSynced() ? MASTERNODELIST_UPDATE_SECONDS : MASTERNODELIST_UPDATE_SECONDS * 10; int64_t nSecondsToWait = nTimeUpdatedDIP3 - GetTime() + nMnListUpdateSecods; if (nSecondsToWait <= 0) { updateDIP3List(); - m_mn_list_changed.store(false, std::memory_order_relaxed); + } else { + m_mn_list_changed.store(true, std::memory_order_relaxed); } } } From 6b9018262fbe5ea5c9f73139c3e18bc38d1458e1 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 12 Feb 2026 07:30:51 +0530 Subject: [PATCH 06/14] fix: acknowledge `updateDIP3List()` fail states --- src/qt/masternodelist.cpp | 17 +++++++++-------- src/qt/masternodelist.h | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/qt/masternodelist.cpp b/src/qt/masternodelist.cpp index a4b37f6b1ac2..3747ca629737 100644 --- a/src/qt/masternodelist.cpp +++ b/src/qt/masternodelist.cpp @@ -181,32 +181,32 @@ void MasternodeList::updateDIP3ListScheduled() return; } - if (m_mn_list_changed.exchange(false)) { + if (m_mn_list_changed.load(std::memory_order_relaxed)) { int64_t nMnListUpdateSecods = clientModel->masternodeSync().isBlockchainSynced() ? MASTERNODELIST_UPDATE_SECONDS : MASTERNODELIST_UPDATE_SECONDS * 10; int64_t nSecondsToWait = nTimeUpdatedDIP3 - GetTime() + nMnListUpdateSecods; if (nSecondsToWait <= 0) { - updateDIP3List(); - } else { - m_mn_list_changed.store(true, std::memory_order_relaxed); + if (updateDIP3List()) { + m_mn_list_changed.store(false, std::memory_order_relaxed); + } } } } -void MasternodeList::updateDIP3List() +bool MasternodeList::updateDIP3List() { if (!clientModel || clientModel->node().shutdownRequested()) { - return; + return false; } auto [mnList, pindex] = clientModel->getMasternodeList(); - if (!pindex) return; + if (!pindex) return false; auto projectedPayees = mnList->getProjectedMNPayees(pindex); if (projectedPayees.empty() && mnList->getValidMNsCount() > 0) { // GetProjectedMNPayees failed to provide results for a list with valid mns. // Keep current list and let it try again later. - return; + return false; } std::map mapCollateralDests; @@ -261,6 +261,7 @@ void MasternodeList::updateDIP3List() } updateFilteredCount(); + return true; } void MasternodeList::updateMyMasternodeHashes(const interfaces::MnListPtr& mnList) diff --git a/src/qt/masternodelist.h b/src/qt/masternodelist.h index 56929aeaf38b..e8cd5fb09748 100644 --- a/src/qt/masternodelist.h +++ b/src/qt/masternodelist.h @@ -96,7 +96,7 @@ class MasternodeList : public QWidget const MasternodeEntry* GetSelectedEntry(); - void updateDIP3List(); + bool updateDIP3List(); void updateMyMasternodeHashes(const interfaces::MnListPtr& mnList); Q_SIGNALS: From abb9aa1e1b5b526f08999d37620dafdc6102215d Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Wed, 11 Feb 2026 22:46:42 +0530 Subject: [PATCH 07/14] fix: use wider integer to avoid potential overflow scenarios Also fetch network params once, remove unusual clamping logic as validators operate on 64-bit integers. --- src/interfaces/node.h | 1 + src/node/interfaces.cpp | 1 + src/qt/proposalwizard.cpp | 52 +++++++++++++++++---------------------- src/qt/proposalwizard.h | 2 ++ 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 3a4d59d55b29..b6fbef3de525 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -146,6 +146,7 @@ class GOV int nextsuperblock{0}; int fundingthreshold{0}; CAmount governancebudget{0}; + int64_t targetSpacing{0}; int relayRequiredConfs{1}; int requiredConfs{6}; }; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 1213e1380666..fcda7fab54d0 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -282,6 +282,7 @@ class GOVImpl : public GOV info.proposalfee = GOVERNANCE_PROPOSAL_FEE_TX; info.superblockcycle = consensusParams.nSuperblockCycle; info.superblockmaturitywindow = consensusParams.nSuperblockMaturityWindow; + info.targetSpacing = consensusParams.nPowTargetSpacing; info.relayRequiredConfs = GOVERNANCE_MIN_RELAY_FEE_CONFIRMATIONS; info.requiredConfs = GOVERNANCE_FEE_CONFIRMATIONS; if (ctx.dmnman) { diff --git a/src/qt/proposalwizard.cpp b/src/qt/proposalwizard.cpp index ab5888b95513..24cff17f860d 100644 --- a/src/qt/proposalwizard.cpp +++ b/src/qt/proposalwizard.cpp @@ -67,6 +67,8 @@ ProposalWizard::ProposalWizard(WalletModel* walletModel, QWidget* parent) : { // Load governance parameters const auto info = m_walletModel->node().gov().getGovernanceInfo(); + m_superblock_cycle = info.superblockcycle; + m_target_spacing = info.targetSpacing; m_relayRequiredConfs = info.relayRequiredConfs; m_requiredConfs = info.requiredConfs; @@ -76,7 +78,7 @@ ProposalWizard::ProposalWizard(WalletModel* walletModel, QWidget* parent) : const int cycle = info.superblockcycle; for (int i = 0; i < 12; ++i) { const int sbHeight = nextSb + i * cycle; - const qint64 secs = static_cast(i) * cycle * Params().GetConsensus().nPowTargetSpacing; + const qint64 secs = static_cast(i) * cycle * m_target_spacing; const auto dt = QDateTime::currentDateTimeUtc().addSecs(secs).toLocalTime(); m_ui->comboFirstPayment->addItem(QLocale().toString(dt, QLocale::ShortFormat), sbHeight); } @@ -130,39 +132,29 @@ ProposalWizard::~ProposalWizard() void ProposalWizard::buildJsonAndHex() { + const int64_t multiplier = std::numeric_limits::max() / std::max(1, m_target_spacing); + // Compute start/end epochs from selected superblocks - int start_epoch = 0; - int end_epoch = 0; - int firstSb = m_ui->comboFirstPayment->currentData().toInt(); - int payments = m_ui->comboPayments->currentData().toInt(); - if (firstSb > 0 && payments > 0) { - const int cycle = Params().GetConsensus().nSuperblockCycle; - if (cycle > 0) { - const int prevSb = firstSb - cycle; - const int lastSb = firstSb + (payments - 1) * cycle; - const int nextAfterLast = lastSb + cycle; + int64_t start_epoch = 0; + int64_t end_epoch = 0; + const int64_t first_sb = m_ui->comboFirstPayment->currentData().toInt(); + const int64_t payments = m_ui->comboPayments->currentData().toInt(); + if (first_sb > 0 && payments > 0) { + if (m_superblock_cycle > 0) { + const int64_t prev_sb{first_sb - m_superblock_cycle}; + const int64_t last_sb{first_sb + (payments - 1) * m_superblock_cycle}; + const int64_t next_sb{last_sb + m_superblock_cycle}; // Midpoints in blocks; convert roughly to seconds relative to now - const int startMidBlocks = (firstSb + prevSb) / 2; - const int endMidBlocks = (lastSb + nextAfterLast) / 2; + const int64_t start_blocks{(first_sb + prev_sb) / 2}; + const int64_t end_blocks{(last_sb + next_sb) / 2}; // We don't know absolute time for those heights in GUI; approximate using consensus block time // Use now as baseline; this is only to pass validator and give a stable window - const qint64 now = QDateTime::currentSecsSinceEpoch(); - const int64_t targetSpacing = Params().GetConsensus().nPowTargetSpacing; // seconds - const int64_t deltaStartBlocks = static_cast(startMidBlocks) - static_cast(firstSb); - const int64_t deltaEndBlocks = static_cast(endMidBlocks) - static_cast(firstSb); - // Guard against overflow when multiplying - const int64_t maxMultiplier = std::numeric_limits::max() / std::max(1, targetSpacing); - const int64_t clampedDeltaStart = std::clamp(deltaStartBlocks, -maxMultiplier, maxMultiplier); - const int64_t clampedDeltaEnd = std::clamp(deltaEndBlocks, -maxMultiplier, maxMultiplier); - const int64_t startOffsetSecs = clampedDeltaStart * targetSpacing; - const int64_t endOffsetSecs = clampedDeltaEnd * targetSpacing; - const int64_t startEpoch64 = now + startOffsetSecs; - const int64_t endEpoch64 = now + endOffsetSecs; - // Clamp to 32-bit int range used by validator - start_epoch = static_cast( - std::clamp(startEpoch64, std::numeric_limits::min(), std::numeric_limits::max())); - end_epoch = static_cast( - std::clamp(endEpoch64, std::numeric_limits::min(), std::numeric_limits::max())); + const qint64 now{QDateTime::currentSecsSinceEpoch()}; + const int64_t delta_start{start_blocks - first_sb}; + const int64_t delta_end{end_blocks - first_sb}; + // Guard against overflow when multiplying by clamping deltas first + start_epoch = now + std::clamp(delta_start, -multiplier, multiplier) * m_target_spacing; + end_epoch = now + std::clamp(delta_end, -multiplier, multiplier) * m_target_spacing; } } diff --git a/src/qt/proposalwizard.h b/src/qt/proposalwizard.h index e24a6390db77..d38998d58d8d 100644 --- a/src/qt/proposalwizard.h +++ b/src/qt/proposalwizard.h @@ -51,6 +51,8 @@ private Q_SLOTS: QString m_txid; QString m_fee_formatted; qint64 m_prepareTime{0}; + int64_t m_superblock_cycle{0}; + int64_t m_target_spacing{0}; int m_relayRequiredConfs{1}; int m_requiredConfs{6}; int m_lastConfs{-1}; From c8ce943bb4abba1ae5c87f6a520e9ce6f4493afe Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Wed, 11 Feb 2026 22:35:50 +0530 Subject: [PATCH 08/14] fix: use UniValue instead of QJsonObject to construct proposal --- src/qt/proposalwizard.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/qt/proposalwizard.cpp b/src/qt/proposalwizard.cpp index 24cff17f860d..4e96f0a81b23 100644 --- a/src/qt/proposalwizard.cpp +++ b/src/qt/proposalwizard.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -20,8 +21,6 @@ #include #include -#include -#include #include #include #include @@ -29,6 +28,8 @@ #include #include +#include + #include namespace { @@ -158,15 +159,17 @@ void ProposalWizard::buildJsonAndHex() } } - QJsonObject o; - o.insert("name", m_ui->editName->text()); - o.insert("payment_address", m_ui->editPayAddr->text()); - o.insert("payment_amount", m_ui->paymentAmount->value() / static_cast(COIN)); - o.insert("url", m_ui->editUrl->text()); - if (start_epoch > 0) o.insert("start_epoch", start_epoch); - if (end_epoch > 0) o.insert("end_epoch", end_epoch); - o.insert("type", 1); - const auto json = QJsonDocument(o).toJson(QJsonDocument::Compact); + UniValue o(UniValue::VOBJ); + o.pushKV("name", m_ui->editName->text().toStdString()); + o.pushKV("payment_address", m_ui->editPayAddr->text().toStdString()); + UniValue amount; + amount.setNumStr(FormatMoney(m_ui->paymentAmount->value())); + o.pushKV("payment_amount", amount); + o.pushKV("url", m_ui->editUrl->text().toStdString()); + if (start_epoch > 0) o.pushKV("start_epoch", start_epoch); + if (end_epoch > 0) o.pushKV("end_epoch", end_epoch); + o.pushKV("type", 1); + const auto json{QByteArray::fromStdString(o.write())}; m_ui->plainJson->setPlainText(QString::fromUtf8(json)); m_hex = toHex(json); m_ui->editHex->setText(m_hex); From 3ee43d950bf437d4752a966c8c5aff24a71382fd Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 12 Feb 2026 04:01:21 +0530 Subject: [PATCH 09/14] fix: switch back to overview page if tab disabled --- src/qt/bitcoingui.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 989c4442a472..dc5055a089c5 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1476,7 +1476,14 @@ void BitcoinGUI::updateGovernanceVisibility() // Show/hide the underlying QAction, hiding the QToolButton itself doesn't // work for the GUI part but is still needed for shortcuts to work properly. if (m_governance_action) m_governance_action->setVisible(fShow); - if (governanceButton) governanceButton->setVisible(fShow); + if (governanceButton) { +#ifdef ENABLE_WALLET + if (!fShow && governanceButton->isChecked()) { + gotoOverviewPage(); + } +#endif // ENABLE_WALLET + governanceButton->setVisible(fShow); + } GUIUtil::updateButtonGroupShortcuts(tabGroup); updateWidth(); @@ -1490,7 +1497,14 @@ void BitcoinGUI::updateMasternodesVisibility() // Show/hide the underlying QAction, hiding the QToolButton itself doesn't // work for the GUI part but is still needed for shortcuts to work properly. if (m_masternode_action) m_masternode_action->setVisible(fShow); - if (masternodeButton) masternodeButton->setVisible(fShow); + if (masternodeButton) { +#ifdef ENABLE_WALLET + if (!fShow && masternodeButton->isChecked()) { + gotoOverviewPage(); + } +#endif // ENABLE_WALLET + masternodeButton->setVisible(fShow); + } GUIUtil::updateButtonGroupShortcuts(tabGroup); updateWidth(); From ec5fb2000c12148bf223c5c0e1602b8ffcd312f6 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Wed, 28 Jan 2026 18:58:53 +0530 Subject: [PATCH 10/14] fix: avoid double-free by clearing before dtor semaphore cleanup --- src/net.cpp | 1 + src/net.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/net.cpp b/src/net.cpp index 4467507f68e0..3b70ee45fe00 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -4200,6 +4200,7 @@ void CConnman::StopNodes() } m_nodes_disconnected.clear(); vhListenSocket.clear(); + WITH_LOCK(m_reconnections_mutex, m_reconnections.clear()); semOutbound.reset(); semAddnode.reset(); /** diff --git a/src/net.h b/src/net.h index 6a742647ac0b..8bdda43e9e75 100644 --- a/src/net.h +++ b/src/net.h @@ -1261,8 +1261,8 @@ friend class CNode; EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex, !m_added_nodes_mutex, !m_addr_fetches_mutex, !mutexMsgProc); void StopThreads(); - void StopNodes() EXCLUSIVE_LOCKS_REQUIRED(!m_nodes_mutex, !cs_mapSocketToNode, !cs_sendable_receivable_nodes); - void Stop() EXCLUSIVE_LOCKS_REQUIRED(!m_nodes_mutex, !cs_mapSocketToNode, !cs_sendable_receivable_nodes) + void StopNodes() EXCLUSIVE_LOCKS_REQUIRED(!m_nodes_mutex, !cs_mapSocketToNode, !cs_sendable_receivable_nodes, !m_reconnections_mutex); + void Stop() EXCLUSIVE_LOCKS_REQUIRED(!m_nodes_mutex, !cs_mapSocketToNode, !cs_sendable_receivable_nodes, !m_reconnections_mutex) { StopThreads(); StopNodes(); From 66f2e8079c5fb1b7ee8592bbac9a2797dc6b348e Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Wed, 28 Jan 2026 19:07:54 +0530 Subject: [PATCH 11/14] fix: check for `llmq_ctx` before accessing `isman` --- src/node/interfaces.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index fcda7fab54d0..018739f399da 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -367,7 +367,7 @@ class LLMQImpl : public LLMQ public: size_t getInstantSentLockCount() override { - if (context().llmq_ctx->isman != nullptr) { + if (context().llmq_ctx && context().llmq_ctx->isman != nullptr) { return context().llmq_ctx->isman->GetInstantSendLockCount(); } return 0; From ce747c857c2a3db0ac4da502a920d85c90731a8a Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Thu, 12 Feb 2026 04:03:50 +0530 Subject: [PATCH 12/14] qt: add vote signing to wallet interface --- src/interfaces/wallet.h | 4 ++++ src/qt/governancelist.cpp | 5 ++--- src/wallet/interfaces.cpp | 4 ++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 8c79ec01ca98..11e1cda99978 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -27,6 +27,7 @@ #include class CFeeRate; +class CGovernanceVote; class CKey; class CRPCCommand; enum class FeeReason; @@ -355,6 +356,9 @@ class Wallet const std::string& data_hex, const COutPoint& outpoint, std::string& out_fee_txid, std::string& error) = 0; + //! Sign a governance vote with the given voting key. + virtual bool signGovernanceVote(const CKeyID& keyID, CGovernanceVote& vote) = 0; + //! Return pointer to internal wallet class, useful for testing. virtual wallet::CWallet* wallet() { return nullptr; } }; diff --git a/src/qt/governancelist.cpp b/src/qt/governancelist.cpp index bba2f2ebcadf..ae560bc8ca32 100644 --- a/src/qt/governancelist.cpp +++ b/src/qt/governancelist.cpp @@ -18,7 +18,6 @@ #include #include