Skip to content

Commit 6859ad2

Browse files
committed
Merge #10706: Improve wallet fee logic and fix GUI bugs
11590d3 Properly bound check conf_target in wallet RPC calls (Alex Morcos) fd29d3d Remove checking of mempool min fee from estimateSmartFee. (Alex Morcos) 2fffaa9 Make QT fee displays use GetMinimumFee instead of estimateSmartFee (Alex Morcos) 1983ca6 Use CoinControl to pass custom fee setting from QT. (Alex Morcos) 03ee701 Refactor to use CoinControl in GetMinimumFee and FeeBumper (Alex Morcos) ecd81df Make CoinControl a required argument to CreateTransaction (Alex Morcos) Pull request description: This builds on #10589 (first 5 commits from that PR, last 5 commits are new) The first couple commits refactor to use the CCoinControl class to pass fee calculation parameters around. This allows for fixing the buggy interaction in QT between the global payTxFee which can be modified by the RPC call settxfee or temporarily modified by the QT custom fee settings. Before these changes the GUI could sometimes send a transaction with a recently set payTxFee and not respect the settings displayed in the GUI. After these changes, using the GUI does not involve the global transaction confirm target or payTxFee. The prospective fee displays in the smart fee slider and the coin control dialog are changed to use the fee calculation from GetMinimumFee, this simplifies the code and makes them slightly more correct in edge cases. Maxing the fee calculation with the mempool min fee is move from estimateSmartFee to GetMinimumFee. This fixes a long standing bug, and should be tagged for 0.15 as it is holding up finalizing the estimatesmartfee RPC API before release. Tree-SHA512: 4d36a1bd5934aa62f3806d380fcafbef73e9fe5bdf190fc5259a3e3a13349e5ce796e50e7068c46dc630ccf56d061bce5804f0bfe2e082bb01ca725b63efd4c1
2 parents bf0a08b + 11590d3 commit 6859ad2

17 files changed

+195
-227
lines changed

src/policy/fees.cpp

Lines changed: 51 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -826,89 +826,81 @@ double CBlockPolicyEstimator::estimateConservativeFee(unsigned int doubleTarget,
826826
* estimates, however, required the 95% threshold at 2 * target be met for any
827827
* longer time horizons also.
828828
*/
829-
CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation *feeCalc, const CTxMemPool& pool, bool conservative) const
829+
CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation *feeCalc, bool conservative) const
830830
{
831+
LOCK(cs_feeEstimator);
832+
831833
if (feeCalc) {
832834
feeCalc->desiredTarget = confTarget;
833835
feeCalc->returnedTarget = confTarget;
834836
}
835837

836838
double median = -1;
837839
EstimationResult tempResult;
838-
{
839-
LOCK(cs_feeEstimator);
840840

841-
// Return failure if trying to analyze a target we're not tracking
842-
if (confTarget <= 0 || (unsigned int)confTarget > longStats->GetMaxConfirms())
843-
return CFeeRate(0);
841+
// Return failure if trying to analyze a target we're not tracking
842+
if (confTarget <= 0 || (unsigned int)confTarget > longStats->GetMaxConfirms())
843+
return CFeeRate(0);
844844

845-
// It's not possible to get reasonable estimates for confTarget of 1
846-
if (confTarget == 1)
847-
confTarget = 2;
845+
// It's not possible to get reasonable estimates for confTarget of 1
846+
if (confTarget == 1)
847+
confTarget = 2;
848848

849-
unsigned int maxUsableEstimate = MaxUsableEstimate();
850-
if (maxUsableEstimate <= 1)
851-
return CFeeRate(0);
849+
unsigned int maxUsableEstimate = MaxUsableEstimate();
850+
if (maxUsableEstimate <= 1)
851+
return CFeeRate(0);
852852

853-
if ((unsigned int)confTarget > maxUsableEstimate) {
854-
confTarget = maxUsableEstimate;
855-
}
853+
if ((unsigned int)confTarget > maxUsableEstimate) {
854+
confTarget = maxUsableEstimate;
855+
}
856856

857-
assert(confTarget > 0); //estimateCombinedFee and estimateConservativeFee take unsigned ints
858-
/** true is passed to estimateCombined fee for target/2 and target so
859-
* that we check the max confirms for shorter time horizons as well.
860-
* This is necessary to preserve monotonically increasing estimates.
861-
* For non-conservative estimates we do the same thing for 2*target, but
862-
* for conservative estimates we want to skip these shorter horizons
863-
* checks for 2*target because we are taking the max over all time
864-
* horizons so we already have monotonically increasing estimates and
865-
* the purpose of conservative estimates is not to let short term
866-
* fluctuations lower our estimates by too much.
867-
*/
868-
double halfEst = estimateCombinedFee(confTarget/2, HALF_SUCCESS_PCT, true, &tempResult);
857+
assert(confTarget > 0); //estimateCombinedFee and estimateConservativeFee take unsigned ints
858+
/** true is passed to estimateCombined fee for target/2 and target so
859+
* that we check the max confirms for shorter time horizons as well.
860+
* This is necessary to preserve monotonically increasing estimates.
861+
* For non-conservative estimates we do the same thing for 2*target, but
862+
* for conservative estimates we want to skip these shorter horizons
863+
* checks for 2*target because we are taking the max over all time
864+
* horizons so we already have monotonically increasing estimates and
865+
* the purpose of conservative estimates is not to let short term
866+
* fluctuations lower our estimates by too much.
867+
*/
868+
double halfEst = estimateCombinedFee(confTarget/2, HALF_SUCCESS_PCT, true, &tempResult);
869+
if (feeCalc) {
870+
feeCalc->est = tempResult;
871+
feeCalc->reason = FeeReason::HALF_ESTIMATE;
872+
}
873+
median = halfEst;
874+
double actualEst = estimateCombinedFee(confTarget, SUCCESS_PCT, true, &tempResult);
875+
if (actualEst > median) {
876+
median = actualEst;
869877
if (feeCalc) {
870878
feeCalc->est = tempResult;
871-
feeCalc->reason = FeeReason::HALF_ESTIMATE;
879+
feeCalc->reason = FeeReason::FULL_ESTIMATE;
872880
}
873-
median = halfEst;
874-
double actualEst = estimateCombinedFee(confTarget, SUCCESS_PCT, true, &tempResult);
875-
if (actualEst > median) {
876-
median = actualEst;
877-
if (feeCalc) {
878-
feeCalc->est = tempResult;
879-
feeCalc->reason = FeeReason::FULL_ESTIMATE;
880-
}
881+
}
882+
double doubleEst = estimateCombinedFee(2 * confTarget, DOUBLE_SUCCESS_PCT, !conservative, &tempResult);
883+
if (doubleEst > median) {
884+
median = doubleEst;
885+
if (feeCalc) {
886+
feeCalc->est = tempResult;
887+
feeCalc->reason = FeeReason::DOUBLE_ESTIMATE;
881888
}
882-
double doubleEst = estimateCombinedFee(2 * confTarget, DOUBLE_SUCCESS_PCT, !conservative, &tempResult);
883-
if (doubleEst > median) {
884-
median = doubleEst;
889+
}
890+
891+
if (conservative || median == -1) {
892+
double consEst = estimateConservativeFee(2 * confTarget, &tempResult);
893+
if (consEst > median) {
894+
median = consEst;
885895
if (feeCalc) {
886896
feeCalc->est = tempResult;
887-
feeCalc->reason = FeeReason::DOUBLE_ESTIMATE;
897+
feeCalc->reason = FeeReason::CONSERVATIVE;
888898
}
889899
}
890-
891-
if (conservative || median == -1) {
892-
double consEst = estimateConservativeFee(2 * confTarget, &tempResult);
893-
if (consEst > median) {
894-
median = consEst;
895-
if (feeCalc) {
896-
feeCalc->est = tempResult;
897-
feeCalc->reason = FeeReason::CONSERVATIVE;
898-
}
899-
}
900-
}
901-
} // Must unlock cs_feeEstimator before taking mempool locks
900+
}
902901

903902
if (feeCalc) feeCalc->returnedTarget = confTarget;
904903

905-
// If mempool is limiting txs , return at least the min feerate from the mempool
906-
CAmount minPoolFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK();
907-
if (minPoolFee > 0 && minPoolFee > median) {
908-
if (feeCalc) feeCalc->reason = FeeReason::MEMPOOL_MIN;
909-
return CFeeRate(minPoolFee);
910-
}
911-
912904
if (median < 0)
913905
return CFeeRate(0);
914906

src/policy/fees.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ class CBlockPolicyEstimator
208208
* the closest target where one can be given. 'conservative' estimates are
209209
* valid over longer time horizons also.
210210
*/
211-
CFeeRate estimateSmartFee(int confTarget, FeeCalculation *feeCalc, const CTxMemPool& pool, bool conservative) const;
211+
CFeeRate estimateSmartFee(int confTarget, FeeCalculation *feeCalc, bool conservative) const;
212212

213213
/** Return a specific fee estimate calculation with a given success
214214
* threshold and time horizon, and optionally return detailed data about

src/qt/coincontroldialog.cpp

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -490,8 +490,6 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
490490
else nBytesInputs += 148;
491491
}
492492

493-
bool conservative_estimate = CalculateEstimateType(FeeEstimateMode::UNSET, coinControl->signalRbf);
494-
495493
// calculation
496494
if (nQuantity > 0)
497495
{
@@ -512,7 +510,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
512510
nBytes -= 34;
513511

514512
// Fee
515-
nPayFee = CWallet::GetMinimumFee(nBytes, coinControl->nConfirmTarget, ::mempool, ::feeEstimator, nullptr /* FeeCalculation */, false /* ignoreGlobalPayTxFee */, conservative_estimate);
513+
nPayFee = CWallet::GetMinimumFee(nBytes, *coinControl, ::mempool, ::feeEstimator, nullptr /* FeeCalculation */);
516514

517515
if (nPayAmount > 0)
518516
{
@@ -583,12 +581,8 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
583581
QString toolTipDust = tr("This label turns red if any recipient receives an amount smaller than the current dust threshold.");
584582

585583
// how many satoshis the estimated fee can vary per byte we guess wrong
586-
double dFeeVary;
587-
if (payTxFee.GetFeePerK() > 0)
588-
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), payTxFee.GetFeePerK()) / 1000;
589-
else {
590-
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), ::feeEstimator.estimateSmartFee(coinControl->nConfirmTarget, NULL, ::mempool, conservative_estimate).GetFeePerK()) / 1000;
591-
}
584+
double dFeeVary = (double)nPayFee / nBytes;
585+
592586
QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary);
593587

594588
l3->setToolTip(toolTip4);

src/qt/sendcoinsdialog.cpp

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -175,26 +175,20 @@ void SendCoinsDialog::setModel(WalletModel *_model)
175175
ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
176176
}
177177
connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSmartFeeLabel()));
178-
connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(updateGlobalFeeVariables()));
179178
connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(coinControlUpdateLabels()));
180179
connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls()));
181-
connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables()));
182180
connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
183-
connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables()));
184181
connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
185-
connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(updateGlobalFeeVariables()));
186182
connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels()));
187183
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee()));
188184
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls()));
189-
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables()));
190185
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
191186
connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel()));
192187
connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
193188
ui->customFee->setSingleStep(CWallet::GetRequiredFee(1000));
194189
updateFeeSectionControls();
195190
updateMinFeeLabel();
196191
updateSmartFeeLabel();
197-
updateGlobalFeeVariables();
198192

199193
// set default rbf checkbox state
200194
ui->optInRBF->setCheckState(model->getDefaultWalletRbf() ? Qt::Checked : Qt::Unchecked);
@@ -274,14 +268,10 @@ void SendCoinsDialog::on_sendButton_clicked()
274268
CCoinControl ctrl;
275269
if (model->getOptionsModel()->getCoinControlFeatures())
276270
ctrl = *CoinControlDialog::coinControl;
277-
if (ui->radioSmartFee->isChecked())
278-
ctrl.nConfirmTarget = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
279-
else
280-
ctrl.nConfirmTarget = 0;
281271

282-
ctrl.signalRbf = ui->optInRBF->isChecked();
272+
updateCoinControlState(ctrl);
283273

284-
prepareStatus = model->prepareTransaction(currentTransaction, &ctrl);
274+
prepareStatus = model->prepareTransaction(currentTransaction, ctrl);
285275

286276
// process prepareStatus and on error generate message shown to user
287277
processSendCoinsReturn(prepareStatus,
@@ -636,18 +626,6 @@ void SendCoinsDialog::updateFeeSectionControls()
636626
ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
637627
}
638628

639-
void SendCoinsDialog::updateGlobalFeeVariables()
640-
{
641-
if (ui->radioSmartFee->isChecked())
642-
{
643-
payTxFee = CFeeRate(0);
644-
}
645-
else
646-
{
647-
payTxFee = CFeeRate(ui->customFee->value());
648-
}
649-
}
650-
651629
void SendCoinsDialog::updateFeeMinimizedLabel()
652630
{
653631
if(!model || !model->getOptionsModel())
@@ -669,19 +647,32 @@ void SendCoinsDialog::updateMinFeeLabel()
669647
);
670648
}
671649

650+
void SendCoinsDialog::updateCoinControlState(CCoinControl& ctrl)
651+
{
652+
if (ui->radioCustomFee->isChecked()) {
653+
ctrl.m_feerate = CFeeRate(ui->customFee->value());
654+
} else {
655+
ctrl.m_feerate.reset();
656+
}
657+
// Avoid using global defaults when sending money from the GUI
658+
// Either custom fee will be used or if not selected, the confirmation target from dropdown box
659+
ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
660+
ctrl.signalRbf = ui->optInRBF->isChecked();
661+
}
662+
672663
void SendCoinsDialog::updateSmartFeeLabel()
673664
{
674665
if(!model || !model->getOptionsModel())
675666
return;
676-
677-
int nBlocksToConfirm = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
667+
CCoinControl coin_control;
668+
updateCoinControlState(coin_control);
669+
coin_control.m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
678670
FeeCalculation feeCalc;
679-
bool conservative_estimate = CalculateEstimateType(FeeEstimateMode::UNSET, ui->optInRBF->isChecked());
680-
CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocksToConfirm, &feeCalc, ::mempool, conservative_estimate);
681-
if (feeRate <= CFeeRate(0)) // not enough data => minfee
682-
{
683-
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
684-
std::max(CWallet::fallbackFee.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB");
671+
CFeeRate feeRate = CFeeRate(CWallet::GetMinimumFee(1000, coin_control, ::mempool, ::feeEstimator, &feeCalc));
672+
673+
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB");
674+
675+
if (feeCalc.reason == FeeReason::FALLBACK) {
685676
ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
686677
ui->labelFeeEstimation->setText("");
687678
ui->fallbackFeeWarningLabel->setVisible(true);
@@ -692,8 +683,6 @@ void SendCoinsDialog::updateSmartFeeLabel()
692683
}
693684
else
694685
{
695-
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
696-
std::max(feeRate.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB");
697686
ui->labelSmartFee2->hide();
698687
ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", feeCalc.returnedTarget));
699688
ui->fallbackFeeWarningLabel->setVisible(false);
@@ -752,8 +741,6 @@ void SendCoinsDialog::coinControlFeatureChanged(bool checked)
752741
if (!checked && model) // coin control features disabled
753742
CoinControlDialog::coinControl->SetNull();
754743

755-
// make sure we set back the confirmation target
756-
updateGlobalFeeVariables();
757744
coinControlUpdateLabels();
758745
}
759746

@@ -844,15 +831,11 @@ void SendCoinsDialog::coinControlUpdateLabels()
844831
if (!model || !model->getOptionsModel())
845832
return;
846833

834+
updateCoinControlState(*CoinControlDialog::coinControl);
835+
847836
// set pay amounts
848837
CoinControlDialog::payAmounts.clear();
849838
CoinControlDialog::fSubtractFeeFromAmount = false;
850-
if (ui->radioSmartFee->isChecked()) {
851-
CoinControlDialog::coinControl->nConfirmTarget = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
852-
} else {
853-
CoinControlDialog::coinControl->nConfirmTarget = model->getDefaultConfirmTarget();
854-
}
855-
CoinControlDialog::coinControl->signalRbf = ui->optInRBF->isChecked();
856839

857840
for(int i = 0; i < ui->entries->count(); ++i)
858841
{

src/qt/sendcoinsdialog.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ public Q_SLOTS:
6868
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg = QString());
6969
void minimizeFeeSection(bool fMinimize);
7070
void updateFeeMinimizedLabel();
71+
// Update the passed in CCoinControl with state from the GUI
72+
void updateCoinControlState(CCoinControl& ctrl);
7173

7274
private Q_SLOTS:
7375
void on_sendButton_clicked();
@@ -91,7 +93,6 @@ private Q_SLOTS:
9193
void updateFeeSectionControls();
9294
void updateMinFeeLabel();
9395
void updateSmartFeeLabel();
94-
void updateGlobalFeeVariables();
9596

9697
Q_SIGNALS:
9798
// Fired when a message should be reported to the user

src/qt/walletmodel.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "sync.h"
2525
#include "ui_interface.h"
2626
#include "util.h" // for GetBoolArg
27+
#include "wallet/coincontrol.h"
2728
#include "wallet/feebumper.h"
2829
#include "wallet/wallet.h"
2930
#include "wallet/walletdb.h" // for BackupWallet
@@ -191,7 +192,7 @@ bool WalletModel::validateAddress(const QString &address)
191192
return addressParsed.IsValid();
192193
}
193194

194-
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl)
195+
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl)
195196
{
196197
CAmount total = 0;
197198
bool fSubtractFeeFromAmount = false;
@@ -258,7 +259,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
258259
return DuplicateAddress;
259260
}
260261

261-
CAmount nBalance = getBalance(coinControl);
262+
CAmount nBalance = getBalance(&coinControl);
262263

263264
if(total > nBalance)
264265
{
@@ -667,8 +668,10 @@ bool WalletModel::bumpFee(uint256 hash)
667668
{
668669
std::unique_ptr<CFeeBumper> feeBump;
669670
{
671+
CCoinControl coin_control;
672+
coin_control.signalRbf = true;
670673
LOCK2(cs_main, wallet->cs_wallet);
671-
feeBump.reset(new CFeeBumper(wallet, hash, nTxConfirmTarget, false, 0, true, FeeEstimateMode::UNSET));
674+
feeBump.reset(new CFeeBumper(wallet, hash, coin_control, 0));
672675
}
673676
if (feeBump->getResult() != BumpFeeResult::OK)
674677
{

src/qt/walletmodel.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ class WalletModel : public QObject
154154
};
155155

156156
// prepare transaction for getting txfee before sending coins
157-
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl = NULL);
157+
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl);
158158

159159
// Send coins to a list of recipients
160160
SendCoinsReturn sendCoins(WalletModelTransaction &transaction);

0 commit comments

Comments
 (0)