@@ -26,8 +26,8 @@ class TxConfirmStats
26
26
{
27
27
private:
28
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
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
31
31
32
32
// For each bucket X:
33
33
// Count the total # of txs in each bucket
@@ -38,9 +38,11 @@ class TxConfirmStats
38
38
39
39
// Count the total # of txs confirmed within Y blocks in each bucket
40
40
// 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]
42
42
// 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
44
46
45
47
// Sum the total feerate of all tx's in each bucket
46
48
// Track the historical moving average of this total over blocks
@@ -53,13 +55,17 @@ class TxConfirmStats
53
55
54
56
double decay;
55
57
58
+ unsigned int scale;
59
+
56
60
// Mempool counts of outstanding transactions
57
61
// For each bucket X, track the number of transactions in the mempool
58
62
// that are unconfirmed for each possible confirmation value Y
59
63
std::vector<std::vector<int > > unconfTxs; // unconfTxs[Y][X]
60
64
// transactions still unconfirmed after MAX_CONFIRMS for each bucket
61
65
std::vector<int > oldUnconfTxs;
62
66
67
+ void resizeInMemoryCounters (size_t newbuckets);
68
+
63
69
public:
64
70
/* *
65
71
* Create new TxConfirmStats. This is called by BlockPolicyEstimator's
@@ -68,7 +74,8 @@ class TxConfirmStats
68
74
* @param maxConfirms max number of confirms to track
69
75
* @param decay how much to decay the historical moving average per block
70
76
*/
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);
72
79
73
80
/* * Clear the state of the curBlock variables to start counting for the new block */
74
81
void ClearCurrent (unsigned int nBlockHeight);
@@ -116,32 +123,39 @@ class TxConfirmStats
116
123
* Read saved state of estimation data from a file and replace all internal data structures and
117
124
* variables with this state.
118
125
*/
119
- void Read (CAutoFile& filein);
126
+ void Read (CAutoFile& filein, int nFileVersion, size_t numBuckets );
120
127
};
121
128
122
129
123
130
TxConfirmStats::TxConfirmStats (const std::vector<double >& defaultBuckets,
131
+ const std::map<double , unsigned int >& defaultBucketMap,
124
132
unsigned int maxConfirms, double _decay)
133
+ : buckets(defaultBuckets), bucketMap(defaultBucketMap)
125
134
{
126
135
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 ;
131
137
confAvg.resize (maxConfirms);
132
- curBlockConf.resize (maxConfirms);
133
- unconfTxs.resize (maxConfirms);
134
138
for (unsigned int i = 0 ; i < maxConfirms; i++) {
135
139
confAvg[i].resize (buckets.size ());
136
- curBlockConf[i].resize (buckets.size ());
137
- unconfTxs[i].resize (buckets.size ());
138
140
}
139
141
140
- oldUnconfTxs.resize (buckets.size ());
141
- curBlockTxCt.resize (buckets.size ());
142
142
txCtAvg.resize (buckets.size ());
143
- curBlockVal.resize (buckets.size ());
144
143
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);
145
159
}
146
160
147
161
// Zero out the data for the current block
@@ -283,70 +297,55 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
283
297
void TxConfirmStats::Write (CAutoFile& fileout) const
284
298
{
285
299
fileout << decay;
286
- fileout << buckets ;
300
+ fileout << scale ;
287
301
fileout << avg;
288
302
fileout << txCtAvg;
289
303
fileout << confAvg;
304
+ fileout << failAvg;
290
305
}
291
306
292
- void TxConfirmStats::Read (CAutoFile& filein)
307
+ void TxConfirmStats::Read (CAutoFile& filein, int nFileVersion, size_t numBuckets )
293
308
{
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
300
312
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) {
312
325
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) {
315
329
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
319
334
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" );
323
335
}
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);
336
336
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
+ }
338
340
}
339
- curBlockTxCt.resize (buckets.size ());
340
- curBlockVal.resize (buckets.size ());
341
341
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;
345
344
}
346
- oldUnconfTxs.resize (buckets.size ());
347
345
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);
350
349
351
350
LogPrint (BCLog::ESTIMATEFEE, " Reading estimates: %u buckets counting confirms up to %u blocks\n " ,
352
351
numBuckets, maxConfirms);
@@ -413,17 +412,25 @@ CBlockPolicyEstimator::CBlockPolicyEstimator()
413
412
{
414
413
static_assert (MIN_BUCKET_FEERATE > 0 , " Min feerate must be nonzero" );
415
414
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;
419
419
}
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);
422
427
}
423
428
424
429
CBlockPolicyEstimator::~CBlockPolicyEstimator ()
425
430
{
426
431
delete feeStats;
432
+ delete shortStats;
433
+ delete longStats;
427
434
}
428
435
429
436
void CBlockPolicyEstimator::processTransaction (const CTxMemPoolEntry& entry, bool validFeeEstimate)
@@ -580,10 +587,15 @@ bool CBlockPolicyEstimator::Write(CAutoFile& fileout) const
580
587
{
581
588
try {
582
589
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
584
591
fileout << CLIENT_VERSION; // version that wrote the file
585
592
fileout << nBestSeenHeight;
593
+ unsigned int future1 = 0 , future2 = 0 ;
594
+ fileout << future1 << future2;
595
+ fileout << buckets;
586
596
feeStats->Write (fileout);
597
+ shortStats->Write (fileout);
598
+ longStats->Write (fileout);
587
599
}
588
600
catch (const std::exception&) {
589
601
LogPrintf (" CBlockPolicyEstimator::Write(): unable to read policy estimator data (non-fatal)\n " );
@@ -596,17 +608,79 @@ bool CBlockPolicyEstimator::Read(CAutoFile& filein)
596
608
{
597
609
try {
598
610
LOCK (cs_feeEstimator);
599
- int nVersionRequired, nVersionThatWrote, nFileBestSeenHeight;
611
+ int nVersionRequired, nVersionThatWrote;
612
+ unsigned int nFileBestSeenHeight;
600
613
filein >> nVersionRequired >> nVersionThatWrote;
601
614
if (nVersionRequired > CLIENT_VERSION)
602
615
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.
603
619
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
+ }
607
681
}
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 () );
610
684
return false ;
611
685
}
612
686
return true ;
0 commit comments