From 36e39a395d0bb829eeb592a6df9ca4459b6c3680 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Sun, 19 Sep 2021 20:14:39 +0100 Subject: [PATCH] Revise fee UI * Change from a block target number to using speed labels which pick predefined fee values. * Remove smart fee labels from send coins control dialog. * Rename slider position configuration for Qt, as smart fee slider settings are not compatible with preset fee settings. --- src/dogecoin-fees.cpp | 46 ++++++++++++++++++++++++- src/dogecoin-fees.h | 21 +++++++++++- src/qt/forms/sendcoinsdialog.ui | 29 ++++++---------- src/qt/sendcoinsdialog.cpp | 61 ++++++++++++++++----------------- src/qt/sendcoinsdialog.h | 2 +- src/wallet/coincontrol.h | 7 ++-- src/wallet/wallet.cpp | 42 ++++++++++++++++++++--- src/wallet/wallet.h | 11 ++++-- 8 files changed, 156 insertions(+), 63 deletions(-) diff --git a/src/dogecoin-fees.cpp b/src/dogecoin-fees.cpp index aafc210cb..28e25b5a0 100644 --- a/src/dogecoin-fees.cpp +++ b/src/dogecoin-fees.cpp @@ -18,6 +18,50 @@ #endif #ifdef ENABLE_WALLET + +CFeeRate GetDogecoinFeeRate(int priority) +{ + switch(priority) + { + case SUCH_EXPENSIVE: + return CFeeRate(COIN / 100 * 521); // 5.21 DOGE, but very carefully avoiding floating point maths + case MANY_GENEROUS: + return CFeeRate(CWallet::minTxFee.GetFeePerK() * 100); + case AMAZE: + return CFeeRate(CWallet::minTxFee.GetFeePerK() * 10); + case WOW: + return CFeeRate(CWallet::minTxFee.GetFeePerK() * 5); + case MORE: + return CFeeRate(CWallet::minTxFee.GetFeePerK() * 2); + case MINIMUM: + default: + break; + } + return CWallet::minTxFee; +} + +const std::string GetDogecoinPriorityLabel(int priority) +{ + switch(priority) + { + case SUCH_EXPENSIVE: + return _("Such expensive"); + case MANY_GENEROUS: + return _("Many generous"); + case AMAZE: + return _("Amaze"); + case WOW: + return _("Wow"); + case MORE: + return _("More"); + case MINIMUM: + return _("Minimum"); + default: + break; + } + return _("Default"); +} + //mlumin 5/2021: walletfees, all attached to GetDogecoinWalletFeeRate which is just the newly exposed ::minWalletTxFee CAmount GetDogecoinWalletFee(size_t nBytes_) { @@ -31,7 +75,7 @@ CAmount GetDogecoinWalletFee(size_t nBytes_) CFeeRate GetDogecoinWalletFeeRate() { //mlumin 5/2021: currently 1x COIN or 1 dogecoin or 100,000,000 koinu - return ::minWalletTxFeeRate; + return CWallet::minTxFee; } #endif diff --git a/src/dogecoin-fees.h b/src/dogecoin-fees.h index ac55dc723..b8f98b69b 100644 --- a/src/dogecoin-fees.h +++ b/src/dogecoin-fees.h @@ -2,13 +2,32 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifndef BITCOIN_DOGECOIN_FEES_H +#define BITCOIN_DOGECOIN_FEES_H + #include "amount.h" #include "chain.h" #include "chainparams.h" #ifdef ENABLE_WALLET + +enum FeeRatePreset +{ + MINIMUM, + MORE, + WOW, + AMAZE, + MANY_GENEROUS, + SUCH_EXPENSIVE +}; + +/** Estimate fee rate needed to get into the next nBlocks */ +CFeeRate GetDogecoinFeeRate(int priority); +const std::string GetDogecoinPriorityLabel(int priority); CFeeRate GetDogecoinWalletFeeRate(); CAmount GetDogecoinMinWalletFee(unsigned int nBytes_); -#endif +#endif // ENABLE_WALLET CAmount GetDogecoinMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree); CAmount GetDogecoinDustFee(const std::vector &vout, CFeeRate &baseFeeRate); + +#endif // BITCOIN_DOGECOIN_FEES_H diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index a5a194536..bd1d45b28 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -1005,7 +1005,7 @@ - + @@ -1020,17 +1020,8 @@ - - - - - (Smart fee not initialized yet. This usually takes a few blocks...) - - - 2 - - - + + @@ -1051,9 +1042,9 @@ - + - Confirmation time target: + Priority: 2 @@ -1086,7 +1077,7 @@ 0 - 23 + 5 1 @@ -1111,9 +1102,9 @@ - + - normal + low @@ -1151,9 +1142,9 @@ - + - fast + high diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 3d7837caa..911c7c764 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -17,6 +17,7 @@ #include "base58.h" #include "chainparams.h" +#include "dogecoin-fees.h" #include "wallet/coincontrol.h" #include "validation.h" // mempool and minRelayTxFeeRate #include "ui_interface.h" @@ -100,8 +101,8 @@ SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *p settings.setValue("nCustomFeeRadio", 1); // total at least if (!settings.contains("nCustomFeeRadio")) settings.setValue("nCustomFeeRadio", 0); // per kilobyte - if (!settings.contains("nSmartFeeSliderPosition")) - settings.setValue("nSmartFeeSliderPosition", 0); + if (!settings.contains("nPresetFeeSliderPosition")) + settings.setValue("nPresetFeeSliderPosition", 0); if (!settings.contains("nTransactionFee")) settings.setValue("nTransactionFee", (qint64)DEFAULT_TRANSACTION_FEE); if (!settings.contains("fPayOnlyMinFee")) @@ -122,7 +123,7 @@ void SendCoinsDialog::setClientModel(ClientModel *_clientModel) this->clientModel = _clientModel; if (_clientModel) { - connect(_clientModel, SIGNAL(numBlocksChanged(int,QDateTime,double,bool)), this, SLOT(updateSmartFeeLabel())); + connect(_clientModel, SIGNAL(numBlocksChanged(int,QDateTime,double,bool)), this, SLOT(updateFeeLabel())); } } @@ -154,7 +155,7 @@ void SendCoinsDialog::setModel(WalletModel *_model) coinControlUpdateLabels(); // fee section - connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(updateSmartFeeLabel())); + connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(updateFeeLabel())); connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(updateGlobalFeeVariables())); connect(ui->sliderSmartFee, SIGNAL(valueChanged(int)), this, SLOT(coinControlUpdateLabels())); connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls())); @@ -171,15 +172,12 @@ void SendCoinsDialog::setModel(WalletModel *_model) ui->customFee->setSingleStep(CWallet::GetRequiredFee(1000)); updateFeeSectionControls(); updateMinFeeLabel(); - updateSmartFeeLabel(); + updateFeeLabel(); updateGlobalFeeVariables(); // set the smartfee-sliders default value (wallets default conf.target or last stored value) QSettings settings; - if (settings.value("nSmartFeeSliderPosition").toInt() == 0) - ui->sliderSmartFee->setValue(ui->sliderSmartFee->maximum() - model->getDefaultConfirmTarget() + 2); - else - ui->sliderSmartFee->setValue(settings.value("nSmartFeeSliderPosition").toInt()); + ui->sliderSmartFee->setValue(settings.value("nPresetFeeSliderPosition").toInt()); } } @@ -189,7 +187,7 @@ SendCoinsDialog::~SendCoinsDialog() settings.setValue("fFeeSectionMinimized", fFeeMinimized); settings.setValue("nFeeRadio", ui->groupFee->checkedId()); settings.setValue("nCustomFeeRadio", ui->groupCustomFee->checkedId()); - settings.setValue("nSmartFeeSliderPosition", ui->sliderSmartFee->value()); + settings.setValue("nPresetFeeSliderPosition", ui->sliderSmartFee->value()); settings.setValue("nTransactionFee", (qint64)ui->customFee->value()); settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked()); @@ -243,9 +241,9 @@ void SendCoinsDialog::on_sendButton_clicked() if (model->getOptionsModel()->getCoinControlFeatures()) ctrl = *CoinControlDialog::coinControl; if (ui->radioSmartFee->isChecked()) - ctrl.nConfirmTarget = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2; + ctrl.nPriority = static_cast(ui->sliderSmartFee->value()); else - ctrl.nConfirmTarget = 0; + ctrl.nPriority = MINIMUM; prepareStatus = model->prepareTransaction(currentTransaction, &ctrl); @@ -502,7 +500,7 @@ void SendCoinsDialog::updateDisplayUnit() setBalance(model->getBalance(), 0, 0, 0, 0, 0); ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); updateMinFeeLabel(); - updateSmartFeeLabel(); + updateFeeLabel(); } void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg) @@ -585,13 +583,13 @@ void SendCoinsDialog::setMinimumFee() void SendCoinsDialog::updateFeeSectionControls() { ui->sliderSmartFee ->setEnabled(ui->radioSmartFee->isChecked()); - ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked()); - ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked()); - ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked()); + ui->labelPriority ->setEnabled(ui->radioSmartFee->isChecked()); + // Dogecoin: We don't use smart fees in the UI, so don't need to warn they're not available + // ui->labelPriority2 ->setEnabled(ui->radioSmartFee->isChecked()); + ui->labelPriority3 ->setEnabled(ui->radioSmartFee->isChecked()); ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked()); - ui->labelSmartFeeNormal ->setEnabled(ui->radioSmartFee->isChecked()); - ui->labelSmartFeeFast ->setEnabled(ui->radioSmartFee->isChecked()); - //mlumin: 5/2021 - this label actually gates the 'slider and smart fee' functionality, so turn it off for dogecoin. + ui->labelPriorityLow ->setEnabled(ui->radioSmartFee->isChecked()); + ui->labelPriorityHigh ->setEnabled(ui->radioSmartFee->isChecked()); ui->confirmationTargetLabel ->setEnabled(ui->radioSmartFee->isChecked()); ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked()); ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked()); @@ -604,7 +602,7 @@ void SendCoinsDialog::updateGlobalFeeVariables() { if (ui->radioSmartFee->isChecked()) { - int nConfirmTarget = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2; + int nPriority = ui->sliderSmartFee->value(); payTxFee = CFeeRate(0); // set nMinimumTotalFee to 0 to not accidentally pay a custom fee @@ -612,7 +610,7 @@ void SendCoinsDialog::updateGlobalFeeVariables() // show the estimated required time for confirmation // Dogecoin: We manually set height well past the last hard fork here - ui->confirmationTargetLabel->setText(GUIUtil::formatDurationStr(nConfirmTarget * Params().GetConsensus(400000).nPowTargetSpacing) + " / " + tr("%n block(s)", "", nConfirmTarget)); + ui->confirmationTargetLabel->setText(GetDogecoinPriorityLabel(nPriority).c_str()); } else { @@ -630,7 +628,7 @@ void SendCoinsDialog::updateFeeMinimizedLabel() return; if (ui->radioSmartFee->isChecked()) - ui->labelFeeMinimized->setText(ui->labelSmartFee->text()); + ui->labelFeeMinimized->setText(ui->labelPriority->text()); else { ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) + ((ui->radioCustomPerKilobyte->isChecked()) ? "/kB" : "")); @@ -645,19 +643,18 @@ void SendCoinsDialog::updateMinFeeLabel() ); } -void SendCoinsDialog::updateSmartFeeLabel() +void SendCoinsDialog::updateFeeLabel() { if(!model || !model->getOptionsModel()) return; - int nBlocksToConfirm = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2; - int estimateFoundAtBlocks = nBlocksToConfirm; - CFeeRate feeRate = mempool.estimateSmartFee(nBlocksToConfirm, &estimateFoundAtBlocks); + int nPriority = ui->sliderSmartFee->value(); + CFeeRate feeRate = GetDogecoinFeeRate(nPriority); if (feeRate <= CFeeRate(0)) // not enough data => minfee { - ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), + ui->labelPriority->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), std::max(CWallet::fallbackFee.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB"); - ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...) + // ui->labelPriority2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...) ui->labelFeeEstimation->setText(""); ui->fallbackFeeWarningLabel->setVisible(true); int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness(); @@ -667,10 +664,12 @@ void SendCoinsDialog::updateSmartFeeLabel() } else { - ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), + ui->labelPriority->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), std::max(feeRate.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB"); - ui->labelSmartFee2->hide(); - ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", estimateFoundAtBlocks)); + // ui->labelPriority2->hide(); + // Dogecoin: We don't use smart fees, so we don't have the data to estimate when it will get in + ui->labelFeeEstimation->setText(""); + // ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", estimateFoundAtBlocks)); ui->fallbackFeeWarningLabel->setVisible(false); } diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index a402edc96..151bc9855 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -91,7 +91,7 @@ private Q_SLOTS: void setMinimumFee(); void updateFeeSectionControls(); void updateMinFeeLabel(); - void updateSmartFeeLabel(); + void updateFeeLabel(); void updateGlobalFeeVariables(); Q_SIGNALS: diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index eaf4ff806..aaa5526e8 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -6,6 +6,7 @@ #define BITCOIN_WALLET_COINCONTROL_H #include "primitives/transaction.h" +#include "dogecoin-fees.h" /** Coin Control Features. */ class CCoinControl @@ -22,8 +23,8 @@ public: bool fOverrideFeeRate; //! Feerate to use if overrideFeeRate is true CFeeRate nFeeRate; - //! Override the default confirmation target, 0 = use default - int nConfirmTarget; + //! Override the default transaction speed, 0 = use default + FeeRatePreset nPriority; CCoinControl() { @@ -39,7 +40,7 @@ public: nMinimumTotalFee = 0; nFeeRate = CFeeRate(0); fOverrideFeeRate = false; - nConfirmTarget = 0; + nPriority = MINIMUM; } bool HasSelected() const diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a39629bdd..1beeb4362 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -39,8 +39,6 @@ using namespace std; CWallet* pwalletMain = NULL; /** Transaction fee set by the user */ CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE); -//mlumin 5/2021: Add minimum wallet tx fee. This really should be expressed as a rate not a final fee. -CFeeRate minWalletTxFeeRate = CFeeRate(DEFAULT_MIN_WALLET_TX_FEE); unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET; bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE; bool fSendFreeTransactions = DEFAULT_SEND_FREE_TRANSACTIONS; @@ -2661,8 +2659,11 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt // Allow to override the default confirmation target over the CoinControl instance int currentConfirmationTarget = nTxConfirmTarget; - if (coinControl && coinControl->nConfirmTarget > 0) - currentConfirmationTarget = coinControl->nConfirmTarget; + FeeRatePreset nPriority; + if (coinControl && coinControl->nPriority > 0) + nPriority = coinControl->nPriority; + else + nPriority = MINIMUM; // Can we complete this as a free transaction? if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE) @@ -2674,7 +2675,13 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt break; } - CAmount nFeeNeeded = GetMinimumFee(txNew, nBytes, currentConfirmationTarget, mempool); + CAmount nFeeNeeded; + if (nPriority == MINIMUM) { + nFeeNeeded = GetMinimumFee(txNew, nBytes, currentConfirmationTarget, mempool); + } else { + // Force the fee rate higher + nFeeNeeded = GetDogecoinPriorityFee(txNew, nBytes, nPriority); + } if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) { nFeeNeeded = coinControl->nMinimumTotalFee; } @@ -2887,6 +2894,31 @@ CAmount CWallet::GetMinimumFee(const CMutableTransaction& tx, unsigned int nTxBy } +CAmount CWallet::GetDogecoinPriorityFee(const CMutableTransaction& tx, unsigned int nTxBytes, FeeRatePreset nPriority) +{ + // payTxFee is the user-set global for desired feerate + return GetDogecoinPriorityFee(tx, nTxBytes, nPriority, payTxFee.GetFee(nTxBytes)); +} +CAmount CWallet::GetDogecoinPriorityFee(const CMutableTransaction& tx, unsigned int nTxBytes, FeeRatePreset nPriority, CAmount targetFee) +{ + CAmount nFeeNeeded = targetFee; + // User didn't set: use -txconfirmtarget to estimate... + if (nFeeNeeded == 0) { + nFeeNeeded = GetDogecoinFeeRate(nPriority).GetFee(nTxBytes); + } + // prevent user from paying a fee below minRelayTxFee or minTxFee + // Dogecoin: as we're adapting minTxFee to never be higher than + // payTxFee unless explicitly set, this should be fine + nFeeNeeded = std::max(nFeeNeeded, GetRequiredFee(tx, nTxBytes)); + + // But always obey the maximum + if (nFeeNeeded > maxTxFee) + nFeeNeeded = maxTxFee; + + return nFeeNeeded; +} + + DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { if (!fFileBacked) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d81e4d1b2..1ff5ffebe 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -8,6 +8,7 @@ #include "amount.h" #include "auxpow.h" +#include "dogecoin-fees.h" #include "streams.h" #include "tinyformat.h" #include "ui_interface.h" @@ -54,8 +55,6 @@ static const CAmount DEFAULT_TRANSACTION_FEE = COIN / 100; static const CAmount DEFAULT_FALLBACK_FEE = COIN / 100; //! -mintxfee default static const CAmount DEFAULT_TRANSACTION_MINFEE = COIN / 100; -//mlumin 5/2021: adding a minimum Wallet fee vs relay, currently still 1 COIN, to be reduced. -static const CAmount DEFAULT_MIN_WALLET_TX_FEE = COIN / 100; //! minimum recommended increment for BIP 125 replacement txs static const CAmount WALLET_INCREMENTAL_RELAY_FEE = COIN/10 * 5; //! target minimum change amount @@ -761,6 +760,14 @@ public: * then fee estimation for nConfirmTarget */ static CAmount GetMinimumFee(const CMutableTransaction& tx, unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, CAmount targetFee); + /** + * Dogecoin: Get a fee targetting a specific transaction speed. + */ + CAmount GetDogecoinPriorityFee(const CMutableTransaction& tx, unsigned int nTxBytes, FeeRatePreset nSpeed); + /** + * Dogecoin: Get a fee targetting a specific transaction speed. + */ + static CAmount GetDogecoinPriorityFee(const CMutableTransaction& tx, unsigned int nTxBytes, FeeRatePreset nSpeed, CAmount targetFee); /** * Return the minimum required fee taking into account the * floating relay fee and user set minimum transaction fee