Skip to content

Commit e5007ba

Browse files
committed
Change parameters for fee estimation and estimates on all 3 time horizons.
Make feerate buckets smaller (5% instead of 10%) and make the 3 different horizons have half lifes of 3 hours, 1 day and 1 week respectively.
1 parent c0a273f commit e5007ba

File tree

3 files changed

+89
-51
lines changed

3 files changed

+89
-51
lines changed

src/policy/fees.cpp

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include "txmempool.h"
1515
#include "util.h"
1616

17+
static constexpr double INF_FEERATE = 1e99;
18+
1719
/**
1820
* We will instantiate an instance of this class to track transactions that were
1921
* included in a block. We will lump transactions into a bucket according to their
@@ -400,6 +402,8 @@ bool CBlockPolicyEstimator::removeTx(uint256 hash)
400402
std::map<uint256, TxStatsInfo>::iterator pos = mapMemPoolTxs.find(hash);
401403
if (pos != mapMemPoolTxs.end()) {
402404
feeStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex);
405+
shortStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex);
406+
longStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex);
403407
mapMemPoolTxs.erase(hash);
404408
return true;
405409
} else {
@@ -421,9 +425,9 @@ CBlockPolicyEstimator::CBlockPolicyEstimator()
421425
bucketMap[INF_FEERATE] = bucketIndex;
422426
assert(bucketMap.size() == buckets.size());
423427

424-
feeStats = new TxConfirmStats(buckets, bucketMap, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
425-
shortStats = new TxConfirmStats(buckets, bucketMap, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
426-
longStats = new TxConfirmStats(buckets, bucketMap, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
428+
feeStats = new TxConfirmStats(buckets, bucketMap, MED_BLOCK_CONFIRMS, MED_DECAY);
429+
shortStats = new TxConfirmStats(buckets, bucketMap, SHORT_BLOCK_CONFIRMS, SHORT_DECAY);
430+
longStats = new TxConfirmStats(buckets, bucketMap, LONG_BLOCK_CONFIRMS, LONG_DECAY);
427431
}
428432

429433
CBlockPolicyEstimator::~CBlockPolicyEstimator()
@@ -464,7 +468,12 @@ void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, boo
464468
CFeeRate feeRate(entry.GetFee(), entry.GetTxSize());
465469

466470
mapMemPoolTxs[hash].blockHeight = txHeight;
467-
mapMemPoolTxs[hash].bucketIndex = feeStats->NewTx(txHeight, (double)feeRate.GetFeePerK());
471+
unsigned int bucketIndex = feeStats->NewTx(txHeight, (double)feeRate.GetFeePerK());
472+
mapMemPoolTxs[hash].bucketIndex = bucketIndex;
473+
unsigned int bucketIndex2 = shortStats->NewTx(txHeight, (double)feeRate.GetFeePerK());
474+
assert(bucketIndex == bucketIndex2);
475+
unsigned int bucketIndex3 = longStats->NewTx(txHeight, (double)feeRate.GetFeePerK());
476+
assert(bucketIndex == bucketIndex3);
468477
}
469478

470479
bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry)
@@ -489,6 +498,8 @@ bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxM
489498
CFeeRate feeRate(entry->GetFee(), entry->GetTxSize());
490499

491500
feeStats->Record(blocksToConfirm, (double)feeRate.GetFeePerK());
501+
shortStats->Record(blocksToConfirm, (double)feeRate.GetFeePerK());
502+
longStats->Record(blocksToConfirm, (double)feeRate.GetFeePerK());
492503
return true;
493504
}
494505

@@ -512,6 +523,8 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
512523

513524
// Clear the current block state and update unconfirmed circular buffer
514525
feeStats->ClearCurrent(nBlockHeight);
526+
shortStats->ClearCurrent(nBlockHeight);
527+
longStats->ClearCurrent(nBlockHeight);
515528

516529
unsigned int countedTxs = 0;
517530
// Repopulate the current block states
@@ -522,6 +535,8 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
522535

523536
// Update all exponential averages with the current block state
524537
feeStats->UpdateMovingAverages();
538+
shortStats->UpdateMovingAverages();
539+
longStats->UpdateMovingAverages();
525540

526541
LogPrint(BCLog::ESTIMATEFEE, "Blockpolicy after updating estimates for %u of %u txs in block, since last block %u of %u tracked, new mempool map size %u\n",
527542
countedTxs, entries.size(), trackedTxs, trackedTxs + untrackedTxs, mapMemPoolTxs.size());
@@ -538,7 +553,7 @@ CFeeRate CBlockPolicyEstimator::estimateFee(int confTarget) const
538553
if (confTarget <= 1 || (unsigned int)confTarget > feeStats->GetMaxConfirms())
539554
return CFeeRate(0);
540555

541-
double median = feeStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
556+
double median = feeStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight);
542557

543558
if (median < 0)
544559
return CFeeRate(0);
@@ -565,7 +580,7 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoun
565580
confTarget = 2;
566581

567582
while (median < 0 && (unsigned int)confTarget <= feeStats->GetMaxConfirms()) {
568-
median = feeStats->EstimateMedianVal(confTarget++, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
583+
median = feeStats->EstimateMedianVal(confTarget++, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight);
569584
}
570585
} // Must unlock cs_feeEstimator before taking mempool locks
571586

@@ -634,7 +649,7 @@ bool CBlockPolicyEstimator::Read(CAutoFile& filein)
634649

635650
std::map<double, unsigned int> tempMap;
636651

637-
std::unique_ptr<TxConfirmStats> tempFeeStats(new TxConfirmStats(tempBuckets, tempMap, MAX_BLOCK_CONFIRMS, tempDecay));
652+
std::unique_ptr<TxConfirmStats> tempFeeStats(new TxConfirmStats(tempBuckets, tempMap, MED_BLOCK_CONFIRMS, tempDecay));
638653
tempFeeStats->Read(filein, nVersionThatWrote, tempNum);
639654
// if nVersionThatWrote < 139900 then another TxConfirmStats (for priority) follows but can be ignored.
640655

@@ -653,9 +668,9 @@ bool CBlockPolicyEstimator::Read(CAutoFile& filein)
653668
if (numBuckets <= 1 || numBuckets > 1000)
654669
throw std::runtime_error("Corrupt estimates file. Must have between 2 and 1000 feerate buckets");
655670

656-
std::unique_ptr<TxConfirmStats> fileFeeStats(new TxConfirmStats(buckets, bucketMap, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY));
657-
std::unique_ptr<TxConfirmStats> fileShortStats(new TxConfirmStats(buckets, bucketMap, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY));
658-
std::unique_ptr<TxConfirmStats> fileLongStats(new TxConfirmStats(buckets, bucketMap, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY));
671+
std::unique_ptr<TxConfirmStats> fileFeeStats(new TxConfirmStats(buckets, bucketMap, MED_BLOCK_CONFIRMS, MED_DECAY));
672+
std::unique_ptr<TxConfirmStats> fileShortStats(new TxConfirmStats(buckets, bucketMap, SHORT_BLOCK_CONFIRMS, SHORT_DECAY));
673+
std::unique_ptr<TxConfirmStats> fileLongStats(new TxConfirmStats(buckets, bucketMap, LONG_BLOCK_CONFIRMS, LONG_DECAY));
659674
fileFeeStats->Read(filein, nVersionThatWrote, numBuckets);
660675
fileShortStats->Read(filein, nVersionThatWrote, numBuckets);
661676
fileLongStats->Read(filein, nVersionThatWrote, numBuckets);
@@ -690,7 +705,7 @@ FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee)
690705
{
691706
CAmount minFeeLimit = std::max(CAmount(1), minIncrementalFee.GetFeePerK() / 2);
692707
feeset.insert(0);
693-
for (double bucketBoundary = minFeeLimit; bucketBoundary <= MAX_BUCKET_FEERATE; bucketBoundary *= FEE_SPACING) {
708+
for (double bucketBoundary = minFeeLimit; bucketBoundary <= MAX_FILTER_FEERATE; bucketBoundary *= FEE_FILTER_SPACING) {
694709
feeset.insert(bucketBoundary);
695710
}
696711
}

src/policy/fees.h

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -61,41 +61,53 @@ class TxConfirmStats;
6161
* they've been outstanding.
6262
*/
6363

64-
/** Track confirm delays up to 25 blocks, can't estimate beyond that */
65-
static const unsigned int MAX_BLOCK_CONFIRMS = 25;
66-
67-
/** Decay of .998 is a half-life of 346 blocks or about 2.4 days */
68-
static const double DEFAULT_DECAY = .998;
69-
70-
/** Require greater than 95% of X feerate transactions to be confirmed within Y blocks for X to be big enough */
71-
static const double MIN_SUCCESS_PCT = .95;
72-
73-
/** Require an avg of 1 tx in the combined feerate bucket per block to have stat significance */
74-
static const double SUFFICIENT_FEETXS = 1;
75-
76-
// Minimum and Maximum values for tracking feerates
77-
// The MIN_BUCKET_FEERATE should just be set to the lowest reasonable feerate we
78-
// might ever want to track. Historically this has been 1000 since it was
79-
// inheriting DEFAULT_MIN_RELAY_TX_FEE and changing it is disruptive as it
80-
// invalidates old estimates files. So leave it at 1000 unless it becomes
81-
// necessary to lower it, and then lower it substantially.
82-
static constexpr double MIN_BUCKET_FEERATE = 1000;
83-
static const double MAX_BUCKET_FEERATE = 1e7;
84-
static const double INF_FEERATE = MAX_MONEY;
85-
86-
// We have to lump transactions into buckets based on feerate, but we want to be able
87-
// to give accurate estimates over a large range of potential feerates
88-
// Therefore it makes sense to exponentially space the buckets
89-
/** Spacing of FeeRate buckets */
90-
static const double FEE_SPACING = 1.1;
91-
9264
/**
9365
* We want to be able to estimate feerates that are needed on tx's to be included in
9466
* a certain number of blocks. Every time a block is added to the best chain, this class records
9567
* stats on the transactions included in that block
9668
*/
9769
class CBlockPolicyEstimator
9870
{
71+
private:
72+
/** Track confirm delays up to 12 blocks medium decay */
73+
static constexpr unsigned int SHORT_BLOCK_CONFIRMS = 12;
74+
/** Track confirm delays up to 48 blocks medium decay */
75+
static constexpr unsigned int MED_BLOCK_CONFIRMS = 48;
76+
/** Track confirm delays up to 1008 blocks for longer decay */
77+
static constexpr unsigned int LONG_BLOCK_CONFIRMS = 1008;
78+
79+
/** Decay of .962 is a half-life of 18 blocks or about 3 hours */
80+
static constexpr double SHORT_DECAY = .962;
81+
/** Decay of .998 is a half-life of 144 blocks or about 1 day */
82+
static constexpr double MED_DECAY = .9952;
83+
/** Decay of .9995 is a half-life of 1008 blocks or about 1 week */
84+
static constexpr double LONG_DECAY = .99931;
85+
86+
/** Require greater than 95% of X feerate transactions to be confirmed within Y blocks for X to be big enough */
87+
static constexpr double HALF_SUCCESS_PCT = .6;
88+
static constexpr double SUCCESS_PCT = .85;
89+
static constexpr double DOUBLE_SUCCESS_PCT = .95;
90+
91+
/** Require an avg of 1 tx in the combined feerate bucket per block to have stat significance */
92+
static constexpr double SUFFICIENT_FEETXS = 1;
93+
94+
/** Minimum and Maximum values for tracking feerates
95+
* The MIN_BUCKET_FEERATE should just be set to the lowest reasonable feerate we
96+
* might ever want to track. Historically this has been 1000 since it was
97+
* inheriting DEFAULT_MIN_RELAY_TX_FEE and changing it is disruptive as it
98+
* invalidates old estimates files. So leave it at 1000 unless it becomes
99+
* necessary to lower it, and then lower it substantially.
100+
*/
101+
static constexpr double MIN_BUCKET_FEERATE = 1000;
102+
static constexpr double MAX_BUCKET_FEERATE = 1e7;
103+
104+
/** Spacing of FeeRate buckets
105+
* We have to lump transactions into buckets based on feerate, but we want to be able
106+
* to give accurate estimates over a large range of potential feerates
107+
* Therefore it makes sense to exponentially space the buckets
108+
*/
109+
static constexpr double FEE_SPACING = 1.05;
110+
99111
public:
100112
/** Create new BlockPolicyEstimator and initialize stats tracking classes with default values */
101113
CBlockPolicyEstimator();
@@ -159,6 +171,14 @@ class CBlockPolicyEstimator
159171

160172
class FeeFilterRounder
161173
{
174+
private:
175+
static constexpr double MAX_FILTER_FEERATE = 1e7;
176+
/** FEE_FILTER_SPACING is just used to provide some quantization of fee
177+
* filter results. Historically it reused FEE_SPACING, but it is completely
178+
* unrelated, and was made a separate constant so the two concepts are not
179+
* tied together */
180+
static constexpr double FEE_FILTER_SPACING = 1.1;
181+
162182
public:
163183
/** Create new FeeFilterRounder */
164184
FeeFilterRounder(const CFeeRate& minIncrementalFee);

src/test/policyestimator_tests.cpp

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
5050
int blocknum = 0;
5151

5252
// Loop through 200 blocks
53-
// At a decay .998 and 4 fee transactions per block
54-
// This makes the tx count about 1.33 per bucket, above the 1 threshold
53+
// At a decay .9952 and 4 fee transactions per block
54+
// This makes the tx count about 2.5 per bucket, well above the 0.1 threshold
5555
while (blocknum < 200) {
5656
for (int j = 0; j < 10; j++) { // For each fee
5757
for (int k = 0; k < 4; k++) { // add 4 fee txs
@@ -75,18 +75,17 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
7575
}
7676
mpool.removeForBlock(block, ++blocknum);
7777
block.clear();
78-
if (blocknum == 30) {
79-
// At this point we should need to combine 5 buckets to get enough data points
80-
// So estimateFee(1,2,3) should fail and estimateFee(4) should return somewhere around
81-
// 8*baserate. estimateFee(4) %'s are 100,100,100,100,90 = average 98%
78+
// Check after just a few txs that combining buckets works as expected
79+
if (blocknum == 3) {
80+
// At this point we should need to combine 3 buckets to get enough data points
81+
// So estimateFee(1) should fail and estimateFee(2) should return somewhere around
82+
// 9*baserate. estimateFee(2) %'s are 100,100,90 = average 97%
8283
BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
83-
BOOST_CHECK(feeEst.estimateFee(2) == CFeeRate(0));
84-
BOOST_CHECK(feeEst.estimateFee(3) == CFeeRate(0));
85-
BOOST_CHECK(feeEst.estimateFee(4).GetFeePerK() < 8*baseRate.GetFeePerK() + deltaFee);
86-
BOOST_CHECK(feeEst.estimateFee(4).GetFeePerK() > 8*baseRate.GetFeePerK() - deltaFee);
84+
BOOST_CHECK(feeEst.estimateFee(2).GetFeePerK() < 9*baseRate.GetFeePerK() + deltaFee);
85+
BOOST_CHECK(feeEst.estimateFee(2).GetFeePerK() > 9*baseRate.GetFeePerK() - deltaFee);
8786
int answerFound;
88-
BOOST_CHECK(feeEst.estimateSmartFee(1, &answerFound, mpool) == feeEst.estimateFee(4) && answerFound == 4);
89-
BOOST_CHECK(feeEst.estimateSmartFee(3, &answerFound, mpool) == feeEst.estimateFee(4) && answerFound == 4);
87+
BOOST_CHECK(feeEst.estimateSmartFee(1, &answerFound, mpool) == feeEst.estimateFee(2) && answerFound == 2);
88+
BOOST_CHECK(feeEst.estimateSmartFee(2, &answerFound, mpool) == feeEst.estimateFee(2) && answerFound == 2);
9089
BOOST_CHECK(feeEst.estimateSmartFee(4, &answerFound, mpool) == feeEst.estimateFee(4) && answerFound == 4);
9190
BOOST_CHECK(feeEst.estimateSmartFee(8, &answerFound, mpool) == feeEst.estimateFee(8) && answerFound == 8);
9291
}
@@ -113,6 +112,10 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
113112
BOOST_CHECK(origFeeEst[i-1] == CFeeRate(0).GetFeePerK());
114113
}
115114
}
115+
// Fill out rest of the original estimates
116+
for (int i = 10; i <= 48; i++) {
117+
origFeeEst.push_back(feeEst.estimateFee(i).GetFeePerK());
118+
}
116119

117120
// Mine 50 more blocks with no transactions happening, estimates shouldn't change
118121
// We haven't decayed the moving average enough so we still have enough data points in every bucket

0 commit comments

Comments
 (0)