Browse Source

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
0.15
Wladimir J. van der Laan 7 years ago
parent
commit
6859ad2936
No known key found for this signature in database
GPG Key ID: 1E4AED62986CD25D
  1. 110
      src/policy/fees.cpp
  2. 2
      src/policy/fees.h
  3. 12
      src/qt/coincontroldialog.cpp
  4. 67
      src/qt/sendcoinsdialog.cpp
  5. 3
      src/qt/sendcoinsdialog.h
  6. 9
      src/qt/walletmodel.cpp
  7. 2
      src/qt/walletmodel.h
  8. 13
      src/rpc/mining.cpp
  9. 3
      src/rpc/mining.h
  10. 10
      src/test/policyestimator_tests.cpp
  11. 16
      src/wallet/coincontrol.h
  12. 8
      src/wallet/feebumper.cpp
  13. 3
      src/wallet/feebumper.h
  14. 44
      src/wallet/rpcwallet.cpp
  15. 4
      src/wallet/test/wallet_tests.cpp
  16. 110
      src/wallet/wallet.cpp
  17. 6
      src/wallet/wallet.h

110
src/policy/fees.cpp

@ -826,8 +826,10 @@ double CBlockPolicyEstimator::estimateConservativeFee(unsigned int doubleTarget,
* estimates, however, required the 95% threshold at 2 * target be met for any * estimates, however, required the 95% threshold at 2 * target be met for any
* longer time horizons also. * longer time horizons also.
*/ */
CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation *feeCalc, const CTxMemPool& pool, bool conservative) const CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation *feeCalc, bool conservative) const
{ {
LOCK(cs_feeEstimator);
if (feeCalc) { if (feeCalc) {
feeCalc->desiredTarget = confTarget; feeCalc->desiredTarget = confTarget;
feeCalc->returnedTarget = confTarget; feeCalc->returnedTarget = confTarget;
@ -835,80 +837,70 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation
double median = -1; double median = -1;
EstimationResult tempResult; EstimationResult tempResult;
{
LOCK(cs_feeEstimator);
// Return failure if trying to analyze a target we're not tracking // Return failure if trying to analyze a target we're not tracking
if (confTarget <= 0 || (unsigned int)confTarget > longStats->GetMaxConfirms()) if (confTarget <= 0 || (unsigned int)confTarget > longStats->GetMaxConfirms())
return CFeeRate(0); return CFeeRate(0);
// It's not possible to get reasonable estimates for confTarget of 1 // It's not possible to get reasonable estimates for confTarget of 1
if (confTarget == 1) if (confTarget == 1)
confTarget = 2; confTarget = 2;
unsigned int maxUsableEstimate = MaxUsableEstimate(); unsigned int maxUsableEstimate = MaxUsableEstimate();
if (maxUsableEstimate <= 1) if (maxUsableEstimate <= 1)
return CFeeRate(0); return CFeeRate(0);
if ((unsigned int)confTarget > maxUsableEstimate) { if ((unsigned int)confTarget > maxUsableEstimate) {
confTarget = maxUsableEstimate; confTarget = maxUsableEstimate;
} }
assert(confTarget > 0); //estimateCombinedFee and estimateConservativeFee take unsigned ints assert(confTarget > 0); //estimateCombinedFee and estimateConservativeFee take unsigned ints
/** true is passed to estimateCombined fee for target/2 and target so /** true is passed to estimateCombined fee for target/2 and target so
* that we check the max confirms for shorter time horizons as well. * that we check the max confirms for shorter time horizons as well.
* This is necessary to preserve monotonically increasing estimates. * This is necessary to preserve monotonically increasing estimates.
* For non-conservative estimates we do the same thing for 2*target, but * For non-conservative estimates we do the same thing for 2*target, but
* for conservative estimates we want to skip these shorter horizons * for conservative estimates we want to skip these shorter horizons
* checks for 2*target because we are taking the max over all time * checks for 2*target because we are taking the max over all time
* horizons so we already have monotonically increasing estimates and * horizons so we already have monotonically increasing estimates and
* the purpose of conservative estimates is not to let short term * the purpose of conservative estimates is not to let short term
* fluctuations lower our estimates by too much. * fluctuations lower our estimates by too much.
*/ */
double halfEst = estimateCombinedFee(confTarget/2, HALF_SUCCESS_PCT, true, &tempResult); double halfEst = estimateCombinedFee(confTarget/2, HALF_SUCCESS_PCT, true, &tempResult);
if (feeCalc) {
feeCalc->est = tempResult;
feeCalc->reason = FeeReason::HALF_ESTIMATE;
}
median = halfEst;
double actualEst = estimateCombinedFee(confTarget, SUCCESS_PCT, true, &tempResult);
if (actualEst > median) {
median = actualEst;
if (feeCalc) { if (feeCalc) {
feeCalc->est = tempResult; feeCalc->est = tempResult;
feeCalc->reason = FeeReason::HALF_ESTIMATE; feeCalc->reason = FeeReason::FULL_ESTIMATE;
} }
median = halfEst; }
double actualEst = estimateCombinedFee(confTarget, SUCCESS_PCT, true, &tempResult); double doubleEst = estimateCombinedFee(2 * confTarget, DOUBLE_SUCCESS_PCT, !conservative, &tempResult);
if (actualEst > median) { if (doubleEst > median) {
median = actualEst; median = doubleEst;
if (feeCalc) { if (feeCalc) {
feeCalc->est = tempResult; feeCalc->est = tempResult;
feeCalc->reason = FeeReason::FULL_ESTIMATE; feeCalc->reason = FeeReason::DOUBLE_ESTIMATE;
}
} }
double doubleEst = estimateCombinedFee(2 * confTarget, DOUBLE_SUCCESS_PCT, !conservative, &tempResult); }
if (doubleEst > median) {
median = doubleEst; if (conservative || median == -1) {
double consEst = estimateConservativeFee(2 * confTarget, &tempResult);
if (consEst > median) {
median = consEst;
if (feeCalc) { if (feeCalc) {
feeCalc->est = tempResult; feeCalc->est = tempResult;
feeCalc->reason = FeeReason::DOUBLE_ESTIMATE; feeCalc->reason = FeeReason::CONSERVATIVE;
} }
} }
}
if (conservative || median == -1) {
double consEst = estimateConservativeFee(2 * confTarget, &tempResult);
if (consEst > median) {
median = consEst;
if (feeCalc) {
feeCalc->est = tempResult;
feeCalc->reason = FeeReason::CONSERVATIVE;
}
}
}
} // Must unlock cs_feeEstimator before taking mempool locks
if (feeCalc) feeCalc->returnedTarget = confTarget; if (feeCalc) feeCalc->returnedTarget = confTarget;
// If mempool is limiting txs , return at least the min feerate from the mempool
CAmount minPoolFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK();
if (minPoolFee > 0 && minPoolFee > median) {
if (feeCalc) feeCalc->reason = FeeReason::MEMPOOL_MIN;
return CFeeRate(minPoolFee);
}
if (median < 0) if (median < 0)
return CFeeRate(0); return CFeeRate(0);

2
src/policy/fees.h

@ -208,7 +208,7 @@ public:
* the closest target where one can be given. 'conservative' estimates are * the closest target where one can be given. 'conservative' estimates are
* valid over longer time horizons also. * valid over longer time horizons also.
*/ */
CFeeRate estimateSmartFee(int confTarget, FeeCalculation *feeCalc, const CTxMemPool& pool, bool conservative) const; CFeeRate estimateSmartFee(int confTarget, FeeCalculation *feeCalc, bool conservative) const;
/** Return a specific fee estimate calculation with a given success /** Return a specific fee estimate calculation with a given success
* threshold and time horizon, and optionally return detailed data about * threshold and time horizon, and optionally return detailed data about

12
src/qt/coincontroldialog.cpp

@ -490,8 +490,6 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
else nBytesInputs += 148; else nBytesInputs += 148;
} }
bool conservative_estimate = CalculateEstimateType(FeeEstimateMode::UNSET, coinControl->signalRbf);
// calculation // calculation
if (nQuantity > 0) if (nQuantity > 0)
{ {
@ -512,7 +510,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
nBytes -= 34; nBytes -= 34;
// Fee // Fee
nPayFee = CWallet::GetMinimumFee(nBytes, coinControl->nConfirmTarget, ::mempool, ::feeEstimator, nullptr /* FeeCalculation */, false /* ignoreGlobalPayTxFee */, conservative_estimate); nPayFee = CWallet::GetMinimumFee(nBytes, *coinControl, ::mempool, ::feeEstimator, nullptr /* FeeCalculation */);
if (nPayAmount > 0) if (nPayAmount > 0)
{ {
@ -583,12 +581,8 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
QString toolTipDust = tr("This label turns red if any recipient receives an amount smaller than the current dust threshold."); QString toolTipDust = tr("This label turns red if any recipient receives an amount smaller than the current dust threshold.");
// how many satoshis the estimated fee can vary per byte we guess wrong // how many satoshis the estimated fee can vary per byte we guess wrong
double dFeeVary; double dFeeVary = (double)nPayFee / nBytes;
if (payTxFee.GetFeePerK() > 0)
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), payTxFee.GetFeePerK()) / 1000;
else {
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), ::feeEstimator.estimateSmartFee(coinControl->nConfirmTarget, NULL, ::mempool, conservative_estimate).GetFeePerK()) / 1000;
}
QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary); QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary);
l3->setToolTip(toolTip4); l3->setToolTip(toolTip4);

67
src/qt/sendcoinsdialog.cpp

@ -175,18 +175,13 @@ void SendCoinsDialog::setModel(WalletModel *_model)
ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n)); ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
} }
connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSmartFeeLabel())); connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSmartFeeLabel()));
connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(updateGlobalFeeVariables()));
connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(coinControlUpdateLabels())); connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(coinControlUpdateLabels()));
connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls())); connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls()));
connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables()));
connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels())); connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(updateGlobalFeeVariables()));
connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels())); connect(ui->groupCustomFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(updateGlobalFeeVariables()));
connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels())); connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels()));
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee())); connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee()));
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls())); connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls()));
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateGlobalFeeVariables()));
connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels())); connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel())); connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(updateSmartFeeLabel()));
connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels())); connect(ui->optInRBF, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
@ -194,7 +189,6 @@ void SendCoinsDialog::setModel(WalletModel *_model)
updateFeeSectionControls(); updateFeeSectionControls();
updateMinFeeLabel(); updateMinFeeLabel();
updateSmartFeeLabel(); updateSmartFeeLabel();
updateGlobalFeeVariables();
// set default rbf checkbox state // set default rbf checkbox state
ui->optInRBF->setCheckState(model->getDefaultWalletRbf() ? Qt::Checked : Qt::Unchecked); ui->optInRBF->setCheckState(model->getDefaultWalletRbf() ? Qt::Checked : Qt::Unchecked);
@ -274,14 +268,10 @@ void SendCoinsDialog::on_sendButton_clicked()
CCoinControl ctrl; CCoinControl ctrl;
if (model->getOptionsModel()->getCoinControlFeatures()) if (model->getOptionsModel()->getCoinControlFeatures())
ctrl = *CoinControlDialog::coinControl; ctrl = *CoinControlDialog::coinControl;
if (ui->radioSmartFee->isChecked())
ctrl.nConfirmTarget = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
else
ctrl.nConfirmTarget = 0;
ctrl.signalRbf = ui->optInRBF->isChecked(); updateCoinControlState(ctrl);
prepareStatus = model->prepareTransaction(currentTransaction, &ctrl); prepareStatus = model->prepareTransaction(currentTransaction, ctrl);
// process prepareStatus and on error generate message shown to user // process prepareStatus and on error generate message shown to user
processSendCoinsReturn(prepareStatus, processSendCoinsReturn(prepareStatus,
@ -636,18 +626,6 @@ void SendCoinsDialog::updateFeeSectionControls()
ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked()); ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
} }
void SendCoinsDialog::updateGlobalFeeVariables()
{
if (ui->radioSmartFee->isChecked())
{
payTxFee = CFeeRate(0);
}
else
{
payTxFee = CFeeRate(ui->customFee->value());
}
}
void SendCoinsDialog::updateFeeMinimizedLabel() void SendCoinsDialog::updateFeeMinimizedLabel()
{ {
if(!model || !model->getOptionsModel()) if(!model || !model->getOptionsModel())
@ -669,19 +647,32 @@ void SendCoinsDialog::updateMinFeeLabel()
); );
} }
void SendCoinsDialog::updateCoinControlState(CCoinControl& ctrl)
{
if (ui->radioCustomFee->isChecked()) {
ctrl.m_feerate = CFeeRate(ui->customFee->value());
} else {
ctrl.m_feerate.reset();
}
// Avoid using global defaults when sending money from the GUI
// Either custom fee will be used or if not selected, the confirmation target from dropdown box
ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
ctrl.signalRbf = ui->optInRBF->isChecked();
}
void SendCoinsDialog::updateSmartFeeLabel() void SendCoinsDialog::updateSmartFeeLabel()
{ {
if(!model || !model->getOptionsModel()) if(!model || !model->getOptionsModel())
return; return;
CCoinControl coin_control;
int nBlocksToConfirm = getConfTargetForIndex(ui->confTargetSelector->currentIndex()); updateCoinControlState(coin_control);
coin_control.m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
FeeCalculation feeCalc; FeeCalculation feeCalc;
bool conservative_estimate = CalculateEstimateType(FeeEstimateMode::UNSET, ui->optInRBF->isChecked()); CFeeRate feeRate = CFeeRate(CWallet::GetMinimumFee(1000, coin_control, ::mempool, ::feeEstimator, &feeCalc));
CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocksToConfirm, &feeCalc, ::mempool, conservative_estimate);
if (feeRate <= CFeeRate(0)) // not enough data => minfee ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB");
{
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), if (feeCalc.reason == FeeReason::FALLBACK) {
std::max(CWallet::fallbackFee.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB");
ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...) ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
ui->labelFeeEstimation->setText(""); ui->labelFeeEstimation->setText("");
ui->fallbackFeeWarningLabel->setVisible(true); ui->fallbackFeeWarningLabel->setVisible(true);
@ -692,8 +683,6 @@ void SendCoinsDialog::updateSmartFeeLabel()
} }
else else
{ {
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
std::max(feeRate.GetFeePerK(), CWallet::GetRequiredFee(1000))) + "/kB");
ui->labelSmartFee2->hide(); ui->labelSmartFee2->hide();
ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", feeCalc.returnedTarget)); ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", feeCalc.returnedTarget));
ui->fallbackFeeWarningLabel->setVisible(false); ui->fallbackFeeWarningLabel->setVisible(false);
@ -752,8 +741,6 @@ void SendCoinsDialog::coinControlFeatureChanged(bool checked)
if (!checked && model) // coin control features disabled if (!checked && model) // coin control features disabled
CoinControlDialog::coinControl->SetNull(); CoinControlDialog::coinControl->SetNull();
// make sure we set back the confirmation target
updateGlobalFeeVariables();
coinControlUpdateLabels(); coinControlUpdateLabels();
} }
@ -844,15 +831,11 @@ void SendCoinsDialog::coinControlUpdateLabels()
if (!model || !model->getOptionsModel()) if (!model || !model->getOptionsModel())
return; return;
updateCoinControlState(*CoinControlDialog::coinControl);
// set pay amounts // set pay amounts
CoinControlDialog::payAmounts.clear(); CoinControlDialog::payAmounts.clear();
CoinControlDialog::fSubtractFeeFromAmount = false; CoinControlDialog::fSubtractFeeFromAmount = false;
if (ui->radioSmartFee->isChecked()) {
CoinControlDialog::coinControl->nConfirmTarget = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
} else {
CoinControlDialog::coinControl->nConfirmTarget = model->getDefaultConfirmTarget();
}
CoinControlDialog::coinControl->signalRbf = ui->optInRBF->isChecked();
for(int i = 0; i < ui->entries->count(); ++i) for(int i = 0; i < ui->entries->count(); ++i)
{ {

3
src/qt/sendcoinsdialog.h

@ -68,6 +68,8 @@ private:
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg = QString()); void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg = QString());
void minimizeFeeSection(bool fMinimize); void minimizeFeeSection(bool fMinimize);
void updateFeeMinimizedLabel(); void updateFeeMinimizedLabel();
// Update the passed in CCoinControl with state from the GUI
void updateCoinControlState(CCoinControl& ctrl);
private Q_SLOTS: private Q_SLOTS:
void on_sendButton_clicked(); void on_sendButton_clicked();
@ -91,7 +93,6 @@ private Q_SLOTS:
void updateFeeSectionControls(); void updateFeeSectionControls();
void updateMinFeeLabel(); void updateMinFeeLabel();
void updateSmartFeeLabel(); void updateSmartFeeLabel();
void updateGlobalFeeVariables();
Q_SIGNALS: Q_SIGNALS:
// Fired when a message should be reported to the user // Fired when a message should be reported to the user

9
src/qt/walletmodel.cpp

@ -24,6 +24,7 @@
#include "sync.h" #include "sync.h"
#include "ui_interface.h" #include "ui_interface.h"
#include "util.h" // for GetBoolArg #include "util.h" // for GetBoolArg
#include "wallet/coincontrol.h"
#include "wallet/feebumper.h" #include "wallet/feebumper.h"
#include "wallet/wallet.h" #include "wallet/wallet.h"
#include "wallet/walletdb.h" // for BackupWallet #include "wallet/walletdb.h" // for BackupWallet
@ -191,7 +192,7 @@ bool WalletModel::validateAddress(const QString &address)
return addressParsed.IsValid(); return addressParsed.IsValid();
} }
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl) WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl)
{ {
CAmount total = 0; CAmount total = 0;
bool fSubtractFeeFromAmount = false; bool fSubtractFeeFromAmount = false;
@ -258,7 +259,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
return DuplicateAddress; return DuplicateAddress;
} }
CAmount nBalance = getBalance(coinControl); CAmount nBalance = getBalance(&coinControl);
if(total > nBalance) if(total > nBalance)
{ {
@ -667,8 +668,10 @@ bool WalletModel::bumpFee(uint256 hash)
{ {
std::unique_ptr<CFeeBumper> feeBump; std::unique_ptr<CFeeBumper> feeBump;
{ {
CCoinControl coin_control;
coin_control.signalRbf = true;
LOCK2(cs_main, wallet->cs_wallet); LOCK2(cs_main, wallet->cs_wallet);
feeBump.reset(new CFeeBumper(wallet, hash, nTxConfirmTarget, false, 0, true, FeeEstimateMode::UNSET)); feeBump.reset(new CFeeBumper(wallet, hash, coin_control, 0));
} }
if (feeBump->getResult() != BumpFeeResult::OK) if (feeBump->getResult() != BumpFeeResult::OK)
{ {

2
src/qt/walletmodel.h

@ -154,7 +154,7 @@ public:
}; };
// prepare transaction for getting txfee before sending coins // prepare transaction for getting txfee before sending coins
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl = NULL); SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl);
// Send coins to a list of recipients // Send coins to a list of recipients
SendCoinsReturn sendCoins(WalletModelTransaction &transaction); SendCoinsReturn sendCoins(WalletModelTransaction &transaction);

13
src/rpc/mining.cpp

@ -30,6 +30,16 @@
#include <univalue.h> #include <univalue.h>
unsigned int ParseConfirmTarget(const UniValue& value)
{
int target = value.get_int();
unsigned int max_target = ::feeEstimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
if (target < 1 || (unsigned int)target > max_target) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid conf_target, must be between %u - %u", 1, max_target));
}
return (unsigned int)target;
}
/** /**
* Return average network hashes per second based on the last 'lookup' blocks, * Return average network hashes per second based on the last 'lookup' blocks,
* or from the last difficulty change if 'lookup' is nonpositive. * or from the last difficulty change if 'lookup' is nonpositive.
@ -815,7 +825,6 @@ UniValue estimatesmartfee(const JSONRPCRequest& request)
"\n" "\n"
"A negative value is returned if not enough transactions and blocks\n" "A negative value is returned if not enough transactions and blocks\n"
"have been observed to make an estimate for any number of blocks.\n" "have been observed to make an estimate for any number of blocks.\n"
"However it will not return a value below the mempool reject fee.\n"
"\nExample:\n" "\nExample:\n"
+ HelpExampleCli("estimatesmartfee", "6") + HelpExampleCli("estimatesmartfee", "6")
); );
@ -831,7 +840,7 @@ UniValue estimatesmartfee(const JSONRPCRequest& request)
UniValue result(UniValue::VOBJ); UniValue result(UniValue::VOBJ);
FeeCalculation feeCalc; FeeCalculation feeCalc;
CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocks, &feeCalc, ::mempool, conservative); CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocks, &feeCalc, conservative);
result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK()))); result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK())));
result.push_back(Pair("blocks", feeCalc.returnedTarget)); result.push_back(Pair("blocks", feeCalc.returnedTarget));
return result; return result;

3
src/rpc/mining.h

@ -12,4 +12,7 @@
/** Generate blocks (mine) */ /** Generate blocks (mine) */
UniValue generateBlocks(std::shared_ptr<CReserveScript> coinbaseScript, int nGenerate, uint64_t nMaxTries, bool keepScript); UniValue generateBlocks(std::shared_ptr<CReserveScript> coinbaseScript, int nGenerate, uint64_t nMaxTries, bool keepScript);
/** Check bounds on a command line confirm target */
unsigned int ParseConfirmTarget(const UniValue& value);
#endif #endif

10
src/test/policyestimator_tests.cpp

@ -177,16 +177,6 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
for (int i = 2; i < 9; i++) { // At 9, the original estimate was already at the bottom (b/c scale = 2) for (int i = 2; i < 9; i++) { // At 9, the original estimate was already at the bottom (b/c scale = 2)
BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee); BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee);
} }
// Test that if the mempool is limited, estimateSmartFee won't return a value below the mempool min fee
mpool.addUnchecked(tx.GetHash(), entry.Fee(feeV[5]).Time(GetTime()).Height(blocknum).FromTx(tx));
// evict that transaction which should set a mempool min fee of minRelayTxFee + feeV[5]
mpool.TrimToSize(1);
BOOST_CHECK(mpool.GetMinFee(1).GetFeePerK() > feeV[5]);
for (int i = 1; i < 10; i++) {
BOOST_CHECK(feeEst.estimateSmartFee(i, NULL, mpool, true).GetFeePerK() >= feeEst.estimateRawFee(i, 0.85, FeeEstimateHorizon::MED_HALFLIFE).GetFeePerK());
BOOST_CHECK(feeEst.estimateSmartFee(i, NULL, mpool, true).GetFeePerK() >= mpool.GetMinFee(1).GetFeePerK());
}
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

16
src/wallet/coincontrol.h

@ -10,6 +10,8 @@
#include "primitives/transaction.h" #include "primitives/transaction.h"
#include "wallet/wallet.h" #include "wallet/wallet.h"
#include <boost/optional.hpp>
/** Coin Control Features. */ /** Coin Control Features. */
class CCoinControl class CCoinControl
{ {
@ -19,12 +21,12 @@ public:
bool fAllowOtherInputs; bool fAllowOtherInputs;
//! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria //! Includes watch only addresses which match the ISMINE_WATCH_SOLVABLE criteria
bool fAllowWatchOnly; bool fAllowWatchOnly;
//! Override estimated feerate //! Override automatic min/max checks on fee, m_feerate must be set if true
bool fOverrideFeeRate; bool fOverrideFeeRate;
//! Feerate to use if overrideFeeRate is true //! Override the default payTxFee if set
CFeeRate nFeeRate; boost::optional<CFeeRate> m_feerate;
//! Override the default confirmation target, 0 = use default //! Override the default confirmation target if set
int nConfirmTarget; boost::optional<unsigned int> m_confirm_target;
//! Signal BIP-125 replace by fee. //! Signal BIP-125 replace by fee.
bool signalRbf; bool signalRbf;
//! Fee estimation mode to control arguments to estimateSmartFee //! Fee estimation mode to control arguments to estimateSmartFee
@ -41,9 +43,9 @@ public:
fAllowOtherInputs = false; fAllowOtherInputs = false;
fAllowWatchOnly = false; fAllowWatchOnly = false;
setSelected.clear(); setSelected.clear();
nFeeRate = CFeeRate(0); m_feerate.reset();
fOverrideFeeRate = false; fOverrideFeeRate = false;
nConfirmTarget = 0; m_confirm_target.reset();
signalRbf = fWalletRbf; signalRbf = fWalletRbf;
m_fee_mode = FeeEstimateMode::UNSET; m_fee_mode = FeeEstimateMode::UNSET;
} }

8
src/wallet/feebumper.cpp

@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php. // file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "consensus/validation.h" #include "consensus/validation.h"
#include "wallet/coincontrol.h"
#include "wallet/feebumper.h" #include "wallet/feebumper.h"
#include "wallet/wallet.h" #include "wallet/wallet.h"
#include "policy/fees.h" #include "policy/fees.h"
@ -66,7 +67,7 @@ bool CFeeBumper::preconditionChecks(const CWallet *pWallet, const CWalletTx& wtx
return true; return true;
} }
CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConfirmTarget, bool ignoreGlobalPayTxFee, CAmount totalFee, bool newTxReplaceable, FeeEstimateMode fee_mode) CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, const CCoinControl& coin_control, CAmount totalFee)
: :
txid(std::move(txidIn)), txid(std::move(txidIn)),
nOldFee(0), nOldFee(0),
@ -165,8 +166,7 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConf
nNewFee = totalFee; nNewFee = totalFee;
nNewFeeRate = CFeeRate(totalFee, maxNewTxSize); nNewFeeRate = CFeeRate(totalFee, maxNewTxSize);
} else { } else {
bool conservative_estimate = CalculateEstimateType(fee_mode, newTxReplaceable); nNewFee = CWallet::GetMinimumFee(maxNewTxSize, coin_control, mempool, ::feeEstimator, nullptr /* FeeCalculation */);
nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator, nullptr /* FeeCalculation */, ignoreGlobalPayTxFee, conservative_estimate);
nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize); nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize);
// New fee rate must be at least old rate + minimum incremental relay rate // New fee rate must be at least old rate + minimum incremental relay rate
@ -221,7 +221,7 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConf
} }
// Mark new tx not replaceable, if requested. // Mark new tx not replaceable, if requested.
if (!newTxReplaceable) { if (!coin_control.signalRbf) {
for (auto& input : mtx.vin) { for (auto& input : mtx.vin) {
if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe; if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe;
} }

3
src/wallet/feebumper.h

@ -10,6 +10,7 @@
class CWallet; class CWallet;
class CWalletTx; class CWalletTx;
class uint256; class uint256;
class CCoinControl;
enum class FeeEstimateMode; enum class FeeEstimateMode;
enum class BumpFeeResult enum class BumpFeeResult
@ -25,7 +26,7 @@ enum class BumpFeeResult
class CFeeBumper class CFeeBumper
{ {
public: public:
CFeeBumper(const CWallet *pWalletIn, const uint256 txidIn, int newConfirmTarget, bool ignoreGlobalPayTxFee, CAmount totalFee, bool newTxReplaceable, FeeEstimateMode fee_mode); CFeeBumper(const CWallet *pWalletIn, const uint256 txidIn, const CCoinControl& coin_control, CAmount totalFee);
BumpFeeResult getResult() const { return currentResult; } BumpFeeResult getResult() const { return currentResult; }
const std::vector<std::string>& getErrors() const { return vErrors; } const std::vector<std::string>& getErrors() const { return vErrors; }
CAmount getOldFee() const { return nOldFee; } CAmount getOldFee() const { return nOldFee; }

44
src/wallet/rpcwallet.cpp

@ -356,7 +356,7 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request)
return ret; return ret;
} }
static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew, CCoinControl *coin_control = nullptr) static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew, const CCoinControl& coin_control)
{ {
CAmount curBalance = pwallet->GetBalance(); CAmount curBalance = pwallet->GetBalance();
@ -460,7 +460,7 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
} }
if (request.params.size() > 6 && !request.params[6].isNull()) { if (request.params.size() > 6 && !request.params[6].isNull()) {
coin_control.nConfirmTarget = request.params[6].get_int(); coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]);
} }
if (request.params.size() > 7 && !request.params[7].isNull()) { if (request.params.size() > 7 && !request.params[7].isNull()) {
@ -472,7 +472,7 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
EnsureWalletIsUnlocked(pwallet); EnsureWalletIsUnlocked(pwallet);
SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx, &coin_control); SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx, coin_control);
return wtx.GetHash().GetHex(); return wtx.GetHash().GetHex();
} }
@ -898,7 +898,8 @@ UniValue sendfrom(const JSONRPCRequest& request)
if (nAmount > nBalance) if (nAmount > nBalance)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds");
SendMoney(pwallet, address.Get(), nAmount, false, wtx); CCoinControl no_coin_control; // This is a deprecated API
SendMoney(pwallet, address.Get(), nAmount, false, wtx, no_coin_control);
return wtx.GetHash().GetHex(); return wtx.GetHash().GetHex();
} }
@ -980,7 +981,7 @@ UniValue sendmany(const JSONRPCRequest& request)
} }
if (request.params.size() > 6 && !request.params[6].isNull()) { if (request.params.size() > 6 && !request.params[6].isNull()) {
coin_control.nConfirmTarget = request.params[6].get_int(); coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]);
} }
if (request.params.size() > 7 && !request.params[7].isNull()) { if (request.params.size() > 7 && !request.params[7].isNull()) {
@ -1033,7 +1034,7 @@ UniValue sendmany(const JSONRPCRequest& request)
CAmount nFeeRequired = 0; CAmount nFeeRequired = 0;
int nChangePosRet = -1; int nChangePosRet = -1;
std::string strFailReason; std::string strFailReason;
bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason, &coin_control); bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control);
if (!fCreated) if (!fCreated)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
CValidationState state; CValidationState state;
@ -2729,13 +2730,9 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
RPCTypeCheck(request.params, {UniValue::VSTR}); RPCTypeCheck(request.params, {UniValue::VSTR});
CCoinControl coinControl; CCoinControl coinControl;
coinControl.destChange = CNoDestination();
int changePosition = -1; int changePosition = -1;
coinControl.fAllowWatchOnly = false; // include watching
bool lockUnspents = false; bool lockUnspents = false;
bool reserveChangeKey = true; bool reserveChangeKey = true;
coinControl.nFeeRate = CFeeRate(0);
coinControl.fOverrideFeeRate = false;
UniValue subtractFeeFromOutputs; UniValue subtractFeeFromOutputs;
std::set<int> setSubtractFeeFromOutputs; std::set<int> setSubtractFeeFromOutputs;
@ -2787,7 +2784,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
if (options.exists("feeRate")) if (options.exists("feeRate"))
{ {
coinControl.nFeeRate = CFeeRate(AmountFromValue(options["feeRate"])); coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"]));
coinControl.fOverrideFeeRate = true; coinControl.fOverrideFeeRate = true;
} }
@ -2798,7 +2795,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
coinControl.signalRbf = options["replaceable"].get_bool(); coinControl.signalRbf = options["replaceable"].get_bool();
} }
if (options.exists("conf_target")) { if (options.exists("conf_target")) {
coinControl.nConfirmTarget = options["conf_target"].get_int(); coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"]);
} }
if (options.exists("estimate_mode")) { if (options.exists("estimate_mode")) {
if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) { if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) {
@ -2904,11 +2901,9 @@ UniValue bumpfee(const JSONRPCRequest& request)
hash.SetHex(request.params[0].get_str()); hash.SetHex(request.params[0].get_str());
// optional parameters // optional parameters
bool ignoreGlobalPayTxFee = false;
int newConfirmTarget = nTxConfirmTarget;
CAmount totalFee = 0; CAmount totalFee = 0;
bool replaceable = true; CCoinControl coin_control;
FeeEstimateMode fee_mode = FeeEstimateMode::UNSET; coin_control.signalRbf = true;
if (request.params.size() > 1) { if (request.params.size() > 1) {
UniValue options = request.params[1]; UniValue options = request.params[1];
RPCTypeCheckObj(options, RPCTypeCheckObj(options,
@ -2922,15 +2917,8 @@ UniValue bumpfee(const JSONRPCRequest& request)
if (options.exists("confTarget") && options.exists("totalFee")) { if (options.exists("confTarget") && options.exists("totalFee")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "confTarget and totalFee options should not both be set. Please provide either a confirmation target for fee estimation or an explicit total fee for the transaction."); throw JSONRPCError(RPC_INVALID_PARAMETER, "confTarget and totalFee options should not both be set. Please provide either a confirmation target for fee estimation or an explicit total fee for the transaction.");
} else if (options.exists("confTarget")) { } else if (options.exists("confTarget")) { // TODO: alias this to conf_target
// If the user has explicitly set a confTarget in this rpc call, coin_control.m_confirm_target = ParseConfirmTarget(options["confTarget"]);
// then override the default logic that uses the global payTxFee
// instead of the confirmation target.
ignoreGlobalPayTxFee = true;
newConfirmTarget = options["confTarget"].get_int();
if (newConfirmTarget <= 0) { // upper-bound will be checked by estimatefee/smartfee
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid confTarget (cannot be <= 0)");
}
} else if (options.exists("totalFee")) { } else if (options.exists("totalFee")) {
totalFee = options["totalFee"].get_int64(); totalFee = options["totalFee"].get_int64();
if (totalFee <= 0) { if (totalFee <= 0) {
@ -2939,10 +2927,10 @@ UniValue bumpfee(const JSONRPCRequest& request)
} }
if (options.exists("replaceable")) { if (options.exists("replaceable")) {
replaceable = options["replaceable"].get_bool(); coin_control.signalRbf = options["replaceable"].get_bool();
} }
if (options.exists("estimate_mode")) { if (options.exists("estimate_mode")) {
if (!FeeModeFromString(options["estimate_mode"].get_str(), fee_mode)) { if (!FeeModeFromString(options["estimate_mode"].get_str(), coin_control.m_fee_mode)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
} }
} }
@ -2951,7 +2939,7 @@ UniValue bumpfee(const JSONRPCRequest& request)
LOCK2(cs_main, pwallet->cs_wallet); LOCK2(cs_main, pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet); EnsureWalletIsUnlocked(pwallet);
CFeeBumper feeBump(pwallet, hash, newConfirmTarget, ignoreGlobalPayTxFee, totalFee, replaceable, fee_mode); CFeeBumper feeBump(pwallet, hash, coin_control, totalFee);
BumpFeeResult res = feeBump.getResult(); BumpFeeResult res = feeBump.getResult();
if (res != BumpFeeResult::OK) if (res != BumpFeeResult::OK)
{ {

4
src/wallet/test/wallet_tests.cpp

@ -13,6 +13,7 @@
#include "rpc/server.h" #include "rpc/server.h"
#include "test/test_bitcoin.h" #include "test/test_bitcoin.h"
#include "validation.h" #include "validation.h"
#include "wallet/coincontrol.h"
#include "wallet/test/wallet_test_fixture.h" #include "wallet/test/wallet_test_fixture.h"
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
@ -617,7 +618,8 @@ public:
CAmount fee; CAmount fee;
int changePos = -1; int changePos = -1;
std::string error; std::string error;
BOOST_CHECK(wallet->CreateTransaction({recipient}, wtx, reservekey, fee, changePos, error)); CCoinControl dummy;
BOOST_CHECK(wallet->CreateTransaction({recipient}, wtx, reservekey, fee, changePos, error, dummy));
CValidationState state; CValidationState state;
BOOST_CHECK(wallet->CommitTransaction(wtx, reservekey, nullptr, state)); BOOST_CHECK(wallet->CommitTransaction(wtx, reservekey, nullptr, state));
auto it = wallet->mapWallet.find(wtx.GetHash()); auto it = wallet->mapWallet.find(wtx.GetHash());

110
src/wallet/wallet.cpp

@ -2469,9 +2469,9 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
CReserveKey reservekey(this); CReserveKey reservekey(this);
CWalletTx wtx; CWalletTx wtx;
if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, &coinControl, false)) if (!CreateTransaction(vecSend, wtx, reservekey, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) {
return false; return false;
}
if (nChangePosInOut != -1) if (nChangePosInOut != -1)
tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]); tx.vout.insert(tx.vout.begin() + nChangePosInOut, wtx.tx->vout[nChangePosInOut]);
@ -2502,7 +2502,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
} }
bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet,
int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign) int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign)
{ {
CAmount nValue = 0; CAmount nValue = 0;
int nChangePosRequest = nChangePosInOut; int nChangePosRequest = nChangePosInOut;
@ -2567,7 +2567,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
LOCK2(cs_main, cs_wallet); LOCK2(cs_main, cs_wallet);
{ {
std::vector<COutput> vAvailableCoins; std::vector<COutput> vAvailableCoins;
AvailableCoins(vAvailableCoins, true, coinControl); AvailableCoins(vAvailableCoins, true, &coin_control);
// Create change script that will be used if we need change // Create change script that will be used if we need change
// TODO: pass in scriptChange instead of reservekey so // TODO: pass in scriptChange instead of reservekey so
@ -2575,12 +2575,9 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
CScript scriptChange; CScript scriptChange;
// coin control: send change to custom address // coin control: send change to custom address
if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange)) if (!boost::get<CNoDestination>(&coin_control.destChange)) {
scriptChange = GetScriptForDestination(coinControl->destChange); scriptChange = GetScriptForDestination(coin_control.destChange);
} else { // no coin control: send change to newly generated address
// no coin control: send change to newly generated address
else
{
// Note: We use a new key here to keep it from being obvious which side is the change. // Note: We use a new key here to keep it from being obvious which side is the change.
// The drawback is that by not reusing a previous key, the change may be lost if a // The drawback is that by not reusing a previous key, the change may be lost if a
// backup is restored, if the backup doesn't have the new private key for the change. // backup is restored, if the backup doesn't have the new private key for the change.
@ -2654,7 +2651,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
if (pick_new_inputs) { if (pick_new_inputs) {
nValueIn = 0; nValueIn = 0;
setCoins.clear(); setCoins.clear();
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coinControl)) if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, &coin_control))
{ {
strFailReason = _("Insufficient funds"); strFailReason = _("Insufficient funds");
return false; return false;
@ -2705,8 +2702,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
// to avoid conflicting with other possible uses of nSequence, // to avoid conflicting with other possible uses of nSequence,
// and in the spirit of "smallest possible change from prior // and in the spirit of "smallest possible change from prior
// behavior." // behavior."
bool rbf = coinControl ? coinControl->signalRbf : fWalletRbf; const uint32_t nSequence = coin_control.signalRbf ? MAX_BIP125_RBF_SEQUENCE : (std::numeric_limits<unsigned int>::max() - 1);
const uint32_t nSequence = rbf ? MAX_BIP125_RBF_SEQUENCE : (std::numeric_limits<unsigned int>::max() - 1);
for (const auto& coin : setCoins) for (const auto& coin : setCoins)
txNew.vin.push_back(CTxIn(coin.outpoint,CScript(), txNew.vin.push_back(CTxIn(coin.outpoint,CScript(),
nSequence)); nSequence));
@ -2725,17 +2721,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
vin.scriptWitness.SetNull(); vin.scriptWitness.SetNull();
} }
// Allow to override the default confirmation target over the CoinControl instance CAmount nFeeNeeded = GetMinimumFee(nBytes, coin_control, ::mempool, ::feeEstimator, &feeCalc);
int currentConfirmationTarget = nTxConfirmTarget;
if (coinControl && coinControl->nConfirmTarget > 0)
currentConfirmationTarget = coinControl->nConfirmTarget;
// Allow to override the default fee estimate mode over the CoinControl instance
bool conservative_estimate = CalculateEstimateType(coinControl ? coinControl->m_fee_mode : FeeEstimateMode::UNSET, rbf);
CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, ::mempool, ::feeEstimator, &feeCalc, false /* ignoreGlobalPayTxFee */, conservative_estimate);
if (coinControl && coinControl->fOverrideFeeRate)
nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes);
// If we made it here and we aren't even able to meet the relay fee on the next pass, give up // If we made it here and we aren't even able to meet the relay fee on the next pass, give up
// because we must be at the maximum allowed fee. // because we must be at the maximum allowed fee.
@ -2760,7 +2746,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
// new inputs. We now know we only need the smaller fee // new inputs. We now know we only need the smaller fee
// (because of reduced tx size) and so we should add a // (because of reduced tx size) and so we should add a
// change output. Only try this once. // change output. Only try this once.
CAmount fee_needed_for_change = GetMinimumFee(change_prototype_size, currentConfirmationTarget, ::mempool, ::feeEstimator, nullptr, false /* ignoreGlobalPayTxFee */, conservative_estimate); CAmount fee_needed_for_change = GetMinimumFee(change_prototype_size, coin_control, ::mempool, ::feeEstimator, nullptr);
CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, ::dustRelayFee); CAmount minimum_value_for_change = GetDustThreshold(change_prototype_txout, ::dustRelayFee);
CAmount max_excess_fee = fee_needed_for_change + minimum_value_for_change; CAmount max_excess_fee = fee_needed_for_change + minimum_value_for_change;
if (nFeeRet > nFeeNeeded + max_excess_fee && nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) { if (nFeeRet > nFeeNeeded + max_excess_fee && nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) {
@ -2936,33 +2922,61 @@ CAmount CWallet::GetRequiredFee(unsigned int nTxBytes)
return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes)); return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes));
} }
CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc, bool ignoreGlobalPayTxFee, bool conservative_estimate) CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc)
{ {
// payTxFee is the user-set global for desired feerate /* User control of how to calculate fee uses the following parameter precedence:
CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes); 1. coin_control.m_feerate
// User didn't set: use -txconfirmtarget to estimate... 2. coin_control.m_confirm_target
if (nFeeNeeded == 0 || ignoreGlobalPayTxFee) { 3. payTxFee (user-set global variable)
nFeeNeeded = estimator.estimateSmartFee(nConfirmTarget, feeCalc, pool, conservative_estimate).GetFee(nTxBytes); 4. nTxConfirmTarget (user-set global variable)
// ... unless we don't have enough mempool data for estimatefee, then use fallbackFee The first parameter that is set is used.
if (nFeeNeeded == 0) { */
nFeeNeeded = fallbackFee.GetFee(nTxBytes); CAmount fee_needed;
if (coin_control.m_feerate) { // 1.
fee_needed = coin_control.m_feerate->GetFee(nTxBytes);
if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE;
// Allow to override automatic min/max check over coin control instance
if (coin_control.fOverrideFeeRate) return fee_needed;
}
else if (!coin_control.m_confirm_target && ::payTxFee != CFeeRate(0)) { // 3. TODO: remove magic value of 0 for global payTxFee
fee_needed = ::payTxFee.GetFee(nTxBytes);
if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE;
}
else { // 2. or 4.
// We will use smart fee estimation
unsigned int target = coin_control.m_confirm_target ? *coin_control.m_confirm_target : ::nTxConfirmTarget;
// By default estimates are economical iff we are signaling opt-in-RBF
bool conservative_estimate = !coin_control.signalRbf;
// Allow to override the default fee estimate mode over the CoinControl instance
if (coin_control.m_fee_mode == FeeEstimateMode::CONSERVATIVE) conservative_estimate = true;
else if (coin_control.m_fee_mode == FeeEstimateMode::ECONOMICAL) conservative_estimate = false;
fee_needed = estimator.estimateSmartFee(target, feeCalc, conservative_estimate).GetFee(nTxBytes);
if (fee_needed == 0) {
// if we don't have enough data for estimateSmartFee, then use fallbackFee
fee_needed = fallbackFee.GetFee(nTxBytes);
if (feeCalc) feeCalc->reason = FeeReason::FALLBACK; if (feeCalc) feeCalc->reason = FeeReason::FALLBACK;
} }
} else { // Obey mempool min fee when using smart fee estimation
if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE; CAmount min_mempool_fee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFee(nTxBytes);
if (fee_needed < min_mempool_fee) {
fee_needed = min_mempool_fee;
if (feeCalc) feeCalc->reason = FeeReason::MEMPOOL_MIN;
}
} }
// prevent user from paying a fee below minRelayTxFee or minTxFee // prevent user from paying a fee below minRelayTxFee or minTxFee
CAmount requiredFee = GetRequiredFee(nTxBytes); CAmount required_fee = GetRequiredFee(nTxBytes);
if (requiredFee > nFeeNeeded) { if (required_fee > fee_needed) {
nFeeNeeded = requiredFee; fee_needed = required_fee;
if (feeCalc) feeCalc->reason = FeeReason::REQUIRED; if (feeCalc) feeCalc->reason = FeeReason::REQUIRED;
} }
// But always obey the maximum // But always obey the maximum
if (nFeeNeeded > maxTxFee) { if (fee_needed > maxTxFee) {
nFeeNeeded = maxTxFee; fee_needed = maxTxFee;
if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE; if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE;
} }
return nFeeNeeded; return fee_needed;
} }
@ -4200,15 +4214,3 @@ bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState&
{ {
return ::AcceptToMemoryPool(mempool, state, tx, true, NULL, NULL, false, nAbsurdFee); return ::AcceptToMemoryPool(mempool, state, tx, true, NULL, NULL, false, nAbsurdFee);
} }
bool CalculateEstimateType(FeeEstimateMode mode, bool opt_in_rbf) {
switch (mode) {
case FeeEstimateMode::UNSET:
return !opt_in_rbf; // Allow for lower fees if RBF is an option
case FeeEstimateMode::CONSERVATIVE:
return true;
case FeeEstimateMode::ECONOMICAL:
return false;
}
return true;
}

6
src/wallet/wallet.h

@ -954,7 +954,7 @@ public:
* @note passing nChangePosInOut as -1 will result in setting a random position * @note passing nChangePosInOut as -1 will result in setting a random position
*/ */
bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut, bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosInOut,
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true); std::string& strFailReason, const CCoinControl& coin_control, bool sign = true);
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CConnman* connman, CValidationState& state);
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries); void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries);
@ -969,7 +969,7 @@ public:
* Estimate the minimum fee considering user set parameters * Estimate the minimum fee considering user set parameters
* and the required fee * and the required fee
*/ */
static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc, bool ignoreGlobalPayTxFee, bool conservative_estimate); static CAmount GetMinimumFee(unsigned int nTxBytes, const CCoinControl& coin_control, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc);
/** /**
* Return the minimum required fee taking into account the * Return the minimum required fee taking into account the
* floating relay fee and user set minimum transaction fee * floating relay fee and user set minimum transaction fee
@ -1220,6 +1220,4 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins
return true; return true;
} }
bool CalculateEstimateType(FeeEstimateMode mode, bool opt_in_rbf);
#endif // BITCOIN_WALLET_WALLET_H #endif // BITCOIN_WALLET_WALLET_H

Loading…
Cancel
Save