From 85a0a3f978739d9f1e618654ee65d9bb4b051f4e Mon Sep 17 00:00:00 2001 From: jamescowens Date: Sun, 8 May 2022 13:42:05 -0400 Subject: [PATCH 01/21] Add mrcmodel.h/cpp skeletons --- src/Makefile.qt.include | 3 +++ src/qt/mrcmodel.cpp | 5 +++++ src/qt/mrcmodel.h | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 src/qt/mrcmodel.cpp create mode 100644 src/qt/mrcmodel.h diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index d603fba308..1c2b9f5307 100755 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -145,6 +145,7 @@ QT_MOC_CPP = \ qt/moc_favoritespage.cpp \ qt/moc_guiutil.cpp \ qt/moc_intro.cpp \ + qt/moc_mrcmodel.cpp \ qt/moc_monitoreddatamapper.cpp \ qt/moc_noresult.cpp \ qt/moc_notificator.cpp \ @@ -252,6 +253,7 @@ GRIDCOINRESEARCH_QT_H = \ qt/macnotificationhandler.h \ qt/macos_appnap.h \ qt/monitoreddatamapper.h \ + qt/mrcmodel.h \ qt/noresult.h \ qt/notificator.h \ qt/optionsdialog.h \ @@ -335,6 +337,7 @@ GRIDCOINRESEARCH_QT_CPP = \ qt/guiutil.cpp \ qt/intro.cpp \ qt/monitoreddatamapper.cpp \ + qt/mrcmodel.cpp \ qt/noresult.cpp \ qt/notificator.cpp \ qt/optionsdialog.cpp \ diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp new file mode 100644 index 0000000000..1690a9a2b1 --- /dev/null +++ b/src/qt/mrcmodel.cpp @@ -0,0 +1,5 @@ +// Copyright (c) 2014-2022 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#include "qt/mrcmodel.h" diff --git a/src/qt/mrcmodel.h b/src/qt/mrcmodel.h new file mode 100644 index 0000000000..12b0be1e9a --- /dev/null +++ b/src/qt/mrcmodel.h @@ -0,0 +1,18 @@ +// Copyright (c) 2014-2022 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef GRIDCOIN_QT_MRCMODEL_H +#define GRIDCOIN_QT_MRCMODEL_H + + +enum class MRCRequestStatus +{ + PENDING, + REJECTED_QUEUE_FULL, + STALE, + DELETED +}; + + +#endif // GRIDCOIN_QT_MRCMODEL_H From abe3ca58de0f029d380fd826c0fbdd179c3312e6 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Sun, 8 May 2022 13:51:18 -0400 Subject: [PATCH 02/21] Add mrcrequestpage.h/cpp skeletons --- src/Makefile.qt.include | 3 +++ src/qt/mrcrequestpage.cpp | 5 +++++ src/qt/mrcrequestpage.h | 10 ++++++++++ 3 files changed, 18 insertions(+) create mode 100644 src/qt/mrcrequestpage.cpp create mode 100644 src/qt/mrcrequestpage.h diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 1c2b9f5307..e2251350e5 100755 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -146,6 +146,7 @@ QT_MOC_CPP = \ qt/moc_guiutil.cpp \ qt/moc_intro.cpp \ qt/moc_mrcmodel.cpp \ + qt/moc_mrcrequestpage.cpp \ qt/moc_monitoreddatamapper.cpp \ qt/moc_noresult.cpp \ qt/moc_notificator.cpp \ @@ -254,6 +255,7 @@ GRIDCOINRESEARCH_QT_H = \ qt/macos_appnap.h \ qt/monitoreddatamapper.h \ qt/mrcmodel.h \ + qt/mrcrequestpage.h \ qt/noresult.h \ qt/notificator.h \ qt/optionsdialog.h \ @@ -338,6 +340,7 @@ GRIDCOINRESEARCH_QT_CPP = \ qt/intro.cpp \ qt/monitoreddatamapper.cpp \ qt/mrcmodel.cpp \ + qt/mrcrequestpage.cpp \ qt/noresult.cpp \ qt/notificator.cpp \ qt/optionsdialog.cpp \ diff --git a/src/qt/mrcrequestpage.cpp b/src/qt/mrcrequestpage.cpp new file mode 100644 index 0000000000..a3cea799fb --- /dev/null +++ b/src/qt/mrcrequestpage.cpp @@ -0,0 +1,5 @@ +// Copyright (c) 2014-2022 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#include "qt/mrcrequestpage.h" diff --git a/src/qt/mrcrequestpage.h b/src/qt/mrcrequestpage.h new file mode 100644 index 0000000000..75ff17c4b3 --- /dev/null +++ b/src/qt/mrcrequestpage.h @@ -0,0 +1,10 @@ +// Copyright (c) 2014-2022 The Gridcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or https://opensource.org/licenses/mit-license.php. + +#ifndef GRIDCOIN_QT_MRCREQUESTPAGE_H +#define GRIDCOIN_QT_MRCREQUESTPAGE_H + + + +#endif // GRIDCOIN_QT_MRCREQUESTPAGE_H From 55e4d16ec6bcf57520e2fb2177a10386dc9cb3b4 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Sun, 8 May 2022 14:35:03 -0400 Subject: [PATCH 03/21] Add mrcrequestpage.ui form skeleton --- src/Makefile.qt.include | 1 + src/qt/forms/mrcrequestpage.ui | 32 ++++++++++++++++++++++++++++++++ src/qt/mrcrequestpage.cpp | 12 +++++++++++- src/qt/mrcrequestpage.h | 17 +++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/qt/forms/mrcrequestpage.ui diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index e2251350e5..29bef1a9a9 100755 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -85,6 +85,7 @@ QT_FORMS_UI = \ qt/forms/editaddressdialog.ui \ qt/forms/favoritespage.ui \ qt/forms/intro.ui \ + qt/forms/mrcrequestpage.ui \ qt/forms/noresult.ui \ qt/forms/optionsdialog.ui \ qt/forms/overviewpage.ui \ diff --git a/src/qt/forms/mrcrequestpage.ui b/src/qt/forms/mrcrequestpage.ui new file mode 100644 index 0000000000..7868f543f2 --- /dev/null +++ b/src/qt/forms/mrcrequestpage.ui @@ -0,0 +1,32 @@ + + + MRCRequestPage + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + 70 + 80 + 58 + 18 + + + + TextLabel + + + + + + diff --git a/src/qt/mrcrequestpage.cpp b/src/qt/mrcrequestpage.cpp index a3cea799fb..3b323b70a2 100644 --- a/src/qt/mrcrequestpage.cpp +++ b/src/qt/mrcrequestpage.cpp @@ -2,4 +2,14 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include "qt/mrcrequestpage.h" +#include "mrcrequestpage.h" +#include "ui_mrcrequestpage.h" +#include "mrcmodel.h" +#include "qt/decoration.h" + +MRCRequestPage::MRCRequestPage(QWidget *parent) : + QWidget(parent), + ui(new Ui::MRCRequestPage) +{ + ui->setupUi(this); +} diff --git a/src/qt/mrcrequestpage.h b/src/qt/mrcrequestpage.h index 75ff17c4b3..13513cd798 100644 --- a/src/qt/mrcrequestpage.h +++ b/src/qt/mrcrequestpage.h @@ -5,6 +5,23 @@ #ifndef GRIDCOIN_QT_MRCREQUESTPAGE_H #define GRIDCOIN_QT_MRCREQUESTPAGE_H +#include +namespace Ui { + class MRCRequestPage; +} + +class MRCRequestPage : public QWidget +{ + Q_OBJECT + +public: + explicit MRCRequestPage(QWidget* parent = nullptr); + ~MRCRequestPage(); + +private: + Ui::MRCRequestPage *ui; + +}; #endif // GRIDCOIN_QT_MRCREQUESTPAGE_H From 4c8a56b12352781c2e06885ff4fdd899796d24c8 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Sun, 8 May 2022 14:51:44 -0400 Subject: [PATCH 04/21] Hook in skeleton mrcmodel --- src/qt/mrcmodel.h | 11 +++++++++++ src/qt/mrcrequestpage.cpp | 14 +++++++++++--- src/qt/mrcrequestpage.h | 5 ++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/qt/mrcmodel.h b/src/qt/mrcmodel.h index 12b0be1e9a..b191acdb12 100644 --- a/src/qt/mrcmodel.h +++ b/src/qt/mrcmodel.h @@ -5,6 +5,7 @@ #ifndef GRIDCOIN_QT_MRCMODEL_H #define GRIDCOIN_QT_MRCMODEL_H +#include enum class MRCRequestStatus { @@ -14,5 +15,15 @@ enum class MRCRequestStatus DELETED }; +class MRCModel : public QObject +{ + Q_OBJECT + +public: + MRCModel(); + ~MRCModel(); + +}; + #endif // GRIDCOIN_QT_MRCMODEL_H diff --git a/src/qt/mrcrequestpage.cpp b/src/qt/mrcrequestpage.cpp index 3b323b70a2..7d5809b305 100644 --- a/src/qt/mrcrequestpage.cpp +++ b/src/qt/mrcrequestpage.cpp @@ -7,9 +7,17 @@ #include "mrcmodel.h" #include "qt/decoration.h" -MRCRequestPage::MRCRequestPage(QWidget *parent) : - QWidget(parent), - ui(new Ui::MRCRequestPage) +MRCRequestPage::MRCRequestPage( + QWidget *parent, + MRCModel* mrc_model) + : QWidget(parent) + , ui(new Ui::MRCRequestPage) + , m_mrc_model(mrc_model) { ui->setupUi(this); } + +MRCRequestPage::~MRCRequestPage() +{ + delete ui; +} diff --git a/src/qt/mrcrequestpage.h b/src/qt/mrcrequestpage.h index 13513cd798..d76a341663 100644 --- a/src/qt/mrcrequestpage.h +++ b/src/qt/mrcrequestpage.h @@ -7,6 +7,8 @@ #include +class MRCModel; + namespace Ui { class MRCRequestPage; } @@ -16,11 +18,12 @@ class MRCRequestPage : public QWidget Q_OBJECT public: - explicit MRCRequestPage(QWidget* parent = nullptr); + explicit MRCRequestPage(QWidget* parent = nullptr, MRCModel *mrc_model = nullptr); ~MRCRequestPage(); private: Ui::MRCRequestPage *ui; + MRCModel *m_mrc_model; }; From 553d7ef451f23e11dcc7639ed2fa95346fd4aed8 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Sun, 8 May 2022 15:04:41 -0400 Subject: [PATCH 05/21] Add MRCChanged core signal --- src/node/ui_interface.cpp | 3 +++ src/node/ui_interface.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/node/ui_interface.cpp b/src/node/ui_interface.cpp index 43b3dd9dac..2a7d5ec950 100644 --- a/src/node/ui_interface.cpp +++ b/src/node/ui_interface.cpp @@ -22,6 +22,7 @@ struct UISignals { boost::signals2::signal MinerStatusChanged; boost::signals2::signal ResearcherChanged; boost::signals2::signal AccrualChangedFromStakeOrMRC; + boost::signals2::signal MRCChanged; boost::signals2::signal BeaconChanged; boost::signals2::signal NewPollReceived; boost::signals2::signal NotifyScraperEvent; @@ -49,6 +50,7 @@ ADD_SIGNALS_IMPL_WRAPPER(BannedListChanged); ADD_SIGNALS_IMPL_WRAPPER(MinerStatusChanged); ADD_SIGNALS_IMPL_WRAPPER(ResearcherChanged); ADD_SIGNALS_IMPL_WRAPPER(AccrualChangedFromStakeOrMRC); +ADD_SIGNALS_IMPL_WRAPPER(MRCChanged); ADD_SIGNALS_IMPL_WRAPPER(BeaconChanged); ADD_SIGNALS_IMPL_WRAPPER(NewPollReceived); ADD_SIGNALS_IMPL_WRAPPER(NotifyScraperEvent); @@ -73,6 +75,7 @@ void CClientUIInterface::BannedListChanged() { return g_ui_signals.BannedListCha void CClientUIInterface::MinerStatusChanged(bool staking, double coin_weight) { return g_ui_signals.MinerStatusChanged(staking, coin_weight); } void CClientUIInterface::ResearcherChanged() { return g_ui_signals.ResearcherChanged(); } void CClientUIInterface::AccrualChangedFromStakeOrMRC() { return g_ui_signals.AccrualChangedFromStakeOrMRC(); } +void CClientUIInterface::MRCChanged() { return g_ui_signals.MRCChanged(); } void CClientUIInterface::BeaconChanged() { return g_ui_signals.BeaconChanged(); } void CClientUIInterface::NewPollReceived(int64_t poll_time) { return g_ui_signals.NewPollReceived(poll_time); } void CClientUIInterface::NotifyAlertChanged(const uint256 &hash, ChangeType status) { return g_ui_signals.NotifyAlertChanged(hash, status); } diff --git a/src/node/ui_interface.h b/src/node/ui_interface.h index 727dd1fde8..8ae74def0c 100644 --- a/src/node/ui_interface.h +++ b/src/node/ui_interface.h @@ -126,6 +126,9 @@ class CClientUIInterface /** Walletholder accrual changed as a result of stake or MRC to the walletholder */ ADD_SIGNALS_DECL_WRAPPER(AccrualChangedFromStakeOrMRC, void); + /** MRC state changed */ + ADD_SIGNALS_DECL_WRAPPER(MRCChanged, void); + /** Beacon changed */ ADD_SIGNALS_DECL_WRAPPER(BeaconChanged, void); From 98b17f3e5a4c4ee45920f82691a33d14b9ffea87 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Sun, 8 May 2022 15:20:38 -0400 Subject: [PATCH 06/21] Wire up MRCChanged signal to AcceptToMemoryPool and Validate in the core --- src/main.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 8d66ad12a8..380c68a45c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -416,6 +416,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CTransaction &tx, bool* pfMissingInput // from the same CPID in the accept to memory pool stage. // // We have implemented a bloom filter to help with the overhead. + bool tx_contains_valid_mrc = false; + for (const auto& contract : tx.GetContracts()) { if (contract.m_type == GRC::ContractType::MRC) { GRC::MRC mrc = contract.CopyPayloadAs(); @@ -428,6 +430,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CTransaction &tx, bool* pfMissingInput if ((mempool.m_mrc_bloom & k) != k) { // The cpid definitely does not exist in the mempool. mempool.m_mrc_bloom |= k; + tx_contains_valid_mrc = true; + continue; } // The cpid might exist in the mempool. @@ -464,6 +468,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CTransaction &tx, bool* pfMissingInput mempool.m_mrc_bloom_dirty = false; if (found) return false; + + tx_contains_valid_mrc = true; } } @@ -550,6 +556,11 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CTransaction &tx, bool* pfMissingInput return false; } + + // If we accepted a transaction with a valid mrc contract, then signal MRC changed. + if (tx_contains_valid_mrc) { + uiInterface.MRCChanged(); + } } // Store transaction in memory @@ -1655,6 +1666,11 @@ class ClaimValidator } // If we arrive here, the MRCRewards are valid. + + // Signal MRCChanged because this method is called from ConnectBlock and the successful validation of an MRC + // indicates an MRC state change. + uiInterface.MRCChanged(); + return true; } }; // ClaimValidator From aaa99f675c610813bc051a899bd2a19f7473c578 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Mon, 9 May 2022 03:04:00 -0400 Subject: [PATCH 07/21] Prototype implementation of mrcmodel and mrcrequestpage --- src/gridcoin/mrc.cpp | 18 +- src/gridcoin/mrc.h | 3 +- src/qt/bitcoin.cpp | 3 + src/qt/bitcoingui.cpp | 11 + src/qt/bitcoingui.h | 9 + src/qt/forms/mrcrequestpage.ui | 248 +++++++++++++-- src/qt/forms/overviewpage.ui | 10 + src/qt/mrcmodel.cpp | 336 +++++++++++++++++++- src/qt/mrcmodel.h | 66 +++- src/qt/mrcrequestpage.cpp | 151 ++++++++- src/qt/mrcrequestpage.h | 20 +- src/qt/overviewpage.cpp | 22 ++ src/qt/overviewpage.h | 4 + src/qt/res/stylesheets/dark_stylesheet.qss | 4 + src/qt/res/stylesheets/light_stylesheet.qss | 4 + src/qt/walletmodel.cpp | 2 + src/rpc/blockchain.cpp | 9 +- 17 files changed, 880 insertions(+), 40 deletions(-) diff --git a/src/gridcoin/mrc.cpp b/src/gridcoin/mrc.cpp index 6aff5379b7..2c89608772 100644 --- a/src/gridcoin/mrc.cpp +++ b/src/gridcoin/mrc.cpp @@ -283,7 +283,7 @@ void GRC::CreateMRC(CBlockIndex* pindex, MRC& mrc, CAmount &nReward, CAmount &fee, - CWallet* pwallet) EXCLUSIVE_LOCKS_REQUIRED(cs_main) + CWallet* pwallet, bool no_sign) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { const GRC::ResearcherPtr researcher = GRC::Researcher::Get(); @@ -356,16 +356,14 @@ void GRC::CreateMRC(CBlockIndex* pindex, } } - if (!TrySignMRC(pwallet, pindex, mrc)) { + if (!no_sign && !TrySignMRC(pwallet, pindex, mrc)) { throw MRC_error(strprintf("%s: Failed to sign mrc.", __func__)); } - LogPrintf( - "INFO: %s: for %s mrc %s magnitude %d Research %s", - __func__, - mrc.m_mining_id.ToString(), - FormatMoney(nReward), - mrc.m_magnitude, - FormatMoney(mrc.m_research_subsidy)); + LogPrintf("INFO: %s: for %s mrc request created: magnitude %d, research rewards %s, mrc fee %s.", + __func__, + mrc.m_mining_id.ToString(), + mrc.m_magnitude, + FormatMoney(mrc.m_research_subsidy), + FormatMoney(mrc.m_fee)); } - diff --git a/src/gridcoin/mrc.h b/src/gridcoin/mrc.h index 32b34c7662..e462fbea79 100644 --- a/src/gridcoin/mrc.h +++ b/src/gridcoin/mrc.h @@ -348,9 +348,10 @@ class MRCContractHandler : public IContractHandler //! \param nReward: The research reward (out parameter) //! \param fee: The MRC fees to be taken out of the research reward (in/out parameter) //! \param pwallet: The wallet object +//! \param no_sign: If true, don't sign the MRC (trail run). //! \return //! -void CreateMRC(CBlockIndex* pindex, MRC& mrc, CAmount &nReward, CAmount &fee, CWallet* pwallet); +void CreateMRC(CBlockIndex* pindex, MRC& mrc, CAmount &nReward, CAmount &fee, CWallet* pwallet, bool no_sign = false); } // namespace GRC diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index ce37f5c113..aa12dc223b 100755 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -12,6 +12,7 @@ #include "clientmodel.h" #include "walletmodel.h" #include "researcher/researchermodel.h" +#include "mrcmodel.h" #include "voting/votingmodel.h" #include "optionsmodel.h" #include "guiutil.h" @@ -663,11 +664,13 @@ int StartGridcoinQt(int argc, char *argv[], QApplication& app, OptionsModel& opt ClientModel clientModel(&optionsModel); WalletModel walletModel(pwalletMain, &optionsModel); ResearcherModel researcherModel; + MRCModel mrcModel(&walletModel, &clientModel); VotingModel votingModel(clientModel, optionsModel, walletModel); window.setResearcherModel(&researcherModel); window.setClientModel(&clientModel); window.setWalletModel(&walletModel); + window.setMRCModel(&mrcModel); window.setVotingModel(&votingModel); // If -min option passed, start window minimized. diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index b3058dc48c..cc36973c2d 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -830,6 +830,17 @@ void BitcoinGUI::setResearcherModel(ResearcherModel *researcherModel) connect(researcherModel, &ResearcherModel::beaconChanged, this, &BitcoinGUI::updateBeaconIcon); } +void BitcoinGUI::setMRCModel(MRCModel *mrcModel) +{ + m_mrc_model = mrcModel; + + if (!mrcModel) { + return; + } + + overviewPage->setMRCModel(mrcModel); +} + void BitcoinGUI::setVotingModel(VotingModel *votingModel) { this->votingModel = votingModel; diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 48a07e44a7..3682e0a3c9 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -18,6 +18,7 @@ class TransactionTableModel; class ClientModel; class WalletModel; class ResearcherModel; +class MRCModel; class VotingModel; class TransactionView; class OverviewPage; @@ -69,6 +70,11 @@ class BitcoinGUI : public QMainWindow */ void setResearcherModel(ResearcherModel *researcherModel); + /** Set the MRC model. + The MRC model provides the model for MRC payment requests. + */ + void setMRCModel(MRCModel *mrcModel); + /** Set the voting model. The voting model facilitates presentation of and interaction with network polls and votes. */ @@ -91,6 +97,7 @@ class BitcoinGUI : public QMainWindow ClientModel *clientModel; WalletModel *walletModel; ResearcherModel *researcherModel; + MRCModel *m_mrc_model; VotingModel *votingModel; QStackedWidget *centralWidget; @@ -239,6 +246,8 @@ private slots: void themeToggled(); /** Show researcher/beacon configuration dialog */ void researcherClicked(); + /** Show MRC payment request dialog */ + //void mrcPaymentClicked(); /** Show about dialog */ void aboutClicked(); /** Open config file */ diff --git a/src/qt/forms/mrcrequestpage.ui b/src/qt/forms/mrcrequestpage.ui index 7868f543f2..9c31e4c466 100644 --- a/src/qt/forms/mrcrequestpage.ui +++ b/src/qt/forms/mrcrequestpage.ui @@ -1,32 +1,244 @@ MRCRequestPage - + 0 0 - 400 - 300 + 687 + 500 + + + 0 + 0 + + - Form + MRC Requests - - - - 70 - 80 - 58 - 18 - - - - TextLabel - - + + + + + + + MRC Fee @ Head of Queue + + + + + + + + + + MRC Request Pay Limit per Block + + + + + + + Your Projected MRC Request Position in Queue + + + + + + + The maximum number of MRCs that can be paid per block + + + + + + + + + + The highest MRC fee being paid of MRCs in the memory pool + + + + + + + + + + The calculated minimum fee for the MRC. This may not be sufficient to submit the MRC if the queue is already full. In that case, you need to use the MRC fee boost to raise the fee to get your MRC in the queue. + + + + + + + + + + MRC Fee @ Tail of Queue + + + + + + + The lowest MRC fee being paid of MRCs in the memory pool + + + + + + + + + + Number of All MRC Requests in Queue + + + + + + + Your projected or actual position among MRCs in the memory pool ordered by MRC fee in descending order + + + + + + + + + + The number of MRCs in the memory pool + + + + + + + + + + Your MRC Calculated Minimum Fee + + + + + + + MRC Fee @ Pay Limit Position in Queue + + + + + + + The MRC fee being paid by the MRC in the last position within the pay limit in the memory pool + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + MRC Fee Boost + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + + + + + Update + + + + + + + Submit + + + + + + + + + + :/icons/transaction_confirmed + + + false + + + + + + + + + + :/icons/warning + + + false + + + + + + + QDialogButtonBox::Ok + + + + + + - + + + BitcoinAmountField + QSpinBox +
bitcoinamountfield.h
+ 1 +
+
+ + +
diff --git a/src/qt/forms/overviewpage.ui b/src/qt/forms/overviewpage.ui index fd5acfad22..7827a69310 100644 --- a/src/qt/forms/overviewpage.ui +++ b/src/qt/forms/overviewpage.ui @@ -747,6 +747,16 @@ + + + + Open the Manual Reward Claim (MRC) request page + + + + + + diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index 1690a9a2b1..926ab81749 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -2,4 +2,338 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. -#include "qt/mrcmodel.h" +#include "main.h" +#include "wallet/wallet.h" +#include "gridcoin/contract/contract.h" +#include "gridcoin/contract/message.h" +#include "mrcmodel.h" +#include "walletmodel.h" +#include "clientmodel.h" +#include "qt/mrcrequestpage.h" +#include "node/ui_interface.h" + +extern CWallet* pwalletMain; + +namespace { +//! +//! \brief Model callback bound to the \c MRCChanged core signal. +//! +void MRCChanged(MRCModel* model) +{ + LogPrint(BCLog::LogFlags::QT, "GUI: received MRCChanged() core signal"); + + QMetaObject::invokeMethod(model, "mrcChanged", Qt::QueuedConnection); + +} +} // anonymous namespace + +MRCModel::MRCModel(WalletModel* wallet_model, ClientModel *client_model, QObject *parent) + : QObject(parent) + , m_wallet_model(wallet_model) + , m_client_model(client_model) + , m_mrc_status(MRCRequestStatus::NONE) + , m_reward(0) + , m_mrc_min_fee(0) + , m_mrc_fee(0) + , m_mrc_fee_boost(0) + , m_mrc_queue_length(0) + , m_mrc_pos(0) + , m_mrc_queue_tail_fee(std::numeric_limits::max()) + , m_mrc_queue_pay_limit_fee(std::numeric_limits::max()) + , m_mrc_queue_head_fee(0) + , m_mrc_output_limit(0) + , m_mrc_error(false) + , m_mrc_error_desc(std::string{}) + , m_wallet_locked(false) +{ + subscribeToCoreSignals(); + + // Here instead of the core signal for number of blocks changed, we take it from the client model, because + // there is a rate limiter there if the wallet is not in sync. This is necessary, because a new block can be + // received with no MRC contract, and therefore an m_mrc already generated here will then be stale. So + // to force a generation of an updated m_mrc against the new block, we need to connect the mrcChanged slot + // to this as well as the core MRCChanged signal. + connect(m_client_model, &ClientModel::numBlocksChanged, this, &MRCModel::mrcChanged); + + // Detects whether the wallet has been locked or unlocked. + connect(m_wallet_model, &WalletModel::encryptionStatusChanged, this, &MRCModel::walletStatusChanged); + + WalletModel::EncryptionStatus encryption_status = m_wallet_model->getEncryptionStatus(); + + walletStatusChanged(encryption_status); + + refresh(); +} + +MRCModel::~MRCModel() +{ + unsubscribeFromCoreSignals(); +} + +WalletModel* MRCModel::getWalletModel() +{ + return m_wallet_model; +} + +void MRCModel::showMRCDialog() +{ + MRCRequestPage *mrc_request = new MRCRequestPage(nullptr, this); + + mrc_request->show(); +} + +void MRCModel::setMRCFeeBoost(CAmount& fee_boost) +{ + m_mrc_fee_boost = fee_boost; + + refresh(); +} + +int MRCModel::getMRCFeeBoost() +{ + return m_mrc_fee_boost; +} + +int MRCModel::getMRCQueueLength() +{ + return m_mrc_queue_length; +} + +int MRCModel::getMRCPos() +{ + return m_mrc_pos; +} + +CAmount MRCModel::getMRCQueueTailFee() +{ + return m_mrc_queue_tail_fee; +} + +CAmount MRCModel::getMRCQueuePayLimitFee() +{ + return m_mrc_queue_pay_limit_fee; +} + +CAmount MRCModel::getMRCQueueHeadFee() +{ + return m_mrc_queue_head_fee; +} + +CAmount MRCModel::getMRCMinimumSubmitFee() +{ + return m_mrc_min_fee; +} + +int MRCModel::getMRCOutputLimit() +{ + return m_mrc_output_limit; +} + +bool MRCModel::isMRCError(MRCRequestStatus &s, std::string& e) +{ + if (m_mrc_error) { + e = m_mrc_error_desc; + s = m_mrc_status; + return true; + } + + return false; +} + +bool MRCModel::submitMRC(MRCRequestStatus& s, std::string& e) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + if (m_mrc_status != MRCRequestStatus::ELIGIBLE) { + return error("%s: submitMRC called while m_mrc_status, %i, is not ELIGIBLE.", + __func__, + static_cast(m_mrc_status)); + } + + LOCK(pwalletMain->cs_wallet); + + CWalletTx wtx; + + std::tie(wtx, e) = GRC::SendContract(GRC::MakeContract(GRC::ContractAction::ADD, m_mrc)); + if (!e.empty()) { + m_mrc_error = true; + m_mrc_status = MRCRequestStatus::SUBMIT_ERROR; + m_mrc_error_desc = e; + s = m_mrc_status; + return false; + } else { + m_mrc_error = false; + m_mrc_status = MRCRequestStatus::PENDING; + m_mrc_error_desc = std::string{}; + s = m_mrc_status; + } + + return true; +} + +bool MRCModel::isWalletLocked() +{ + return m_wallet_locked; +} + +void MRCModel::subscribeToCoreSignals() +{ + uiInterface.MRCChanged_connect(std::bind(MRCChanged, this)); +} + +void MRCModel::unsubscribeFromCoreSignals() +{ + // Disconnect signals from client (no-op currently) +} + +void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + // This is similar to createmrcrequest + + AssertLockHeld(cs_main); + + CBlockIndex* pindex = mapBlockIndex[hashBestChain]; + + if (!IsV12Enabled(pindex->nHeight)) { + return; + } + + m_mrc_error = false; + m_mrc_error_desc = std::string{}; + m_mrc_min_fee = 0; + m_mrc_fee = 0; + + m_mrc_output_limit = static_cast(GetMRCOutputLimit(pindex->nVersion, false)); + + // Do a first run with m_mrc_min_fee = 0 to compute the mrc min fee required. + try { + GRC::CreateMRC(pindex, m_mrc, m_reward, m_mrc_min_fee, pwalletMain, m_wallet_locked); + } catch (GRC::MRC_error& e) { + m_mrc_error |= true; + m_mrc_status = MRCRequestStatus::CREATE_ERROR; + m_mrc_error_desc = e.what(); + } + + // If the (mininum) fee comes back equal to the reward we are in the zero payout interval (i.e. too soon). + if (m_mrc_min_fee == m_reward) { + m_mrc_error |= true; + m_mrc_status = MRCRequestStatus::ZERO_PAYOUT; + m_mrc_error_desc = "Too soon since your last research rewards payment."; + } + + // If there is a fee boost, add the boost to the fee from the initial run above. + if (m_mrc_fee_boost != 0) { + m_mrc_fee = m_mrc_min_fee + m_mrc_fee_boost; + } + + // If the total mrc free which is the min fee + boost is greater than the reward, then the fee is excessive. + if (m_mrc_fee > m_reward) { + m_mrc_error |= true; + m_mrc_status = MRCRequestStatus::EXCESSIVE_FEE; + m_mrc_error_desc = "The total fee (the minimum fee + fee boost) is greater than the rewards due."; + } + + // Rerun CreateMRC with that new total fee + if (m_mrc_fee_boost != 0) { + try { + GRC::CreateMRC(pindex, m_mrc, m_reward, m_mrc_fee, pwalletMain, m_wallet_locked); + } catch (GRC::MRC_error& e) { + m_mrc_error |= true; + m_mrc_status = MRCRequestStatus::CREATE_ERROR; + m_mrc_error_desc = e.what(); + } + } + + LogPrintf("INFO: %s: After stage 1: m_mrc_error = %u, m_mrc_status = %i, m_mrc_error_desc = %s", + __func__, + m_mrc_error, + static_cast(m_mrc_status), + m_mrc_error_desc); + + // We do the mempool loop here regardless of whether there is an error condition or not. + m_mrc_queue_length = 0; + m_mrc_pos = 0; + m_mrc_queue_tail_fee = std::numeric_limits::max(); + m_mrc_queue_head_fee = 0; + + bool found{false}; + + // This will allow sorting the fees in descending order to help determine the payout limit fee. + std::vector mrc_fee_vector; + + for (const auto& [_, tx] : mempool.mapTx) { + if (!tx.GetContracts().empty()) { + // By protocol the MRC contract MUST be the only one in the transaction. + const GRC::Contract& contract = tx.GetContracts()[0]; + + if (contract.m_type == GRC::ContractType::MRC) { + GRC::MRC mempool_mrc = contract.CopyPayloadAs(); + + found |= m_mrc.m_mining_id == mempool_mrc.m_mining_id; + + if (!found && mempool_mrc.m_fee >= m_mrc.m_fee) ++m_mrc_pos; + m_mrc_queue_head_fee = std::max(m_mrc_queue_head_fee, mempool_mrc.m_fee); + m_mrc_queue_tail_fee = std::min(m_mrc_queue_tail_fee, mempool_mrc.m_fee); + + mrc_fee_vector.push_back(mempool_mrc.m_fee); + + ++m_mrc_queue_length; + } // match to mrc contract type + } // contract present in transaction? + } // mempool transaction iterator + + // The tail fee converges from the max numeric limit of CAmount; however, when the above loop is done + // it cannot end up with a number higher than the head fee. This can happen if there are no MRC transactions + // in the loop. + m_mrc_queue_tail_fee = std::min(m_mrc_queue_head_fee, m_mrc_queue_tail_fee); + + // Sort the fees in descending order for the pay limit calculation. + std::sort(mrc_fee_vector.begin(), mrc_fee_vector.end(), std::greater()); + + // Here we select the minimum of the mrc_fee_vector.size() - 1 in the case where the sorted vector does not reach the + // m_mrc_output_limit - 1, or the m_mrc_output_limit - 1 if the sorted vector indicates the queue is (over)full, + // i.e. the number of MRC's in the queue exceeds the m_mrc_output_limit for paying in a block. + int pay_limit_fee_pos = std::min(mrc_fee_vector.size(), m_mrc_output_limit) - 1; + + if (pay_limit_fee_pos >= 0) { + m_mrc_queue_pay_limit_fee = mrc_fee_vector[pay_limit_fee_pos]; + } + + m_mrc_queue_pay_limit_fee = std::min(m_mrc_queue_head_fee, m_mrc_queue_pay_limit_fee); + + LogPrintf("INFO: %s: Post mempool loop: m_mrc_pos = %i, m_mrc_output_limit - 1 = %i", + __func__, + m_mrc_pos, + m_mrc_output_limit - 1); + + if (found) { + m_mrc_error |= true; + m_mrc_status = MRCRequestStatus::PENDING; + m_mrc_error_desc = "You have a pending MRC request."; + } else if (m_mrc_pos > m_mrc_output_limit - 1) { + m_mrc_error |= true; + m_mrc_status = MRCRequestStatus::QUEUE_FULL; + m_mrc_error_desc = "The MRC queue is full. You can try inputting a provided fee high enough to put your MRC " + "request in the queue and displace another MRC request."; + } else if (m_wallet_locked) { + m_mrc_error |= true; + m_mrc_status = MRCRequestStatus::WALLET_LOCKED; + m_mrc_error_desc = "The wallet is locked."; + } else if (!m_mrc_error) { + m_mrc_status = MRCRequestStatus::ELIGIBLE; + m_mrc_error_desc = std::string{}; + } + + LogPrintf("INFO: %s: After stage 2: m_mrc_error = %u, m_mrc_status = %i, m_mrc_error_desc = %s", + __func__, + m_mrc_error, + static_cast(m_mrc_status), + m_mrc_error_desc); +} + +void MRCModel::walletStatusChanged(int encryption_status) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + m_wallet_locked = (encryption_status == static_cast(WalletModel::EncryptionStatus::Locked)); + + refresh(); + + emit walletStatusChangedSignal(); +} diff --git a/src/qt/mrcmodel.h b/src/qt/mrcmodel.h index b191acdb12..f7539e2254 100644 --- a/src/qt/mrcmodel.h +++ b/src/qt/mrcmodel.h @@ -6,13 +6,23 @@ #define GRIDCOIN_QT_MRCMODEL_H #include +#include "amount.h" +#include "gridcoin/mrc.h" + +class WalletModel; +class ClientModel; enum class MRCRequestStatus { + NONE, + CREATE_ERROR, + ELIGIBLE, PENDING, - REJECTED_QUEUE_FULL, - STALE, - DELETED + QUEUE_FULL, + ZERO_PAYOUT, + EXCESSIVE_FEE, + WALLET_LOCKED, + SUBMIT_ERROR }; class MRCModel : public QObject @@ -20,10 +30,56 @@ class MRCModel : public QObject Q_OBJECT public: - MRCModel(); + explicit MRCModel(WalletModel* wallet_model, ClientModel* client_model, QObject* parent = nullptr); ~MRCModel(); -}; + WalletModel* getWalletModel(); + + void showMRCDialog(); + void setMRCFeeBoost(CAmount& fee); + int getMRCFeeBoost(); + int getMRCQueueLength(); + int getMRCPos(); + CAmount getMRCQueueTailFee(); + CAmount getMRCQueuePayLimitFee(); + CAmount getMRCQueueHeadFee(); + CAmount getMRCMinimumSubmitFee(); + int getMRCOutputLimit(); + bool isMRCError(MRCRequestStatus& s, std::string& e); + bool submitMRC(MRCRequestStatus& s, std::string& e); + bool isWalletLocked(); + +private: + void subscribeToCoreSignals(); + void unsubscribeFromCoreSignals(); + + WalletModel* m_wallet_model; + ClientModel* m_client_model; + GRC::MRC m_mrc; + MRCRequestStatus m_mrc_status; + CAmount m_reward; + CAmount m_mrc_min_fee; + CAmount m_mrc_fee; + CAmount m_mrc_fee_boost; + int m_mrc_queue_length; + int m_mrc_pos; + CAmount m_mrc_queue_tail_fee; + CAmount m_mrc_queue_pay_limit_fee; + CAmount m_mrc_queue_head_fee; + int m_mrc_output_limit; + + bool m_mrc_error; + std::string m_mrc_error_desc; + bool m_wallet_locked; + +signals: + void mrcChanged(); + void walletStatusChangedSignal(); + +public slots: + void refresh(); + void walletStatusChanged(int encryption_status); +}; #endif // GRIDCOIN_QT_MRCMODEL_H diff --git a/src/qt/mrcrequestpage.cpp b/src/qt/mrcrequestpage.cpp index 7d5809b305..68ac67455c 100644 --- a/src/qt/mrcrequestpage.cpp +++ b/src/qt/mrcrequestpage.cpp @@ -2,22 +2,171 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. +#include "main.h" +#include "qspinbox.h" +#include "sync.h" #include "mrcrequestpage.h" #include "ui_mrcrequestpage.h" #include "mrcmodel.h" +#include "walletmodel.h" +#include "optionsmodel.h" #include "qt/decoration.h" +#include "bitcoinunits.h" MRCRequestPage::MRCRequestPage( QWidget *parent, MRCModel* mrc_model) - : QWidget(parent) + : QDialog(parent) , ui(new Ui::MRCRequestPage) , m_mrc_model(mrc_model) { + if (m_mrc_model) { + m_wallet_model = m_mrc_model->getWalletModel(); + } + ui->setupUi(this); + + m_orig_geometry = this->geometry(); + m_gridLayout_orig_geometry = ui->gridLayout->geometry(); + + m_scaled_size = GRC::ScaleSize(this, width(), height()); + + resize(m_scaled_size); + + ui->SubmittedIconLabel->setPixmap(GRC::ScaleIcon(this, ":/icons/round_green_check", 32)); + ui->ErrorIconLabel->setPixmap(GRC::ScaleIcon(this, ":/icons/warning", 32)); + + connect(m_mrc_model, &MRCModel::mrcChanged, this, &MRCRequestPage::updateMRCStatus); + connect(m_mrc_model, &MRCModel::walletStatusChangedSignal, this, &MRCRequestPage::updateMRCStatus); + + connect(ui->mrcRequestButtonBox, &QDialogButtonBox::clicked, this, &MRCRequestPage::buttonBoxClicked); + connect(ui->mrcUpdateButton, &QAbstractButton::clicked, this, &MRCRequestPage::updateMRCStatus); + connect(ui->mrcFeeBoostSpinBox, &BitcoinAmountField::textChanged, this, &MRCRequestPage::setMRCProvidedFee); + connect(ui->mrcSubmitButton, &QAbstractButton::clicked, this, &MRCRequestPage::submitMRC); + + ui->mrcFeeBoostSpinBox->setValue(m_mrc_model->getMRCFeeBoost()); + + ui->ErrorIconLabel->hide(); + ui->SubmittedIconLabel->hide(); + + updateMRCStatus(); } MRCRequestPage::~MRCRequestPage() { delete ui; } + +void MRCRequestPage::updateMRCModel() +{ + if (!m_mrc_model) return; + + m_mrc_model->refresh(); +} + +void MRCRequestPage::setMRCProvidedFee() +{ + if (!m_mrc_model) return; + + CAmount fee_boost = ui->mrcFeeBoostSpinBox->value(); + + m_mrc_model->setMRCFeeBoost(fee_boost); + + updateMRCStatus(); +} + +void MRCRequestPage::buttonBoxClicked(QAbstractButton* button) +{ + if (ui->mrcRequestButtonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { + done(QDialog::Accepted); + } else { + done(QDialog::Rejected); + } +} + +void MRCRequestPage::updateMRCStatus() +{ + if (!m_mrc_model) return; + + updateMRCModel(); + + int display_unit = BitcoinUnits::BTC; + if (m_wallet_model && m_wallet_model->getOptionsModel()) { + display_unit = m_wallet_model->getOptionsModel()->getDisplayUnit(); + } + + ui->mrcQueueLimit->setText(QString::number(m_mrc_model->getMRCOutputLimit())); + ui->numMRCInQueue->setText(QString::number(m_mrc_model->getMRCQueueLength())); + ui->mrcQueueHeadFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCQueueHeadFee())); + ui->mrcQueuePayLimitFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCQueuePayLimitFee())); + ui->mrcQueueTailFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCQueueTailFee())); + + MRCRequestStatus s; + std::string e; + QString message; + + // Note MRCError treats the PENDING status as an error from a handling point of view, because it blocks the + // submission of a new MRC while there is one already in progress. + if (m_mrc_model->isMRCError(s, e)) { + message = QString::fromStdString(e) + " MRC request cannot be submitted."; + + ui->mrcSubmitButton->setEnabled(false); + ui->mrcSubmitButton->setToolTip(message); + + if (s == MRCRequestStatus::PENDING) { + ui->mrcQueuePosition->setText(QString::number(m_mrc_model->getMRCPos() + 1)); + ui->mrcQueuePositionLabel->setText("Your Submitted MRC Request Position in Queue"); + + ui->mrcMinimumSubmitFee->setText("N/A"); + + ui->SubmittedIconLabel->show(); + ui->ErrorIconLabel->hide(); + ui->ErrorIconLabel->setToolTip(""); + } else if (s == MRCRequestStatus::QUEUE_FULL) { + ui->mrcQueuePosition->setText("N/A"); + ui->mrcQueuePositionLabel->setText("Your Projected MRC Request Position in Queue"); + + ui->mrcMinimumSubmitFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCMinimumSubmitFee())); + + ui->ErrorIconLabel->show(); + ui->ErrorIconLabel->setToolTip(message); + } else { + ui->mrcQueuePosition->setText("N/A"); + ui->mrcQueuePositionLabel->setText("Your Projected MRC Request Position in Queue"); + + ui->mrcMinimumSubmitFee->setText("N/A"); + + ui->SubmittedIconLabel->hide(); + ui->ErrorIconLabel->show(); + ui->ErrorIconLabel->setToolTip(message); + } + } else { + message = "Submits the MRC request."; + + ui->mrcQueuePosition->setText(QString::number(m_mrc_model->getMRCPos() + 1)); + ui->mrcQueuePositionLabel->setText("Your Projected MRC Request Position in Queue"); + + ui->mrcMinimumSubmitFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCMinimumSubmitFee())); + + ui->mrcSubmitButton->setEnabled(true); + ui->mrcSubmitButton->setToolTip(message); + ui->SubmittedIconLabel->hide(); + ui->ErrorIconLabel->hide(); + ui->ErrorIconLabel->setToolTip(""); + } +} + +void MRCRequestPage::submitMRC() +{ + MRCRequestStatus s; + std::string e; + QString message; + + if (!m_mrc_model) return; + + if (!m_mrc_model->submitMRC(s, e)) { + message = QString::fromStdString(e) + " MRC request cannot be submitted."; + + ui->mrcSubmitButton->setToolTip(message); + } +} diff --git a/src/qt/mrcrequestpage.h b/src/qt/mrcrequestpage.h index d76a341663..ec941cdc21 100644 --- a/src/qt/mrcrequestpage.h +++ b/src/qt/mrcrequestpage.h @@ -5,15 +5,19 @@ #ifndef GRIDCOIN_QT_MRCREQUESTPAGE_H #define GRIDCOIN_QT_MRCREQUESTPAGE_H -#include +#include +#include + +#include "bitcoinamountfield.h" class MRCModel; +class WalletModel; namespace Ui { class MRCRequestPage; } -class MRCRequestPage : public QWidget +class MRCRequestPage : public QDialog { Q_OBJECT @@ -24,7 +28,19 @@ class MRCRequestPage : public QWidget private: Ui::MRCRequestPage *ui; MRCModel *m_mrc_model; + WalletModel *m_wallet_model; + + QRect m_orig_geometry; + QRect m_gridLayout_orig_geometry; + QSize m_scaled_size; + + void updateMRCModel(); +private slots: + void buttonBoxClicked(QAbstractButton* button); + void updateMRCStatus(); + void setMRCProvidedFee(); + void submitMRC(); }; #endif // GRIDCOIN_QT_MRCREQUESTPAGE_H diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 6fedc61d75..d1b7e6df98 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -9,6 +9,7 @@ #include "main.h" #endif #include "researcher/researchermodel.h" +#include "mrcmodel.h" #include "walletmodel.h" #include "bitcoinunits.h" #include "optionsmodel.h" @@ -120,6 +121,7 @@ OverviewPage::OverviewPage(QWidget *parent) : QWidget(parent), ui(new Ui::OverviewPage), researcherModel(nullptr), + m_mrc_model(nullptr), walletModel(nullptr), currentBalance(-1), currentStake(0), @@ -365,6 +367,17 @@ void OverviewPage::setResearcherModel(ResearcherModel *researcherModel) connect(ui->researcherConfigToolButton, &QAbstractButton::clicked, this, &OverviewPage::onBeaconButtonClicked); } +void OverviewPage::setMRCModel(MRCModel *mrcModel) +{ + m_mrc_model = mrcModel; + + if (!mrcModel) { + return; + } + + connect(ui->mrcRequestToolButton, &QAbstractButton::clicked, this, &OverviewPage::onMRCRequestClicked); +} + void OverviewPage::setWalletModel(WalletModel *model) { this->walletModel = model; @@ -503,6 +516,15 @@ void OverviewPage::onBeaconButtonClicked() researcherModel->showWizard(walletModel); } +void OverviewPage::onMRCRequestClicked() +{ + if (!m_mrc_model) { + return; + } + + m_mrc_model->showMRCDialog(); +} + void OverviewPage::showOutOfSyncWarning(bool fShow) { ui->walletStatusLabel->setVisible(fShow); diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index 135279b442..48bfd28766 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -12,6 +12,7 @@ namespace Ui { class OverviewPage; } class ResearcherModel; +class MRCModel; class WalletModel; class TxViewDelegate; class TransactionFilterProxy; @@ -26,6 +27,7 @@ class OverviewPage : public QWidget ~OverviewPage(); void setResearcherModel(ResearcherModel *model); + void setMRCModel(MRCModel *model); void setWalletModel(WalletModel *model); void showOutOfSyncWarning(bool fShow); @@ -50,6 +52,7 @@ public slots: Ui::OverviewPage *ui; ResearcherModel *researcherModel; + MRCModel *m_mrc_model; WalletModel *walletModel; qint64 currentBalance; qint64 currentStake; @@ -69,6 +72,7 @@ private slots: void updatePendingAccrual(); void updateResearcherAlert(); void onBeaconButtonClicked(); + void onMRCRequestClicked(); void handleTransactionClicked(const QModelIndex &index); void handlePollLabelClicked(); }; diff --git a/src/qt/res/stylesheets/dark_stylesheet.qss b/src/qt/res/stylesheets/dark_stylesheet.qss index 5b27b662d6..76a7678727 100644 --- a/src/qt/res/stylesheets/dark_stylesheet.qss +++ b/src/qt/res/stylesheets/dark_stylesheet.qss @@ -798,6 +798,10 @@ NoResult #titleLabel { image: url(:/icons/dark_settings_action_needed); } +#mrcRequestToolButton { + image: url(:/icons/tx_contract_mrc) +} + #listTransactions { background: none; border: none; diff --git a/src/qt/res/stylesheets/light_stylesheet.qss b/src/qt/res/stylesheets/light_stylesheet.qss index ea7666df45..3cfe1845a5 100644 --- a/src/qt/res/stylesheets/light_stylesheet.qss +++ b/src/qt/res/stylesheets/light_stylesheet.qss @@ -778,6 +778,10 @@ NoResult #titleLabel { image: url(:/icons/light_settings_action_needed); } +#mrcRequestToolButton { + image: url(:/icons/tx_contract_mrc) +} + #listTransactions { background: none; border: none; diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index f8c8e167a9..e4c9c2c82b 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -79,6 +79,8 @@ void WalletModel::updateStatus() { EncryptionStatus newEncryptionStatus = getEncryptionStatus(); + LogPrintf("INFO: %s called.", __func__); + if(cachedEncryptionStatus != newEncryptionStatus) emit encryptionStatusChanged(newEncryptionStatus); } diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index b3e729eaf0..357cb5ccd3 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2542,7 +2542,7 @@ UniValue createmrcrequest(const UniValue& params, const bool fHelp) { int pos{0}; bool found{false}; - CAmount tail_fee{0}; + CAmount tail_fee{std::numeric_limits::max()}; CAmount head_fee{0}; for (const auto& [_, tx] : mempool.mapTx) { for (const auto& contract: tx.GetContracts()) { @@ -2551,12 +2551,17 @@ UniValue createmrcrequest(const UniValue& params, const bool fHelp) { found |= mrc.m_mining_id == mempool_mrc.m_mining_id; pos += mempool_mrc.m_fee >= mrc.m_fee; - tail_fee = std::min(tail_fee, mempool_mrc.m_fee); head_fee = std::max(head_fee, mempool_mrc.m_fee); + tail_fee = std::min(tail_fee, mempool_mrc.m_fee); } // match to mrc contract type } // contract iterator } // mempool transaction iterator + // The tail fee converges from the max numeric limit of CAmount; however, when the above loop is done + // it cannot end up with a number higher than the head fee. This can happen if there are no MRC transactions + // in the loop. + tail_fee = std::min(head_fee, tail_fee); + int limit = static_cast(GetMRCOutputLimit(pindex->nVersion, false)); if (!dry_run && !force) { From 8d927d1d2c948f7e89a3dbad9610123494193e67 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Fri, 13 May 2022 15:10:46 -0400 Subject: [PATCH 08/21] Redo memory pool loop in MRCModel::refresh() and createmrcrewards --- src/qt/mrcmodel.cpp | 41 ++++++++++++++------------ src/qt/mrcrequestpage.cpp | 21 ++++++++++++-- src/rpc/blockchain.cpp | 61 ++++++++++++++++++++++++++++++++------- 3 files changed, 92 insertions(+), 31 deletions(-) diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index 926ab81749..794ed53713 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -256,8 +256,10 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) bool found{false}; - // This will allow sorting the fees in descending order to help determine the payout limit fee. - std::vector mrc_fee_vector; + // This sorts the MRCs in descending order of MRC fees to allow determination of the payout limit fee. + + // ---------- mrc fee --- mrc ------ descending order + std::multimap> mrc_multimap; for (const auto& [_, tx] : mempool.mapTx) { if (!tx.GetContracts().empty()) { @@ -267,34 +269,37 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) if (contract.m_type == GRC::ContractType::MRC) { GRC::MRC mempool_mrc = contract.CopyPayloadAs(); - found |= m_mrc.m_mining_id == mempool_mrc.m_mining_id; + mrc_multimap.insert(std::make_pair(mempool_mrc.m_fee, mempool_mrc)); + } // match to mrc contract type + } // contract present in transaction? + } - if (!found && mempool_mrc.m_fee >= m_mrc.m_fee) ++m_mrc_pos; - m_mrc_queue_head_fee = std::max(m_mrc_queue_head_fee, mempool_mrc.m_fee); - m_mrc_queue_tail_fee = std::min(m_mrc_queue_tail_fee, mempool_mrc.m_fee); + for (const auto& [_, mempool_mrc] : mrc_multimap) { + found |= m_mrc.m_mining_id == mempool_mrc.m_mining_id; - mrc_fee_vector.push_back(mempool_mrc.m_fee); + if (!found && mempool_mrc.m_fee >= m_mrc.m_fee) ++m_mrc_pos; + m_mrc_queue_head_fee = std::max(m_mrc_queue_head_fee, mempool_mrc.m_fee); + m_mrc_queue_tail_fee = std::min(m_mrc_queue_tail_fee, mempool_mrc.m_fee); - ++m_mrc_queue_length; - } // match to mrc contract type - } // contract present in transaction? - } // mempool transaction iterator + ++m_mrc_queue_length; + } // The tail fee converges from the max numeric limit of CAmount; however, when the above loop is done // it cannot end up with a number higher than the head fee. This can happen if there are no MRC transactions // in the loop. m_mrc_queue_tail_fee = std::min(m_mrc_queue_head_fee, m_mrc_queue_tail_fee); - // Sort the fees in descending order for the pay limit calculation. - std::sort(mrc_fee_vector.begin(), mrc_fee_vector.end(), std::greater()); - - // Here we select the minimum of the mrc_fee_vector.size() - 1 in the case where the sorted vector does not reach the - // m_mrc_output_limit - 1, or the m_mrc_output_limit - 1 if the sorted vector indicates the queue is (over)full, + // Here we select the minimum of the mrc_multimap.size() - 1 in the case where the multimap does not reach the + // m_mrc_output_limit - 1, or the m_mrc_output_limit - 1 if the multimap indicates the queue is (over)full, // i.e. the number of MRC's in the queue exceeds the m_mrc_output_limit for paying in a block. - int pay_limit_fee_pos = std::min(mrc_fee_vector.size(), m_mrc_output_limit) - 1; + int pay_limit_fee_pos = std::min(mrc_multimap.size(), m_mrc_output_limit) - 1; if (pay_limit_fee_pos >= 0) { - m_mrc_queue_pay_limit_fee = mrc_fee_vector[pay_limit_fee_pos]; + std::multimap>::iterator iter = mrc_multimap.begin(); + + std::advance(iter, pay_limit_fee_pos); + + m_mrc_queue_pay_limit_fee = iter->first; } m_mrc_queue_pay_limit_fee = std::min(m_mrc_queue_head_fee, m_mrc_queue_pay_limit_fee); diff --git a/src/qt/mrcrequestpage.cpp b/src/qt/mrcrequestpage.cpp index 68ac67455c..501ea3db7a 100644 --- a/src/qt/mrcrequestpage.cpp +++ b/src/qt/mrcrequestpage.cpp @@ -97,9 +97,24 @@ void MRCRequestPage::updateMRCStatus() ui->mrcQueueLimit->setText(QString::number(m_mrc_model->getMRCOutputLimit())); ui->numMRCInQueue->setText(QString::number(m_mrc_model->getMRCQueueLength())); - ui->mrcQueueHeadFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCQueueHeadFee())); - ui->mrcQueuePayLimitFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCQueuePayLimitFee())); - ui->mrcQueueTailFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCQueueTailFee())); + + if (m_mrc_model->getMRCQueueLength() > 0) { + ui->mrcQueueHeadFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCQueueHeadFee())); + } else { + ui->mrcQueueHeadFee->setText("N/A"); + } + + if (m_mrc_model->getMRCQueueLength() >= m_mrc_model->getMRCOutputLimit()) { + ui->mrcQueuePayLimitFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCQueuePayLimitFee())); + } else { + ui->mrcQueuePayLimitFee->setText("N/A"); + } + + if (m_mrc_model->getMRCQueueLength() > 0) { + ui->mrcQueueTailFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCQueueTailFee())); + } else { + ui->mrcQueueTailFee->setText("N/A"); + } MRCRequestStatus s; std::string e; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 357cb5ccd3..2fa6f479d3 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2543,26 +2543,59 @@ UniValue createmrcrequest(const UniValue& params, const bool fHelp) { int pos{0}; bool found{false}; CAmount tail_fee{std::numeric_limits::max()}; + CAmount pay_limit_fee{std::numeric_limits::max()}; CAmount head_fee{0}; + int queue_length{0}; + int limit = static_cast(GetMRCOutputLimit(pindex->nVersion, false)); + + // This sorts the MRCs in descending order of MRC fees to allow determination of the payout limit fee. + + // ---------- mrc fee --- mrc ------ descending order + std::multimap> mrc_multimap; + for (const auto& [_, tx] : mempool.mapTx) { - for (const auto& contract: tx.GetContracts()) { + if (!tx.GetContracts().empty()) { + // By protocol the MRC contract MUST be the only one in the transaction. + const GRC::Contract& contract = tx.GetContracts()[0]; + if (contract.m_type == GRC::ContractType::MRC) { GRC::MRC mempool_mrc = contract.CopyPayloadAs(); - found |= mrc.m_mining_id == mempool_mrc.m_mining_id; - pos += mempool_mrc.m_fee >= mrc.m_fee; - head_fee = std::max(head_fee, mempool_mrc.m_fee); - tail_fee = std::min(tail_fee, mempool_mrc.m_fee); + mrc_multimap.insert(std::make_pair(mempool_mrc.m_fee, mempool_mrc)); } // match to mrc contract type - } // contract iterator - } // mempool transaction iterator + } // contract present in transaction? + } + + for (const auto& [_, mempool_mrc] : mrc_multimap) { + found |= mrc.m_mining_id == mempool_mrc.m_mining_id; + + if (!found && mempool_mrc.m_fee >= mrc.m_fee) ++pos; + head_fee = std::max(head_fee, mempool_mrc.m_fee); + tail_fee = std::min(tail_fee, mempool_mrc.m_fee); + + ++queue_length; + } // The tail fee converges from the max numeric limit of CAmount; however, when the above loop is done // it cannot end up with a number higher than the head fee. This can happen if there are no MRC transactions // in the loop. tail_fee = std::min(head_fee, tail_fee); - int limit = static_cast(GetMRCOutputLimit(pindex->nVersion, false)); + // Here we select the minimum of the mrc_multimap.size() - 1 in the case where the multimap does not reach the + // m_mrc_output_limit - 1, or the m_mrc_output_limit - 1 if the multimap indicates the queue is (over)full, + // i.e. the number of MRC's in the queue exceeds the m_mrc_output_limit for paying in a block. + int pay_limit_fee_pos = std::min(mrc_multimap.size(), limit) - 1; + + if (pay_limit_fee_pos >= 0) { + std::multimap>::iterator iter = mrc_multimap.begin(); + + std::advance(iter, pay_limit_fee_pos); + + pay_limit_fee = iter->first; + } + + pay_limit_fee = std::min(head_fee, pay_limit_fee); + if (!dry_run && !force) { if (found) { @@ -2576,11 +2609,19 @@ UniValue createmrcrequest(const UniValue& params, const bool fHelp) { resp.pushKV("outstanding_request", found); // Sadly, humans start indexing by 1. - resp.pushKV("pos", pos + 1); resp.pushKV("limit", limit); - resp.pushKV("tail_fee", ValueFromAmount(tail_fee)); + resp.pushKV("mrcs_in_queue", queue_length); resp.pushKV("head_fee", ValueFromAmount(head_fee)); + if (queue_length >= limit) { + resp.pushKV("pay_limit_position_fee", ValueFromAmount(pay_limit_fee)); + } else { + resp.pushKV("pay_limit_position_fee", "N/A"); + } + + resp.pushKV("tail_fee", ValueFromAmount(tail_fee)); + resp.pushKV("pos", pos + 1); + if (!dry_run) { LOCK(pwalletMain->cs_wallet); From 8b4d6c556dcbb12febd08555363ac182d15c3303 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Fri, 13 May 2022 18:52:59 -0400 Subject: [PATCH 09/21] Add ModelStatus and string translations Also implement cover wait page for MRC screen when MRC submission is impossible. --- src/qt/forms/mrcrequestpage.ui | 474 +++++++++++++++++++-------------- src/qt/mrcmodel.cpp | 38 ++- src/qt/mrcmodel.h | 11 + src/qt/mrcrequestpage.cpp | 56 +++- src/qt/mrcrequestpage.h | 3 +- src/rpc/blockchain.cpp | 1 - 6 files changed, 354 insertions(+), 229 deletions(-) diff --git a/src/qt/forms/mrcrequestpage.ui b/src/qt/forms/mrcrequestpage.ui index 9c31e4c466..8aa6b30a9b 100644 --- a/src/qt/forms/mrcrequestpage.ui +++ b/src/qt/forms/mrcrequestpage.ui @@ -6,8 +6,8 @@ 0 0 - 687 - 500 + 694 + 580 @@ -19,213 +19,275 @@ MRC Requests - + - - - - - MRC Fee @ Head of Queue - - - - - - - - - - MRC Request Pay Limit per Block - - - - - - - Your Projected MRC Request Position in Queue - - - - - - - The maximum number of MRCs that can be paid per block - - - - - - - - - - The highest MRC fee being paid of MRCs in the memory pool - - - - - - - - - - The calculated minimum fee for the MRC. This may not be sufficient to submit the MRC if the queue is already full. In that case, you need to use the MRC fee boost to raise the fee to get your MRC in the queue. - - - - - - - - - - MRC Fee @ Tail of Queue - - - - - - - The lowest MRC fee being paid of MRCs in the memory pool - - - - - - - - - - Number of All MRC Requests in Queue - - - - - - - Your projected or actual position among MRCs in the memory pool ordered by MRC fee in descending order - - - - - - - - - - The number of MRCs in the memory pool - - - - - - - - - - Your MRC Calculated Minimum Fee - - - - - - - MRC Fee @ Pay Limit Position in Queue - - - - - - - The MRC fee being paid by the MRC in the last position within the pay limit in the memory pool - - - - - - - + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Please wait. + + + Qt::AlignCenter + + + + + + + + + + :/icons/no_result + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - MRC Fee Boost - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - - - - - Update - - - - - - - Submit - - - - - - - - - - :/icons/transaction_confirmed - - - false - - - - - - - - - - :/icons/warning - - - false - - - - - - - QDialogButtonBox::Ok - - - - + + + + + + + + MRC Fee @ Pay Limit Position in Queue + + + + + + + MRC Fee @ Tail of Queue + + + + + + + Your projected or actual position among MRCs in the memory pool ordered by MRC fee in descending order + + + + + + + + + + Number of All MRC Requests in Queue + + + + + + + The number of MRCs in the memory pool + + + + + + + + + + Your Projected MRC Request Position in Queue + + + + + + + The MRC fee being paid by the MRC in the last position within the pay limit in the memory pool + + + + + + + + + + + + + MRC Request Pay Limit per Block + + + + + + + Your MRC Calculated Minimum Fee + + + + + + + The calculated minimum fee for the MRC. This may not be sufficient to submit the MRC if the queue is already full. In that case, you need to use the MRC fee boost to raise the fee to get your MRC in the queue. + + + + + + + + + + The lowest MRC fee being paid of MRCs in the memory pool + + + + + + + + + + The maximum number of MRCs that can be paid per block + + + + + + + + + + The highest MRC fee being paid of MRCs in the memory pool + + + + + + + + + + MRC Fee @ Head of Queue + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + MRC Fee Boost + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + + + + + Update + + + + + + + Submit + + + + + + + + + + :/icons/transaction_confirmed + + + false + + + + + + + + + + :/icons/warning + + + false + + + + + + + QDialogButtonBox::Ok + + + + + + + diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index 794ed53713..55ea000a64 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -45,6 +45,7 @@ MRCModel::MRCModel(WalletModel* wallet_model, ClientModel *client_model, QObject , m_mrc_error(false) , m_mrc_error_desc(std::string{}) , m_wallet_locked(false) + , m_init_block_height(0) { subscribeToCoreSignals(); @@ -129,6 +130,19 @@ int MRCModel::getMRCOutputLimit() return m_mrc_output_limit; } +MRCModel::ModelStatus MRCModel::getMRCModelStatus() +{ + if (!IsV12Enabled(m_block_height)) { + return MRCModel::ModelStatus::INVALID_BLOCK_VERSION; + } else if (OutOfSyncByAge()) { + return MRCModel::ModelStatus::OUT_OF_SYNC; + } else if (m_block_height <= m_init_block_height) { + return MRCModel::ModelStatus::NO_BLOCK_UPDATE_FROM_INIT; + } + + return MRCModel::ModelStatus::VALID; +} + bool MRCModel::isMRCError(MRCRequestStatus &s, std::string& e) { if (m_mrc_error) { @@ -190,9 +204,15 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) AssertLockHeld(cs_main); - CBlockIndex* pindex = mapBlockIndex[hashBestChain]; + // Record initial block height during init run. + if (!m_init_block_height) { + m_init_block_height = pindexBest->nHeight; + } + + // Store this locally so we don't have to get this from the client model, which takes another lock on cs_main. + m_block_height = pindexBest->nHeight; - if (!IsV12Enabled(pindex->nHeight)) { + if (!IsV12Enabled(pindexBest->nHeight)) { return; } @@ -201,11 +221,11 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) m_mrc_min_fee = 0; m_mrc_fee = 0; - m_mrc_output_limit = static_cast(GetMRCOutputLimit(pindex->nVersion, false)); + m_mrc_output_limit = static_cast(GetMRCOutputLimit(pindexBest->nVersion, false)); // Do a first run with m_mrc_min_fee = 0 to compute the mrc min fee required. try { - GRC::CreateMRC(pindex, m_mrc, m_reward, m_mrc_min_fee, pwalletMain, m_wallet_locked); + GRC::CreateMRC(pindexBest, m_mrc, m_reward, m_mrc_min_fee, pwalletMain, m_wallet_locked); } catch (GRC::MRC_error& e) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::CREATE_ERROR; @@ -234,7 +254,7 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) // Rerun CreateMRC with that new total fee if (m_mrc_fee_boost != 0) { try { - GRC::CreateMRC(pindex, m_mrc, m_reward, m_mrc_fee, pwalletMain, m_wallet_locked); + GRC::CreateMRC(pindexBest, m_mrc, m_reward, m_mrc_fee, pwalletMain, m_wallet_locked); } catch (GRC::MRC_error& e) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::CREATE_ERROR; @@ -312,16 +332,16 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) if (found) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::PENDING; - m_mrc_error_desc = "You have a pending MRC request."; + m_mrc_error_desc = tr("You have a pending MRC request.").toStdString(); } else if (m_mrc_pos > m_mrc_output_limit - 1) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::QUEUE_FULL; - m_mrc_error_desc = "The MRC queue is full. You can try inputting a provided fee high enough to put your MRC " - "request in the queue and displace another MRC request."; + m_mrc_error_desc = tr("The MRC queue is full. You can try inputting a provided fee high enough to put your MRC " + "request in the queue and displace another MRC request.").toStdString(); } else if (m_wallet_locked) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::WALLET_LOCKED; - m_mrc_error_desc = "The wallet is locked."; + m_mrc_error_desc = tr("The wallet is locked.").toStdString(); } else if (!m_mrc_error) { m_mrc_status = MRCRequestStatus::ELIGIBLE; m_mrc_error_desc = std::string{}; diff --git a/src/qt/mrcmodel.h b/src/qt/mrcmodel.h index f7539e2254..1e755a4d86 100644 --- a/src/qt/mrcmodel.h +++ b/src/qt/mrcmodel.h @@ -33,6 +33,13 @@ class MRCModel : public QObject explicit MRCModel(WalletModel* wallet_model, ClientModel* client_model, QObject* parent = nullptr); ~MRCModel(); + enum ModelStatus { + VALID, + INVALID_BLOCK_VERSION, + OUT_OF_SYNC, + NO_BLOCK_UPDATE_FROM_INIT + }; + WalletModel* getWalletModel(); void showMRCDialog(); @@ -45,6 +52,7 @@ class MRCModel : public QObject CAmount getMRCQueueHeadFee(); CAmount getMRCMinimumSubmitFee(); int getMRCOutputLimit(); + ModelStatus getMRCModelStatus(); bool isMRCError(MRCRequestStatus& s, std::string& e); bool submitMRC(MRCRequestStatus& s, std::string& e); bool isWalletLocked(); @@ -73,6 +81,9 @@ class MRCModel : public QObject std::string m_mrc_error_desc; bool m_wallet_locked; + int m_init_block_height; + int m_block_height; + signals: void mrcChanged(); void walletStatusChangedSignal(); diff --git a/src/qt/mrcrequestpage.cpp b/src/qt/mrcrequestpage.cpp index 501ea3db7a..76ece9c439 100644 --- a/src/qt/mrcrequestpage.cpp +++ b/src/qt/mrcrequestpage.cpp @@ -7,7 +7,6 @@ #include "sync.h" #include "mrcrequestpage.h" #include "ui_mrcrequestpage.h" -#include "mrcmodel.h" #include "walletmodel.h" #include "optionsmodel.h" #include "qt/decoration.h" @@ -35,6 +34,7 @@ MRCRequestPage::MRCRequestPage( ui->SubmittedIconLabel->setPixmap(GRC::ScaleIcon(this, ":/icons/round_green_check", 32)); ui->ErrorIconLabel->setPixmap(GRC::ScaleIcon(this, ":/icons/warning", 32)); + ui->waitForBlockUpdate->setPixmap(GRC::ScaleIcon(this, ":/icons/no_result", 32)); connect(m_mrc_model, &MRCModel::mrcChanged, this, &MRCRequestPage::updateMRCStatus); connect(m_mrc_model, &MRCModel::walletStatusChangedSignal, this, &MRCRequestPage::updateMRCStatus); @@ -101,19 +101,19 @@ void MRCRequestPage::updateMRCStatus() if (m_mrc_model->getMRCQueueLength() > 0) { ui->mrcQueueHeadFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCQueueHeadFee())); } else { - ui->mrcQueueHeadFee->setText("N/A"); + ui->mrcQueueHeadFee->setText(tr("N/A")); } if (m_mrc_model->getMRCQueueLength() >= m_mrc_model->getMRCOutputLimit()) { ui->mrcQueuePayLimitFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCQueuePayLimitFee())); } else { - ui->mrcQueuePayLimitFee->setText("N/A"); + ui->mrcQueuePayLimitFee->setText(tr("N/A")); } if (m_mrc_model->getMRCQueueLength() > 0) { ui->mrcQueueTailFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCQueueTailFee())); } else { - ui->mrcQueueTailFee->setText("N/A"); + ui->mrcQueueTailFee->setText(tr("N/A")); } MRCRequestStatus s; @@ -130,26 +130,26 @@ void MRCRequestPage::updateMRCStatus() if (s == MRCRequestStatus::PENDING) { ui->mrcQueuePosition->setText(QString::number(m_mrc_model->getMRCPos() + 1)); - ui->mrcQueuePositionLabel->setText("Your Submitted MRC Request Position in Queue"); + ui->mrcQueuePositionLabel->setText(tr("Your Submitted MRC Request Position in Queue")); - ui->mrcMinimumSubmitFee->setText("N/A"); + ui->mrcMinimumSubmitFee->setText(tr("N/A")); ui->SubmittedIconLabel->show(); ui->ErrorIconLabel->hide(); ui->ErrorIconLabel->setToolTip(""); } else if (s == MRCRequestStatus::QUEUE_FULL) { - ui->mrcQueuePosition->setText("N/A"); - ui->mrcQueuePositionLabel->setText("Your Projected MRC Request Position in Queue"); + ui->mrcQueuePosition->setText(tr("N/A")); + ui->mrcQueuePositionLabel->setText(tr("Your Projected MRC Request Position in Queue")); ui->mrcMinimumSubmitFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCMinimumSubmitFee())); ui->ErrorIconLabel->show(); ui->ErrorIconLabel->setToolTip(message); } else { - ui->mrcQueuePosition->setText("N/A"); - ui->mrcQueuePositionLabel->setText("Your Projected MRC Request Position in Queue"); + ui->mrcQueuePosition->setText(tr("N/A")); + ui->mrcQueuePositionLabel->setText(tr("Your Projected MRC Request Position in Queue")); - ui->mrcMinimumSubmitFee->setText("N/A"); + ui->mrcMinimumSubmitFee->setText(tr("N/A")); ui->SubmittedIconLabel->hide(); ui->ErrorIconLabel->show(); @@ -159,7 +159,7 @@ void MRCRequestPage::updateMRCStatus() message = "Submits the MRC request."; ui->mrcQueuePosition->setText(QString::number(m_mrc_model->getMRCPos() + 1)); - ui->mrcQueuePositionLabel->setText("Your Projected MRC Request Position in Queue"); + ui->mrcQueuePositionLabel->setText(tr("Your Projected MRC Request Position in Queue")); ui->mrcMinimumSubmitFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCMinimumSubmitFee())); @@ -169,6 +169,38 @@ void MRCRequestPage::updateMRCStatus() ui->ErrorIconLabel->hide(); ui->ErrorIconLabel->setToolTip(""); } + + LogPrintf("INFO: %s: getMRCModelStatus = %i", + __func__, + (int) m_mrc_model->getMRCModelStatus()); + + showMRCStatus(m_mrc_model->getMRCModelStatus()); +} + +void MRCRequestPage::showMRCStatus(MRCModel::ModelStatus status) { + switch (status) { + case MRCModel::ModelStatus::INVALID_BLOCK_VERSION: + ui->waitForBlockUpdateLabel->setText(tr("The block version must be v12 or higher to submit MRCs.")); + ui->waitForNextBlockUpdateFrame->show(); + ui->mrcStatusSubmitFrame->hide(); + return; + case MRCModel::ModelStatus::OUT_OF_SYNC: + ui->waitForBlockUpdateLabel->setText(tr("The wallet must be in sync to submit MRCs.")); + ui->waitForNextBlockUpdateFrame->show(); + ui->mrcStatusSubmitFrame->hide(); + return; + case MRCModel::ModelStatus::NO_BLOCK_UPDATE_FROM_INIT: + ui->waitForBlockUpdateLabel->setText(tr("A block update must have occurred since the wallet start to submit MRCs.")); + ui->waitForNextBlockUpdateFrame->show(); + ui->mrcStatusSubmitFrame->hide(); + return; + case MRCModel::ModelStatus::VALID: + ui->waitForBlockUpdateLabel->setText(""); + ui->waitForNextBlockUpdateFrame->hide(); + ui->mrcStatusSubmitFrame->show(); + return; + } + assert(false); } void MRCRequestPage::submitMRC() diff --git a/src/qt/mrcrequestpage.h b/src/qt/mrcrequestpage.h index ec941cdc21..d1d6907aa1 100644 --- a/src/qt/mrcrequestpage.h +++ b/src/qt/mrcrequestpage.h @@ -9,8 +9,8 @@ #include #include "bitcoinamountfield.h" +#include "mrcmodel.h" -class MRCModel; class WalletModel; namespace Ui { @@ -35,6 +35,7 @@ class MRCRequestPage : public QDialog QSize m_scaled_size; void updateMRCModel(); + void showMRCStatus(MRCModel::ModelStatus status); private slots: void buttonBoxClicked(QAbstractButton* button); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 2fa6f479d3..ffeeee21d1 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2596,7 +2596,6 @@ UniValue createmrcrequest(const UniValue& params, const bool fHelp) { pay_limit_fee = std::min(head_fee, pay_limit_fee); - if (!dry_run && !force) { if (found) { throw runtime_error("Outstanding MRC request already present in the mempool for CPID."); From 152c64d8d98c451352a55879ea4e47f908fe3fd4 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sat, 14 May 2022 09:25:52 -0400 Subject: [PATCH 10/21] Tweak MRCModel::refresh() to properly handle already submitted MRC A successfully submitted MRC can be subsequently pushed down in the queue past the pay limit by other MRCs submitted with higher fees. This means that the submitted MRC should revert to QUEUE_FULL status. This also tweaks the WALLET_LOCKED status to only apply if an MRC is not "found", i.e. there is no submitted MRC present. This is the correct behavior, because the wallet had to be unlocked to submit the MRC, and this would represent the situation where the wallet was relocked after the MRC was submitted. You can't submit a new MRC until after the existing submitted MRC is either paid or deleted, so the WALLET_LOCKED status should only apply when there is no MRC with the walletholder's CPID in the queue. --- src/qt/mrcmodel.cpp | 15 +++++++++++---- src/qt/mrcrequestpage.cpp | 7 +++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index 55ea000a64..32bf62fbf7 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -329,16 +329,23 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) m_mrc_pos, m_mrc_output_limit - 1); - if (found) { + if (found && m_mrc_pos <= m_mrc_output_limit - 1) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::PENDING; m_mrc_error_desc = tr("You have a pending MRC request.").toStdString(); + } else if (found && m_mrc_pos > m_mrc_output_limit - 1) { + m_mrc_error |= true; + m_mrc_status = MRCRequestStatus::QUEUE_FULL; + m_mrc_error_desc = tr("Your MRC was successfully submitted, but other MRCs with higher fees have pushed your MRC " + "down in the queue past the pay limit, and your MRC will be canceled. Wait until the next " + "block is received and the queue clears and try again. Your fee for the canceled MRC will " + "be refunded.").toStdString(); } else if (m_mrc_pos > m_mrc_output_limit - 1) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::QUEUE_FULL; - m_mrc_error_desc = tr("The MRC queue is full. You can try inputting a provided fee high enough to put your MRC " - "request in the queue and displace another MRC request.").toStdString(); - } else if (m_wallet_locked) { + m_mrc_error_desc = tr("The MRC queue is full. You can try boosting your fee to put your MRC request in the queue " + "and displace another MRC request.").toStdString(); + } else if (m_wallet_locked && !found) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::WALLET_LOCKED; m_mrc_error_desc = tr("The wallet is locked.").toStdString(); diff --git a/src/qt/mrcrequestpage.cpp b/src/qt/mrcrequestpage.cpp index 76ece9c439..4d9571055a 100644 --- a/src/qt/mrcrequestpage.cpp +++ b/src/qt/mrcrequestpage.cpp @@ -143,6 +143,7 @@ void MRCRequestPage::updateMRCStatus() ui->mrcMinimumSubmitFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCMinimumSubmitFee())); + ui->SubmittedIconLabel->hide(); ui->ErrorIconLabel->show(); ui->ErrorIconLabel->setToolTip(message); } else { @@ -215,5 +216,11 @@ void MRCRequestPage::submitMRC() message = QString::fromStdString(e) + " MRC request cannot be submitted."; ui->mrcSubmitButton->setToolTip(message); + } else { + // Since MRC was successfully submitted, reset the fee boost to zero. + CAmount fee_boost = 0; + + ui->mrcFeeBoostSpinBox->clear(); + m_mrc_model->setMRCFeeBoost(fee_boost); } } From a1006b5592f1d4ac63a4c04d6f4f85a737fe15fb Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sat, 14 May 2022 13:26:30 -0400 Subject: [PATCH 11/21] Implement check and status for invalid researcher --- src/qt/bitcoin.cpp | 2 +- src/qt/mrcmodel.cpp | 28 ++++++++++++++++++++++++---- src/qt/mrcmodel.h | 11 +++++++++-- src/qt/mrcrequestpage.cpp | 6 ++++++ 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index aa12dc223b..3ab0d855ba 100755 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -664,7 +664,7 @@ int StartGridcoinQt(int argc, char *argv[], QApplication& app, OptionsModel& opt ClientModel clientModel(&optionsModel); WalletModel walletModel(pwalletMain, &optionsModel); ResearcherModel researcherModel; - MRCModel mrcModel(&walletModel, &clientModel); + MRCModel mrcModel(&walletModel, &clientModel, &researcherModel); VotingModel votingModel(clientModel, optionsModel, walletModel); window.setResearcherModel(&researcherModel); diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index 32bf62fbf7..c6e605fb87 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -9,6 +9,7 @@ #include "mrcmodel.h" #include "walletmodel.h" #include "clientmodel.h" +#include "researcher/researchermodel.h" #include "qt/mrcrequestpage.h" #include "node/ui_interface.h" @@ -27,10 +28,11 @@ void MRCChanged(MRCModel* model) } } // anonymous namespace -MRCModel::MRCModel(WalletModel* wallet_model, ClientModel *client_model, QObject *parent) +MRCModel::MRCModel(WalletModel* wallet_model, ClientModel *client_model, ResearcherModel *researcher_model, QObject *parent) : QObject(parent) , m_wallet_model(wallet_model) , m_client_model(client_model) + , m_researcher_model(researcher_model) , m_mrc_status(MRCRequestStatus::NONE) , m_reward(0) , m_mrc_min_fee(0) @@ -132,7 +134,9 @@ int MRCModel::getMRCOutputLimit() MRCModel::ModelStatus MRCModel::getMRCModelStatus() { - if (!IsV12Enabled(m_block_height)) { + if (m_mrc_status == MRCRequestStatus::NOT_VALID_RESEARCHER) { + return MRCModel::NOT_VALID_RESEARCHER; + } else if (!IsV12Enabled(m_block_height)) { return MRCModel::ModelStatus::INVALID_BLOCK_VERSION; } else if (OutOfSyncByAge()) { return MRCModel::ModelStatus::OUT_OF_SYNC; @@ -200,6 +204,24 @@ void MRCModel::unsubscribeFromCoreSignals() void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) { + m_mrc_error = false; + m_mrc_status = MRCRequestStatus::NONE; + m_mrc_error_desc = std::string{}; + + if (!m_researcher_model) { + m_mrc_error |= true; + m_mrc_status = MRCRequestStatus::NOT_VALID_RESEARCHER; + return; + } + + if (!m_researcher_model->hasActiveBeacon() + || m_researcher_model->configuredForInvestorMode() + || m_researcher_model->detectedPoolMode()) { + m_mrc_error |= true; + m_mrc_status = MRCRequestStatus::NOT_VALID_RESEARCHER; + return; + } + // This is similar to createmrcrequest AssertLockHeld(cs_main); @@ -216,8 +238,6 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) return; } - m_mrc_error = false; - m_mrc_error_desc = std::string{}; m_mrc_min_fee = 0; m_mrc_fee = 0; diff --git a/src/qt/mrcmodel.h b/src/qt/mrcmodel.h index 1e755a4d86..71c8244fb9 100644 --- a/src/qt/mrcmodel.h +++ b/src/qt/mrcmodel.h @@ -11,12 +11,14 @@ class WalletModel; class ClientModel; +class ResearcherModel; enum class MRCRequestStatus { NONE, - CREATE_ERROR, ELIGIBLE, + NOT_VALID_RESEARCHER, + CREATE_ERROR, PENDING, QUEUE_FULL, ZERO_PAYOUT, @@ -30,11 +32,15 @@ class MRCModel : public QObject Q_OBJECT public: - explicit MRCModel(WalletModel* wallet_model, ClientModel* client_model, QObject* parent = nullptr); + explicit MRCModel(WalletModel* wallet_model, + ClientModel* client_model, + ResearcherModel* researcher_model, + QObject* parent = nullptr); ~MRCModel(); enum ModelStatus { VALID, + NOT_VALID_RESEARCHER, INVALID_BLOCK_VERSION, OUT_OF_SYNC, NO_BLOCK_UPDATE_FROM_INIT @@ -63,6 +69,7 @@ class MRCModel : public QObject WalletModel* m_wallet_model; ClientModel* m_client_model; + ResearcherModel* m_researcher_model; GRC::MRC m_mrc; MRCRequestStatus m_mrc_status; diff --git a/src/qt/mrcrequestpage.cpp b/src/qt/mrcrequestpage.cpp index 4d9571055a..134f3ed1f9 100644 --- a/src/qt/mrcrequestpage.cpp +++ b/src/qt/mrcrequestpage.cpp @@ -180,6 +180,12 @@ void MRCRequestPage::updateMRCStatus() void MRCRequestPage::showMRCStatus(MRCModel::ModelStatus status) { switch (status) { + case MRCModel::ModelStatus::NOT_VALID_RESEARCHER: + ui->waitForBlockUpdateLabel->setText(tr("You must have an active beacon and the wallet must be in solo mode to " + "submit MRCs.")); + ui->waitForNextBlockUpdateFrame->show(); + ui->mrcStatusSubmitFrame->hide(); + return; case MRCModel::ModelStatus::INVALID_BLOCK_VERSION: ui->waitForBlockUpdateLabel->setText(tr("The block version must be v12 or higher to submit MRCs.")); ui->waitForNextBlockUpdateFrame->show(); From fc562b9cb4e2ac42a3918ecb7b92922405a9d033 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Sat, 14 May 2022 19:19:40 -0400 Subject: [PATCH 12/21] Implement showHideMRCToolButton() and associated signals --- src/qt/overviewpage.cpp | 28 ++++++++++++++++++++++++++++ src/qt/overviewpage.h | 1 + 2 files changed, 29 insertions(+) diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index d1b7e6df98..c99a5c3fde 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -167,6 +167,8 @@ OverviewPage::OverviewPage(QWidget *parent) : // start with displaying the "out of sync" warnings showOutOfSyncWarning(true); + + showHideMRCToolButton(); } void OverviewPage::handleTransactionClicked(const QModelIndex &index) @@ -359,11 +361,20 @@ void OverviewPage::setResearcherModel(ResearcherModel *researcherModel) return; } + updateResearcherStatus(); + showHideMRCToolButton(); + connect(researcherModel, &ResearcherModel::researcherChanged, this, &OverviewPage::updateResearcherStatus); connect(researcherModel, &ResearcherModel::magnitudeChanged, this, &OverviewPage::updateMagnitude); connect(researcherModel, &ResearcherModel::accrualChanged, this, &OverviewPage::updatePendingAccrual); connect(researcherModel, &ResearcherModel::beaconChanged, this, &OverviewPage::updateResearcherAlert); + + // Show or hide the MRC request tool button based on the researcher and beacon status. + connect(researcherModel, &ResearcherModel::researcherChanged, this, &OverviewPage::showHideMRCToolButton); + connect(researcherModel, &ResearcherModel::beaconChanged, this, &OverviewPage::showHideMRCToolButton); + + connect(ui->researcherConfigToolButton, &QAbstractButton::clicked, this, &OverviewPage::onBeaconButtonClicked); } @@ -525,6 +536,23 @@ void OverviewPage::onMRCRequestClicked() m_mrc_model->showMRCDialog(); } +void OverviewPage::showHideMRCToolButton() +{ + if (!researcherModel) { + ui->mrcRequestToolButton->hide(); + return; + } + + if (!researcherModel->hasActiveBeacon() + || researcherModel->configuredForInvestorMode() + || researcherModel->detectedPoolMode()) { + ui->mrcRequestToolButton->hide(); + return; + } + + ui->mrcRequestToolButton->show(); +} + void OverviewPage::showOutOfSyncWarning(bool fShow) { ui->walletStatusLabel->setVisible(fShow); diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index 48bfd28766..6e71ce424d 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -38,6 +38,7 @@ public slots: void setCoinWeight(double coin_weight); void setCurrentPollTitle(const QString& title); void setPrivacy(bool privacy); + void showHideMRCToolButton(); signals: void transactionClicked(const QModelIndex &index); From f8eed2acbd49e75b53a6006758d02292dda4da29 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Sat, 14 May 2022 19:35:43 -0400 Subject: [PATCH 13/21] Add documentation to mrcmodel and mrcrequestpage --- src/qt/mrcmodel.h | 21 +++++++++++++++++++++ src/qt/mrcrequestpage.h | 10 ++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/qt/mrcmodel.h b/src/qt/mrcmodel.h index 71c8244fb9..b9b2c2eb81 100644 --- a/src/qt/mrcmodel.h +++ b/src/qt/mrcmodel.h @@ -13,6 +13,9 @@ class WalletModel; class ClientModel; class ResearcherModel; +//! +//! \brief The MRCRequestStatus enum describes the status of the MRC request +//! enum class MRCRequestStatus { NONE, @@ -27,6 +30,11 @@ enum class MRCRequestStatus SUBMIT_ERROR }; +//! +//! \brief The MRCModel class provides a GUI model of the state of the MRC queue and the researcher's MRC status +//! +//! Note that the MRC queue is "synthetic". It is filtered from the transactions in the memory pool and consists +//! of the transactions that have a valid MRC contract. class MRCModel : public QObject { Q_OBJECT @@ -38,6 +46,11 @@ class MRCModel : public QObject QObject* parent = nullptr); ~MRCModel(); + //! + //! \brief The ModelStatus enum describes the overall status of the MRC model. + //! + //! The MRC request widgets are only shown in the MRCRequestPage if the model is VALID. + //! enum ModelStatus { VALID, NOT_VALID_RESEARCHER, @@ -96,6 +109,14 @@ class MRCModel : public QObject void walletStatusChangedSignal(); public slots: + //! + //! \brief refresh does the brunt of the work in the MRCModel. + //! + //! It runs trial CreateMRCs and loops through the memory pool to construct the synthetic MRC queue and + //! provides the necessary information for the MRCRequestPage to control the MRC submission by the user. + //! + //! It provides structured status and error states of the MRC request(s). + //! void refresh(); void walletStatusChanged(int encryption_status); }; diff --git a/src/qt/mrcrequestpage.h b/src/qt/mrcrequestpage.h index d1d6907aa1..ae98aeb422 100644 --- a/src/qt/mrcrequestpage.h +++ b/src/qt/mrcrequestpage.h @@ -17,6 +17,9 @@ namespace Ui { class MRCRequestPage; } +//! +//! \brief The MRCRequestPage class implements the GUI MRC request form as a non-model dialog +//! class MRCRequestPage : public QDialog { Q_OBJECT @@ -35,6 +38,13 @@ class MRCRequestPage : public QDialog QSize m_scaled_size; void updateMRCModel(); + //! + //! \brief showMRCStatus shows or hides the MRC status and submission widgets. + //! \param status + //! + //! showMRCStatus shows or hides the MRC status and submission widgets based on the value of the status parameter. + //! If the status is VALID, then the widgets allowing the view of the queue state and the submission are shown. + //! In any other state, a customized blank form with a specialized message is shown based on the status value. void showMRCStatus(MRCModel::ModelStatus status); private slots: From 34ce424ac92b2f5145c82b45b47dfc35ac48c0c4 Mon Sep 17 00:00:00 2001 From: jamescowens Date: Sat, 14 May 2022 19:51:12 -0400 Subject: [PATCH 14/21] Correct spelling error in comments in mrcmodel.cpp --- src/qt/mrcmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index c6e605fb87..fab93f87ed 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -252,7 +252,7 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) m_mrc_error_desc = e.what(); } - // If the (mininum) fee comes back equal to the reward we are in the zero payout interval (i.e. too soon). + // If the (minimum) fee comes back equal to the reward we are in the zero payout interval (i.e. too soon). if (m_mrc_min_fee == m_reward) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::ZERO_PAYOUT; From 0b3050058047723e868761f5682809d4c298950e Mon Sep 17 00:00:00 2001 From: jamescowens Date: Sat, 14 May 2022 23:21:34 -0400 Subject: [PATCH 15/21] Implementation of mrcFeeBoostRaiseToMinimumButton Also some other cleanups --- src/qt/forms/mrcrequestpage.ui | 9 ++++- src/qt/mrcmodel.cpp | 53 ++++++++++-------------- src/qt/mrcmodel.h | 8 ++-- src/qt/mrcrequestpage.cpp | 73 +++++++++++++++++++++++++--------- src/qt/mrcrequestpage.h | 10 ++--- 5 files changed, 93 insertions(+), 60 deletions(-) diff --git a/src/qt/forms/mrcrequestpage.ui b/src/qt/forms/mrcrequestpage.ui index 8aa6b30a9b..91e5c2f42c 100644 --- a/src/qt/forms/mrcrequestpage.ui +++ b/src/qt/forms/mrcrequestpage.ui @@ -219,7 +219,7 @@ - + @@ -230,6 +230,13 @@ + + + + Raise to Minimum For Submit + + + diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index fab93f87ed..2c5a1bac72 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -45,7 +45,7 @@ MRCModel::MRCModel(WalletModel* wallet_model, ClientModel *client_model, Researc , m_mrc_queue_head_fee(0) , m_mrc_output_limit(0) , m_mrc_error(false) - , m_mrc_error_desc(std::string{}) + , m_mrc_error_desc(QString{}) , m_wallet_locked(false) , m_init_block_height(0) { @@ -127,6 +127,11 @@ CAmount MRCModel::getMRCMinimumSubmitFee() return m_mrc_min_fee; } +CAmount MRCModel::getMRCReward() +{ + return m_reward; +} + int MRCModel::getMRCOutputLimit() { return m_mrc_output_limit; @@ -147,7 +152,7 @@ MRCModel::ModelStatus MRCModel::getMRCModelStatus() return MRCModel::ModelStatus::VALID; } -bool MRCModel::isMRCError(MRCRequestStatus &s, std::string& e) +bool MRCModel::isMRCError(MRCRequestStatus &s, QString& e) { if (m_mrc_error) { e = m_mrc_error_desc; @@ -158,7 +163,7 @@ bool MRCModel::isMRCError(MRCRequestStatus &s, std::string& e) return false; } -bool MRCModel::submitMRC(MRCRequestStatus& s, std::string& e) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +bool MRCModel::submitMRC(MRCRequestStatus& s, QString& e) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { if (m_mrc_status != MRCRequestStatus::ELIGIBLE) { return error("%s: submitMRC called while m_mrc_status, %i, is not ELIGIBLE.", @@ -169,9 +174,10 @@ bool MRCModel::submitMRC(MRCRequestStatus& s, std::string& e) EXCLUSIVE_LOCKS_RE LOCK(pwalletMain->cs_wallet); CWalletTx wtx; + std::string e_str; - std::tie(wtx, e) = GRC::SendContract(GRC::MakeContract(GRC::ContractAction::ADD, m_mrc)); - if (!e.empty()) { + std::tie(wtx, e_str) = GRC::SendContract(GRC::MakeContract(GRC::ContractAction::ADD, m_mrc)); + if (!QString::fromStdString(e_str).isEmpty()) { m_mrc_error = true; m_mrc_status = MRCRequestStatus::SUBMIT_ERROR; m_mrc_error_desc = e; @@ -180,7 +186,7 @@ bool MRCModel::submitMRC(MRCRequestStatus& s, std::string& e) EXCLUSIVE_LOCKS_RE } else { m_mrc_error = false; m_mrc_status = MRCRequestStatus::PENDING; - m_mrc_error_desc = std::string{}; + m_mrc_error_desc = QString{}; s = m_mrc_status; } @@ -206,7 +212,7 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) { m_mrc_error = false; m_mrc_status = MRCRequestStatus::NONE; - m_mrc_error_desc = std::string{}; + m_mrc_error_desc = QString{}; if (!m_researcher_model) { m_mrc_error |= true; @@ -256,7 +262,7 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) if (m_mrc_min_fee == m_reward) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::ZERO_PAYOUT; - m_mrc_error_desc = "Too soon since your last research rewards payment."; + m_mrc_error_desc = tr("Too soon since your last research rewards payment."); } // If there is a fee boost, add the boost to the fee from the initial run above. @@ -268,7 +274,7 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) if (m_mrc_fee > m_reward) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::EXCESSIVE_FEE; - m_mrc_error_desc = "The total fee (the minimum fee + fee boost) is greater than the rewards due."; + m_mrc_error_desc = tr("The total fee (the minimum fee + fee boost) is greater than the rewards due."); } // Rerun CreateMRC with that new total fee @@ -282,12 +288,6 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) } } - LogPrintf("INFO: %s: After stage 1: m_mrc_error = %u, m_mrc_status = %i, m_mrc_error_desc = %s", - __func__, - m_mrc_error, - static_cast(m_mrc_status), - m_mrc_error_desc); - // We do the mempool loop here regardless of whether there is an error condition or not. m_mrc_queue_length = 0; m_mrc_pos = 0; @@ -344,41 +344,30 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) m_mrc_queue_pay_limit_fee = std::min(m_mrc_queue_head_fee, m_mrc_queue_pay_limit_fee); - LogPrintf("INFO: %s: Post mempool loop: m_mrc_pos = %i, m_mrc_output_limit - 1 = %i", - __func__, - m_mrc_pos, - m_mrc_output_limit - 1); - if (found && m_mrc_pos <= m_mrc_output_limit - 1) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::PENDING; - m_mrc_error_desc = tr("You have a pending MRC request.").toStdString(); + m_mrc_error_desc = tr("You have a pending MRC request."); } else if (found && m_mrc_pos > m_mrc_output_limit - 1) { m_mrc_error |= true; - m_mrc_status = MRCRequestStatus::QUEUE_FULL; + m_mrc_status = MRCRequestStatus::PENDING_CANCEL; m_mrc_error_desc = tr("Your MRC was successfully submitted, but other MRCs with higher fees have pushed your MRC " "down in the queue past the pay limit, and your MRC will be canceled. Wait until the next " "block is received and the queue clears and try again. Your fee for the canceled MRC will " - "be refunded.").toStdString(); + "be refunded."); } else if (m_mrc_pos > m_mrc_output_limit - 1) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::QUEUE_FULL; m_mrc_error_desc = tr("The MRC queue is full. You can try boosting your fee to put your MRC request in the queue " - "and displace another MRC request.").toStdString(); + "and displace another MRC request."); } else if (m_wallet_locked && !found) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::WALLET_LOCKED; - m_mrc_error_desc = tr("The wallet is locked.").toStdString(); + m_mrc_error_desc = tr("The wallet is locked."); } else if (!m_mrc_error) { m_mrc_status = MRCRequestStatus::ELIGIBLE; - m_mrc_error_desc = std::string{}; + m_mrc_error_desc = QString{}; } - - LogPrintf("INFO: %s: After stage 2: m_mrc_error = %u, m_mrc_status = %i, m_mrc_error_desc = %s", - __func__, - m_mrc_error, - static_cast(m_mrc_status), - m_mrc_error_desc); } void MRCModel::walletStatusChanged(int encryption_status) EXCLUSIVE_LOCKS_REQUIRED(cs_main) diff --git a/src/qt/mrcmodel.h b/src/qt/mrcmodel.h index b9b2c2eb81..50a510dfb7 100644 --- a/src/qt/mrcmodel.h +++ b/src/qt/mrcmodel.h @@ -24,6 +24,7 @@ enum class MRCRequestStatus CREATE_ERROR, PENDING, QUEUE_FULL, + PENDING_CANCEL, ZERO_PAYOUT, EXCESSIVE_FEE, WALLET_LOCKED, @@ -70,10 +71,11 @@ class MRCModel : public QObject CAmount getMRCQueuePayLimitFee(); CAmount getMRCQueueHeadFee(); CAmount getMRCMinimumSubmitFee(); + CAmount getMRCReward(); int getMRCOutputLimit(); ModelStatus getMRCModelStatus(); - bool isMRCError(MRCRequestStatus& s, std::string& e); - bool submitMRC(MRCRequestStatus& s, std::string& e); + bool isMRCError(MRCRequestStatus& s, QString& e); + bool submitMRC(MRCRequestStatus& s, QString& e); bool isWalletLocked(); private: @@ -98,7 +100,7 @@ class MRCModel : public QObject int m_mrc_output_limit; bool m_mrc_error; - std::string m_mrc_error_desc; + QString m_mrc_error_desc; bool m_wallet_locked; int m_init_block_height; diff --git a/src/qt/mrcrequestpage.cpp b/src/qt/mrcrequestpage.cpp index 134f3ed1f9..276fe223b0 100644 --- a/src/qt/mrcrequestpage.cpp +++ b/src/qt/mrcrequestpage.cpp @@ -25,12 +25,7 @@ MRCRequestPage::MRCRequestPage( ui->setupUi(this); - m_orig_geometry = this->geometry(); - m_gridLayout_orig_geometry = ui->gridLayout->geometry(); - - m_scaled_size = GRC::ScaleSize(this, width(), height()); - - resize(m_scaled_size); + resize(GRC::ScaleSize(this, width(), height())); ui->SubmittedIconLabel->setPixmap(GRC::ScaleIcon(this, ":/icons/round_green_check", 32)); ui->ErrorIconLabel->setPixmap(GRC::ScaleIcon(this, ":/icons/warning", 32)); @@ -41,8 +36,12 @@ MRCRequestPage::MRCRequestPage( connect(ui->mrcRequestButtonBox, &QDialogButtonBox::clicked, this, &MRCRequestPage::buttonBoxClicked); connect(ui->mrcUpdateButton, &QAbstractButton::clicked, this, &MRCRequestPage::updateMRCStatus); - connect(ui->mrcFeeBoostSpinBox, &BitcoinAmountField::textChanged, this, &MRCRequestPage::setMRCProvidedFee); + connect(ui->mrcFeeBoostSpinBox, &BitcoinAmountField::textChanged, this, &MRCRequestPage::setMRCFeeBoost); connect(ui->mrcSubmitButton, &QAbstractButton::clicked, this, &MRCRequestPage::submitMRC); + connect(ui->mrcFeeBoostRaiseToMinimumButton, &QAbstractButton::clicked, + this, &MRCRequestPage::setMRCFeeBoostToSubmitMinimum); + + ui->mrcFeeBoostRaiseToMinimumButton->hide(); ui->mrcFeeBoostSpinBox->setValue(m_mrc_model->getMRCFeeBoost()); @@ -64,7 +63,7 @@ void MRCRequestPage::updateMRCModel() m_mrc_model->refresh(); } -void MRCRequestPage::setMRCProvidedFee() +void MRCRequestPage::setMRCFeeBoost() { if (!m_mrc_model) return; @@ -75,6 +74,22 @@ void MRCRequestPage::setMRCProvidedFee() updateMRCStatus(); } +void MRCRequestPage::setMRCFeeBoostToSubmitMinimum() +{ + // Note that the button must be enabled for this function to be called, because it is connected to the button press + // and is a private slot, so therefore we are already in the situation where + // m_mrc_model->getMRCQueueLength() >= m_mrc_model->getMRCOutputLimit(). + + // This condition should not happen because of the checks in updateMRCStatus, but protect against it anyway. + if (m_mrc_model->getMRCReward() <= m_mrc_model->getMRCQueuePayLimitFee()) return; + + // Set the boost to the pay limit plus 1 Halford minus the mrc minimum fee to submit calculated from the MRC trail run. + CAmount minimum_boost_needed = m_mrc_model->getMRCQueuePayLimitFee() + 1 - m_mrc_model->getMRCMinimumSubmitFee(); + + ui->mrcFeeBoostSpinBox->setValue(minimum_boost_needed); + m_mrc_model->setMRCFeeBoost(minimum_boost_needed); +} + void MRCRequestPage::buttonBoxClicked(QAbstractButton* button) { if (ui->mrcRequestButtonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { @@ -117,13 +132,13 @@ void MRCRequestPage::updateMRCStatus() } MRCRequestStatus s; - std::string e; + QString e; QString message; // Note MRCError treats the PENDING status as an error from a handling point of view, because it blocks the // submission of a new MRC while there is one already in progress. if (m_mrc_model->isMRCError(s, e)) { - message = QString::fromStdString(e) + " MRC request cannot be submitted."; + message = e + " MRC request cannot be submitted."; ui->mrcSubmitButton->setEnabled(false); ui->mrcSubmitButton->setToolTip(message); @@ -134,15 +149,38 @@ void MRCRequestPage::updateMRCStatus() ui->mrcMinimumSubmitFee->setText(tr("N/A")); + ui->mrcFeeBoostRaiseToMinimumButton->setEnabled(false); + ui->mrcFeeBoostRaiseToMinimumButton->hide(); + ui->SubmittedIconLabel->show(); ui->ErrorIconLabel->hide(); ui->ErrorIconLabel->setToolTip(""); + } else if (s == MRCRequestStatus::PENDING_CANCEL){ + ui->mrcQueuePosition->setText(QString::number(m_mrc_model->getMRCPos() + 1)); + ui->mrcQueuePositionLabel->setText(tr("Your Submitted MRC Request Position in Queue")); + + ui->mrcMinimumSubmitFee->setText(tr("N/A")); + + ui->mrcFeeBoostRaiseToMinimumButton->setEnabled(false); + ui->mrcFeeBoostRaiseToMinimumButton->hide(); + + ui->SubmittedIconLabel->hide(); + ui->ErrorIconLabel->show(); + ui->ErrorIconLabel->setToolTip(message); } else if (s == MRCRequestStatus::QUEUE_FULL) { ui->mrcQueuePosition->setText(tr("N/A")); ui->mrcQueuePositionLabel->setText(tr("Your Projected MRC Request Position in Queue")); ui->mrcMinimumSubmitFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCMinimumSubmitFee())); + if (m_mrc_model->getMRCReward() > m_mrc_model->getMRCQueuePayLimitFee()) { + ui->mrcFeeBoostRaiseToMinimumButton->setEnabled(true); + ui->mrcFeeBoostRaiseToMinimumButton->show(); + } else { + ui->mrcFeeBoostRaiseToMinimumButton->setEnabled(false); + ui->mrcFeeBoostRaiseToMinimumButton->hide(); + } + ui->SubmittedIconLabel->hide(); ui->ErrorIconLabel->show(); ui->ErrorIconLabel->setToolTip(message); @@ -164,6 +202,9 @@ void MRCRequestPage::updateMRCStatus() ui->mrcMinimumSubmitFee->setText(BitcoinUnits::formatWithUnit(display_unit, m_mrc_model->getMRCMinimumSubmitFee())); + ui->mrcFeeBoostRaiseToMinimumButton->setEnabled(false); + ui->mrcFeeBoostRaiseToMinimumButton->hide(); + ui->mrcSubmitButton->setEnabled(true); ui->mrcSubmitButton->setToolTip(message); ui->SubmittedIconLabel->hide(); @@ -171,14 +212,10 @@ void MRCRequestPage::updateMRCStatus() ui->ErrorIconLabel->setToolTip(""); } - LogPrintf("INFO: %s: getMRCModelStatus = %i", - __func__, - (int) m_mrc_model->getMRCModelStatus()); - showMRCStatus(m_mrc_model->getMRCModelStatus()); } -void MRCRequestPage::showMRCStatus(MRCModel::ModelStatus status) { +void MRCRequestPage::showMRCStatus(const MRCModel::ModelStatus& status) { switch (status) { case MRCModel::ModelStatus::NOT_VALID_RESEARCHER: ui->waitForBlockUpdateLabel->setText(tr("You must have an active beacon and the wallet must be in solo mode to " @@ -213,20 +250,20 @@ void MRCRequestPage::showMRCStatus(MRCModel::ModelStatus status) { void MRCRequestPage::submitMRC() { MRCRequestStatus s; - std::string e; + QString e; QString message; if (!m_mrc_model) return; if (!m_mrc_model->submitMRC(s, e)) { - message = QString::fromStdString(e) + " MRC request cannot be submitted."; + message = e + " MRC request cannot be submitted."; ui->mrcSubmitButton->setToolTip(message); } else { // Since MRC was successfully submitted, reset the fee boost to zero. CAmount fee_boost = 0; - ui->mrcFeeBoostSpinBox->clear(); + ui->mrcFeeBoostSpinBox->setValue(fee_boost); m_mrc_model->setMRCFeeBoost(fee_boost); } } diff --git a/src/qt/mrcrequestpage.h b/src/qt/mrcrequestpage.h index ae98aeb422..e63fcdf02b 100644 --- a/src/qt/mrcrequestpage.h +++ b/src/qt/mrcrequestpage.h @@ -33,11 +33,8 @@ class MRCRequestPage : public QDialog MRCModel *m_mrc_model; WalletModel *m_wallet_model; - QRect m_orig_geometry; - QRect m_gridLayout_orig_geometry; - QSize m_scaled_size; - void updateMRCModel(); + //! //! \brief showMRCStatus shows or hides the MRC status and submission widgets. //! \param status @@ -45,12 +42,13 @@ class MRCRequestPage : public QDialog //! showMRCStatus shows or hides the MRC status and submission widgets based on the value of the status parameter. //! If the status is VALID, then the widgets allowing the view of the queue state and the submission are shown. //! In any other state, a customized blank form with a specialized message is shown based on the status value. - void showMRCStatus(MRCModel::ModelStatus status); + void showMRCStatus(const MRCModel::ModelStatus& status); private slots: void buttonBoxClicked(QAbstractButton* button); void updateMRCStatus(); - void setMRCProvidedFee(); + void setMRCFeeBoost(); + void setMRCFeeBoostToSubmitMinimum(); void submitMRC(); }; From 22f8053870e21d383136c05c82d442ea1aaae76a Mon Sep 17 00:00:00 2001 From: jamescowens Date: Sun, 15 May 2022 12:27:41 -0400 Subject: [PATCH 16/21] Implement STALE_CANCEL state --- src/qt/mrcmodel.cpp | 58 ++++++++++++++++++++++++++- src/qt/mrcmodel.h | 3 ++ src/qt/mrcrequestpage.cpp | 46 +++++++++++++++++---- src/qt/researcher/researchermodel.cpp | 5 +++ src/qt/researcher/researchermodel.h | 3 ++ 5 files changed, 106 insertions(+), 9 deletions(-) diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index 2c5a1bac72..bdaa4d1a68 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -33,6 +33,7 @@ MRCModel::MRCModel(WalletModel* wallet_model, ClientModel *client_model, Researc , m_wallet_model(wallet_model) , m_client_model(client_model) , m_researcher_model(researcher_model) + , m_submitted_mrc({}) , m_mrc_status(MRCRequestStatus::NONE) , m_reward(0) , m_mrc_min_fee(0) @@ -48,6 +49,8 @@ MRCModel::MRCModel(WalletModel* wallet_model, ClientModel *client_model, Researc , m_mrc_error_desc(QString{}) , m_wallet_locked(false) , m_init_block_height(0) + , m_block_height(0) + , m_submitted_height(0) { subscribeToCoreSignals(); @@ -184,6 +187,8 @@ bool MRCModel::submitMRC(MRCRequestStatus& s, QString& e) EXCLUSIVE_LOCKS_REQUIR s = m_mrc_status; return false; } else { + m_submitted_mrc = m_mrc; + m_submitted_height = pindexBest->nHeight; m_mrc_error = false; m_mrc_status = MRCRequestStatus::PENDING; m_mrc_error_desc = QString{}; @@ -191,6 +196,8 @@ bool MRCModel::submitMRC(MRCRequestStatus& s, QString& e) EXCLUSIVE_LOCKS_REQUIR } return true; + + // note this will call refresh() indirectly through the signalling from the core. } bool MRCModel::isWalletLocked() @@ -228,6 +235,9 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) return; } + // Stop here if out of sync. + if (OutOfSyncByAge()) return; + // This is similar to createmrcrequest AssertLockHeld(cs_main); @@ -244,6 +254,28 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) return; } + if (m_submitted_mrc) { + LogPrintf("INFO: %s: Before clearing statement: m_submitted_mrc optional is present.", + __func__); + } else { + LogPrintf("INFO: %s: Before clearing statement: m_submitted_mrc optional is null.", + __func__); + } + + // Clear the submitted mrc once the block advances again after the stake (which is one more than the submission block). + if (m_submitted_mrc && m_block_height >= m_submitted_height + 2) { + m_submitted_mrc = {}; + m_submitted_height = 0; + } + + if (m_submitted_mrc) { + LogPrintf("INFO: %s: After clearing statement: m_submitted_mrc optional is present.", + __func__); + } else { + LogPrintf("INFO: %s: After clearing statement: m_submitted_mrc optional is null.", + __func__); + } + m_mrc_min_fee = 0; m_mrc_fee = 0; @@ -344,7 +376,31 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) m_mrc_queue_pay_limit_fee = std::min(m_mrc_queue_head_fee, m_mrc_queue_pay_limit_fee); - if (found && m_mrc_pos <= m_mrc_output_limit - 1) { + // The first if statement is rather complex, but it looks for the situation where... 1. an mrc has been submitted, + // 2. The block height has advanced from the original submission height, and 3. The accrual is equal or higher than + // the research subsidy in the submitted MRC, which means the MRC has not been paid by the staker. This method avoids + // more expensive lookups against the block/transactions. + CAmount submitted_research_subsidy = m_submitted_mrc ? m_submitted_mrc->m_research_subsidy : 0; + + LogPrintf("INFO: %s: m_block_height = %i, m_submitted_height = %i, m_submitted_mrc->m_research_subsidy = %s, " + "m_researcher_model->getAccrual() = %s", + __func__, + m_block_height, + m_submitted_height, + FormatMoney(submitted_research_subsidy), + FormatMoney(m_researcher_model->getAccrual())); + + if (m_submitted_mrc + && m_block_height > m_submitted_height + && m_submitted_mrc->m_research_subsidy <= m_researcher_model->getAccrual()) { + m_mrc_error |= true; + m_mrc_status = MRCRequestStatus::STALE_CANCEL; + m_mrc_error_desc = tr("Your MRC was successfully submitted earlier but has now become stale without being bound " + "to the just received block by a staker. This may be because your MRC was submitted just " + "before the block was staked and the MRC didn't make it to the staker in time, or your MRC " + "was pushed down in the queue past the pay limit. Please wait for the next block to clear " + "the queue and try again."); + } else if (found && m_mrc_pos <= m_mrc_output_limit - 1) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::PENDING; m_mrc_error_desc = tr("You have a pending MRC request."); diff --git a/src/qt/mrcmodel.h b/src/qt/mrcmodel.h index 50a510dfb7..dfe5da153c 100644 --- a/src/qt/mrcmodel.h +++ b/src/qt/mrcmodel.h @@ -25,6 +25,7 @@ enum class MRCRequestStatus PENDING, QUEUE_FULL, PENDING_CANCEL, + STALE_CANCEL, ZERO_PAYOUT, EXCESSIVE_FEE, WALLET_LOCKED, @@ -87,6 +88,7 @@ class MRCModel : public QObject ResearcherModel* m_researcher_model; GRC::MRC m_mrc; + std::optional m_submitted_mrc; MRCRequestStatus m_mrc_status; CAmount m_reward; CAmount m_mrc_min_fee; @@ -105,6 +107,7 @@ class MRCModel : public QObject int m_init_block_height; int m_block_height; + int m_submitted_height; signals: void mrcChanged(); diff --git a/src/qt/mrcrequestpage.cpp b/src/qt/mrcrequestpage.cpp index 276fe223b0..b2b536f4f7 100644 --- a/src/qt/mrcrequestpage.cpp +++ b/src/qt/mrcrequestpage.cpp @@ -138,12 +138,12 @@ void MRCRequestPage::updateMRCStatus() // Note MRCError treats the PENDING status as an error from a handling point of view, because it blocks the // submission of a new MRC while there is one already in progress. if (m_mrc_model->isMRCError(s, e)) { - message = e + " MRC request cannot be submitted."; - ui->mrcSubmitButton->setEnabled(false); - ui->mrcSubmitButton->setToolTip(message); - if (s == MRCRequestStatus::PENDING) { + switch (s) { + case MRCRequestStatus::PENDING: + message = e + " MRC request cannot be submitted."; + ui->mrcQueuePosition->setText(QString::number(m_mrc_model->getMRCPos() + 1)); ui->mrcQueuePositionLabel->setText(tr("Your Submitted MRC Request Position in Queue")); @@ -155,7 +155,11 @@ void MRCRequestPage::updateMRCStatus() ui->SubmittedIconLabel->show(); ui->ErrorIconLabel->hide(); ui->ErrorIconLabel->setToolTip(""); - } else if (s == MRCRequestStatus::PENDING_CANCEL){ + + break; + case MRCRequestStatus::PENDING_CANCEL: + message = e; + ui->mrcQueuePosition->setText(QString::number(m_mrc_model->getMRCPos() + 1)); ui->mrcQueuePositionLabel->setText(tr("Your Submitted MRC Request Position in Queue")); @@ -167,7 +171,27 @@ void MRCRequestPage::updateMRCStatus() ui->SubmittedIconLabel->hide(); ui->ErrorIconLabel->show(); ui->ErrorIconLabel->setToolTip(message); - } else if (s == MRCRequestStatus::QUEUE_FULL) { + + break; + case MRCRequestStatus::STALE_CANCEL: + message = e; + + ui->mrcQueuePosition->setText(QString::number(m_mrc_model->getMRCPos() + 1)); + ui->mrcQueuePositionLabel->setText(tr("Your Submitted MRC Request Position in Queue")); + + ui->mrcMinimumSubmitFee->setText(tr("N/A")); + + ui->mrcFeeBoostRaiseToMinimumButton->setEnabled(false); + ui->mrcFeeBoostRaiseToMinimumButton->hide(); + + ui->SubmittedIconLabel->hide(); + ui->ErrorIconLabel->show(); + ui->ErrorIconLabel->setToolTip(message); + + break; + case MRCRequestStatus::QUEUE_FULL: + message = e + " MRC request cannot be submitted."; + ui->mrcQueuePosition->setText(tr("N/A")); ui->mrcQueuePositionLabel->setText(tr("Your Projected MRC Request Position in Queue")); @@ -184,7 +208,11 @@ void MRCRequestPage::updateMRCStatus() ui->SubmittedIconLabel->hide(); ui->ErrorIconLabel->show(); ui->ErrorIconLabel->setToolTip(message); - } else { + + break; + default: + message = e + " MRC request cannot be submitted."; + ui->mrcQueuePosition->setText(tr("N/A")); ui->mrcQueuePositionLabel->setText(tr("Your Projected MRC Request Position in Queue")); @@ -193,7 +221,9 @@ void MRCRequestPage::updateMRCStatus() ui->SubmittedIconLabel->hide(); ui->ErrorIconLabel->show(); ui->ErrorIconLabel->setToolTip(message); - } + } // switch for error conditions + + ui->mrcSubmitButton->setToolTip(message); } else { message = "Submits the MRC request."; diff --git a/src/qt/researcher/researchermodel.cpp b/src/qt/researcher/researchermodel.cpp index 8c9a993562..37e5e943cd 100644 --- a/src/qt/researcher/researchermodel.cpp +++ b/src/qt/researcher/researchermodel.cpp @@ -314,6 +314,11 @@ bool ResearcherModel::needsBeaconAuth() const return m_beacon->m_public_key != m_pending_beacon->m_public_key; } +CAmount ResearcherModel::getAccrual() const +{ + return m_researcher->Accrual(); +} + QString ResearcherModel::email() const { return QString::fromStdString(Researcher::Email()); diff --git a/src/qt/researcher/researchermodel.h b/src/qt/researcher/researchermodel.h index 4d28085a80..b6b1bcaa7a 100644 --- a/src/qt/researcher/researchermodel.h +++ b/src/qt/researcher/researchermodel.h @@ -6,6 +6,7 @@ #define GRIDCOIN_QT_RESEARCHER_RESEARCHERMODEL_H #include +#include "amount.h" #include QT_BEGIN_NAMESPACE @@ -98,6 +99,8 @@ class ResearcherModel : public QObject bool hasSplitCpid() const; bool needsBeaconAuth() const; + CAmount getAccrual() const; + QString email() const; QString formatCpid() const; QString formatMagnitude() const; From 092b242f237d49356e3aed6049af979251481b3f Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sun, 15 May 2022 22:13:45 -0400 Subject: [PATCH 17/21] Move OutOfSyncByAge check in MRCModel::refresh --- src/qt/mrcmodel.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index bdaa4d1a68..9b61fad04e 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -147,6 +147,7 @@ MRCModel::ModelStatus MRCModel::getMRCModelStatus() } else if (!IsV12Enabled(m_block_height)) { return MRCModel::ModelStatus::INVALID_BLOCK_VERSION; } else if (OutOfSyncByAge()) { + // Note that m_mrc_status == MRCRequestStatus::NONE if OutOfSyncByAge() is true. return MRCModel::ModelStatus::OUT_OF_SYNC; } else if (m_block_height <= m_init_block_height) { return MRCModel::ModelStatus::NO_BLOCK_UPDATE_FROM_INIT; @@ -221,6 +222,11 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) m_mrc_status = MRCRequestStatus::NONE; m_mrc_error_desc = QString{}; + // Stop here if out of sync. + if (OutOfSyncByAge()) { + return; + } + if (!m_researcher_model) { m_mrc_error |= true; m_mrc_status = MRCRequestStatus::NOT_VALID_RESEARCHER; @@ -235,9 +241,6 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) return; } - // Stop here if out of sync. - if (OutOfSyncByAge()) return; - // This is similar to createmrcrequest AssertLockHeld(cs_main); From 4b9dc163c3606eb6fad8d4cb59361118535ce9bc Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Sun, 15 May 2022 23:48:05 -0400 Subject: [PATCH 18/21] Fix mrc request tooltip opening up more than one mrc request page Also fix dangling mrc request page when exit called from main screen while mrc request dialog is open. --- src/qt/mrcmodel.cpp | 13 +++++++++++-- src/qt/mrcmodel.h | 2 ++ src/qt/mrcrequestpage.cpp | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index 9b61fad04e..c50b86c27f 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -33,6 +33,7 @@ MRCModel::MRCModel(WalletModel* wallet_model, ClientModel *client_model, Researc , m_wallet_model(wallet_model) , m_client_model(client_model) , m_researcher_model(researcher_model) + , m_mrc_request(nullptr) , m_submitted_mrc({}) , m_mrc_status(MRCRequestStatus::NONE) , m_reward(0) @@ -73,6 +74,10 @@ MRCModel::MRCModel(WalletModel* wallet_model, ClientModel *client_model, Researc MRCModel::~MRCModel() { + if (m_mrc_request) { + m_mrc_request->done(QDialog::Accepted); + } + unsubscribeFromCoreSignals(); } @@ -83,9 +88,13 @@ WalletModel* MRCModel::getWalletModel() void MRCModel::showMRCDialog() { - MRCRequestPage *mrc_request = new MRCRequestPage(nullptr, this); + if (!m_mrc_request) { + m_mrc_request = new MRCRequestPage(nullptr, this); + } + + //MRCRequestPage *mrc_request = new MRCRequestPage(nullptr, this); - mrc_request->show(); + m_mrc_request->show(); } void MRCModel::setMRCFeeBoost(CAmount& fee_boost) diff --git a/src/qt/mrcmodel.h b/src/qt/mrcmodel.h index dfe5da153c..e451f5883a 100644 --- a/src/qt/mrcmodel.h +++ b/src/qt/mrcmodel.h @@ -12,6 +12,7 @@ class WalletModel; class ClientModel; class ResearcherModel; +class MRCRequestPage; //! //! \brief The MRCRequestStatus enum describes the status of the MRC request @@ -86,6 +87,7 @@ class MRCModel : public QObject WalletModel* m_wallet_model; ClientModel* m_client_model; ResearcherModel* m_researcher_model; + MRCRequestPage* m_mrc_request; GRC::MRC m_mrc; std::optional m_submitted_mrc; diff --git a/src/qt/mrcrequestpage.cpp b/src/qt/mrcrequestpage.cpp index b2b536f4f7..ccd9be5083 100644 --- a/src/qt/mrcrequestpage.cpp +++ b/src/qt/mrcrequestpage.cpp @@ -264,7 +264,8 @@ void MRCRequestPage::showMRCStatus(const MRCModel::ModelStatus& status) { ui->mrcStatusSubmitFrame->hide(); return; case MRCModel::ModelStatus::NO_BLOCK_UPDATE_FROM_INIT: - ui->waitForBlockUpdateLabel->setText(tr("A block update must have occurred since the wallet start to submit MRCs.")); + ui->waitForBlockUpdateLabel->setText(tr("A block update must have occurred after wallet start or sync to submit " + "MRCs.")); ui->waitForNextBlockUpdateFrame->show(); ui->mrcStatusSubmitFrame->hide(); return; From 6b1caaa31aa7aa1bdf8acbea48f3d919758a2739 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Mon, 16 May 2022 00:40:12 -0400 Subject: [PATCH 19/21] Remove detailed debug logging and clean up comments --- src/qt/mrcmodel.cpp | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index c50b86c27f..6f6279c0f8 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -92,8 +92,6 @@ void MRCModel::showMRCDialog() m_mrc_request = new MRCRequestPage(nullptr, this); } - //MRCRequestPage *mrc_request = new MRCRequestPage(nullptr, this); - m_mrc_request->show(); } @@ -250,7 +248,7 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) return; } - // This is similar to createmrcrequest + // This is similar to createmrcrequest in many ways, but the state tracking is more complicated. AssertLockHeld(cs_main); @@ -266,28 +264,12 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) return; } - if (m_submitted_mrc) { - LogPrintf("INFO: %s: Before clearing statement: m_submitted_mrc optional is present.", - __func__); - } else { - LogPrintf("INFO: %s: Before clearing statement: m_submitted_mrc optional is null.", - __func__); - } - // Clear the submitted mrc once the block advances again after the stake (which is one more than the submission block). if (m_submitted_mrc && m_block_height >= m_submitted_height + 2) { m_submitted_mrc = {}; m_submitted_height = 0; } - if (m_submitted_mrc) { - LogPrintf("INFO: %s: After clearing statement: m_submitted_mrc optional is present.", - __func__); - } else { - LogPrintf("INFO: %s: After clearing statement: m_submitted_mrc optional is null.", - __func__); - } - m_mrc_min_fee = 0; m_mrc_fee = 0; @@ -392,16 +374,6 @@ void MRCModel::refresh() EXCLUSIVE_LOCKS_REQUIRED(cs_main) // 2. The block height has advanced from the original submission height, and 3. The accrual is equal or higher than // the research subsidy in the submitted MRC, which means the MRC has not been paid by the staker. This method avoids // more expensive lookups against the block/transactions. - CAmount submitted_research_subsidy = m_submitted_mrc ? m_submitted_mrc->m_research_subsidy : 0; - - LogPrintf("INFO: %s: m_block_height = %i, m_submitted_height = %i, m_submitted_mrc->m_research_subsidy = %s, " - "m_researcher_model->getAccrual() = %s", - __func__, - m_block_height, - m_submitted_height, - FormatMoney(submitted_research_subsidy), - FormatMoney(m_researcher_model->getAccrual())); - if (m_submitted_mrc && m_block_height > m_submitted_height && m_submitted_mrc->m_research_subsidy <= m_researcher_model->getAccrual()) { From c85fe7c268a3a9f1cd51d72bd78c71c506fb195e Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Fri, 20 May 2022 11:06:27 -0400 Subject: [PATCH 20/21] Minor cleanup of commented code, a comment misspelling Also removal of a now unnecessary debugging LogPrintf. --- src/gridcoin/mrc.h | 2 +- src/qt/bitcoingui.h | 2 -- src/qt/walletmodel.cpp | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/gridcoin/mrc.h b/src/gridcoin/mrc.h index e462fbea79..3c4ff9015d 100644 --- a/src/gridcoin/mrc.h +++ b/src/gridcoin/mrc.h @@ -348,7 +348,7 @@ class MRCContractHandler : public IContractHandler //! \param nReward: The research reward (out parameter) //! \param fee: The MRC fees to be taken out of the research reward (in/out parameter) //! \param pwallet: The wallet object -//! \param no_sign: If true, don't sign the MRC (trail run). +//! \param no_sign: If true, don't sign the MRC (trial run). //! \return //! void CreateMRC(CBlockIndex* pindex, MRC& mrc, CAmount &nReward, CAmount &fee, CWallet* pwallet, bool no_sign = false); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 3682e0a3c9..eec17f954f 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -246,8 +246,6 @@ private slots: void themeToggled(); /** Show researcher/beacon configuration dialog */ void researcherClicked(); - /** Show MRC payment request dialog */ - //void mrcPaymentClicked(); /** Show about dialog */ void aboutClicked(); /** Open config file */ diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index e4c9c2c82b..f8c8e167a9 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -79,8 +79,6 @@ void WalletModel::updateStatus() { EncryptionStatus newEncryptionStatus = getEncryptionStatus(); - LogPrintf("INFO: %s called.", __func__); - if(cachedEncryptionStatus != newEncryptionStatus) emit encryptionStatusChanged(newEncryptionStatus); } From 6167cabe3d52b005d161a8cf9acfa3af278ed99a Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Fri, 20 May 2022 13:26:39 -0400 Subject: [PATCH 21/21] Fix string handling in error handler in submitMRC Co-authored-by: div72 <60045611+div72@users.noreply.github.com> --- src/qt/mrcmodel.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/qt/mrcmodel.cpp b/src/qt/mrcmodel.cpp index 6f6279c0f8..cfd77e40c8 100644 --- a/src/qt/mrcmodel.cpp +++ b/src/qt/mrcmodel.cpp @@ -188,11 +188,10 @@ bool MRCModel::submitMRC(MRCRequestStatus& s, QString& e) EXCLUSIVE_LOCKS_REQUIR std::string e_str; std::tie(wtx, e_str) = GRC::SendContract(GRC::MakeContract(GRC::ContractAction::ADD, m_mrc)); - if (!QString::fromStdString(e_str).isEmpty()) { + if (!e_str.empty()) { m_mrc_error = true; - m_mrc_status = MRCRequestStatus::SUBMIT_ERROR; - m_mrc_error_desc = e; - s = m_mrc_status; + s = m_mrc_status = MRCRequestStatus::SUBMIT_ERROR; + e = m_mrc_error_desc = QString::fromStdString(e_str); return false; } else { m_submitted_mrc = m_mrc;