Skip to content

Commit c0a273f

Browse files
committed
Change file format for fee estimates.
Move buckets and bucketMap to be stored as part of overall serialization of estimator. Add some placeholder data so file format is only changed once. Maintain 3 different TxConfirmStats with potential for different decays and scales.
1 parent 14c9489 commit c0a273f

File tree

2 files changed

+156
-77
lines changed

2 files changed

+156
-77
lines changed

src/policy/fees.cpp

Lines changed: 151 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ class TxConfirmStats
2626
{
2727
private:
2828
//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
29+
const std::vector<double>& buckets; // The upper-bound of the range for the bucket (inclusive)
30+
const std::map<double, unsigned int>& bucketMap; // Map of bucket upper-bound to index into all vectors by bucket
3131

3232
// For each bucket X:
3333
// Count the total # of txs in each bucket
@@ -38,9 +38,11 @@ class TxConfirmStats
3838

3939
// Count the total # of txs confirmed within Y blocks in each bucket
4040
// Track the historical moving average of theses totals over blocks
41-
std::vector<std::vector<double> > confAvg; // confAvg[Y][X]
41+
std::vector<std::vector<double>> confAvg; // confAvg[Y][X]
4242
// and calculate the totals for the current block to update the moving averages
43-
std::vector<std::vector<int> > curBlockConf; // curBlockConf[Y][X]
43+
std::vector<std::vector<int>> curBlockConf; // curBlockConf[Y][X]
44+
45+
std::vector<std::vector<double>> failAvg; // future use
4446

4547
// Sum the total feerate of all tx's in each bucket
4648
// Track the historical moving average of this total over blocks
@@ -53,13 +55,17 @@ class TxConfirmStats
5355

5456
double decay;
5557

58+
unsigned int scale;
59+
5660
// Mempool counts of outstanding transactions
5761
// For each bucket X, track the number of transactions in the mempool
5862
// that are unconfirmed for each possible confirmation value Y
5963
std::vector<std::vector<int> > unconfTxs; //unconfTxs[Y][X]
6064
// transactions still unconfirmed after MAX_CONFIRMS for each bucket
6165
std::vector<int> oldUnconfTxs;
6266

67+
void resizeInMemoryCounters(size_t newbuckets);
68+
6369
public:
6470
/**
6571
* Create new TxConfirmStats. This is called by BlockPolicyEstimator's
@@ -68,7 +74,8 @@ class TxConfirmStats
6874
* @param maxConfirms max number of confirms to track
6975
* @param decay how much to decay the historical moving average per block
7076
*/
71-
TxConfirmStats(const std::vector<double>& defaultBuckets, unsigned int maxConfirms, double decay);
77+
TxConfirmStats(const std::vector<double>& defaultBuckets, const std::map<double, unsigned int>& defaultBucketMap,
78+
unsigned int maxConfirms, double decay);
7279

7380
/** Clear the state of the curBlock variables to start counting for the new block */
7481
void ClearCurrent(unsigned int nBlockHeight);
@@ -116,32 +123,39 @@ class TxConfirmStats
116123
* Read saved state of estimation data from a file and replace all internal data structures and
117124
* variables with this state.
118125
*/
119-
void Read(CAutoFile& filein);
126+
void Read(CAutoFile& filein, int nFileVersion, size_t numBuckets);
120127
};
121128

122129

123130
TxConfirmStats::TxConfirmStats(const std::vector<double>& defaultBuckets,
131+
const std::map<double, unsigned int>& defaultBucketMap,
124132
unsigned int maxConfirms, double _decay)
133+
: buckets(defaultBuckets), bucketMap(defaultBucketMap)
125134
{
126135
decay = _decay;
127-
for (unsigned int i = 0; i < defaultBuckets.size(); i++) {
128-
buckets.push_back(defaultBuckets[i]);
129-
bucketMap[defaultBuckets[i]] = i;
130-
}
136+
scale = 1;
131137
confAvg.resize(maxConfirms);
132-
curBlockConf.resize(maxConfirms);
133-
unconfTxs.resize(maxConfirms);
134138
for (unsigned int i = 0; i < maxConfirms; i++) {
135139
confAvg[i].resize(buckets.size());
136-
curBlockConf[i].resize(buckets.size());
137-
unconfTxs[i].resize(buckets.size());
138140
}
139141

140-
oldUnconfTxs.resize(buckets.size());
141-
curBlockTxCt.resize(buckets.size());
142142
txCtAvg.resize(buckets.size());
143-
curBlockVal.resize(buckets.size());
144143
avg.resize(buckets.size());
144+
145+
resizeInMemoryCounters(buckets.size());
146+
}
147+
148+
void TxConfirmStats::resizeInMemoryCounters(size_t newbuckets) {
149+
curBlockConf.resize(GetMaxConfirms());
150+
// newbuckets must be passed in because the buckets referred to during Read have not been updated yet.
151+
unconfTxs.resize(GetMaxConfirms());
152+
for (unsigned int i = 0; i < unconfTxs.size(); i++) {
153+
curBlockConf[i].resize(newbuckets);
154+
unconfTxs[i].resize(newbuckets);
155+
}
156+
oldUnconfTxs.resize(newbuckets);
157+
curBlockTxCt.resize(newbuckets);
158+
curBlockVal.resize(newbuckets);
145159
}
146160

147161
// Zero out the data for the current block
@@ -283,70 +297,55 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
283297
void TxConfirmStats::Write(CAutoFile& fileout) const
284298
{
285299
fileout << decay;
286-
fileout << buckets;
300+
fileout << scale;
287301
fileout << avg;
288302
fileout << txCtAvg;
289303
fileout << confAvg;
304+
fileout << failAvg;
290305
}
291306

292-
void TxConfirmStats::Read(CAutoFile& filein)
307+
void TxConfirmStats::Read(CAutoFile& filein, int nFileVersion, size_t numBuckets)
293308
{
294-
// Read data file into temporary variables and do some very basic sanity checking
295-
std::vector<double> fileBuckets;
296-
std::vector<double> fileAvg;
297-
std::vector<std::vector<double> > fileConfAvg;
298-
std::vector<double> fileTxCtAvg;
299-
double fileDecay;
309+
// Read data file and do some very basic sanity checking
310+
// buckets and bucketMap are not updated yet, so don't access them
311+
// If there is a read failure, we'll just discard this entire object anyway
300312
size_t maxConfirms;
301-
size_t numBuckets;
302-
303-
filein >> fileDecay;
304-
if (fileDecay <= 0 || fileDecay >= 1)
305-
throw std::runtime_error("Corrupt estimates file. Decay must be between 0 and 1 (non-inclusive)");
306-
filein >> fileBuckets;
307-
numBuckets = fileBuckets.size();
308-
if (numBuckets <= 1 || numBuckets > 1000)
309-
throw std::runtime_error("Corrupt estimates file. Must have between 2 and 1000 feerate buckets");
310-
filein >> fileAvg;
311-
if (fileAvg.size() != numBuckets)
313+
314+
// The current version will store the decay with each individual TxConfirmStats and also keep a scale factor
315+
if (nFileVersion >= 149900) {
316+
filein >> decay;
317+
if (decay <= 0 || decay >= 1) {
318+
throw std::runtime_error("Corrupt estimates file. Decay must be between 0 and 1 (non-inclusive)");
319+
}
320+
filein >> scale; //Unused for now
321+
}
322+
323+
filein >> avg;
324+
if (avg.size() != numBuckets) {
312325
throw std::runtime_error("Corrupt estimates file. Mismatch in feerate average bucket count");
313-
filein >> fileTxCtAvg;
314-
if (fileTxCtAvg.size() != numBuckets)
326+
}
327+
filein >> txCtAvg;
328+
if (txCtAvg.size() != numBuckets) {
315329
throw std::runtime_error("Corrupt estimates file. Mismatch in tx count bucket count");
316-
filein >> fileConfAvg;
317-
maxConfirms = fileConfAvg.size();
318-
if (maxConfirms <= 0 || maxConfirms > 6 * 24 * 7) // one week
330+
}
331+
filein >> confAvg;
332+
maxConfirms = confAvg.size();
333+
if (maxConfirms <= 0 || maxConfirms > 6 * 24 * 7) { // one week
319334
throw std::runtime_error("Corrupt estimates file. Must maintain estimates for between 1 and 1008 (one week) confirms");
320-
for (unsigned int i = 0; i < maxConfirms; i++) {
321-
if (fileConfAvg[i].size() != numBuckets)
322-
throw std::runtime_error("Corrupt estimates file. Mismatch in feerate conf average bucket count");
323335
}
324-
// Now that we've processed the entire feerate estimate data file and not
325-
// thrown any errors, we can copy it to our data structures
326-
decay = fileDecay;
327-
buckets = fileBuckets;
328-
avg = fileAvg;
329-
confAvg = fileConfAvg;
330-
txCtAvg = fileTxCtAvg;
331-
bucketMap.clear();
332-
333-
// Resize the current block variables which aren't stored in the data file
334-
// to match the number of confirms and buckets
335-
curBlockConf.resize(maxConfirms);
336336
for (unsigned int i = 0; i < maxConfirms; i++) {
337-
curBlockConf[i].resize(buckets.size());
337+
if (confAvg[i].size() != numBuckets) {
338+
throw std::runtime_error("Corrupt estimates file. Mismatch in feerate conf average bucket count");
339+
}
338340
}
339-
curBlockTxCt.resize(buckets.size());
340-
curBlockVal.resize(buckets.size());
341341

342-
unconfTxs.resize(maxConfirms);
343-
for (unsigned int i = 0; i < maxConfirms; i++) {
344-
unconfTxs[i].resize(buckets.size());
342+
if (nFileVersion >= 149900) {
343+
filein >> failAvg;
345344
}
346-
oldUnconfTxs.resize(buckets.size());
347345

348-
for (unsigned int i = 0; i < buckets.size(); i++)
349-
bucketMap[buckets[i]] = i;
346+
// Resize the current block variables which aren't stored in the data file
347+
// to match the number of confirms and buckets
348+
resizeInMemoryCounters(numBuckets);
350349

351350
LogPrint(BCLog::ESTIMATEFEE, "Reading estimates: %u buckets counting confirms up to %u blocks\n",
352351
numBuckets, maxConfirms);
@@ -413,17 +412,25 @@ CBlockPolicyEstimator::CBlockPolicyEstimator()
413412
{
414413
static_assert(MIN_BUCKET_FEERATE > 0, "Min feerate must be nonzero");
415414
minTrackedFee = CFeeRate(MIN_BUCKET_FEERATE);
416-
std::vector<double> vfeelist;
417-
for (double bucketBoundary = minTrackedFee.GetFeePerK(); bucketBoundary <= MAX_BUCKET_FEERATE; bucketBoundary *= FEE_SPACING) {
418-
vfeelist.push_back(bucketBoundary);
415+
size_t bucketIndex = 0;
416+
for (double bucketBoundary = minTrackedFee.GetFeePerK(); bucketBoundary <= MAX_BUCKET_FEERATE; bucketBoundary *= FEE_SPACING, bucketIndex++) {
417+
buckets.push_back(bucketBoundary);
418+
bucketMap[bucketBoundary] = bucketIndex;
419419
}
420-
vfeelist.push_back(INF_FEERATE);
421-
feeStats = new TxConfirmStats(vfeelist, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
420+
buckets.push_back(INF_FEERATE);
421+
bucketMap[INF_FEERATE] = bucketIndex;
422+
assert(bucketMap.size() == buckets.size());
423+
424+
feeStats = new TxConfirmStats(buckets, bucketMap, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
425+
shortStats = new TxConfirmStats(buckets, bucketMap, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
426+
longStats = new TxConfirmStats(buckets, bucketMap, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
422427
}
423428

424429
CBlockPolicyEstimator::~CBlockPolicyEstimator()
425430
{
426431
delete feeStats;
432+
delete shortStats;
433+
delete longStats;
427434
}
428435

429436
void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate)
@@ -580,10 +587,15 @@ bool CBlockPolicyEstimator::Write(CAutoFile& fileout) const
580587
{
581588
try {
582589
LOCK(cs_feeEstimator);
583-
fileout << 139900; // version required to read: 0.13.99 or later
590+
fileout << 149900; // version required to read: 0.14.99 or later
584591
fileout << CLIENT_VERSION; // version that wrote the file
585592
fileout << nBestSeenHeight;
593+
unsigned int future1 = 0, future2 = 0;
594+
fileout << future1 << future2;
595+
fileout << buckets;
586596
feeStats->Write(fileout);
597+
shortStats->Write(fileout);
598+
longStats->Write(fileout);
587599
}
588600
catch (const std::exception&) {
589601
LogPrintf("CBlockPolicyEstimator::Write(): unable to read policy estimator data (non-fatal)\n");
@@ -596,17 +608,79 @@ bool CBlockPolicyEstimator::Read(CAutoFile& filein)
596608
{
597609
try {
598610
LOCK(cs_feeEstimator);
599-
int nVersionRequired, nVersionThatWrote, nFileBestSeenHeight;
611+
int nVersionRequired, nVersionThatWrote;
612+
unsigned int nFileBestSeenHeight;
600613
filein >> nVersionRequired >> nVersionThatWrote;
601614
if (nVersionRequired > CLIENT_VERSION)
602615
return error("CBlockPolicyEstimator::Read(): up-version (%d) fee estimate file", nVersionRequired);
616+
617+
// Read fee estimates file into temporary variables so existing data
618+
// structures aren't corrupted if there is an exception.
603619
filein >> nFileBestSeenHeight;
604-
feeStats->Read(filein);
605-
nBestSeenHeight = nFileBestSeenHeight;
606-
// if nVersionThatWrote < 139900 then another TxConfirmStats (for priority) follows but can be ignored.
620+
621+
if (nVersionThatWrote < 149900) {
622+
// Read the old fee estimates file for temporary use, but then discard. Will start collecting data from scratch.
623+
// decay is stored before buckets in old versions, so pre-read decay and pass into TxConfirmStats constructor
624+
double tempDecay;
625+
filein >> tempDecay;
626+
if (tempDecay <= 0 || tempDecay >= 1)
627+
throw std::runtime_error("Corrupt estimates file. Decay must be between 0 and 1 (non-inclusive)");
628+
629+
std::vector<double> tempBuckets;
630+
filein >> tempBuckets;
631+
size_t tempNum = tempBuckets.size();
632+
if (tempNum <= 1 || tempNum > 1000)
633+
throw std::runtime_error("Corrupt estimates file. Must have between 2 and 1000 feerate buckets");
634+
635+
std::map<double, unsigned int> tempMap;
636+
637+
std::unique_ptr<TxConfirmStats> tempFeeStats(new TxConfirmStats(tempBuckets, tempMap, MAX_BLOCK_CONFIRMS, tempDecay));
638+
tempFeeStats->Read(filein, nVersionThatWrote, tempNum);
639+
// if nVersionThatWrote < 139900 then another TxConfirmStats (for priority) follows but can be ignored.
640+
641+
tempMap.clear();
642+
for (unsigned int i = 0; i < tempBuckets.size(); i++) {
643+
tempMap[tempBuckets[i]] = i;
644+
}
645+
}
646+
else { // nVersionThatWrote >= 149900
647+
unsigned int future1, future2;
648+
filein >> future1 >> future2;
649+
650+
std::vector<double> fileBuckets;
651+
filein >> fileBuckets;
652+
size_t numBuckets = fileBuckets.size();
653+
if (numBuckets <= 1 || numBuckets > 1000)
654+
throw std::runtime_error("Corrupt estimates file. Must have between 2 and 1000 feerate buckets");
655+
656+
std::unique_ptr<TxConfirmStats> fileFeeStats(new TxConfirmStats(buckets, bucketMap, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY));
657+
std::unique_ptr<TxConfirmStats> fileShortStats(new TxConfirmStats(buckets, bucketMap, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY));
658+
std::unique_ptr<TxConfirmStats> fileLongStats(new TxConfirmStats(buckets, bucketMap, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY));
659+
fileFeeStats->Read(filein, nVersionThatWrote, numBuckets);
660+
fileShortStats->Read(filein, nVersionThatWrote, numBuckets);
661+
fileLongStats->Read(filein, nVersionThatWrote, numBuckets);
662+
663+
// Fee estimates file parsed correctly
664+
// Copy buckets from file and refresh our bucketmap
665+
buckets = fileBuckets;
666+
bucketMap.clear();
667+
for (unsigned int i = 0; i < buckets.size(); i++) {
668+
bucketMap[buckets[i]] = i;
669+
}
670+
671+
// Destroy old TxConfirmStats and point to new ones that already reference buckets and bucketMap
672+
delete feeStats;
673+
delete shortStats;
674+
delete longStats;
675+
feeStats = fileFeeStats.release();
676+
shortStats = fileShortStats.release();
677+
longStats = fileLongStats.release();
678+
679+
nBestSeenHeight = nFileBestSeenHeight;
680+
}
607681
}
608-
catch (const std::exception&) {
609-
LogPrintf("CBlockPolicyEstimator::Read(): unable to read policy estimator data (non-fatal)\n");
682+
catch (const std::exception& e) {
683+
LogPrintf("CBlockPolicyEstimator::Read(): unable to read policy estimator data (non-fatal): %s\n",e.what());
610684
return false;
611685
}
612686
return true;

src/policy/fees.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,15 @@ class CBlockPolicyEstimator
141141

142142
/** Classes to track historical data on transaction confirmations */
143143
TxConfirmStats* feeStats;
144+
TxConfirmStats* shortStats;
145+
TxConfirmStats* longStats;
144146

145147
unsigned int trackedTxs;
146148
unsigned int untrackedTxs;
147149

150+
std::vector<double> buckets; // The upper-bound of the range for the bucket (inclusive)
151+
std::map<double, unsigned int> bucketMap; // Map of bucket upper-bound to index into all vectors by bucket
152+
148153
mutable CCriticalSection cs_feeEstimator;
149154

150155
/** Process a transaction confirmed in a block*/

0 commit comments

Comments
 (0)