Skip to content

Commit 318ea50

Browse files
committed
Merge #10199: Better fee estimates
38bc1ec Make more json-like output from estimaterawfee (Alex Morcos) 2d2e170 Comments and improved documentation (Alex Morcos) ef589f8 minor cleanup: remove unnecessary variable (Alex Morcos) 3ee76d6 Introduce a scale factor (Alex Morcos) 5f1f0c6 Historical block span (Alex Morcos) aa19b8e Clean up fee estimate debug printing (Alex Morcos) 10f7cbd Track first recorded height (Alex Morcos) 3810e97 Rewrite estimateSmartFee (Alex Morcos) c7447ec Track failures in fee estimation. (Alex Morcos) 4186d3f Expose estimaterawfee (Alex Morcos) 2681153 minor refactor: explicitly track start of new bucket range and don't update curNearBucket on final loop. (Alex Morcos) 1ba43cc Make EstimateMedianVal smarter about small failures. (Alex Morcos) d3e30bc Refactor to update moving average on fly (Alex Morcos) e5007ba Change parameters for fee estimation and estimates on all 3 time horizons. (Alex Morcos) c0a273f Change file format for fee estimates. (Alex Morcos) Tree-SHA512: 186e7508d86a1f351bb656edcd84ee9091f5f2706331eda9ee29da9c8eb5bf67b8c1f2abf6662835560e7f613b1377099054f20767f41ddcdbc89c4f9e78946d
2 parents e61fc2d + 38bc1ec commit 318ea50

File tree

8 files changed

+744
-228
lines changed

8 files changed

+744
-228
lines changed

src/init.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ void Shutdown()
215215

216216
if (fFeeEstimatesInitialized)
217217
{
218+
::feeEstimator.FlushUnconfirmed(::mempool);
218219
fs::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME;
219220
CAutoFile est_fileout(fsbridge::fopen(est_path, "wb"), SER_DISK, CLIENT_VERSION);
220221
if (!est_fileout.IsNull())

src/policy/fees.cpp

Lines changed: 477 additions & 142 deletions
Large diffs are not rendered by default.

src/policy/fees.h

Lines changed: 140 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -42,53 +42,57 @@ class TxConfirmStats;
4242
* within your desired 5 blocks.
4343
*
4444
* Here is a brief description of the implementation:
45-
* When a transaction enters the mempool, we
46-
* track the height of the block chain at entry. Whenever a block comes in,
47-
* we count the number of transactions in each bucket and the total amount of feerate
48-
* paid in each bucket. Then we calculate how many blocks Y it took each
49-
* transaction to be mined and we track an array of counters in each bucket
50-
* for how long it to took transactions to get confirmed from 1 to a max of 25
51-
* and we increment all the counters from Y up to 25. This is because for any
52-
* number Z>=Y the transaction was successfully mined within Z blocks. We
53-
* want to save a history of this information, so at any time we have a
54-
* counter of the total number of transactions that happened in a given feerate
55-
* bucket and the total number that were confirmed in each number 1-25 blocks
56-
* or less for any bucket. We save this history by keeping an exponentially
57-
* decaying moving average of each one of these stats. Furthermore we also
58-
* keep track of the number unmined (in mempool) transactions in each bucket
59-
* and for how many blocks they have been outstanding and use that to increase
60-
* the number of transactions we've seen in that feerate bucket when calculating
61-
* an estimate for any number of confirmations below the number of blocks
62-
* they've been outstanding.
45+
* When a transaction enters the mempool, we track the height of the block chain
46+
* at entry. All further calculations are conducted only on this set of "seen"
47+
* transactions. Whenever a block comes in, we count the number of transactions
48+
* in each bucket and the total amount of feerate paid in each bucket. Then we
49+
* calculate how many blocks Y it took each transaction to be mined. We convert
50+
* from a number of blocks to a number of periods Y' each encompassing "scale"
51+
* blocks. This is is tracked in 3 different data sets each up to a maximum
52+
* number of periods. Within each data set we have an array of counters in each
53+
* feerate bucket and we increment all the counters from Y' up to max periods
54+
* representing that a tx was successfullly confirmed in less than or equal to
55+
* that many periods. We want to save a history of this information, so at any
56+
* time we have a counter of the total number of transactions that happened in a
57+
* given feerate bucket and the total number that were confirmed in each of the
58+
* periods or less for any bucket. We save this history by keeping an
59+
* exponentially decaying moving average of each one of these stats. This is
60+
* done for a different decay in each of the 3 data sets to keep relevant data
61+
* from different time horizons. Furthermore we also keep track of the number
62+
* unmined (in mempool or left mempool without being included in a block)
63+
* transactions in each bucket and for how many blocks they have been
64+
* outstanding and use both of these numbers to increase the number of transactions
65+
* we've seen in that feerate bucket when calculating an estimate for any number
66+
* of confirmations below the number of blocks they've been outstanding.
6367
*/
6468

65-
/** Track confirm delays up to 25 blocks, can't estimate beyond that */
66-
static const unsigned int MAX_BLOCK_CONFIRMS = 25;
67-
68-
/** Decay of .998 is a half-life of 346 blocks or about 2.4 days */
69-
static const double DEFAULT_DECAY = .998;
70-
71-
/** Require greater than 95% of X feerate transactions to be confirmed within Y blocks for X to be big enough */
72-
static const double MIN_SUCCESS_PCT = .95;
73-
74-
/** Require an avg of 1 tx in the combined feerate bucket per block to have stat significance */
75-
static const double SUFFICIENT_FEETXS = 1;
69+
/* Identifier for each of the 3 different TxConfirmStats which will track
70+
* history over different time horizons. */
71+
enum FeeEstimateHorizon {
72+
SHORT_HALFLIFE = 0,
73+
MED_HALFLIFE = 1,
74+
LONG_HALFLIFE = 2
75+
};
7676

77-
// Minimum and Maximum values for tracking feerates
78-
// The MIN_BUCKET_FEERATE should just be set to the lowest reasonable feerate we
79-
// might ever want to track. Historically this has been 1000 since it was
80-
// inheriting DEFAULT_MIN_RELAY_TX_FEE and changing it is disruptive as it
81-
// invalidates old estimates files. So leave it at 1000 unless it becomes
82-
// necessary to lower it, and then lower it substantially.
83-
static constexpr double MIN_BUCKET_FEERATE = 1000;
84-
static const double MAX_BUCKET_FEERATE = 1e7;
85-
static const double INF_FEERATE = MAX_MONEY;
77+
/* Used to return detailed information about a feerate bucket */
78+
struct EstimatorBucket
79+
{
80+
double start = -1;
81+
double end = -1;
82+
double withinTarget = 0;
83+
double totalConfirmed = 0;
84+
double inMempool = 0;
85+
double leftMempool = 0;
86+
};
8687

87-
// We have to lump transactions into buckets based on feerate, but we want to be able
88-
// to give accurate estimates over a large range of potential feerates
89-
// Therefore it makes sense to exponentially space the buckets
90-
/** Spacing of FeeRate buckets */
91-
static const double FEE_SPACING = 1.1;
88+
/* Used to return detailed information about a fee estimate calculation */
89+
struct EstimationResult
90+
{
91+
EstimatorBucket pass;
92+
EstimatorBucket fail;
93+
double decay;
94+
unsigned int scale;
95+
};
9296

9397
/**
9498
* We want to be able to estimate feerates that are needed on tx's to be included in
@@ -97,6 +101,55 @@ static const double FEE_SPACING = 1.1;
97101
*/
98102
class CBlockPolicyEstimator
99103
{
104+
private:
105+
/** Track confirm delays up to 12 blocks for short horizon */
106+
static constexpr unsigned int SHORT_BLOCK_PERIODS = 12;
107+
static constexpr unsigned int SHORT_SCALE = 1;
108+
/** Track confirm delays up to 48 blocks for medium horizon */
109+
static constexpr unsigned int MED_BLOCK_PERIODS = 24;
110+
static constexpr unsigned int MED_SCALE = 2;
111+
/** Track confirm delays up to 1008 blocks for long horizon */
112+
static constexpr unsigned int LONG_BLOCK_PERIODS = 42;
113+
static constexpr unsigned int LONG_SCALE = 24;
114+
/** Historical estimates that are older than this aren't valid */
115+
static const unsigned int OLDEST_ESTIMATE_HISTORY = 6 * 1008;
116+
117+
/** Decay of .962 is a half-life of 18 blocks or about 3 hours */
118+
static constexpr double SHORT_DECAY = .962;
119+
/** Decay of .998 is a half-life of 144 blocks or about 1 day */
120+
static constexpr double MED_DECAY = .9952;
121+
/** Decay of .9995 is a half-life of 1008 blocks or about 1 week */
122+
static constexpr double LONG_DECAY = .99931;
123+
124+
/** Require greater than 60% of X feerate transactions to be confirmed within Y/2 blocks*/
125+
static constexpr double HALF_SUCCESS_PCT = .6;
126+
/** Require greater than 85% of X feerate transactions to be confirmed within Y blocks*/
127+
static constexpr double SUCCESS_PCT = .85;
128+
/** Require greater than 95% of X feerate transactions to be confirmed within 2 * Y blocks*/
129+
static constexpr double DOUBLE_SUCCESS_PCT = .95;
130+
131+
/** Require an avg of 0.1 tx in the combined feerate bucket per block to have stat significance */
132+
static constexpr double SUFFICIENT_FEETXS = 0.1;
133+
/** Require an avg of 0.5 tx when using short decay since there are fewer blocks considered*/
134+
static constexpr double SUFFICIENT_TXS_SHORT = 0.5;
135+
136+
/** Minimum and Maximum values for tracking feerates
137+
* The MIN_BUCKET_FEERATE should just be set to the lowest reasonable feerate we
138+
* might ever want to track. Historically this has been 1000 since it was
139+
* inheriting DEFAULT_MIN_RELAY_TX_FEE and changing it is disruptive as it
140+
* invalidates old estimates files. So leave it at 1000 unless it becomes
141+
* necessary to lower it, and then lower it substantially.
142+
*/
143+
static constexpr double MIN_BUCKET_FEERATE = 1000;
144+
static constexpr double MAX_BUCKET_FEERATE = 1e7;
145+
146+
/** Spacing of FeeRate buckets
147+
* We have to lump transactions into buckets based on feerate, but we want to be able
148+
* to give accurate estimates over a large range of potential feerates
149+
* Therefore it makes sense to exponentially space the buckets
150+
*/
151+
static constexpr double FEE_SPACING = 1.05;
152+
100153
public:
101154
/** Create new BlockPolicyEstimator and initialize stats tracking classes with default values */
102155
CBlockPolicyEstimator();
@@ -110,26 +163,39 @@ class CBlockPolicyEstimator
110163
void processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate);
111164

112165
/** Remove a transaction from the mempool tracking stats*/
113-
bool removeTx(uint256 hash);
166+
bool removeTx(uint256 hash, bool inBlock);
114167

115-
/** Return a feerate estimate */
168+
/** DEPRECATED. Return a feerate estimate */
116169
CFeeRate estimateFee(int confTarget) const;
117170

118-
/** Estimate feerate needed to get be included in a block within
119-
* confTarget blocks. If no answer can be given at confTarget, return an
120-
* estimate at the lowest target where one can be given.
171+
/** Estimate feerate needed to get be included in a block within confTarget
172+
* blocks. If no answer can be given at confTarget, return an estimate at
173+
* the closest target where one can be given. 'conservative' estimates are
174+
* valid over longer time horizons also.
175+
*/
176+
CFeeRate estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool, bool conservative = true) const;
177+
178+
/** Return a specific fee estimate calculation with a given success
179+
* threshold and time horizon, and optionally return detailed data about
180+
* calculation
121181
*/
122-
CFeeRate estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool) const;
182+
CFeeRate estimateRawFee(int confTarget, double successThreshold, FeeEstimateHorizon horizon, EstimationResult *result = nullptr) const;
123183

124184
/** Write estimation data to a file */
125185
bool Write(CAutoFile& fileout) const;
126186

127187
/** Read estimation data from a file */
128188
bool Read(CAutoFile& filein);
129189

190+
/** Empty mempool transactions on shutdown to record failure to confirm for txs still in mempool */
191+
void FlushUnconfirmed(CTxMemPool& pool);
192+
130193
private:
131-
CFeeRate minTrackedFee; //!< Passed to constructor to avoid dependency on main
132194
unsigned int nBestSeenHeight;
195+
unsigned int firstRecordedHeight;
196+
unsigned int historicalFirst;
197+
unsigned int historicalBest;
198+
133199
struct TxStatsInfo
134200
{
135201
unsigned int blockHeight;
@@ -142,19 +208,42 @@ class CBlockPolicyEstimator
142208

143209
/** Classes to track historical data on transaction confirmations */
144210
TxConfirmStats* feeStats;
211+
TxConfirmStats* shortStats;
212+
TxConfirmStats* longStats;
145213

146214
unsigned int trackedTxs;
147215
unsigned int untrackedTxs;
148216

217+
std::vector<double> buckets; // The upper-bound of the range for the bucket (inclusive)
218+
std::map<double, unsigned int> bucketMap; // Map of bucket upper-bound to index into all vectors by bucket
219+
149220
mutable CCriticalSection cs_feeEstimator;
150221

151222
/** Process a transaction confirmed in a block*/
152223
bool processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry);
153224

225+
/** Helper for estimateSmartFee */
226+
double estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon) const;
227+
/** Helper for estimateSmartFee */
228+
double estimateConservativeFee(unsigned int doubleTarget) const;
229+
/** Number of blocks of data recorded while fee estimates have been running */
230+
unsigned int BlockSpan() const;
231+
/** Number of blocks of recorded fee estimate data represented in saved data file */
232+
unsigned int HistoricalBlockSpan() const;
233+
/** Calculation of highest target that reasonable estimate can be provided for */
234+
unsigned int MaxUsableEstimate() const;
154235
};
155236

156237
class FeeFilterRounder
157238
{
239+
private:
240+
static constexpr double MAX_FILTER_FEERATE = 1e7;
241+
/** FEE_FILTER_SPACING is just used to provide some quantization of fee
242+
* filter results. Historically it reused FEE_SPACING, but it is completely
243+
* unrelated, and was made a separate constant so the two concepts are not
244+
* tied together */
245+
static constexpr double FEE_FILTER_SPACING = 1.1;
246+
158247
public:
159248
/** Create new FeeFilterRounder */
160249
FeeFilterRounder(const CFeeRate& minIncrementalFee);
@@ -166,4 +255,5 @@ class FeeFilterRounder
166255
std::set<double> feeset;
167256
FastRandomContext insecure_rand;
168257
};
258+
169259
#endif /*BITCOIN_POLICYESTIMATOR_H */

src/rpc/client.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ static const CRPCConvertParam vRPCConvertParams[] =
108108
{ "getrawmempool", 0, "verbose" },
109109
{ "estimatefee", 0, "nblocks" },
110110
{ "estimatesmartfee", 0, "nblocks" },
111+
{ "estimatesmartfee", 1, "conservative" },
112+
{ "estimaterawfee", 0, "nblocks" },
113+
{ "estimaterawfee", 1, "threshold" },
114+
{ "estimaterawfee", 2, "horizon" },
111115
{ "prioritisetransaction", 1, "fee_delta" },
112116
{ "setban", 2, "bantime" },
113117
{ "setban", 3, "absolute" },

0 commit comments

Comments
 (0)