|
|
@ -16,6 +16,26 @@ |
|
|
|
|
|
|
|
|
|
|
|
static constexpr double INF_FEERATE = 1e99; |
|
|
|
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 |
|
|
|
* 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 |
|
|
|
* 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 |
|
|
|
* time horizon which tracks confirmations up to the desired target. If |
|
|
|
* checkShorterHorizon is requested, also allow short time horizon estimates |
|
|
|
* checkShorterHorizon is requested, also allow short time horizon estimates |
|
|
|
* for a lower target to reduce the given answer */ |
|
|
|
* 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; |
|
|
|
double estimate = -1; |
|
|
|
if (confTarget >= 1 && confTarget <= longStats->GetMaxConfirms()) { |
|
|
|
if (confTarget >= 1 && confTarget <= longStats->GetMaxConfirms()) { |
|
|
|
// Find estimate from shortest time horizon possible
|
|
|
|
// Find estimate from shortest time horizon possible
|
|
|
|
if (confTarget <= shortStats->GetMaxConfirms()) { // short horizon
|
|
|
|
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
|
|
|
|
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
|
|
|
|
else { // long horizon
|
|
|
|
estimate = longStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight); |
|
|
|
estimate = longStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, result); |
|
|
|
} |
|
|
|
} |
|
|
|
if (checkShorterHorizon) { |
|
|
|
if (checkShorterHorizon) { |
|
|
|
|
|
|
|
EstimationResult tempResult; |
|
|
|
// If a lower confTarget from a more recent horizon returns a lower answer use it.
|
|
|
|
// If a lower confTarget from a more recent horizon returns a lower answer use it.
|
|
|
|
if (confTarget > feeStats->GetMaxConfirms()) { |
|
|
|
if (confTarget > feeStats->GetMaxConfirms()) { |
|
|
|
double medMax = feeStats->EstimateMedianVal(feeStats->GetMaxConfirms(), SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight); |
|
|
|
double medMax = feeStats->EstimateMedianVal(feeStats->GetMaxConfirms(), SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, &tempResult); |
|
|
|
if (medMax > 0 && (estimate == -1 || medMax < estimate)) |
|
|
|
if (medMax > 0 && (estimate == -1 || medMax < estimate)) { |
|
|
|
estimate = medMax; |
|
|
|
estimate = medMax; |
|
|
|
|
|
|
|
if (result) *result = tempResult; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (confTarget > shortStats->GetMaxConfirms()) { |
|
|
|
if (confTarget > shortStats->GetMaxConfirms()) { |
|
|
|
double shortMax = shortStats->EstimateMedianVal(shortStats->GetMaxConfirms(), SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight); |
|
|
|
double shortMax = shortStats->EstimateMedianVal(shortStats->GetMaxConfirms(), SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight, &tempResult); |
|
|
|
if (shortMax > 0 && (estimate == -1 || shortMax < estimate)) |
|
|
|
if (shortMax > 0 && (estimate == -1 || shortMax < estimate)) { |
|
|
|
estimate = shortMax; |
|
|
|
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
|
|
|
|
/** Ensure that for a conservative estimate, the DOUBLE_SUCCESS_PCT is also met
|
|
|
|
* at 2 * target for any longer time horizons. |
|
|
|
* 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; |
|
|
|
double estimate = -1; |
|
|
|
|
|
|
|
EstimationResult tempResult; |
|
|
|
if (doubleTarget <= shortStats->GetMaxConfirms()) { |
|
|
|
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()) { |
|
|
|
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) { |
|
|
|
if (longEstimate > estimate) { |
|
|
|
estimate = longEstimate; |
|
|
|
estimate = longEstimate; |
|
|
|
|
|
|
|
if (result) *result = tempResult; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return estimate; |
|
|
|
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 |
|
|
|
* 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, int *answerFoundAtTarget, const CTxMemPool& pool, bool conservative) const |
|
|
|
CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation *feeCalc, const CTxMemPool& pool, bool conservative) const |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (answerFoundAtTarget) |
|
|
|
if (feeCalc) { |
|
|
|
*answerFoundAtTarget = confTarget; |
|
|
|
feeCalc->desiredTarget = confTarget; |
|
|
|
|
|
|
|
feeCalc->returnedTarget = confTarget; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
double median = -1; |
|
|
|
double median = -1; |
|
|
|
|
|
|
|
EstimationResult tempResult; |
|
|
|
{ |
|
|
|
{ |
|
|
|
LOCK(cs_feeEstimator); |
|
|
|
LOCK(cs_feeEstimator); |
|
|
|
|
|
|
|
|
|
|
@ -780,7 +810,6 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoun |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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. |
|
|
@ -791,32 +820,49 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoun |
|
|
|
* 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); |
|
|
|
double halfEst = estimateCombinedFee(confTarget/2, HALF_SUCCESS_PCT, true, &tempResult); |
|
|
|
double actualEst = estimateCombinedFee(confTarget, SUCCESS_PCT, true); |
|
|
|
if (feeCalc) { |
|
|
|
double doubleEst = estimateCombinedFee(2 * confTarget, DOUBLE_SUCCESS_PCT, !conservative); |
|
|
|
feeCalc->est = tempResult; |
|
|
|
|
|
|
|
feeCalc->reason = FeeReason::HALF_ESTIMATE; |
|
|
|
|
|
|
|
} |
|
|
|
median = halfEst; |
|
|
|
median = halfEst; |
|
|
|
|
|
|
|
double actualEst = estimateCombinedFee(confTarget, SUCCESS_PCT, true, &tempResult); |
|
|
|
if (actualEst > median) { |
|
|
|
if (actualEst > median) { |
|
|
|
median = actualEst; |
|
|
|
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) { |
|
|
|
if (doubleEst > median) { |
|
|
|
median = doubleEst; |
|
|
|
median = doubleEst; |
|
|
|
|
|
|
|
if (feeCalc) { |
|
|
|
|
|
|
|
feeCalc->est = tempResult; |
|
|
|
|
|
|
|
feeCalc->reason = FeeReason::DOUBLE_ESTIMATE; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (conservative || median == -1) { |
|
|
|
if (conservative || median == -1) { |
|
|
|
double consEst = estimateConservativeFee(2 * confTarget); |
|
|
|
double consEst = estimateConservativeFee(2 * confTarget, &tempResult); |
|
|
|
if (consEst > median) { |
|
|
|
if (consEst > median) { |
|
|
|
median = consEst; |
|
|
|
median = consEst; |
|
|
|
|
|
|
|
if (feeCalc) { |
|
|
|
|
|
|
|
feeCalc->est = tempResult; |
|
|
|
|
|
|
|
feeCalc->reason = FeeReason::CONSERVATIVE; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} // Must unlock cs_feeEstimator before taking mempool locks
|
|
|
|
} // Must unlock cs_feeEstimator before taking mempool locks
|
|
|
|
|
|
|
|
|
|
|
|
if (answerFoundAtTarget) |
|
|
|
if (feeCalc) feeCalc->returnedTarget = confTarget; |
|
|
|
*answerFoundAtTarget = confTarget; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If mempool is limiting txs , return at least the min feerate from the mempool
|
|
|
|
// 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(); |
|
|
|
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); |
|
|
|
return CFeeRate(minPoolFee); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (median < 0) |
|
|
|
if (median < 0) |
|
|
|
return CFeeRate(0); |
|
|
|
return CFeeRate(0); |
|
|
|