Skip to content

Commit ec9b449

Browse files
committed
Merge #19630: Cleanup fee estimation code
a3abeec policy/fees: remove a floating-point division by zero (Antoine Poinsot) c36869b policy/fees: unify some duplicated for loops (Antoine Poinsot) 569d92a policy/fees: small readability improvements (Antoine Poinsot) 5b8cb35 policy/fee: remove requireGreater parameter in EstimateMedianVal() (Antoine Poinsot) dba8196 policy/fees: correct decay explanation comments (Antoine Poinsot) Pull request description: This (*does not* change behaviour and) cleans up a bit of unused code in `CBlockPolicyEstimator` and friends, and slightly improves readability of the rest (comment correction etc.). The last commit is a small reformatting one which I could not resist but am happy to remove at will. ACKs for top commit: jnewbery: utACK a3abeec MarcoFalke: ACK a3abeec 💹 ariard: Code Review ACK a3abeec. Tree-SHA512: b7620bcd23a2ffa8f7ed859467868fc0f6488279e3ee634f6d408872cb866ad086a037e8ace76599a05b7e9c07768adf5016b0ae782d153196b9c030db4c34a5
2 parents 6af9b31 + a3abeec commit ec9b449

File tree

3 files changed

+56
-61
lines changed

3 files changed

+56
-61
lines changed

src/policy/fees.cpp

Lines changed: 54 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class TxConfirmStats
5555

5656
// Sum the total feerate of all tx's in each bucket
5757
// Track the historical moving average of this total over blocks
58-
std::vector<double> avg;
58+
std::vector<double> m_feerate_avg;
5959

6060
// Combine the conf counts with tx counts to calculate the confirmation % for each Y,X
6161
// Combine the total value with the tx counts to calculate the avg feerate per bucket
@@ -114,12 +114,10 @@ class TxConfirmStats
114114
* @param confTarget target number of confirmations
115115
* @param sufficientTxVal required average number of transactions per block in a bucket range
116116
* @param minSuccess the success probability we require
117-
* @param requireGreater return the lowest feerate such that all higher values pass minSuccess OR
118-
* return the highest feerate such that all lower values fail minSuccess
119117
* @param nBlockHeight the current block height
120118
*/
121119
double EstimateMedianVal(int confTarget, double sufficientTxVal,
122-
double minSuccess, bool requireGreater, unsigned int nBlockHeight,
120+
double minSuccess, unsigned int nBlockHeight,
123121
EstimationResult *result = nullptr) const;
124122

125123
/** Return the max number of confirms we're tracking */
@@ -139,22 +137,18 @@ class TxConfirmStats
139137
TxConfirmStats::TxConfirmStats(const std::vector<double>& defaultBuckets,
140138
const std::map<double, unsigned int>& defaultBucketMap,
141139
unsigned int maxPeriods, double _decay, unsigned int _scale)
142-
: buckets(defaultBuckets), bucketMap(defaultBucketMap)
140+
: buckets(defaultBuckets), bucketMap(defaultBucketMap), decay(_decay), scale(_scale)
143141
{
144-
decay = _decay;
145142
assert(_scale != 0 && "_scale must be non-zero");
146-
scale = _scale;
147143
confAvg.resize(maxPeriods);
148-
for (unsigned int i = 0; i < maxPeriods; i++) {
149-
confAvg[i].resize(buckets.size());
150-
}
151144
failAvg.resize(maxPeriods);
152145
for (unsigned int i = 0; i < maxPeriods; i++) {
146+
confAvg[i].resize(buckets.size());
153147
failAvg[i].resize(buckets.size());
154148
}
155149

156150
txCtAvg.resize(buckets.size());
157-
avg.resize(buckets.size());
151+
m_feerate_avg.resize(buckets.size());
158152

159153
resizeInMemoryCounters(buckets.size());
160154
}
@@ -172,68 +166,61 @@ void TxConfirmStats::resizeInMemoryCounters(size_t newbuckets) {
172166
void TxConfirmStats::ClearCurrent(unsigned int nBlockHeight)
173167
{
174168
for (unsigned int j = 0; j < buckets.size(); j++) {
175-
oldUnconfTxs[j] += unconfTxs[nBlockHeight%unconfTxs.size()][j];
169+
oldUnconfTxs[j] += unconfTxs[nBlockHeight % unconfTxs.size()][j];
176170
unconfTxs[nBlockHeight%unconfTxs.size()][j] = 0;
177171
}
178172
}
179173

180174

181-
void TxConfirmStats::Record(int blocksToConfirm, double val)
175+
void TxConfirmStats::Record(int blocksToConfirm, double feerate)
182176
{
183177
// blocksToConfirm is 1-based
184178
if (blocksToConfirm < 1)
185179
return;
186-
int periodsToConfirm = (blocksToConfirm + scale - 1)/scale;
187-
unsigned int bucketindex = bucketMap.lower_bound(val)->second;
180+
int periodsToConfirm = (blocksToConfirm + scale - 1) / scale;
181+
unsigned int bucketindex = bucketMap.lower_bound(feerate)->second;
188182
for (size_t i = periodsToConfirm; i <= confAvg.size(); i++) {
189183
confAvg[i - 1][bucketindex]++;
190184
}
191185
txCtAvg[bucketindex]++;
192-
avg[bucketindex] += val;
186+
m_feerate_avg[bucketindex] += feerate;
193187
}
194188

195189
void TxConfirmStats::UpdateMovingAverages()
196190
{
191+
assert(confAvg.size() == failAvg.size());
197192
for (unsigned int j = 0; j < buckets.size(); j++) {
198-
for (unsigned int i = 0; i < confAvg.size(); i++)
199-
confAvg[i][j] = confAvg[i][j] * decay;
200-
for (unsigned int i = 0; i < failAvg.size(); i++)
201-
failAvg[i][j] = failAvg[i][j] * decay;
202-
avg[j] = avg[j] * decay;
203-
txCtAvg[j] = txCtAvg[j] * decay;
193+
for (unsigned int i = 0; i < confAvg.size(); i++) {
194+
confAvg[i][j] *= decay;
195+
failAvg[i][j] *= decay;
196+
}
197+
m_feerate_avg[j] *= decay;
198+
txCtAvg[j] *= decay;
204199
}
205200
}
206201

207202
// returns -1 on error conditions
208203
double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
209-
double successBreakPoint, bool requireGreater,
210-
unsigned int nBlockHeight, EstimationResult *result) const
204+
double successBreakPoint, unsigned int nBlockHeight,
205+
EstimationResult *result) const
211206
{
212207
// Counters for a bucket (or range of buckets)
213208
double nConf = 0; // Number of tx's confirmed within the confTarget
214209
double totalNum = 0; // Total number of tx's that were ever confirmed
215210
int extraNum = 0; // Number of tx's still in mempool for confTarget or longer
216211
double failNum = 0; // Number of tx's that were never confirmed but removed from the mempool after confTarget
217-
int periodTarget = (confTarget + scale - 1)/scale;
218-
219-
int maxbucketindex = buckets.size() - 1;
220-
221-
// requireGreater means we are looking for the lowest feerate such that all higher
222-
// values pass, so we start at maxbucketindex (highest feerate) and look at successively
223-
// smaller buckets until we reach failure. Otherwise, we are looking for the highest
224-
// feerate such that all lower values fail, and we go in the opposite direction.
225-
unsigned int startbucket = requireGreater ? maxbucketindex : 0;
226-
int step = requireGreater ? -1 : 1;
212+
const int periodTarget = (confTarget + scale - 1) / scale;
213+
const int maxbucketindex = buckets.size() - 1;
227214

228215
// We'll combine buckets until we have enough samples.
229216
// The near and far variables will define the range we've combined
230217
// The best variables are the last range we saw which still had a high
231218
// enough confirmation rate to count as success.
232219
// The cur variables are the current range we're counting.
233-
unsigned int curNearBucket = startbucket;
234-
unsigned int bestNearBucket = startbucket;
235-
unsigned int curFarBucket = startbucket;
236-
unsigned int bestFarBucket = startbucket;
220+
unsigned int curNearBucket = maxbucketindex;
221+
unsigned int bestNearBucket = maxbucketindex;
222+
unsigned int curFarBucket = maxbucketindex;
223+
unsigned int bestFarBucket = maxbucketindex;
237224

238225
bool foundAnswer = false;
239226
unsigned int bins = unconfTxs.size();
@@ -242,8 +229,8 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
242229
EstimatorBucket passBucket;
243230
EstimatorBucket failBucket;
244231

245-
// Start counting from highest(default) or lowest feerate transactions
246-
for (int bucket = startbucket; bucket >= 0 && bucket <= maxbucketindex; bucket += step) {
232+
// Start counting from highest feerate transactions
233+
for (int bucket = maxbucketindex; bucket >= 0; --bucket) {
247234
if (newBucketRange) {
248235
curNearBucket = bucket;
249236
newBucketRange = false;
@@ -253,7 +240,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
253240
totalNum += txCtAvg[bucket];
254241
failNum += failAvg[periodTarget - 1][bucket];
255242
for (unsigned int confct = confTarget; confct < GetMaxConfirms(); confct++)
256-
extraNum += unconfTxs[(nBlockHeight - confct)%bins][bucket];
243+
extraNum += unconfTxs[(nBlockHeight - confct) % bins][bucket];
257244
extraNum += oldUnconfTxs[bucket];
258245
// If we have enough transaction data points in this range of buckets,
259246
// we can test for success
@@ -263,7 +250,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
263250
double curPct = nConf / (totalNum + failNum + extraNum);
264251

265252
// Check to see if we are no longer getting confirmed at the success rate
266-
if ((requireGreater && curPct < successBreakPoint) || (!requireGreater && curPct > successBreakPoint)) {
253+
if (curPct < successBreakPoint) {
267254
if (passing == true) {
268255
// First time we hit a failure record the failed bucket
269256
unsigned int failMinBucket = std::min(curNearBucket, curFarBucket);
@@ -317,7 +304,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
317304
if (txCtAvg[j] < txSum)
318305
txSum -= txCtAvg[j];
319306
else { // we're in the right bucket
320-
median = avg[j] / txCtAvg[j];
307+
median = m_feerate_avg[j] / txCtAvg[j];
321308
break;
322309
}
323310
}
@@ -338,13 +325,22 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
338325
failBucket.leftMempool = failNum;
339326
}
340327

341-
LogPrint(BCLog::ESTIMATEFEE, "FeeEst: %d %s%.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",
342-
confTarget, requireGreater ? ">" : "<", 100.0 * successBreakPoint, decay,
328+
float passed_within_target_perc = 0.0;
329+
float failed_within_target_perc = 0.0;
330+
if ((passBucket.totalConfirmed + passBucket.inMempool + passBucket.leftMempool)) {
331+
passed_within_target_perc = 100 * passBucket.withinTarget / (passBucket.totalConfirmed + passBucket.inMempool + passBucket.leftMempool);
332+
}
333+
if ((failBucket.totalConfirmed + failBucket.inMempool + failBucket.leftMempool)) {
334+
failed_within_target_perc = 100 * failBucket.withinTarget / (failBucket.totalConfirmed + failBucket.inMempool + failBucket.leftMempool);
335+
}
336+
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,
343339
median, passBucket.start, passBucket.end,
344-
100 * passBucket.withinTarget / (passBucket.totalConfirmed + passBucket.inMempool + passBucket.leftMempool),
340+
passed_within_target_perc,
345341
passBucket.withinTarget, passBucket.totalConfirmed, passBucket.inMempool, passBucket.leftMempool,
346342
failBucket.start, failBucket.end,
347-
100 * failBucket.withinTarget / (failBucket.totalConfirmed + failBucket.inMempool + failBucket.leftMempool),
343+
failed_within_target_perc,
348344
failBucket.withinTarget, failBucket.totalConfirmed, failBucket.inMempool, failBucket.leftMempool);
349345

350346

@@ -361,7 +357,7 @@ void TxConfirmStats::Write(CAutoFile& fileout) const
361357
{
362358
fileout << decay;
363359
fileout << scale;
364-
fileout << avg;
360+
fileout << m_feerate_avg;
365361
fileout << txCtAvg;
366362
fileout << confAvg;
367363
fileout << failAvg;
@@ -384,8 +380,8 @@ void TxConfirmStats::Read(CAutoFile& filein, int nFileVersion, size_t numBuckets
384380
throw std::runtime_error("Corrupt estimates file. Scale must be non-zero");
385381
}
386382

387-
filein >> avg;
388-
if (avg.size() != numBuckets) {
383+
filein >> m_feerate_avg;
384+
if (m_feerate_avg.size() != numBuckets) {
389385
throw std::runtime_error("Corrupt estimates file. Mismatch in feerate average bucket count");
390386
}
391387
filein >> txCtAvg;
@@ -664,7 +660,7 @@ CFeeRate CBlockPolicyEstimator::estimateRawFee(int confTarget, double successThr
664660
if (successThreshold > 1)
665661
return CFeeRate(0);
666662

667-
double median = stats->EstimateMedianVal(confTarget, sufficientTxs, successThreshold, true, nBestSeenHeight, result);
663+
double median = stats->EstimateMedianVal(confTarget, sufficientTxs, successThreshold, nBestSeenHeight, result);
668664

669665
if (median < 0)
670666
return CFeeRate(0);
@@ -725,26 +721,26 @@ double CBlockPolicyEstimator::estimateCombinedFee(unsigned int confTarget, doubl
725721
if (confTarget >= 1 && confTarget <= longStats->GetMaxConfirms()) {
726722
// Find estimate from shortest time horizon possible
727723
if (confTarget <= shortStats->GetMaxConfirms()) { // short horizon
728-
estimate = shortStats->EstimateMedianVal(confTarget, SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight, result);
724+
estimate = shortStats->EstimateMedianVal(confTarget, SUFFICIENT_TXS_SHORT, successThreshold, nBestSeenHeight, result);
729725
}
730726
else if (confTarget <= feeStats->GetMaxConfirms()) { // medium horizon
731-
estimate = feeStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, result);
727+
estimate = feeStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, nBestSeenHeight, result);
732728
}
733729
else { // long horizon
734-
estimate = longStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, result);
730+
estimate = longStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, successThreshold, nBestSeenHeight, result);
735731
}
736732
if (checkShorterHorizon) {
737733
EstimationResult tempResult;
738734
// If a lower confTarget from a more recent horizon returns a lower answer use it.
739735
if (confTarget > feeStats->GetMaxConfirms()) {
740-
double medMax = feeStats->EstimateMedianVal(feeStats->GetMaxConfirms(), SUFFICIENT_FEETXS, successThreshold, true, nBestSeenHeight, &tempResult);
736+
double medMax = feeStats->EstimateMedianVal(feeStats->GetMaxConfirms(), SUFFICIENT_FEETXS, successThreshold, nBestSeenHeight, &tempResult);
741737
if (medMax > 0 && (estimate == -1 || medMax < estimate)) {
742738
estimate = medMax;
743739
if (result) *result = tempResult;
744740
}
745741
}
746742
if (confTarget > shortStats->GetMaxConfirms()) {
747-
double shortMax = shortStats->EstimateMedianVal(shortStats->GetMaxConfirms(), SUFFICIENT_TXS_SHORT, successThreshold, true, nBestSeenHeight, &tempResult);
743+
double shortMax = shortStats->EstimateMedianVal(shortStats->GetMaxConfirms(), SUFFICIENT_TXS_SHORT, successThreshold, nBestSeenHeight, &tempResult);
748744
if (shortMax > 0 && (estimate == -1 || shortMax < estimate)) {
749745
estimate = shortMax;
750746
if (result) *result = tempResult;
@@ -763,10 +759,10 @@ double CBlockPolicyEstimator::estimateConservativeFee(unsigned int doubleTarget,
763759
double estimate = -1;
764760
EstimationResult tempResult;
765761
if (doubleTarget <= shortStats->GetMaxConfirms()) {
766-
estimate = feeStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight, result);
762+
estimate = feeStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, nBestSeenHeight, result);
767763
}
768764
if (doubleTarget <= feeStats->GetMaxConfirms()) {
769-
double longEstimate = longStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, true, nBestSeenHeight, &tempResult);
765+
double longEstimate = longStats->EstimateMedianVal(doubleTarget, SUFFICIENT_FEETXS, DOUBLE_SUCCESS_PCT, nBestSeenHeight, &tempResult);
770766
if (longEstimate > estimate) {
771767
estimate = longEstimate;
772768
if (result) *result = tempResult;

src/policy/fees.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ class CBlockPolicyEstimator
138138

139139
/** Decay of .962 is a half-life of 18 blocks or about 3 hours */
140140
static constexpr double SHORT_DECAY = .962;
141-
/** Decay of .998 is a half-life of 144 blocks or about 1 day */
141+
/** Decay of .9952 is a half-life of 144 blocks or about 1 day */
142142
static constexpr double MED_DECAY = .9952;
143-
/** Decay of .9995 is a half-life of 1008 blocks or about 1 week */
143+
/** Decay of .99931 is a half-life of 1008 blocks or about 1 week */
144144
static constexpr double LONG_DECAY = .99931;
145145

146146
/** Require greater than 60% of X feerate transactions to be confirmed within Y/2 blocks*/

test/sanitizer_suppressions/ubsan

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# -fsanitize=undefined suppressions
22
# =================================
3-
float-divide-by-zero:policy/fees.cpp
43
float-divide-by-zero:validation.cpp
54
float-divide-by-zero:wallet/wallet.cpp
65

0 commit comments

Comments
 (0)