Skip to content

Commit f8c5a14

Browse files
laanwjPastaPastaPasta
authored andcommitted
Merge bitcoin#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 finish bitcoin#9942 by removing removed functions Signed-off-by: Pasta <Pasta@dash.org>
1 parent 6f55827 commit f8c5a14

File tree

14 files changed

+244
-259
lines changed

14 files changed

+244
-259
lines changed

src/init.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "netbase.h"
2727
#include "net.h"
2828
#include "net_processing.h"
29+
#include "policy/fees.h"
2930
#include "policy/policy.h"
3031
#include "rpc/server.h"
3132
#include "rpc/register.h"
@@ -287,7 +288,7 @@ void PrepareShutdown()
287288
boost::filesystem::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME;
288289
CAutoFile est_fileout(fopen(est_path.string().c_str(), "wb"), SER_DISK, CLIENT_VERSION);
289290
if (!est_fileout.IsNull())
290-
mempool.WriteFeeEstimates(est_fileout);
291+
::feeEstimator.Write(est_fileout);
291292
else
292293
LogPrintf("%s: Failed to write fee estimates to %s\n", __func__, est_path.string());
293294
fFeeEstimatesInitialized = false;
@@ -1926,7 +1927,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
19261927
CAutoFile est_filein(fopen(est_path.string().c_str(), "rb"), SER_DISK, CLIENT_VERSION);
19271928
// Allowed to fail as this file IS missing on first startup.
19281929
if (!est_filein.IsNull())
1929-
mempool.ReadFeeEstimates(est_filein);
1930+
::feeEstimator.Read(est_filein);
19301931
fFeeEstimatesInitialized = true;
19311932

19321933
// ********************************************************* Step 8: load wallet

src/policy/fees.cpp

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

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

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

175-
void TxConfirmStats::Write(CAutoFile& fileout)
282+
void TxConfirmStats::Write(CAutoFile& fileout) const
176283
{
177284
fileout << decay;
178285
fileout << buckets;
@@ -289,9 +396,10 @@ void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHe
289396
// of no harm to try to remove them again.
290397
bool CBlockPolicyEstimator::removeTx(uint256 hash)
291398
{
399+
LOCK(cs_feeEstimator);
292400
std::map<uint256, TxStatsInfo>::iterator pos = mapMemPoolTxs.find(hash);
293401
if (pos != mapMemPoolTxs.end()) {
294-
feeStats.removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex);
402+
feeStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex);
295403
mapMemPoolTxs.erase(hash);
296404
return true;
297405
} else {
@@ -309,11 +417,17 @@ CBlockPolicyEstimator::CBlockPolicyEstimator()
309417
vfeelist.push_back(bucketBoundary);
310418
}
311419
vfeelist.push_back(INF_FEERATE);
312-
feeStats.Initialize(vfeelist, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
420+
feeStats = new TxConfirmStats(vfeelist, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
421+
}
422+
423+
CBlockPolicyEstimator::~CBlockPolicyEstimator()
424+
{
425+
delete feeStats;
313426
}
314427

315428
void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate)
316429
{
430+
LOCK(cs_feeEstimator);
317431
unsigned int txHeight = entry.GetHeight();
318432
uint256 hash = entry.GetTx().GetHash();
319433
if (mapMemPoolTxs.count(hash)) {
@@ -341,7 +455,7 @@ void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, boo
341455
CFeeRate feeRate(entry.GetFee(), entry.GetTxSize());
342456

343457
mapMemPoolTxs[hash].blockHeight = txHeight;
344-
mapMemPoolTxs[hash].bucketIndex = feeStats.NewTx(txHeight, (double)feeRate.GetFeePerK());
458+
mapMemPoolTxs[hash].bucketIndex = feeStats->NewTx(txHeight, (double)feeRate.GetFeePerK());
345459
}
346460

347461
bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry)
@@ -365,13 +479,14 @@ bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxM
365479
// Feerates are stored and reported as BTC-per-kb:
366480
CFeeRate feeRate(entry->GetFee(), entry->GetTxSize());
367481

368-
feeStats.Record(blocksToConfirm, (double)feeRate.GetFeePerK());
482+
feeStats->Record(blocksToConfirm, (double)feeRate.GetFeePerK());
369483
return true;
370484
}
371485

372486
void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
373487
std::vector<const CTxMemPoolEntry*>& entries)
374488
{
489+
LOCK(cs_feeEstimator);
375490
if (nBlockHeight <= nBestSeenHeight) {
376491
// Ignore side chains and re-orgs; assuming they are random
377492
// they don't affect the estimate.
@@ -387,7 +502,7 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
387502
nBestSeenHeight = nBlockHeight;
388503

389504
// Clear the current block state and update unconfirmed circular buffer
390-
feeStats.ClearCurrent(nBlockHeight);
505+
feeStats->ClearCurrent(nBlockHeight);
391506

392507
unsigned int countedTxs = 0;
393508
// Repopulate the current block states
@@ -397,7 +512,7 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
397512
}
398513

399514
// Update all exponential averages with the current block state
400-
feeStats.UpdateMovingAverages();
515+
feeStats->UpdateMovingAverages();
401516

402517
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",
403518
countedTxs, entries.size(), trackedTxs, trackedTxs + untrackedTxs, mapMemPoolTxs.size());
@@ -406,37 +521,44 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
406521
untrackedTxs = 0;
407522
}
408523

409-
CFeeRate CBlockPolicyEstimator::estimateFee(int confTarget)
524+
CFeeRate CBlockPolicyEstimator::estimateFee(int confTarget) const
410525
{
526+
LOCK(cs_feeEstimator);
411527
// Return failure if trying to analyze a target we're not tracking
412528
// It's not possible to get reasonable estimates for confTarget of 1
413-
if (confTarget <= 1 || (unsigned int)confTarget > feeStats.GetMaxConfirms())
529+
if (confTarget <= 1 || (unsigned int)confTarget > feeStats->GetMaxConfirms())
414530
return CFeeRate(0);
415531

416-
double median = feeStats.EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
532+
double median = feeStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
417533

418534
if (median < 0)
419535
return CFeeRate(0);
420536

421537
return CFeeRate(median);
422538
}
423539

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

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

441563
if (answerFoundAtTarget)
442564
*answerFoundAtTarget = confTarget - 1;
@@ -452,18 +574,39 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoun
452574
return CFeeRate(median);
453575
}
454576

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

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

0 commit comments

Comments
 (0)