|
|
|
@ -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);
|
|
|
|
|