Skip to content

Commit c7447ec

Browse files
committed
Track failures in fee estimation.
Start tracking transactions which fail to confirm within the target and are then evicted or otherwise leave mempool. Fix slight error in unit test.
1 parent 4186d3f commit c7447ec

File tree

6 files changed

+72
-20
lines changed

6 files changed

+72
-20
lines changed

src/init.cpp

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

214214
if (fFeeEstimatesInitialized)
215215
{
216+
::feeEstimator.FlushUnconfirmed(::mempool);
216217
fs::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME;
217218
CAutoFile est_fileout(fsbridge::fopen(est_path, "wb"), SER_DISK, CLIENT_VERSION);
218219
if (!est_fileout.IsNull())

src/policy/fees.cpp

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ class TxConfirmStats
4040
// Track the historical moving average of theses totals over blocks
4141
std::vector<std::vector<double>> confAvg; // confAvg[Y][X]
4242

43-
std::vector<std::vector<double>> failAvg; // future use
43+
// Track moving avg of txs which have been evicted from the mempool
44+
// after failing to be confirmed within Y blocks
45+
std::vector<std::vector<double>> failAvg; // failAvg[Y][X]
4446

4547
// Sum the total feerate of all tx's in each bucket
4648
// Track the historical moving average of this total over blocks
@@ -89,7 +91,7 @@ class TxConfirmStats
8991

9092
/** Remove a transaction from mempool tracking stats*/
9193
void removeTx(unsigned int entryHeight, unsigned int nBestSeenHeight,
92-
unsigned int bucketIndex);
94+
unsigned int bucketIndex, bool inBlock);
9395

9496
/** Update our estimates by decaying our historical moving average and updating
9597
with the data gathered from the current block */
@@ -135,6 +137,10 @@ TxConfirmStats::TxConfirmStats(const std::vector<double>& defaultBuckets,
135137
for (unsigned int i = 0; i < maxConfirms; i++) {
136138
confAvg[i].resize(buckets.size());
137139
}
140+
failAvg.resize(maxConfirms);
141+
for (unsigned int i = 0; i < maxConfirms; i++) {
142+
failAvg[i].resize(buckets.size());
143+
}
138144

139145
txCtAvg.resize(buckets.size());
140146
avg.resize(buckets.size());
@@ -179,6 +185,8 @@ void TxConfirmStats::UpdateMovingAverages()
179185
for (unsigned int j = 0; j < buckets.size(); j++) {
180186
for (unsigned int i = 0; i < confAvg.size(); i++)
181187
confAvg[i][j] = confAvg[i][j] * decay;
188+
for (unsigned int i = 0; i < failAvg.size(); i++)
189+
failAvg[i][j] = failAvg[i][j] * decay;
182190
avg[j] = avg[j] * decay;
183191
txCtAvg[j] = txCtAvg[j] * decay;
184192
}
@@ -193,6 +201,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
193201
double nConf = 0; // Number of tx's confirmed within the confTarget
194202
double totalNum = 0; // Total number of tx's that were ever confirmed
195203
int extraNum = 0; // Number of tx's still in mempool for confTarget or longer
204+
double failNum = 0; // Number of tx's that were never confirmed but removed from the mempool after confTarget
196205

197206
int maxbucketindex = buckets.size() - 1;
198207

@@ -229,6 +238,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
229238
curFarBucket = bucket;
230239
nConf += confAvg[confTarget - 1][bucket];
231240
totalNum += txCtAvg[bucket];
241+
failNum += failAvg[confTarget - 1][bucket];
232242
for (unsigned int confct = confTarget; confct < GetMaxConfirms(); confct++)
233243
extraNum += unconfTxs[(nBlockHeight - confct)%bins][bucket];
234244
extraNum += oldUnconfTxs[bucket];
@@ -237,7 +247,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
237247
// (Only count the confirmed data points, so that each confirmation count
238248
// will be looking at the same amount of data and same bucket breaks)
239249
if (totalNum >= sufficientTxVal / (1 - decay)) {
240-
double curPct = nConf / (totalNum + extraNum);
250+
double curPct = nConf / (totalNum + failNum + extraNum);
241251

242252
// Check to see if we are no longer getting confirmed at the success rate
243253
if ((requireGreater && curPct < successBreakPoint) || (!requireGreater && curPct > successBreakPoint)) {
@@ -250,6 +260,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
250260
failBucket.withinTarget = nConf;
251261
failBucket.totalConfirmed = totalNum;
252262
failBucket.inMempool = extraNum;
263+
failBucket.leftMempool = failNum;
253264
passing = false;
254265
}
255266
continue;
@@ -265,6 +276,8 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
265276
passBucket.totalConfirmed = totalNum;
266277
totalNum = 0;
267278
passBucket.inMempool = extraNum;
279+
passBucket.leftMempool = failNum;
280+
failNum = 0;
268281
extraNum = 0;
269282
bestNearBucket = curNearBucket;
270283
bestFarBucket = curFarBucket;
@@ -309,16 +322,17 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
309322
failBucket.withinTarget = nConf;
310323
failBucket.totalConfirmed = totalNum;
311324
failBucket.inMempool = extraNum;
325+
failBucket.leftMempool = failNum;
312326
}
313327

314-
LogPrint(BCLog::ESTIMATEFEE, "FeeEst: %d %s%.0f%% decay %.5f: need feerate: %g from (%g - %g) %.2f%% %.1f/(%.1f+%d mem) Fail: (%g - %g) %.2f%% %.1f/(%.1f+%d mem)\n",
328+
LogPrint(BCLog::ESTIMATEFEE, "FeeEst: %d %s%.0f%% decay %.5f: need feerate: %g from (%g - %g) %.2f%% %.1f/(%.1f+%d mem+%.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f+%d mem+%.1f out)\n",
315329
confTarget, requireGreater ? ">" : "<", 100.0 * successBreakPoint, decay,
316330
median, passBucket.start, passBucket.end,
317-
100 * passBucket.withinTarget / (passBucket.totalConfirmed + passBucket.inMempool),
318-
passBucket.withinTarget, passBucket.totalConfirmed, passBucket.inMempool,
331+
100 * passBucket.withinTarget / (passBucket.totalConfirmed + passBucket.inMempool + passBucket.leftMempool),
332+
passBucket.withinTarget, passBucket.totalConfirmed, passBucket.inMempool, passBucket.leftMempool,
319333
failBucket.start, failBucket.end,
320-
100 * failBucket.withinTarget / (failBucket.totalConfirmed + failBucket.inMempool),
321-
failBucket.withinTarget, failBucket.totalConfirmed, failBucket.inMempool);
334+
100 * failBucket.withinTarget / (failBucket.totalConfirmed + failBucket.inMempool + failBucket.leftMempool),
335+
failBucket.withinTarget, failBucket.totalConfirmed, failBucket.inMempool, failBucket.leftMempool);
322336

323337

324338
if (result) {
@@ -376,6 +390,19 @@ void TxConfirmStats::Read(CAutoFile& filein, int nFileVersion, size_t numBuckets
376390

377391
if (nFileVersion >= 149900) {
378392
filein >> failAvg;
393+
if (maxConfirms != failAvg.size()) {
394+
throw std::runtime_error("Corrupt estimates file. Mismatch in confirms tracked for failures");
395+
}
396+
for (unsigned int i = 0; i < maxConfirms; i++) {
397+
if (failAvg[i].size() != numBuckets) {
398+
throw std::runtime_error("Corrupt estimates file. Mismatch in one of failure average bucket counts");
399+
}
400+
}
401+
} else {
402+
failAvg.resize(confAvg.size());
403+
for (unsigned int i = 0; i < failAvg.size(); i++) {
404+
failAvg[i].resize(numBuckets);
405+
}
379406
}
380407

381408
// Resize the current block variables which aren't stored in the data file
@@ -394,7 +421,7 @@ unsigned int TxConfirmStats::NewTx(unsigned int nBlockHeight, double val)
394421
return bucketindex;
395422
}
396423

397-
void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHeight, unsigned int bucketindex)
424+
void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHeight, unsigned int bucketindex, bool inBlock)
398425
{
399426
//nBestSeenHeight is not updated yet for the new block
400427
int blocksAgo = nBestSeenHeight - entryHeight;
@@ -422,21 +449,26 @@ void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHe
422449
blockIndex, bucketindex);
423450
}
424451
}
452+
if (!inBlock && blocksAgo >= 1) {
453+
for (size_t i = 0; i < blocksAgo && i < failAvg.size(); i++) {
454+
failAvg[i][bucketindex]++;
455+
}
456+
}
425457
}
426458

427459
// This function is called from CTxMemPool::removeUnchecked to ensure
428460
// txs removed from the mempool for any reason are no longer
429461
// tracked. Txs that were part of a block have already been removed in
430462
// processBlockTx to ensure they are never double tracked, but it is
431463
// of no harm to try to remove them again.
432-
bool CBlockPolicyEstimator::removeTx(uint256 hash)
464+
bool CBlockPolicyEstimator::removeTx(uint256 hash, bool inBlock)
433465
{
434466
LOCK(cs_feeEstimator);
435467
std::map<uint256, TxStatsInfo>::iterator pos = mapMemPoolTxs.find(hash);
436468
if (pos != mapMemPoolTxs.end()) {
437-
feeStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex);
438-
shortStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex);
439-
longStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex);
469+
feeStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex, inBlock);
470+
shortStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex, inBlock);
471+
longStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex, inBlock);
440472
mapMemPoolTxs.erase(hash);
441473
return true;
442474
} else {
@@ -511,7 +543,7 @@ void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, boo
511543

512544
bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry)
513545
{
514-
if (!removeTx(entry->GetTx().GetHash())) {
546+
if (!removeTx(entry->GetTx().GetHash(), true)) {
515547
// This transaction wasn't being tracked for fee estimation
516548
return false;
517549
}
@@ -766,6 +798,18 @@ bool CBlockPolicyEstimator::Read(CAutoFile& filein)
766798
return true;
767799
}
768800

801+
void CBlockPolicyEstimator::FlushUnconfirmed(CTxMemPool& pool) {
802+
int64_t startclear = GetTimeMicros();
803+
std::vector<uint256> txids;
804+
pool.queryHashes(txids);
805+
LOCK(cs_feeEstimator);
806+
for (auto& txid : txids) {
807+
removeTx(txid, false);
808+
}
809+
int64_t endclear = GetTimeMicros();
810+
LogPrint(BCLog::ESTIMATEFEE, "Recorded %u unconfirmed txs from mempool in %ld micros\n",txids.size(), endclear - startclear);
811+
}
812+
769813
FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee)
770814
{
771815
CAmount minFeeLimit = std::max(CAmount(1), minIncrementalFee.GetFeePerK() / 2);

src/policy/fees.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ struct EstimatorBucket
7474
double withinTarget = 0;
7575
double totalConfirmed = 0;
7676
double inMempool = 0;
77+
double leftMempool = 0;
7778
};
7879

7980
struct EstimationResult
@@ -145,7 +146,7 @@ class CBlockPolicyEstimator
145146
void processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate);
146147

147148
/** Remove a transaction from the mempool tracking stats*/
148-
bool removeTx(uint256 hash);
149+
bool removeTx(uint256 hash, bool inBlock);
149150

150151
/** Return a feerate estimate */
151152
CFeeRate estimateFee(int confTarget) const;
@@ -166,6 +167,9 @@ class CBlockPolicyEstimator
166167
/** Read estimation data from a file */
167168
bool Read(CAutoFile& filein);
168169

170+
/** Empty mempool transactions on shutdown to record failure to confirm for txs still in mempool */
171+
void FlushUnconfirmed(CTxMemPool& pool);
172+
169173
private:
170174
CFeeRate minTrackedFee; //!< Passed to constructor to avoid dependency on main
171175
unsigned int nBestSeenHeight;

src/rpc/mining.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,7 @@ UniValue estimaterawfee(const JSONRPCRequest& request)
893893
" \"withintarget\" : x.x, (numeric) number of txs over history horizon in the feerate range that were confirmed within target\n"
894894
" \"totalconfirmed\" : x.x, (numeric) number of txs over history horizon in the feerate range that were confirmed at any point\n"
895895
" \"inmempool\" : x.x, (numeric) current number of txs in mempool in the feerate range unconfirmed for at least target blocks\n"
896+
" \"leftmempool\" : x.x, (numeric) number of txs over history horizon in the feerate range that left mempool unconfirmed after target\n"
896897
"}\n"
897898
"\n"
898899
"A negative feerate is returned if no answer can be given.\n"
@@ -927,11 +928,13 @@ UniValue estimaterawfee(const JSONRPCRequest& request)
927928
result.push_back(Pair("pass.withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0));
928929
result.push_back(Pair("pass.totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0));
929930
result.push_back(Pair("pass.inmempool", round(buckets.pass.inMempool * 100.0) / 100.0));
931+
result.push_back(Pair("pass.leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0));
930932
result.push_back(Pair("fail.startrange", round(buckets.fail.start)));
931933
result.push_back(Pair("fail.endrange", round(buckets.fail.end)));
932934
result.push_back(Pair("fail.withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0));
933935
result.push_back(Pair("fail.totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0));
934936
result.push_back(Pair("fail.inmempool", round(buckets.fail.inMempool * 100.0) / 100.0));
937+
result.push_back(Pair("fail.leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0));
935938
return result;
936939
}
937940

src/test/policyestimator_tests.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,16 +159,16 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
159159
txHashes[j].pop_back();
160160
}
161161
}
162-
mpool.removeForBlock(block, 265);
162+
mpool.removeForBlock(block, 266);
163163
block.clear();
164164
BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
165165
for (int i = 2; i < 10;i++) {
166-
BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
166+
BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
167167
}
168168

169-
// Mine 200 more blocks where everything is mined every block
169+
// Mine 400 more blocks where everything is mined every block
170170
// Estimates should be below original estimates
171-
while (blocknum < 465) {
171+
while (blocknum < 665) {
172172
for (int j = 0; j < 10; j++) { // For each fee multiple
173173
for (int k = 0; k < 4; k++) { // add 4 fee txs
174174
tx.vin[0].prevout.n = 10000*blocknum+100*j+k;

src/txmempool.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
448448
mapLinks.erase(it);
449449
mapTx.erase(it);
450450
nTransactionsUpdated++;
451-
if (minerPolicyEstimator) {minerPolicyEstimator->removeTx(hash);}
451+
if (minerPolicyEstimator) {minerPolicyEstimator->removeTx(hash, false);}
452452
}
453453

454454
// Calculates descendants of entry that are not already in setDescendants, and adds to

0 commit comments

Comments
 (0)