aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/policy/fees.cpp90
-rw-r--r--src/policy/fees.h34
-rw-r--r--src/qt/sendcoinsdialog.cpp6
-rw-r--r--src/rpc/mining.cpp6
-rw-r--r--src/wallet/feebumper.cpp2
-rw-r--r--src/wallet/wallet.cpp37
-rw-r--r--src/wallet/wallet.h3
7 files changed, 134 insertions, 44 deletions
diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp
index 3c3a2fb651..771491770e 100644
--- a/src/policy/fees.cpp
+++ b/src/policy/fees.cpp
@@ -16,6 +16,26 @@
static constexpr double INF_FEERATE = 1e99;
+std::string StringForFeeReason(FeeReason reason) {
+ static const std::map<FeeReason, std::string> fee_reason_strings = {
+ {FeeReason::NONE, "None"},
+ {FeeReason::HALF_ESTIMATE, "Half Target 60% Threshold"},
+ {FeeReason::FULL_ESTIMATE, "Target 85% Threshold"},
+ {FeeReason::DOUBLE_ESTIMATE, "Double Target 95% Threshold"},
+ {FeeReason::CONSERVATIVE, "Conservative Double Target longer horizon"},
+ {FeeReason::MEMPOOL_MIN, "Mempool Min Fee"},
+ {FeeReason::PAYTXFEE, "PayTxFee set"},
+ {FeeReason::FALLBACK, "Fallback fee"},
+ {FeeReason::REQUIRED, "Minimum Required Fee"},
+ {FeeReason::MAXTXFEE, "MaxTxFee limit"}
+ };
+ auto reason_string = fee_reason_strings.find(reason);
+
+ if (reason_string == fee_reason_strings.end()) return "Unknown";
+
+ return reason_string->second;
+}
+
/**
* We will instantiate an instance of this class to track transactions that were
* included in a block. We will lump transactions into a bucket according to their
@@ -698,31 +718,36 @@ unsigned int CBlockPolicyEstimator::MaxUsableEstimate() const
* time horizon which tracks confirmations up to the desired target. If
* checkShorterHorizon is requested, also allow short time horizon estimates
* for a lower target to reduce the given answer */
-double CBlockPolicyEstimator::estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon) const
+double CBlockPolicyEstimator::estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon, EstimationResult *result) const
{
double estimate = -1;
if (confTarget >= 1 && confTarget <= longStats->GetMaxConfirms()) {
// Find estimate from shortest time horizon possible
if (confTarget <= shortStats->GetMaxConfirms()) { // short horizon
- estimate = shortStats->EstimateMedianVal(confTarget, SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight);
+ estimate = shortStats->EstimateMedianVal(confTarget, SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight, result);
}
else if (confTarget <= feeStats->GetMaxConfirms()) { // medium horizon
- estimate = feeStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight);
+ estimate = feeStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, result);
}
else { // long horizon
- estimate = longStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight);
+ estimate = longStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, result);
}
if (checkShorterHorizon) {
+ EstimationResult tempResult;
// If a lower confTarget from a more recent horizon returns a lower answer use it.
if (confTarget > feeStats->GetMaxConfirms()) {
- double medMax = feeStats->EstimateMedianVal(feeStats->GetMaxConfirms(), SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight);
- if (medMax > 0 && (estimate == -1 || medMax < estimate))
+ double medMax = feeStats->EstimateMedianVal(feeStats->GetMaxConfirms(), SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, &tempResult);
+ if (medMax > 0 && (estimate == -1 || medMax < estimate)) {
estimate = medMax;
+ if (result) *result = tempResult;
+ }
}
if (confTarget > shortStats->GetMaxConfirms()) {
- double shortMax = shortStats->EstimateMedianVal(shortStats->GetMaxConfirms(), SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight);
- if (shortMax > 0 && (estimate == -1 || shortMax < estimate))
+ double shortMax = shortStats->EstimateMedianVal(shortStats->GetMaxConfirms(), SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight, &tempResult);
+ if (shortMax > 0 && (estimate == -1 || shortMax < estimate)) {
estimate = shortMax;
+ if (result) *result = tempResult;
+ }
}
}
}
@@ -732,16 +757,18 @@ double CBlockPolicyEstimator::estimateCombinedFee(unsigned int confTarget, doubl
/** Ensure that for a conservative estimate, the DOUBLE_SUCCESS_PCT is also met
* at 2 * target for any longer time horizons.
*/
-double CBlockPolicyEstimator::estimateConservativeFee(unsigned int doubleTarget) const
+double CBlockPolicyEstimator::estimateConservativeFee(unsigned int doubleTarget, EstimationResult *result) const
{
double estimate = -1;
+ EstimationResult tempResult;
if (doubleTarget <= shortStats->GetMaxConfirms()) {
- estimate = feeStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight);
+ estimate = feeStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight, result);
}
if (doubleTarget <= feeStats->GetMaxConfirms()) {
- double longEstimate = longStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight);
+ double longEstimate = longStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight, &tempResult);
if (longEstimate > estimate) {
estimate = longEstimate;
+ if (result) *result = tempResult;
}
}
return estimate;
@@ -754,12 +781,15 @@ double CBlockPolicyEstimator::estimateConservativeFee(unsigned int doubleTarget)
* estimates, however, required the 95% threshold at 2 * target be met for any
* longer time horizons also.
*/
-CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool, bool conservative) const
+CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation *feeCalc, const CTxMemPool& pool, bool conservative) const
{
- if (answerFoundAtTarget)
- *answerFoundAtTarget = confTarget;
+ if (feeCalc) {
+ feeCalc->desiredTarget = confTarget;
+ feeCalc->returnedTarget = confTarget;
+ }
double median = -1;
+ EstimationResult tempResult;
{
LOCK(cs_feeEstimator);
@@ -780,7 +810,6 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoun
}
assert(confTarget > 0); //estimateCombinedFee and estimateConservativeFee take unsigned ints
-
/** true is passed to estimateCombined fee for target/2 and target so
* that we check the max confirms for shorter time horizons as well.
* This is necessary to preserve monotonically increasing estimates.
@@ -791,32 +820,49 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoun
* the purpose of conservative estimates is not to let short term
* fluctuations lower our estimates by too much.
*/
- double halfEst = estimateCombinedFee(confTarget/2, HALF_SUCCESS_PCT, true);
- double actualEst = estimateCombinedFee(confTarget, SUCCESS_PCT, true);
- double doubleEst = estimateCombinedFee(2 * confTarget, DOUBLE_SUCCESS_PCT, !conservative);
+ 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) {
+ feeCalc->est = tempResult;
+ feeCalc->reason = FeeReason::FULL_ESTIMATE;
+ }
}
+ double doubleEst = estimateCombinedFee(2 * confTarget, DOUBLE_SUCCESS_PCT, !conservative, &tempResult);
if (doubleEst > median) {
median = doubleEst;
+ if (feeCalc) {
+ feeCalc->est = tempResult;
+ feeCalc->reason = FeeReason::DOUBLE_ESTIMATE;
+ }
}
if (conservative || median == -1) {
- double consEst = estimateConservativeFee(2 * confTarget);
+ 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 (answerFoundAtTarget)
- *answerFoundAtTarget = 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 (minPoolFee > 0 && minPoolFee > median) {
+ if (feeCalc) feeCalc->reason = FeeReason::MEMPOOL_MIN;
return CFeeRate(minPoolFee);
+ }
if (median < 0)
return CFeeRate(0);
diff --git a/src/policy/fees.h b/src/policy/fees.h
index e99fec2c39..7125a74f03 100644
--- a/src/policy/fees.h
+++ b/src/policy/fees.h
@@ -74,6 +74,22 @@ enum FeeEstimateHorizon {
LONG_HALFLIFE = 2
};
+/* Enumeration of reason for returned fee estimate */
+enum class FeeReason {
+ NONE,
+ HALF_ESTIMATE,
+ FULL_ESTIMATE,
+ DOUBLE_ESTIMATE,
+ CONSERVATIVE,
+ MEMPOOL_MIN,
+ PAYTXFEE,
+ FALLBACK,
+ REQUIRED,
+ MAXTXFEE,
+};
+
+std::string StringForFeeReason(FeeReason reason);
+
/* Used to return detailed information about a feerate bucket */
struct EstimatorBucket
{
@@ -90,8 +106,16 @@ struct EstimationResult
{
EstimatorBucket pass;
EstimatorBucket fail;
- double decay;
- unsigned int scale;
+ double decay = 0;
+ unsigned int scale = 0;
+};
+
+struct FeeCalculation
+{
+ EstimationResult est;
+ FeeReason reason = FeeReason::NONE;
+ int desiredTarget = 0;
+ int returnedTarget = 0;
};
/**
@@ -173,7 +197,7 @@ public:
* the closest target where one can be given. 'conservative' estimates are
* valid over longer time horizons also.
*/
- CFeeRate estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool, bool conservative = true) const;
+ CFeeRate estimateSmartFee(int confTarget, FeeCalculation *feeCalc, const CTxMemPool& pool, bool conservative = true) const;
/** Return a specific fee estimate calculation with a given success
* threshold and time horizon, and optionally return detailed data about
@@ -223,9 +247,9 @@ private:
bool processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry);
/** Helper for estimateSmartFee */
- double estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon) const;
+ double estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon, EstimationResult *result) const;
/** Helper for estimateSmartFee */
- double estimateConservativeFee(unsigned int doubleTarget) const;
+ double estimateConservativeFee(unsigned int doubleTarget, EstimationResult *result) const;
/** Number of blocks of data recorded while fee estimates have been running */
unsigned int BlockSpan() const;
/** Number of blocks of recorded fee estimate data represented in saved data file */
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index fafd1855d0..12d2d0f31c 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -651,8 +651,8 @@ void SendCoinsDialog::updateSmartFeeLabel()
return;
int nBlocksToConfirm = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2;
- int estimateFoundAtBlocks = nBlocksToConfirm;
- CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocksToConfirm, &estimateFoundAtBlocks, ::mempool);
+ FeeCalculation feeCalc;
+ CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocksToConfirm, &feeCalc, ::mempool);
if (feeRate <= CFeeRate(0)) // not enough data => minfee
{
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),
@@ -670,7 +670,7 @@ void SendCoinsDialog::updateSmartFeeLabel()
ui->labelSmartFee->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->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", feeCalc.returnedTarget));
ui->fallbackFeeWarningLabel->setVisible(false);
}
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index 9af29652cf..3b212dc0e4 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -867,10 +867,10 @@ UniValue estimatesmartfee(const JSONRPCRequest& request)
}
UniValue result(UniValue::VOBJ);
- int answerFound;
- CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocks, &answerFound, ::mempool, conservative);
+ FeeCalculation feeCalc;
+ CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocks, &feeCalc, ::mempool, conservative);
result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK())));
- result.push_back(Pair("blocks", answerFound));
+ result.push_back(Pair("blocks", feeCalc.returnedTarget));
return result;
}
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 46ef87b7b1..6a9e6cf9ff 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.cpp
@@ -165,7 +165,7 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConf
nNewFee = totalFee;
nNewFeeRate = CFeeRate(totalFee, maxNewTxSize);
} else {
- nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator, ignoreGlobalPayTxFee);
+ nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator, nullptr, ignoreGlobalPayTxFee);
nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize);
// New fee rate must be at least old rate + minimum incremental relay rate
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 02de3cceed..2c5c38eb94 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -2534,7 +2534,8 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
assert(txNew.nLockTime <= (unsigned int)chainActive.Height());
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
-
+ FeeCalculation feeCalc;
+ unsigned int nBytes;
{
std::set<CInputCoin> setCoins;
LOCK2(cs_main, cs_wallet);
@@ -2706,7 +2707,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
return false;
}
- unsigned int nBytes = GetVirtualTransactionSize(txNew);
+ nBytes = GetVirtualTransactionSize(txNew);
CTransaction txNewConst(txNew);
@@ -2721,7 +2722,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
if (coinControl && coinControl->nConfirmTarget > 0)
currentConfirmationTarget = coinControl->nConfirmTarget;
- CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, ::mempool, ::feeEstimator);
+ CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, ::mempool, ::feeEstimator, &feeCalc);
if (coinControl && coinControl->fOverrideFeeRate)
nFeeNeeded = coinControl->nFeeRate.GetFee(nBytes);
@@ -2818,6 +2819,15 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
return false;
}
}
+
+ LogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
+ nFeeRet, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
+ feeCalc.est.pass.start, feeCalc.est.pass.end,
+ 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool),
+ feeCalc.est.pass.withinTarget, feeCalc.est.pass.totalConfirmed, feeCalc.est.pass.inMempool, feeCalc.est.pass.leftMempool,
+ feeCalc.est.fail.start, feeCalc.est.fail.end,
+ 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool),
+ feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool);
return true;
}
@@ -2893,23 +2903,32 @@ CAmount CWallet::GetRequiredFee(unsigned int 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, bool ignoreGlobalPayTxFee)
+CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc, bool ignoreGlobalPayTxFee)
{
// payTxFee is the user-set global for desired feerate
CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes);
// User didn't set: use -txconfirmtarget to estimate...
if (nFeeNeeded == 0 || ignoreGlobalPayTxFee) {
- int estimateFoundTarget = nConfirmTarget;
- nFeeNeeded = estimator.estimateSmartFee(nConfirmTarget, &estimateFoundTarget, pool).GetFee(nTxBytes);
+ nFeeNeeded = estimator.estimateSmartFee(nConfirmTarget, feeCalc, pool, true).GetFee(nTxBytes);
// ... unless we don't have enough mempool data for estimatefee, then use fallbackFee
- if (nFeeNeeded == 0)
+ if (nFeeNeeded == 0) {
nFeeNeeded = fallbackFee.GetFee(nTxBytes);
+ if (feeCalc) feeCalc->reason = FeeReason::FALLBACK;
+ }
+ } else {
+ if (feeCalc) feeCalc->reason = FeeReason::PAYTXFEE;
}
// prevent user from paying a fee below minRelayTxFee or minTxFee
- nFeeNeeded = std::max(nFeeNeeded, GetRequiredFee(nTxBytes));
+ CAmount requiredFee = GetRequiredFee(nTxBytes);
+ if (requiredFee > nFeeNeeded) {
+ nFeeNeeded = requiredFee;
+ if (feeCalc) feeCalc->reason = FeeReason::REQUIRED;
+ }
// But always obey the maximum
- if (nFeeNeeded > maxTxFee)
+ if (nFeeNeeded > maxTxFee) {
nFeeNeeded = maxTxFee;
+ if (feeCalc) feeCalc->reason = FeeReason::MAXTXFEE;
+ }
return nFeeNeeded;
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 6c6eb69180..45937ee21f 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -77,6 +77,7 @@ class CScheduler;
class CTxMemPool;
class CBlockPolicyEstimator;
class CWalletTx;
+class FeeCalculation;
/** (client) version numbers for particular wallet features */
enum WalletFeature
@@ -959,7 +960,7 @@ public:
* Estimate the minimum fee considering user set parameters
* and the required fee
*/
- static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, bool ignoreGlobalPayTxFee = false);
+ static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, FeeCalculation *feeCalc = nullptr, bool ignoreGlobalPayTxFee = false);
/**
* Return the minimum required fee taking into account the
* floating relay fee and user set minimum transaction fee