16 static const std::map<FeeEstimateHorizon, std::string> horizon_strings = {
21 auto horizon_string = horizon_strings.find(horizon);
23 if (horizon_string == horizon_strings.end())
return "unknown";
25 return horizon_string->second;
85 TxConfirmStats(
const std::vector<double>& defaultBuckets,
const std::map<double, unsigned int>& defaultBucketMap,
86 unsigned int maxPeriods,
double decay,
unsigned int scale);
97 void Record(
int blocksToConfirm,
double val);
100 unsigned int NewTx(
unsigned int nBlockHeight,
double val);
103 void removeTx(
unsigned int entryHeight,
unsigned int nBestSeenHeight,
104 unsigned int bucketIndex,
bool inBlock);
120 double minSuccess,
unsigned int nBlockHeight,
133 void Read(
CAutoFile& filein,
int nFileVersion,
size_t numBuckets);
138 const std::map<double, unsigned int>& defaultBucketMap,
139 unsigned int maxPeriods,
double _decay,
unsigned int _scale)
140 : buckets(defaultBuckets), bucketMap(defaultBucketMap), decay(_decay), scale(_scale)
142 assert(_scale != 0 &&
"_scale must be non-zero");
145 for (
unsigned int i = 0; i < maxPeriods; i++) {
159 for (
unsigned int i = 0; i <
unconfTxs.size(); i++) {
168 for (
unsigned int j = 0; j <
buckets.size(); j++) {
178 if (blocksToConfirm < 1)
180 int periodsToConfirm = (blocksToConfirm +
scale - 1) /
scale;
181 unsigned int bucketindex =
bucketMap.lower_bound(feerate)->second;
182 for (
size_t i = periodsToConfirm; i <=
confAvg.size(); i++) {
192 for (
unsigned int j = 0; j <
buckets.size(); j++) {
193 for (
unsigned int i = 0; i <
confAvg.size(); i++) {
204 double successBreakPoint,
unsigned int nBlockHeight,
212 const int periodTarget = (confTarget +
scale - 1) /
scale;
213 const int maxbucketindex =
buckets.size() - 1;
220 unsigned int curNearBucket = maxbucketindex;
221 unsigned int bestNearBucket = maxbucketindex;
222 unsigned int curFarBucket = maxbucketindex;
223 unsigned int bestFarBucket = maxbucketindex;
225 bool foundAnswer =
false;
227 bool newBucketRange =
true;
233 for (
int bucket = maxbucketindex; bucket >= 0; --bucket) {
234 if (newBucketRange) {
235 curNearBucket = bucket;
236 newBucketRange =
false;
238 curFarBucket = bucket;
239 nConf +=
confAvg[periodTarget - 1][bucket];
241 failNum +=
failAvg[periodTarget - 1][bucket];
242 for (
unsigned int confct = confTarget; confct <
GetMaxConfirms(); confct++)
243 extraNum +=
unconfTxs[(nBlockHeight - confct) % bins][bucket];
249 if (totalNum >= sufficientTxVal / (1 -
decay)) {
250 double curPct = nConf / (totalNum + failNum + extraNum);
253 if (curPct < successBreakPoint) {
254 if (passing ==
true) {
256 unsigned int failMinBucket = std::min(curNearBucket, curFarBucket);
257 unsigned int failMaxBucket = std::max(curNearBucket, curFarBucket);
258 failBucket.
start = failMinBucket ?
buckets[failMinBucket - 1] : 0;
282 bestNearBucket = curNearBucket;
283 bestFarBucket = curFarBucket;
284 newBucketRange =
true;
296 unsigned int minBucket = std::min(bestNearBucket, bestFarBucket);
297 unsigned int maxBucket = std::max(bestNearBucket, bestFarBucket);
298 for (
unsigned int j = minBucket; j <= maxBucket; j++) {
301 if (foundAnswer && txSum != 0) {
303 for (
unsigned int j = minBucket; j <= maxBucket; j++) {
312 passBucket.
start = minBucket ?
buckets[minBucket-1] : 0;
317 if (passing && !newBucketRange) {
318 unsigned int failMinBucket = std::min(curNearBucket, curFarBucket);
319 unsigned int failMaxBucket = std::max(curNearBucket, curFarBucket);
320 failBucket.
start = failMinBucket ?
buckets[failMinBucket - 1] : 0;
328 float passed_within_target_perc = 0.0;
329 float failed_within_target_perc = 0.0;
337 LogPrint(
BCLog::ESTIMATEFEE,
"FeeEst: %d > %.0f%% decay %.5f: feerate: %g from (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
338 confTarget, 100.0 * successBreakPoint,
decay,
339 median, passBucket.
start, passBucket.
end,
340 passed_within_target_perc,
343 failed_within_target_perc,
348 result->
pass = passBucket;
349 result->
fail = failBucket;
371 size_t maxConfirms, maxPeriods;
375 if (decay <= 0 || decay >= 1) {
376 throw std::runtime_error(
"Corrupt estimates file. Decay must be between 0 and 1 (non-inclusive)");
380 throw std::runtime_error(
"Corrupt estimates file. Scale must be non-zero");
384 if (m_feerate_avg.size() != numBuckets) {
385 throw std::runtime_error(
"Corrupt estimates file. Mismatch in feerate average bucket count");
388 if (txCtAvg.size() != numBuckets) {
389 throw std::runtime_error(
"Corrupt estimates file. Mismatch in tx count bucket count");
392 maxPeriods = confAvg.size();
393 maxConfirms = scale * maxPeriods;
395 if (maxConfirms <= 0 || maxConfirms > 6 * 24 * 7) {
396 throw std::runtime_error(
"Corrupt estimates file. Must maintain estimates for between 1 and 1008 (one week) confirms");
398 for (
unsigned int i = 0; i < maxPeriods; i++) {
399 if (confAvg[i].size() != numBuckets) {
400 throw std::runtime_error(
"Corrupt estimates file. Mismatch in feerate conf average bucket count");
405 if (maxPeriods != failAvg.size()) {
406 throw std::runtime_error(
"Corrupt estimates file. Mismatch in confirms tracked for failures");
408 for (
unsigned int i = 0; i < maxPeriods; i++) {
409 if (failAvg[i].size() != numBuckets) {
410 throw std::runtime_error(
"Corrupt estimates file. Mismatch in one of failure average bucket counts");
419 numBuckets, maxConfirms);
424 unsigned int bucketindex =
bucketMap.lower_bound(val)->second;
425 unsigned int blockIndex = nBlockHeight %
unconfTxs.size();
433 int blocksAgo = nBestSeenHeight - entryHeight;
434 if (nBestSeenHeight == 0)
441 if (blocksAgo >= (
int)
unconfTxs.size()) {
450 unsigned int blockIndex = entryHeight %
unconfTxs.size();
451 if (
unconfTxs[blockIndex][bucketindex] > 0) {
455 blockIndex, bucketindex);
458 if (!inBlock && (
unsigned int)blocksAgo >=
scale) {
460 unsigned int periodsAgo = blocksAgo /
scale;
461 for (
size_t i = 0; i < periodsAgo && i <
failAvg.size(); i++) {
475 std::map<uint256, TxStatsInfo>::iterator pos = mapMemPoolTxs.find(hash);
476 if (pos != mapMemPoolTxs.end()) {
477 feeStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex, inBlock);
478 shortStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex, inBlock);
479 longStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex, inBlock);
480 mapMemPoolTxs.erase(hash);
488 : nBestSeenHeight(0), firstRecordedHeight(0), historicalFirst(0), historicalBest(0), trackedTxs(0), untrackedTxs(0)
491 size_t bucketIndex = 0;
493 buckets.push_back(bucketBoundary);
494 bucketMap[bucketBoundary] = bucketIndex;
498 assert(bucketMap.size() == buckets.size());
512 unsigned int txHeight = entry.
GetHeight();
514 if (mapMemPoolTxs.count(hash)) {
520 if (txHeight != nBestSeenHeight) {
530 if (!validFeeEstimate) {
539 mapMemPoolTxs[hash].blockHeight = txHeight;
540 unsigned int bucketIndex = feeStats->NewTx(txHeight, (
double)feeRate.GetFeePerK());
541 mapMemPoolTxs[hash].bucketIndex = bucketIndex;
542 unsigned int bucketIndex2 = shortStats->NewTx(txHeight, (
double)feeRate.GetFeePerK());
543 assert(bucketIndex == bucketIndex2);
544 unsigned int bucketIndex3 = longStats->NewTx(txHeight, (
double)feeRate.GetFeePerK());
545 assert(bucketIndex == bucketIndex3);
558 int blocksToConfirm = nBlockHeight - entry->
GetHeight();
559 if (blocksToConfirm <= 0) {
569 feeStats->Record(blocksToConfirm, (
double)feeRate.GetFeePerK());
570 shortStats->Record(blocksToConfirm, (
double)feeRate.GetFeePerK());
571 longStats->Record(blocksToConfirm, (
double)feeRate.GetFeePerK());
576 std::vector<const CTxMemPoolEntry*>& entries)
579 if (nBlockHeight <= nBestSeenHeight) {
591 nBestSeenHeight = nBlockHeight;
594 feeStats->ClearCurrent(nBlockHeight);
595 shortStats->ClearCurrent(nBlockHeight);
596 longStats->ClearCurrent(nBlockHeight);
599 feeStats->UpdateMovingAverages();
600 shortStats->UpdateMovingAverages();
601 longStats->UpdateMovingAverages();
603 unsigned int countedTxs = 0;
605 for (
const auto& entry : entries) {
610 if (firstRecordedHeight == 0 && countedTxs > 0) {
611 firstRecordedHeight = nBestSeenHeight;
616 LogPrint(
BCLog::ESTIMATEFEE,
"Blockpolicy estimates updated by %u of %u block txs, since last block %u of %u tracked, mempool map size %u, max target %u from %s\n",
617 countedTxs, entries.size(), trackedTxs, trackedTxs + untrackedTxs, mapMemPoolTxs.size(),
639 stats = shortStats.get();
644 stats = feeStats.get();
648 stats = longStats.get();
652 throw std::out_of_range(
"CBlockPolicyEstimator::estimateRawFee unknown FeeEstimateHorizon");
658 if (confTarget <= 0 || (
unsigned int)confTarget > stats->
GetMaxConfirms())
660 if (successThreshold > 1)
663 double median = stats->
EstimateMedianVal(confTarget, sufficientTxs, successThreshold, nBestSeenHeight, result);
676 return shortStats->GetMaxConfirms();
679 return feeStats->GetMaxConfirms();
682 return longStats->GetMaxConfirms();
685 throw std::out_of_range(
"CBlockPolicyEstimator::HighestTargetTracked unknown FeeEstimateHorizon");
692 if (firstRecordedHeight == 0)
return 0;
693 assert(nBestSeenHeight >= firstRecordedHeight);
695 return nBestSeenHeight - firstRecordedHeight;
700 if (historicalFirst == 0)
return 0;
701 assert(historicalBest >= historicalFirst);
705 return historicalBest - historicalFirst;
720 double estimate = -1;
721 if (confTarget >= 1 && confTarget <= longStats->GetMaxConfirms()) {
723 if (confTarget <= shortStats->GetMaxConfirms()) {
724 estimate = shortStats->EstimateMedianVal(confTarget,
SUFFICIENT_TXS_SHORT, successThreshold, nBestSeenHeight, result);
726 else if (confTarget <= feeStats->GetMaxConfirms()) {
727 estimate = feeStats->EstimateMedianVal(confTarget,
SUFFICIENT_FEETXS, successThreshold, nBestSeenHeight, result);
730 estimate = longStats->EstimateMedianVal(confTarget,
SUFFICIENT_FEETXS, successThreshold, nBestSeenHeight, result);
732 if (checkShorterHorizon) {
735 if (confTarget > feeStats->GetMaxConfirms()) {
736 double medMax = feeStats->EstimateMedianVal(feeStats->GetMaxConfirms(),
SUFFICIENT_FEETXS, successThreshold, nBestSeenHeight, &tempResult);
737 if (medMax > 0 && (estimate == -1 || medMax < estimate)) {
739 if (result) *result = tempResult;
742 if (confTarget > shortStats->GetMaxConfirms()) {
743 double shortMax = shortStats->EstimateMedianVal(shortStats->GetMaxConfirms(),
SUFFICIENT_TXS_SHORT, successThreshold, nBestSeenHeight, &tempResult);
744 if (shortMax > 0 && (estimate == -1 || shortMax < estimate)) {
746 if (result) *result = tempResult;
759 double estimate = -1;
761 if (doubleTarget <= shortStats->GetMaxConfirms()) {
764 if (doubleTarget <= feeStats->GetMaxConfirms()) {
766 if (longEstimate > estimate) {
767 estimate = longEstimate;
768 if (result) *result = tempResult;
794 if (confTarget <= 0 || (
unsigned int)confTarget > longStats->GetMaxConfirms()) {
799 if (confTarget == 1) confTarget = 2;
802 if ((
unsigned int)confTarget > maxUsableEstimate) {
803 confTarget = maxUsableEstimate;
807 if (confTarget <= 1)
return CFeeRate(0);
809 assert(confTarget > 0);
822 feeCalc->
est = tempResult;
827 if (actualEst > median) {
830 feeCalc->
est = tempResult;
835 if (doubleEst > median) {
838 feeCalc->
est = tempResult;
843 if (conservative || median == -1) {
845 if (consEst > median) {
848 feeCalc->
est = tempResult;
866 fileout << nBestSeenHeight;
868 fileout << firstRecordedHeight << nBestSeenHeight;
871 fileout << historicalFirst << historicalBest;
874 feeStats->Write(fileout);
875 shortStats->Write(fileout);
876 longStats->Write(fileout);
878 catch (
const std::exception&) {
879 LogPrintf(
"CBlockPolicyEstimator::Write(): unable to write policy estimator data (non-fatal)\n");
889 int nVersionRequired, nVersionThatWrote;
890 filein >> nVersionRequired >> nVersionThatWrote;
892 return error(
"CBlockPolicyEstimator::Read(): up-version (%d) fee estimate file", nVersionRequired);
896 unsigned int nFileBestSeenHeight;
897 filein >> nFileBestSeenHeight;
899 if (nVersionRequired < 149900) {
900 LogPrintf(
"%s: incompatible old fee estimation data (non-fatal). Version: %d\n", __func__, nVersionRequired);
902 unsigned int nFileHistoricalFirst, nFileHistoricalBest;
903 filein >> nFileHistoricalFirst >> nFileHistoricalBest;
904 if (nFileHistoricalFirst > nFileHistoricalBest || nFileHistoricalBest > nFileBestSeenHeight) {
905 throw std::runtime_error(
"Corrupt estimates file. Historical block range for estimates is invalid");
907 std::vector<double> fileBuckets;
908 filein >> fileBuckets;
909 size_t numBuckets = fileBuckets.size();
910 if (numBuckets <= 1 || numBuckets > 1000)
911 throw std::runtime_error(
"Corrupt estimates file. Must have between 2 and 1000 feerate buckets");
916 fileFeeStats->Read(filein, nVersionThatWrote, numBuckets);
917 fileShortStats->Read(filein, nVersionThatWrote, numBuckets);
918 fileLongStats->Read(filein, nVersionThatWrote, numBuckets);
922 buckets = fileBuckets;
924 for (
unsigned int i = 0; i < buckets.size(); i++) {
925 bucketMap[buckets[i]] = i;
929 feeStats = std::move(fileFeeStats);
930 shortStats = std::move(fileShortStats);
931 longStats = std::move(fileLongStats);
933 nBestSeenHeight = nFileBestSeenHeight;
934 historicalFirst = nFileHistoricalFirst;
935 historicalBest = nFileHistoricalBest;
938 catch (
const std::exception& e) {
939 LogPrintf(
"CBlockPolicyEstimator::Read(): unable to read policy estimator data (non-fatal): %s\n",e.what());
948 size_t num_entries = mapMemPoolTxs.size();
950 while (!mapMemPoolTxs.empty()) {
951 auto mi = mapMemPoolTxs.begin();
955 LogPrint(
BCLog::ESTIMATEFEE,
"Recorded %u unconfirmed txs from mempool in %gs\n", num_entries, (endclear - startclear)*0.000001);
963 feeset.insert(bucketBoundary);
969 std::set<double>::iterator
it =
feeset.lower_bound(currentMinFee);
973 return static_cast<CAmount>(*it);
static constexpr double MED_DECAY
Decay of .9952 is a half-life of 144 blocks or about 1 day.
CAmount GetFeePerK() const
Return the fee in satoshis for a size of 1000 bytes.
std::vector< std::vector< double > > failAvg
unsigned int GetHeight() const
unsigned int GetMaxConfirms() const
Return the max number of confirms we're tracking.
bool processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry *entry) EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator)
Process a transaction confirmed in a block.
#define LogPrint(category,...)
static constexpr double MAX_BUCKET_FEERATE
static constexpr double HALF_SUCCESS_PCT
Require greater than 60% of X feerate transactions to be confirmed within Y/2 blocks.
static constexpr unsigned int MED_BLOCK_PERIODS
Track confirm delays up to 48 blocks for medium horizon.
bool removeTx(uint256 hash, bool inBlock)
Remove a transaction from the mempool tracking stats.
CBlockPolicyEstimator()
Create new BlockPolicyEstimator and initialize stats tracking classes with default values...
TxConfirmStats(const std::vector< double > &defaultBuckets, const std::map< double, unsigned int > &defaultBucketMap, unsigned int maxPeriods, double decay, unsigned int scale)
Create new TxConfirmStats.
static void LogPrintf(const char *fmt, const Args &...args)
double estimateConservativeFee(unsigned int doubleTarget, EstimationResult *result) const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator)
Helper for estimateSmartFee.
void FlushUnconfirmed()
Empty mempool transactions on shutdown to record failure to confirm for txs still in mempool...
unsigned int HistoricalBlockSpan() const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator)
Number of blocks of recorded fee estimate data represented in saved data file.
static constexpr double MAX_FILTER_FEERATE
We will instantiate an instance of this class to track transactions that were included in a block...
std::string StringForFeeEstimateHorizon(FeeEstimateHorizon horizon)
static constexpr double DOUBLE_SUCCESS_PCT
Require greater than 95% of X feerate transactions to be confirmed within 2 * Y blocks.
static constexpr double FEE_SPACING
Spacing of FeeRate buckets We have to lump transactions into buckets based on feerate, but we want to be able to give accurate estimates over a large range of potential feerates Therefore it makes sense to exponentially space the buckets.
CFeeRate estimateFee(int confTarget) const
DEPRECATED.
unsigned int MaxUsableEstimate() const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator)
Calculation of highest target that reasonable estimate can be provided for.
void Record(int blocksToConfirm, double val)
Record a new transaction data point in the current block stats.
static constexpr double MIN_BUCKET_FEERATE
Minimum and Maximum values for tracking feerates The MIN_BUCKET_FEERATE should just be set to the low...
void Write(CAutoFile &fileout) const
Write state of estimation data to a file.
double EstimateMedianVal(int confTarget, double sufficientTxVal, double minSuccess, unsigned int nBlockHeight, EstimationResult *result=nullptr) const
Calculate a feerate estimate.
void resizeInMemoryCounters(size_t newbuckets)
void ClearCurrent(unsigned int nBlockHeight)
Roll the circular buffer for unconfirmed txs.
static constexpr double SUFFICIENT_TXS_SHORT
Require an avg of 0.5 tx when using short decay since there are fewer blocks considered.
std::string ToString() const
CTxMemPoolEntry stores data about the corresponding transaction, as well as data about all in-mempool...
static constexpr double FEE_FILTER_SPACING
FEE_FILTER_SPACING is just used to provide some quantization of fee filter results.
int64_t CAmount
Amount in satoshis (Can be negative)
static constexpr double SUCCESS_PCT
Require greater than 85% of X feerate transactions to be confirmed within Y blocks.
void Read(CAutoFile &filein, int nFileVersion, size_t numBuckets)
Read saved state of estimation data from a file and replace all internal data structures and variable...
const CAmount & GetFee() const
const std::map< double, unsigned int > & bucketMap
static constexpr unsigned int SHORT_SCALE
bool error(const char *fmt, const Args &...args)
static constexpr double LONG_DECAY
Decay of .99931 is a half-life of 1008 blocks or about 1 week.
unsigned int HighestTargetTracked(FeeEstimateHorizon horizon) const
Calculation of highest target that estimates are tracked for.
std::vector< std::vector< double > > confAvg
CFeeRate estimateSmartFee(int confTarget, FeeCalculation *feeCalc, bool conservative) const
Estimate feerate needed to get be included in a block within confTarget blocks.
unsigned int NewTx(unsigned int nBlockHeight, double val)
Record a new transaction entering the mempool.
void removeTx(unsigned int entryHeight, unsigned int nBestSeenHeight, unsigned int bucketIndex, bool inBlock)
Remove a transaction from mempool tracking stats.
const std::vector< double > & buckets
unsigned int BlockSpan() const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator)
Number of blocks of data recorded while fee estimates have been running.
double estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon, EstimationResult *result) const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator)
Helper for estimateSmartFee.
FeeFilterRounder(const CFeeRate &minIncrementalFee)
Create new FeeFilterRounder.
FastRandomContext insecure_rand
static constexpr unsigned int LONG_SCALE
uint32_t rand32() noexcept
Generate a random 32-bit integer.
int64_t GetTimeMicros()
Returns the system time (not mockable)
bool Write(CAutoFile &fileout) const
Write estimation data to a file.
std::vector< std::vector< int > > unconfTxs
static constexpr unsigned int MED_SCALE
static const unsigned int OLDEST_ESTIMATE_HISTORY
Historical estimates that are older than this aren't valid.
const CTransaction & GetTx() const
std::vector< int > oldUnconfTxs
bool Read(CAutoFile &filein)
Read estimation data from a file.
static constexpr unsigned int LONG_BLOCK_PERIODS
Track confirm delays up to 1008 blocks for long horizon.
Fee rate in satoshis per kilobyte: CAmount / kB.
RecursiveMutex m_cs_fee_estimator
static constexpr unsigned int SHORT_BLOCK_PERIODS
Track confirm delays up to 12 blocks for short horizon.
static constexpr double INF_FEERATE
const uint256 & GetHash() const
static constexpr double SUFFICIENT_FEETXS
Require an avg of 0.1 tx in the combined feerate bucket per block to have stat significance.
std::vector< double > m_feerate_avg
CAmount round(CAmount currentMinFee)
Quantize a minimum fee for privacy purpose before broadcast.
static constexpr double SHORT_DECAY
Decay of .962 is a half-life of 18 blocks or about 3 hours.
CFeeRate estimateRawFee(int confTarget, double successThreshold, FeeEstimateHorizon horizon, EstimationResult *result=nullptr) const
Return a specific fee estimate calculation with a given success threshold and time horizon...
std::set< double > feeset
std::vector< double > txCtAvg
void processBlock(unsigned int nBlockHeight, std::vector< const CTxMemPoolEntry * > &entries)
Process all the transactions that have been included in a block.
void UpdateMovingAverages()
Update our estimates by decaying our historical moving average and updating with the data gathered fr...
static const int CLIENT_VERSION
bitcoind-res.rc includes this file, but it cannot cope with real c++ code.
Non-refcounted RAII wrapper for FILE*.
void processTransaction(const CTxMemPoolEntry &entry, bool validFeeEstimate)
Process a transaction accepted to the mempool.