Skip to content

Commit 14c9489

Browse files
committed
Merge #9942: Refactor CBlockPolicyEstimator
68af651 MOVEONLY: move TxConfirmStats to cpp (Alex Morcos) 2332f19 Initialize TxConfirmStats in constructor (Alex Morcos) 5ba81e5 Read and Write fee estimate file directly from CBlockPolicyEstimator (Alex Morcos) 14e10aa Call estimate(Smart)Fee directly from CBlockPolicyEstimator (Alex Morcos) dbb9e36 Give CBlockPolicyEstimator it's own lock (Alex Morcos) f6187d6 Make processBlockTx private. (Alex Morcos) ae7327b Make feeEstimator its own global instance of CBlockPolicyEstimator (Alex Morcos) Tree-SHA512: dbf3bd2b30822e609a35f3da519b62d23f8a50e564750695ddebd08553b4c01874ae3e07d792c6cc78cc377d2db33b951ffedc46ac7edaf5793f9ebb931713af
2 parents 987a6c0 + 68af651 commit 14c9489

15 files changed

+247
-261
lines changed

src/init.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "netbase.h"
2626
#include "net.h"
2727
#include "net_processing.h"
28+
#include "policy/fees.h"
2829
#include "policy/policy.h"
2930
#include "rpc/server.h"
3031
#include "rpc/register.h"
@@ -215,7 +216,7 @@ void Shutdown()
215216
fs::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME;
216217
CAutoFile est_fileout(fsbridge::fopen(est_path, "wb"), SER_DISK, CLIENT_VERSION);
217218
if (!est_fileout.IsNull())
218-
mempool.WriteFeeEstimates(est_fileout);
219+
::feeEstimator.Write(est_fileout);
219220
else
220221
LogPrintf("%s: Failed to write fee estimates to %s\n", __func__, est_path.string());
221222
fFeeEstimatesInitialized = false;
@@ -1550,7 +1551,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
15501551
CAutoFile est_filein(fsbridge::fopen(est_path, "rb"), SER_DISK, CLIENT_VERSION);
15511552
// Allowed to fail as this file IS missing on first startup.
15521553
if (!est_filein.IsNull())
1553-
mempool.ReadFeeEstimates(est_filein);
1554+
::feeEstimator.Read(est_filein);
15541555
fFeeEstimatesInitialized = true;
15551556

15561557
// ********************************************************* Step 8: load wallet

src/policy/fees.cpp

Lines changed: 176 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,121 @@
77
#include "policy/policy.h"
88

99
#include "amount.h"
10+
#include "clientversion.h"
1011
#include "primitives/transaction.h"
1112
#include "random.h"
1213
#include "streams.h"
1314
#include "txmempool.h"
1415
#include "util.h"
1516

16-
void TxConfirmStats::Initialize(std::vector<double>& defaultBuckets,
17-
unsigned int maxConfirms, double _decay)
17+
/**
18+
* We will instantiate an instance of this class to track transactions that were
19+
* included in a block. We will lump transactions into a bucket according to their
20+
* approximate feerate and then track how long it took for those txs to be included in a block
21+
*
22+
* The tracking of unconfirmed (mempool) transactions is completely independent of the
23+
* historical tracking of transactions that have been confirmed in a block.
24+
*/
25+
class TxConfirmStats
26+
{
27+
private:
28+
//Define the buckets we will group transactions into
29+
std::vector<double> buckets; // The upper-bound of the range for the bucket (inclusive)
30+
std::map<double, unsigned int> bucketMap; // Map of bucket upper-bound to index into all vectors by bucket
31+
32+
// For each bucket X:
33+
// Count the total # of txs in each bucket
34+
// Track the historical moving average of this total over blocks
35+
std::vector<double> txCtAvg;
36+
// and calculate the total for the current block to update the moving average
37+
std::vector<int> curBlockTxCt;
38+
39+
// Count the total # of txs confirmed within Y blocks in each bucket
40+
// Track the historical moving average of theses totals over blocks
41+
std::vector<std::vector<double> > confAvg; // confAvg[Y][X]
42+
// and calculate the totals for the current block to update the moving averages
43+
std::vector<std::vector<int> > curBlockConf; // curBlockConf[Y][X]
44+
45+
// Sum the total feerate of all tx's in each bucket
46+
// Track the historical moving average of this total over blocks
47+
std::vector<double> avg;
48+
// and calculate the total for the current block to update the moving average
49+
std::vector<double> curBlockVal;
50+
51+
// Combine the conf counts with tx counts to calculate the confirmation % for each Y,X
52+
// Combine the total value with the tx counts to calculate the avg feerate per bucket
53+
54+
double decay;
55+
56+
// Mempool counts of outstanding transactions
57+
// For each bucket X, track the number of transactions in the mempool
58+
// that are unconfirmed for each possible confirmation value Y
59+
std::vector<std::vector<int> > unconfTxs; //unconfTxs[Y][X]
60+
// transactions still unconfirmed after MAX_CONFIRMS for each bucket
61+
std::vector<int> oldUnconfTxs;
62+
63+
public:
64+
/**
65+
* Create new TxConfirmStats. This is called by BlockPolicyEstimator's
66+
* constructor with default values.
67+
* @param defaultBuckets contains the upper limits for the bucket boundaries
68+
* @param maxConfirms max number of confirms to track
69+
* @param decay how much to decay the historical moving average per block
70+
*/
71+
TxConfirmStats(const std::vector<double>& defaultBuckets, unsigned int maxConfirms, double decay);
72+
73+
/** Clear the state of the curBlock variables to start counting for the new block */
74+
void ClearCurrent(unsigned int nBlockHeight);
75+
76+
/**
77+
* Record a new transaction data point in the current block stats
78+
* @param blocksToConfirm the number of blocks it took this transaction to confirm
79+
* @param val the feerate of the transaction
80+
* @warning blocksToConfirm is 1-based and has to be >= 1
81+
*/
82+
void Record(int blocksToConfirm, double val);
83+
84+
/** Record a new transaction entering the mempool*/
85+
unsigned int NewTx(unsigned int nBlockHeight, double val);
86+
87+
/** Remove a transaction from mempool tracking stats*/
88+
void removeTx(unsigned int entryHeight, unsigned int nBestSeenHeight,
89+
unsigned int bucketIndex);
90+
91+
/** Update our estimates by decaying our historical moving average and updating
92+
with the data gathered from the current block */
93+
void UpdateMovingAverages();
94+
95+
/**
96+
* Calculate a feerate estimate. Find the lowest value bucket (or range of buckets
97+
* to make sure we have enough data points) whose transactions still have sufficient likelihood
98+
* of being confirmed within the target number of confirmations
99+
* @param confTarget target number of confirmations
100+
* @param sufficientTxVal required average number of transactions per block in a bucket range
101+
* @param minSuccess the success probability we require
102+
* @param requireGreater return the lowest feerate such that all higher values pass minSuccess OR
103+
* return the highest feerate such that all lower values fail minSuccess
104+
* @param nBlockHeight the current block height
105+
*/
106+
double EstimateMedianVal(int confTarget, double sufficientTxVal,
107+
double minSuccess, bool requireGreater, unsigned int nBlockHeight) const;
108+
109+
/** Return the max number of confirms we're tracking */
110+
unsigned int GetMaxConfirms() const { return confAvg.size(); }
111+
112+
/** Write state of estimation data to a file*/
113+
void Write(CAutoFile& fileout) const;
114+
115+
/**
116+
* Read saved state of estimation data from a file and replace all internal data structures and
117+
* variables with this state.
118+
*/
119+
void Read(CAutoFile& filein);
120+
};
121+
122+
123+
TxConfirmStats::TxConfirmStats(const std::vector<double>& defaultBuckets,
124+
unsigned int maxConfirms, double _decay)
18125
{
19126
decay = _decay;
20127
for (unsigned int i = 0; i < defaultBuckets.size(); i++) {
@@ -77,7 +184,7 @@ void TxConfirmStats::UpdateMovingAverages()
77184
// returns -1 on error conditions
78185
double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
79186
double successBreakPoint, bool requireGreater,
80-
unsigned int nBlockHeight)
187+
unsigned int nBlockHeight) const
81188
{
82189
// Counters for a bucket (or range of buckets)
83190
double nConf = 0; // Number of tx's confirmed within the confTarget
@@ -173,7 +280,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
173280
return median;
174281
}
175282

176-
void TxConfirmStats::Write(CAutoFile& fileout)
283+
void TxConfirmStats::Write(CAutoFile& fileout) const
177284
{
178285
fileout << decay;
179286
fileout << buckets;
@@ -290,9 +397,10 @@ void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHe
290397
// of no harm to try to remove them again.
291398
bool CBlockPolicyEstimator::removeTx(uint256 hash)
292399
{
400+
LOCK(cs_feeEstimator);
293401
std::map<uint256, TxStatsInfo>::iterator pos = mapMemPoolTxs.find(hash);
294402
if (pos != mapMemPoolTxs.end()) {
295-
feeStats.removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex);
403+
feeStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex);
296404
mapMemPoolTxs.erase(hash);
297405
return true;
298406
} else {
@@ -310,11 +418,17 @@ CBlockPolicyEstimator::CBlockPolicyEstimator()
310418
vfeelist.push_back(bucketBoundary);
311419
}
312420
vfeelist.push_back(INF_FEERATE);
313-
feeStats.Initialize(vfeelist, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
421+
feeStats = new TxConfirmStats(vfeelist, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
422+
}
423+
424+
CBlockPolicyEstimator::~CBlockPolicyEstimator()
425+
{
426+
delete feeStats;
314427
}
315428

316429
void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate)
317430
{
431+
LOCK(cs_feeEstimator);
318432
unsigned int txHeight = entry.GetHeight();
319433
uint256 hash = entry.GetTx().GetHash();
320434
if (mapMemPoolTxs.count(hash)) {
@@ -343,7 +457,7 @@ void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, boo
343457
CFeeRate feeRate(entry.GetFee(), entry.GetTxSize());
344458

345459
mapMemPoolTxs[hash].blockHeight = txHeight;
346-
mapMemPoolTxs[hash].bucketIndex = feeStats.NewTx(txHeight, (double)feeRate.GetFeePerK());
460+
mapMemPoolTxs[hash].bucketIndex = feeStats->NewTx(txHeight, (double)feeRate.GetFeePerK());
347461
}
348462

349463
bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry)
@@ -367,13 +481,14 @@ bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxM
367481
// Feerates are stored and reported as BTC-per-kb:
368482
CFeeRate feeRate(entry->GetFee(), entry->GetTxSize());
369483

370-
feeStats.Record(blocksToConfirm, (double)feeRate.GetFeePerK());
484+
feeStats->Record(blocksToConfirm, (double)feeRate.GetFeePerK());
371485
return true;
372486
}
373487

374488
void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
375489
std::vector<const CTxMemPoolEntry*>& entries)
376490
{
491+
LOCK(cs_feeEstimator);
377492
if (nBlockHeight <= nBestSeenHeight) {
378493
// Ignore side chains and re-orgs; assuming they are random
379494
// they don't affect the estimate.
@@ -389,7 +504,7 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
389504
nBestSeenHeight = nBlockHeight;
390505

391506
// Clear the current block state and update unconfirmed circular buffer
392-
feeStats.ClearCurrent(nBlockHeight);
507+
feeStats->ClearCurrent(nBlockHeight);
393508

394509
unsigned int countedTxs = 0;
395510
// Repopulate the current block states
@@ -399,7 +514,7 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
399514
}
400515

401516
// Update all exponential averages with the current block state
402-
feeStats.UpdateMovingAverages();
517+
feeStats->UpdateMovingAverages();
403518

404519
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",
405520
countedTxs, entries.size(), trackedTxs, trackedTxs + untrackedTxs, mapMemPoolTxs.size());
@@ -408,37 +523,44 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
408523
untrackedTxs = 0;
409524
}
410525

411-
CFeeRate CBlockPolicyEstimator::estimateFee(int confTarget)
526+
CFeeRate CBlockPolicyEstimator::estimateFee(int confTarget) const
412527
{
528+
LOCK(cs_feeEstimator);
413529
// Return failure if trying to analyze a target we're not tracking
414530
// It's not possible to get reasonable estimates for confTarget of 1
415-
if (confTarget <= 1 || (unsigned int)confTarget > feeStats.GetMaxConfirms())
531+
if (confTarget <= 1 || (unsigned int)confTarget > feeStats->GetMaxConfirms())
416532
return CFeeRate(0);
417533

418-
double median = feeStats.EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
534+
double median = feeStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
419535

420536
if (median < 0)
421537
return CFeeRate(0);
422538

423539
return CFeeRate(median);
424540
}
425541

426-
CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool)
542+
CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool) const
427543
{
428544
if (answerFoundAtTarget)
429545
*answerFoundAtTarget = confTarget;
430-
// Return failure if trying to analyze a target we're not tracking
431-
if (confTarget <= 0 || (unsigned int)confTarget > feeStats.GetMaxConfirms())
432-
return CFeeRate(0);
433-
434-
// It's not possible to get reasonable estimates for confTarget of 1
435-
if (confTarget == 1)
436-
confTarget = 2;
437546

438547
double median = -1;
439-
while (median < 0 && (unsigned int)confTarget <= feeStats.GetMaxConfirms()) {
440-
median = feeStats.EstimateMedianVal(confTarget++, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
441-
}
548+
549+
{
550+
LOCK(cs_feeEstimator);
551+
552+
// Return failure if trying to analyze a target we're not tracking
553+
if (confTarget <= 0 || (unsigned int)confTarget > feeStats->GetMaxConfirms())
554+
return CFeeRate(0);
555+
556+
// It's not possible to get reasonable estimates for confTarget of 1
557+
if (confTarget == 1)
558+
confTarget = 2;
559+
560+
while (median < 0 && (unsigned int)confTarget <= feeStats->GetMaxConfirms()) {
561+
median = feeStats->EstimateMedianVal(confTarget++, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
562+
}
563+
} // Must unlock cs_feeEstimator before taking mempool locks
442564

443565
if (answerFoundAtTarget)
444566
*answerFoundAtTarget = confTarget - 1;
@@ -454,19 +576,40 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoun
454576
return CFeeRate(median);
455577
}
456578

457-
void CBlockPolicyEstimator::Write(CAutoFile& fileout)
579+
bool CBlockPolicyEstimator::Write(CAutoFile& fileout) const
458580
{
459-
fileout << nBestSeenHeight;
460-
feeStats.Write(fileout);
581+
try {
582+
LOCK(cs_feeEstimator);
583+
fileout << 139900; // version required to read: 0.13.99 or later
584+
fileout << CLIENT_VERSION; // version that wrote the file
585+
fileout << nBestSeenHeight;
586+
feeStats->Write(fileout);
587+
}
588+
catch (const std::exception&) {
589+
LogPrintf("CBlockPolicyEstimator::Write(): unable to read policy estimator data (non-fatal)\n");
590+
return false;
591+
}
592+
return true;
461593
}
462594

463-
void CBlockPolicyEstimator::Read(CAutoFile& filein, int nFileVersion)
595+
bool CBlockPolicyEstimator::Read(CAutoFile& filein)
464596
{
465-
int nFileBestSeenHeight;
466-
filein >> nFileBestSeenHeight;
467-
feeStats.Read(filein);
468-
nBestSeenHeight = nFileBestSeenHeight;
469-
// if nVersionThatWrote < 139900 then another TxConfirmStats (for priority) follows but can be ignored.
597+
try {
598+
LOCK(cs_feeEstimator);
599+
int nVersionRequired, nVersionThatWrote, nFileBestSeenHeight;
600+
filein >> nVersionRequired >> nVersionThatWrote;
601+
if (nVersionRequired > CLIENT_VERSION)
602+
return error("CBlockPolicyEstimator::Read(): up-version (%d) fee estimate file", nVersionRequired);
603+
filein >> nFileBestSeenHeight;
604+
feeStats->Read(filein);
605+
nBestSeenHeight = nFileBestSeenHeight;
606+
// if nVersionThatWrote < 139900 then another TxConfirmStats (for priority) follows but can be ignored.
607+
}
608+
catch (const std::exception&) {
609+
LogPrintf("CBlockPolicyEstimator::Read(): unable to read policy estimator data (non-fatal)\n");
610+
return false;
611+
}
612+
return true;
470613
}
471614

472615
FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee)

0 commit comments

Comments
 (0)