Browse Source

Remove priority estimation

0.14
Alex Morcos 8 years ago committed by Wladimir J. van der Laan
parent
commit
b2322e0fc6
  1. 168
      src/policy/fees.cpp
  2. 115
      src/policy/fees.h
  3. 4
      src/rpc/mining.cpp
  4. 60
      src/test/policyestimator_tests.cpp
  5. 5
      src/txmempool.cpp

168
src/policy/fees.cpp

@ -14,10 +14,9 @@
#include "util.h" #include "util.h"
void TxConfirmStats::Initialize(std::vector<double>& defaultBuckets, void TxConfirmStats::Initialize(std::vector<double>& defaultBuckets,
unsigned int maxConfirms, double _decay, std::string _dataTypeString) unsigned int maxConfirms, double _decay)
{ {
decay = _decay; decay = _decay;
dataTypeString = _dataTypeString;
for (unsigned int i = 0; i < defaultBuckets.size(); i++) { for (unsigned int i = 0; i < defaultBuckets.size(); i++) {
buckets.push_back(defaultBuckets[i]); buckets.push_back(defaultBuckets[i]);
bucketMap[defaultBuckets[i]] = i; bucketMap[defaultBuckets[i]] = i;
@ -87,10 +86,10 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
int maxbucketindex = buckets.size() - 1; int maxbucketindex = buckets.size() - 1;
// requireGreater means we are looking for the lowest fee/priority such that all higher // requireGreater means we are looking for the lowest feerate such that all higher
// values pass, so we start at maxbucketindex (highest fee) and look at successively // values pass, so we start at maxbucketindex (highest feerate) and look at successively
// smaller buckets until we reach failure. Otherwise, we are looking for the highest // smaller buckets until we reach failure. Otherwise, we are looking for the highest
// fee/priority such that all lower values fail, and we go in the opposite direction. // feerate such that all lower values fail, and we go in the opposite direction.
unsigned int startbucket = requireGreater ? maxbucketindex : 0; unsigned int startbucket = requireGreater ? maxbucketindex : 0;
int step = requireGreater ? -1 : 1; int step = requireGreater ? -1 : 1;
@ -107,7 +106,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
bool foundAnswer = false; bool foundAnswer = false;
unsigned int bins = unconfTxs.size(); unsigned int bins = unconfTxs.size();
// Start counting from highest(default) or lowest fee/pri transactions // Start counting from highest(default) or lowest feerate transactions
for (int bucket = startbucket; bucket >= 0 && bucket <= maxbucketindex; bucket += step) { for (int bucket = startbucket; bucket >= 0 && bucket <= maxbucketindex; bucket += step) {
curFarBucket = bucket; curFarBucket = bucket;
nConf += confAvg[confTarget - 1][bucket]; nConf += confAvg[confTarget - 1][bucket];
@ -145,8 +144,8 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
double median = -1; double median = -1;
double txSum = 0; double txSum = 0;
// Calculate the "average" fee of the best bucket range that met success conditions // Calculate the "average" feerate of the best bucket range that met success conditions
// Find the bucket with the median transaction and then report the average fee from that bucket // Find the bucket with the median transaction and then report the average feerate from that bucket
// This is a compromise between finding the median which we can't since we don't save all tx's // This is a compromise between finding the median which we can't since we don't save all tx's
// and reporting the average which is less accurate // and reporting the average which is less accurate
unsigned int minBucket = bestNearBucket < bestFarBucket ? bestNearBucket : bestFarBucket; unsigned int minBucket = bestNearBucket < bestFarBucket ? bestNearBucket : bestFarBucket;
@ -166,8 +165,8 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
} }
} }
LogPrint("estimatefee", "%3d: For conf success %s %4.2f need %s %s: %12.5g from buckets %8g - %8g Cur Bucket stats %6.2f%% %8.1f/(%.1f+%d mempool)\n", LogPrint("estimatefee", "%3d: For conf success %s %4.2f need feerate %s: %12.5g from buckets %8g - %8g Cur Bucket stats %6.2f%% %8.1f/(%.1f+%d mempool)\n",
confTarget, requireGreater ? ">" : "<", successBreakPoint, dataTypeString, confTarget, requireGreater ? ">" : "<", successBreakPoint,
requireGreater ? ">" : "<", median, buckets[minBucket], buckets[maxBucket], requireGreater ? ">" : "<", median, buckets[minBucket], buckets[maxBucket],
100 * nConf / (totalNum + extraNum), nConf, totalNum, extraNum); 100 * nConf / (totalNum + extraNum), nConf, totalNum, extraNum);
@ -200,10 +199,10 @@ void TxConfirmStats::Read(CAutoFile& filein)
filein >> fileBuckets; filein >> fileBuckets;
numBuckets = fileBuckets.size(); numBuckets = fileBuckets.size();
if (numBuckets <= 1 || numBuckets > 1000) if (numBuckets <= 1 || numBuckets > 1000)
throw std::runtime_error("Corrupt estimates file. Must have between 2 and 1000 fee/pri buckets"); throw std::runtime_error("Corrupt estimates file. Must have between 2 and 1000 feerate buckets");
filein >> fileAvg; filein >> fileAvg;
if (fileAvg.size() != numBuckets) if (fileAvg.size() != numBuckets)
throw std::runtime_error("Corrupt estimates file. Mismatch in fee/pri average bucket count"); throw std::runtime_error("Corrupt estimates file. Mismatch in feerate average bucket count");
filein >> fileTxCtAvg; filein >> fileTxCtAvg;
if (fileTxCtAvg.size() != numBuckets) if (fileTxCtAvg.size() != numBuckets)
throw std::runtime_error("Corrupt estimates file. Mismatch in tx count bucket count"); throw std::runtime_error("Corrupt estimates file. Mismatch in tx count bucket count");
@ -213,9 +212,9 @@ void TxConfirmStats::Read(CAutoFile& filein)
throw std::runtime_error("Corrupt estimates file. Must maintain estimates for between 1 and 1008 (one week) confirms"); throw std::runtime_error("Corrupt estimates file. Must maintain estimates for between 1 and 1008 (one week) confirms");
for (unsigned int i = 0; i < maxConfirms; i++) { for (unsigned int i = 0; i < maxConfirms; i++) {
if (fileConfAvg[i].size() != numBuckets) if (fileConfAvg[i].size() != numBuckets)
throw std::runtime_error("Corrupt estimates file. Mismatch in fee/pri conf average bucket count"); throw std::runtime_error("Corrupt estimates file. Mismatch in feerate conf average bucket count");
} }
// Now that we've processed the entire fee estimate data file and not // Now that we've processed the entire feerate estimate data file and not
// thrown any errors, we can copy it to our data structures // thrown any errors, we can copy it to our data structures
decay = fileDecay; decay = fileDecay;
buckets = fileBuckets; buckets = fileBuckets;
@ -242,8 +241,8 @@ void TxConfirmStats::Read(CAutoFile& filein)
for (unsigned int i = 0; i < buckets.size(); i++) for (unsigned int i = 0; i < buckets.size(); i++)
bucketMap[buckets[i]] = i; bucketMap[buckets[i]] = i;
LogPrint("estimatefee", "Reading estimates: %u %s buckets counting confirms up to %u blocks\n", LogPrint("estimatefee", "Reading estimates: %u buckets counting confirms up to %u blocks\n",
numBuckets, dataTypeString, maxConfirms); numBuckets, maxConfirms);
} }
unsigned int TxConfirmStats::NewTx(unsigned int nBlockHeight, double val) unsigned int TxConfirmStats::NewTx(unsigned int nBlockHeight, double val)
@ -251,7 +250,6 @@ unsigned int TxConfirmStats::NewTx(unsigned int nBlockHeight, double val)
unsigned int bucketindex = bucketMap.lower_bound(val)->second; unsigned int bucketindex = bucketMap.lower_bound(val)->second;
unsigned int blockIndex = nBlockHeight % unconfTxs.size(); unsigned int blockIndex = nBlockHeight % unconfTxs.size();
unconfTxs[blockIndex][bucketindex]++; unconfTxs[blockIndex][bucketindex]++;
LogPrint("estimatefee", "adding to %s", dataTypeString);
return bucketindex; return bucketindex;
} }
@ -291,12 +289,10 @@ void CBlockPolicyEstimator::removeTx(uint256 hash)
hash.ToString().c_str()); hash.ToString().c_str());
return; return;
} }
TxConfirmStats *stats = pos->second.stats;
unsigned int entryHeight = pos->second.blockHeight; unsigned int entryHeight = pos->second.blockHeight;
unsigned int bucketIndex = pos->second.bucketIndex; unsigned int bucketIndex = pos->second.bucketIndex;
if (stats != NULL) feeStats.removeTx(entryHeight, nBestSeenHeight, bucketIndex);
stats->removeTx(entryHeight, nBestSeenHeight, bucketIndex);
mapMemPoolTxs.erase(hash); mapMemPoolTxs.erase(hash);
} }
@ -309,45 +305,14 @@ CBlockPolicyEstimator::CBlockPolicyEstimator(const CFeeRate& _minRelayFee)
vfeelist.push_back(bucketBoundary); vfeelist.push_back(bucketBoundary);
} }
vfeelist.push_back(INF_FEERATE); vfeelist.push_back(INF_FEERATE);
feeStats.Initialize(vfeelist, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY, "FeeRate"); feeStats.Initialize(vfeelist, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
minTrackedPriority = AllowFreeThreshold() < MIN_PRIORITY ? MIN_PRIORITY : AllowFreeThreshold();
std::vector<double> vprilist;
for (double bucketBoundary = minTrackedPriority; bucketBoundary <= MAX_PRIORITY; bucketBoundary *= PRI_SPACING) {
vprilist.push_back(bucketBoundary);
}
vprilist.push_back(INF_PRIORITY);
priStats.Initialize(vprilist, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY, "Priority");
feeUnlikely = CFeeRate(0);
feeLikely = CFeeRate(INF_FEERATE);
priUnlikely = 0;
priLikely = INF_PRIORITY;
}
bool CBlockPolicyEstimator::isFeeDataPoint(const CFeeRate &fee, double pri)
{
if ((pri < minTrackedPriority && fee >= minTrackedFee) ||
(pri < priUnlikely && fee > feeLikely)) {
return true;
}
return false;
}
bool CBlockPolicyEstimator::isPriDataPoint(const CFeeRate &fee, double pri)
{
if ((fee < minTrackedFee && pri >= minTrackedPriority) ||
(fee < feeUnlikely && pri > priLikely)) {
return true;
}
return false;
} }
void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool fCurrentEstimate) void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool fCurrentEstimate)
{ {
unsigned int txHeight = entry.GetHeight(); unsigned int txHeight = entry.GetHeight();
uint256 hash = entry.GetTx().GetHash(); uint256 hash = entry.GetTx().GetHash();
if (mapMemPoolTxs[hash].stats != NULL) { if (mapMemPoolTxs.count(hash)) {
LogPrint("estimatefee", "Blockpolicy error mempool tx %s already being tracked\n", LogPrint("estimatefee", "Blockpolicy error mempool tx %s already being tracked\n",
hash.ToString().c_str()); hash.ToString().c_str());
return; return;
@ -371,30 +336,11 @@ void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, boo
return; return;
} }
// Fees are stored and reported as BTC-per-kb: // Feerates are stored and reported as BTC-per-kb:
CFeeRate feeRate(entry.GetFee(), entry.GetTxSize()); CFeeRate feeRate(entry.GetFee(), entry.GetTxSize());
// Want the priority of the tx at confirmation. However we don't know
// what that will be and its too hard to continue updating it
// so use starting priority as a proxy
double curPri = entry.GetPriority(txHeight);
mapMemPoolTxs[hash].blockHeight = txHeight; mapMemPoolTxs[hash].blockHeight = txHeight;
mapMemPoolTxs[hash].bucketIndex = feeStats.NewTx(txHeight, (double)feeRate.GetFeePerK());
LogPrint("estimatefee", "Blockpolicy mempool tx %s ", hash.ToString().substr(0,10));
// Record this as a priority estimate
if (entry.GetFee() == 0 || isPriDataPoint(feeRate, curPri)) {
mapMemPoolTxs[hash].stats = &priStats;
mapMemPoolTxs[hash].bucketIndex = priStats.NewTx(txHeight, curPri);
}
// Record this as a fee estimate
else if (isFeeDataPoint(feeRate, curPri)) {
mapMemPoolTxs[hash].stats = &feeStats;
mapMemPoolTxs[hash].bucketIndex = feeStats.NewTx(txHeight, (double)feeRate.GetFeePerK());
}
else {
LogPrint("estimatefee", "not adding");
}
LogPrint("estimatefee", "\n");
} }
void CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry& entry) void CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry& entry)
@ -417,21 +363,10 @@ void CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxM
return; return;
} }
// Fees are stored and reported as BTC-per-kb: // Feerates are stored and reported as BTC-per-kb:
CFeeRate feeRate(entry.GetFee(), entry.GetTxSize()); CFeeRate feeRate(entry.GetFee(), entry.GetTxSize());
// Want the priority of the tx at confirmation. The priority when it feeStats.Record(blocksToConfirm, (double)feeRate.GetFeePerK());
// entered the mempool could easily be very small and change quickly
double curPri = entry.GetPriority(nBlockHeight);
// Record this as a priority estimate
if (entry.GetFee() == 0 || isPriDataPoint(feeRate, curPri)) {
priStats.Record(blocksToConfirm, curPri);
}
// Record this as a fee estimate
else if (isFeeDataPoint(feeRate, curPri)) {
feeStats.Record(blocksToConfirm, (double)feeRate.GetFeePerK());
}
} }
void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight, void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
@ -452,41 +387,15 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
if (!fCurrentEstimate) if (!fCurrentEstimate)
return; return;
// Update the dynamic cutoffs // Clear the current block state
// a fee/priority is "likely" the reason your tx was included in a block if >85% of such tx's
// were confirmed in 2 blocks and is "unlikely" if <50% were confirmed in 10 blocks
LogPrint("estimatefee", "Blockpolicy recalculating dynamic cutoffs:\n");
priLikely = priStats.EstimateMedianVal(2, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT, true, nBlockHeight);
if (priLikely == -1)
priLikely = INF_PRIORITY;
double feeLikelyEst = feeStats.EstimateMedianVal(2, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBlockHeight);
if (feeLikelyEst == -1)
feeLikely = CFeeRate(INF_FEERATE);
else
feeLikely = CFeeRate(feeLikelyEst);
priUnlikely = priStats.EstimateMedianVal(10, SUFFICIENT_PRITXS, UNLIKELY_PCT, false, nBlockHeight);
if (priUnlikely == -1)
priUnlikely = 0;
double feeUnlikelyEst = feeStats.EstimateMedianVal(10, SUFFICIENT_FEETXS, UNLIKELY_PCT, false, nBlockHeight);
if (feeUnlikelyEst == -1)
feeUnlikely = CFeeRate(0);
else
feeUnlikely = CFeeRate(feeUnlikelyEst);
// Clear the current block states
feeStats.ClearCurrent(nBlockHeight); feeStats.ClearCurrent(nBlockHeight);
priStats.ClearCurrent(nBlockHeight);
// Repopulate the current block states // Repopulate the current block states
for (unsigned int i = 0; i < entries.size(); i++) for (unsigned int i = 0; i < entries.size(); i++)
processBlockTx(nBlockHeight, entries[i]); processBlockTx(nBlockHeight, entries[i]);
// Update all exponential averages with the current block states // Update all exponential averages with the current block state
feeStats.UpdateMovingAverages(); feeStats.UpdateMovingAverages();
priStats.UpdateMovingAverages();
LogPrint("estimatefee", "Blockpolicy after updating estimates for %u confirmed entries, new mempool map size %u\n", LogPrint("estimatefee", "Blockpolicy after updating estimates for %u confirmed entries, new mempool map size %u\n",
entries.size(), mapMemPoolTxs.size()); entries.size(), mapMemPoolTxs.size());
@ -522,7 +431,7 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoun
if (answerFoundAtTarget) if (answerFoundAtTarget)
*answerFoundAtTarget = confTarget - 1; *answerFoundAtTarget = confTarget - 1;
// If mempool is limiting txs , return at least the min fee from the mempool // If mempool is limiting txs , return at least the min feerate from the mempool
CAmount minPoolFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK(); CAmount minPoolFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK();
if (minPoolFee > 0 && minPoolFee > median) if (minPoolFee > 0 && minPoolFee > median)
return CFeeRate(minPoolFee); return CFeeRate(minPoolFee);
@ -535,51 +444,38 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoun
double CBlockPolicyEstimator::estimatePriority(int confTarget) double CBlockPolicyEstimator::estimatePriority(int confTarget)
{ {
// Return failure if trying to analyze a target we're not tracking return -1;
if (confTarget <= 0 || (unsigned int)confTarget > priStats.GetMaxConfirms())
return -1;
return priStats.EstimateMedianVal(confTarget, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
} }
double CBlockPolicyEstimator::estimateSmartPriority(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool) double CBlockPolicyEstimator::estimateSmartPriority(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool)
{ {
if (answerFoundAtTarget) if (answerFoundAtTarget)
*answerFoundAtTarget = confTarget; *answerFoundAtTarget = confTarget;
// Return failure if trying to analyze a target we're not tracking
if (confTarget <= 0 || (unsigned int)confTarget > priStats.GetMaxConfirms())
return -1;
// If mempool is limiting txs, no priority txs are allowed // If mempool is limiting txs, no priority txs are allowed
CAmount minPoolFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK(); CAmount minPoolFee = pool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK();
if (minPoolFee > 0) if (minPoolFee > 0)
return INF_PRIORITY; return INF_PRIORITY;
double median = -1; return -1;
while (median < 0 && (unsigned int)confTarget <= priStats.GetMaxConfirms()) {
median = priStats.EstimateMedianVal(confTarget++, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
}
if (answerFoundAtTarget)
*answerFoundAtTarget = confTarget - 1;
return median;
} }
void CBlockPolicyEstimator::Write(CAutoFile& fileout) void CBlockPolicyEstimator::Write(CAutoFile& fileout)
{ {
fileout << nBestSeenHeight; fileout << nBestSeenHeight;
feeStats.Write(fileout); feeStats.Write(fileout);
priStats.Write(fileout);
} }
void CBlockPolicyEstimator::Read(CAutoFile& filein) void CBlockPolicyEstimator::Read(CAutoFile& filein, int nFileVersion)
{ {
int nFileBestSeenHeight; int nFileBestSeenHeight;
filein >> nFileBestSeenHeight; filein >> nFileBestSeenHeight;
feeStats.Read(filein); feeStats.Read(filein);
priStats.Read(filein);
nBestSeenHeight = nFileBestSeenHeight; nBestSeenHeight = nFileBestSeenHeight;
if (nFileVersion < 139900) {
TxConfirmStats priStats;
priStats.Read(filein);
}
} }
FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee) FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee)

115
src/policy/fees.h

@ -19,60 +19,50 @@ class CTxMemPoolEntry;
class CTxMemPool; class CTxMemPool;
/** \class CBlockPolicyEstimator /** \class CBlockPolicyEstimator
* The BlockPolicyEstimator is used for estimating the fee or priority needed * The BlockPolicyEstimator is used for estimating the feerate needed
* for a transaction to be included in a block within a certain number of * for a transaction to be included in a block within a certain number of
* blocks. * blocks.
* *
* At a high level the algorithm works by grouping transactions into buckets * At a high level the algorithm works by grouping transactions into buckets
* based on having similar priorities or fees and then tracking how long it * based on having similar feerates and then tracking how long it
* takes transactions in the various buckets to be mined. It operates under * takes transactions in the various buckets to be mined. It operates under
* the assumption that in general transactions of higher fee/priority will be * the assumption that in general transactions of higher feerate will be
* included in blocks before transactions of lower fee/priority. So for * included in blocks before transactions of lower feerate. So for
* example if you wanted to know what fee you should put on a transaction to * example if you wanted to know what feerate you should put on a transaction to
* be included in a block within the next 5 blocks, you would start by looking * be included in a block within the next 5 blocks, you would start by looking
* at the bucket with the highest fee transactions and verifying that a * at the bucket with the highest feerate transactions and verifying that a
* sufficiently high percentage of them were confirmed within 5 blocks and * sufficiently high percentage of them were confirmed within 5 blocks and
* then you would look at the next highest fee bucket, and so on, stopping at * then you would look at the next highest feerate bucket, and so on, stopping at
* the last bucket to pass the test. The average fee of transactions in this * the last bucket to pass the test. The average feerate of transactions in this
* bucket will give you an indication of the lowest fee you can put on a * bucket will give you an indication of the lowest feerate you can put on a
* transaction and still have a sufficiently high chance of being confirmed * transaction and still have a sufficiently high chance of being confirmed
* within your desired 5 blocks. * within your desired 5 blocks.
* *
* When a transaction enters the mempool or is included within a block we * Here is a brief description of the implementation:
* decide whether it can be used as a data point for fee estimation, priority * When a transaction enters the mempool, we
* estimation or neither. If the value of exactly one of those properties was
* below the required minimum it can be used to estimate the other. In
* addition, if a priori our estimation code would indicate that the
* transaction would be much more quickly included in a block because of one
* of the properties compared to the other, we can also decide to use it as
* an estimate for that property.
*
* Here is a brief description of the implementation for fee estimation.
* When a transaction that counts for fee estimation enters the mempool, we
* track the height of the block chain at entry. Whenever a block comes in, * track the height of the block chain at entry. Whenever a block comes in,
* we count the number of transactions in each bucket and the total amount of fee * we count the number of transactions in each bucket and the total amount of feerate
* paid in each bucket. Then we calculate how many blocks Y it took each * paid in each bucket. Then we calculate how many blocks Y it took each
* transaction to be mined and we track an array of counters in each bucket * transaction to be mined and we track an array of counters in each bucket
* for how long it to took transactions to get confirmed from 1 to a max of 25 * for how long it to took transactions to get confirmed from 1 to a max of 25
* and we increment all the counters from Y up to 25. This is because for any * and we increment all the counters from Y up to 25. This is because for any
* number Z>=Y the transaction was successfully mined within Z blocks. We * number Z>=Y the transaction was successfully mined within Z blocks. We
* want to save a history of this information, so at any time we have a * want to save a history of this information, so at any time we have a
* counter of the total number of transactions that happened in a given fee * counter of the total number of transactions that happened in a given feerate
* bucket and the total number that were confirmed in each number 1-25 blocks * bucket and the total number that were confirmed in each number 1-25 blocks
* or less for any bucket. We save this history by keeping an exponentially * or less for any bucket. We save this history by keeping an exponentially
* decaying moving average of each one of these stats. Furthermore we also * decaying moving average of each one of these stats. Furthermore we also
* keep track of the number unmined (in mempool) transactions in each bucket * keep track of the number unmined (in mempool) transactions in each bucket
* and for how many blocks they have been outstanding and use that to increase * and for how many blocks they have been outstanding and use that to increase
* the number of transactions we've seen in that fee bucket when calculating * the number of transactions we've seen in that feerate bucket when calculating
* an estimate for any number of confirmations below the number of blocks * an estimate for any number of confirmations below the number of blocks
* they've been outstanding. * they've been outstanding.
*/ */
/** /**
* We will instantiate two instances of this class, one to track transactions * We will instantiate an instance of this class to track transactions that were
* that were included in a block due to fee, and one for tx's included due to * included in a block. We will lump transactions into a bucket according to their
* priority. We will lump transactions into a bucket according to their approximate * approximate feerate and then track how long it took for those txs to be included in a block
* fee or priority and then track how long it took for those txs to be included in a block
* *
* The tracking of unconfirmed (mempool) transactions is completely independent of the * The tracking of unconfirmed (mempool) transactions is completely independent of the
* historical tracking of transactions that have been confirmed in a block. * historical tracking of transactions that have been confirmed in a block.
@ -80,7 +70,7 @@ class CTxMemPool;
class TxConfirmStats class TxConfirmStats
{ {
private: private:
//Define the buckets we will group transactions into (both fee buckets and priority buckets) //Define the buckets we will group transactions into
std::vector<double> buckets; // The upper-bound of the range for the bucket (inclusive) std::vector<double> buckets; // The upper-bound of the range for the bucket (inclusive)
std::map<double, unsigned int> bucketMap; // Map of bucket upper-bound to index into all vectors by bucket std::map<double, unsigned int> bucketMap; // Map of bucket upper-bound to index into all vectors by bucket
@ -97,16 +87,15 @@ private:
// and calculate the totals for the current block to update the moving averages // and calculate the totals for the current block to update the moving averages
std::vector<std::vector<int> > curBlockConf; // curBlockConf[Y][X] std::vector<std::vector<int> > curBlockConf; // curBlockConf[Y][X]
// Sum the total priority/fee of all tx's in each bucket // Sum the total feerate of all tx's in each bucket
// Track the historical moving average of this total over blocks // Track the historical moving average of this total over blocks
std::vector<double> avg; std::vector<double> avg;
// and calculate the total for the current block to update the moving average // and calculate the total for the current block to update the moving average
std::vector<double> curBlockVal; std::vector<double> curBlockVal;
// Combine the conf counts with tx counts to calculate the confirmation % for each Y,X // Combine the conf counts with tx counts to calculate the confirmation % for each Y,X
// Combine the total value with the tx counts to calculate the avg fee/priority per bucket // Combine the total value with the tx counts to calculate the avg feerate per bucket
std::string dataTypeString;
double decay; double decay;
// Mempool counts of outstanding transactions // Mempool counts of outstanding transactions
@ -123,9 +112,8 @@ public:
* @param defaultBuckets contains the upper limits for the bucket boundaries * @param defaultBuckets contains the upper limits for the bucket boundaries
* @param maxConfirms max number of confirms to track * @param maxConfirms max number of confirms to track
* @param decay how much to decay the historical moving average per block * @param decay how much to decay the historical moving average per block
* @param dataTypeString for logging purposes
*/ */
void Initialize(std::vector<double>& defaultBuckets, unsigned int maxConfirms, double decay, std::string dataTypeString); void Initialize(std::vector<double>& defaultBuckets, unsigned int maxConfirms, double decay);
/** Clear the state of the curBlock variables to start counting for the new block */ /** Clear the state of the curBlock variables to start counting for the new block */
void ClearCurrent(unsigned int nBlockHeight); void ClearCurrent(unsigned int nBlockHeight);
@ -133,7 +121,7 @@ public:
/** /**
* Record a new transaction data point in the current block stats * Record a new transaction data point in the current block stats
* @param blocksToConfirm the number of blocks it took this transaction to confirm * @param blocksToConfirm the number of blocks it took this transaction to confirm
* @param val either the fee or the priority when entered of the transaction * @param val the feerate of the transaction
* @warning blocksToConfirm is 1-based and has to be >= 1 * @warning blocksToConfirm is 1-based and has to be >= 1
*/ */
void Record(int blocksToConfirm, double val); void Record(int blocksToConfirm, double val);
@ -150,14 +138,14 @@ public:
void UpdateMovingAverages(); void UpdateMovingAverages();
/** /**
* Calculate a fee or priority estimate. Find the lowest value bucket (or range of buckets * Calculate a feerate estimate. Find the lowest value bucket (or range of buckets
* to make sure we have enough data points) whose transactions still have sufficient likelihood * to make sure we have enough data points) whose transactions still have sufficient likelihood
* of being confirmed within the target number of confirmations * of being confirmed within the target number of confirmations
* @param confTarget target number of confirmations * @param confTarget target number of confirmations
* @param sufficientTxVal required average number of transactions per block in a bucket range * @param sufficientTxVal required average number of transactions per block in a bucket range
* @param minSuccess the success probability we require * @param minSuccess the success probability we require
* @param requireGreater return the lowest fee/pri such that all higher values pass minSuccess OR * @param requireGreater return the lowest feerate such that all higher values pass minSuccess OR
* return the highest fee/pri such that all lower values fail minSuccess * return the highest feerate such that all lower values fail minSuccess
* @param nBlockHeight the current block height * @param nBlockHeight the current block height
*/ */
double EstimateMedianVal(int confTarget, double sufficientTxVal, double EstimateMedianVal(int confTarget, double sufficientTxVal,
@ -184,35 +172,27 @@ static const unsigned int MAX_BLOCK_CONFIRMS = 25;
/** Decay of .998 is a half-life of 346 blocks or about 2.4 days */ /** Decay of .998 is a half-life of 346 blocks or about 2.4 days */
static const double DEFAULT_DECAY = .998; static const double DEFAULT_DECAY = .998;
/** Require greater than 95% of X fee transactions to be confirmed within Y blocks for X to be big enough */ /** Require greater than 95% of X feerate transactions to be confirmed within Y blocks for X to be big enough */
static const double MIN_SUCCESS_PCT = .95; static const double MIN_SUCCESS_PCT = .95;
static const double UNLIKELY_PCT = .5; static const double UNLIKELY_PCT = .5;
/** Require an avg of 1 tx in the combined fee bucket per block to have stat significance */ /** Require an avg of 1 tx in the combined feerate bucket per block to have stat significance */
static const double SUFFICIENT_FEETXS = 1; static const double SUFFICIENT_FEETXS = 1;
/** Require only an avg of 1 tx every 5 blocks in the combined pri bucket (way less pri txs) */ // Minimum and Maximum values for tracking feerates
static const double SUFFICIENT_PRITXS = .2;
// Minimum and Maximum values for tracking fees and priorities
static const double MIN_FEERATE = 10; static const double MIN_FEERATE = 10;
static const double MAX_FEERATE = 1e7; static const double MAX_FEERATE = 1e7;
static const double INF_FEERATE = MAX_MONEY; static const double INF_FEERATE = MAX_MONEY;
static const double MIN_PRIORITY = 10;
static const double MAX_PRIORITY = 1e16;
static const double INF_PRIORITY = 1e9 * MAX_MONEY; static const double INF_PRIORITY = 1e9 * MAX_MONEY;
// We have to lump transactions into buckets based on fee or priority, but we want to be able // We have to lump transactions into buckets based on feerate, but we want to be able
// to give accurate estimates over a large range of potential fees and priorities // to give accurate estimates over a large range of potential feerates
// Therefore it makes sense to exponentially space the buckets // Therefore it makes sense to exponentially space the buckets
/** Spacing of FeeRate buckets */ /** Spacing of FeeRate buckets */
static const double FEE_SPACING = 1.1; static const double FEE_SPACING = 1.1;
/** Spacing of Priority buckets */
static const double PRI_SPACING = 2;
/** /**
* We want to be able to estimate fees or priorities that are needed on tx's to be included in * We want to be able to estimate feerates that are needed on tx's to be included in
* a certain number of blocks. Every time a block is added to the best chain, this class records * a certain number of blocks. Every time a block is added to the best chain, this class records
* stats on the transactions included in that block * stats on the transactions included in that block
*/ */
@ -235,27 +215,26 @@ public:
/** Remove a transaction from the mempool tracking stats*/ /** Remove a transaction from the mempool tracking stats*/
void removeTx(uint256 hash); void removeTx(uint256 hash);
/** Is this transaction likely included in a block because of its fee?*/ /** Return a feerate estimate */
bool isFeeDataPoint(const CFeeRate &fee, double pri);
/** Is this transaction likely included in a block because of its priority?*/
bool isPriDataPoint(const CFeeRate &fee, double pri);
/** Return a fee estimate */
CFeeRate estimateFee(int confTarget); CFeeRate estimateFee(int confTarget);
/** Estimate fee rate needed to get be included in a block within /** Estimate feerate needed to get be included in a block within
* confTarget blocks. If no answer can be given at confTarget, return an * confTarget blocks. If no answer can be given at confTarget, return an
* estimate at the lowest target where one can be given. * estimate at the lowest target where one can be given.
*/ */
CFeeRate estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool); CFeeRate estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool);
/** Return a priority estimate */ /** Return a priority estimate.
* DEPRECATED
* Returns -1
*/
double estimatePriority(int confTarget); double estimatePriority(int confTarget);
/** Estimate priority needed to get be included in a block within /** Estimate priority needed to get be included in a block within
* confTarget blocks. If no answer can be given at confTarget, return an * confTarget blocks.
* estimate at the lowest target where one can be given. * DEPRECATED
* Returns -1 unless mempool is currently limited then returns INF_PRIORITY
* answerFoundAtTarget is set to confTarget
*/ */
double estimateSmartPriority(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool); double estimateSmartPriority(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool);
@ -263,29 +242,23 @@ public:
void Write(CAutoFile& fileout); void Write(CAutoFile& fileout);
/** Read estimation data from a file */ /** Read estimation data from a file */
void Read(CAutoFile& filein); void Read(CAutoFile& filein, int nFileVersion);
private: private:
CFeeRate minTrackedFee; //!< Passed to constructor to avoid dependency on main CFeeRate minTrackedFee; //!< Passed to constructor to avoid dependency on main
double minTrackedPriority; //!< Set to AllowFreeThreshold
unsigned int nBestSeenHeight; unsigned int nBestSeenHeight;
struct TxStatsInfo struct TxStatsInfo
{ {
TxConfirmStats *stats;
unsigned int blockHeight; unsigned int blockHeight;
unsigned int bucketIndex; unsigned int bucketIndex;
TxStatsInfo() : stats(NULL), blockHeight(0), bucketIndex(0) {} TxStatsInfo() : blockHeight(0), bucketIndex(0) {}
}; };
// map of txids to information about that transaction // map of txids to information about that transaction
std::map<uint256, TxStatsInfo> mapMemPoolTxs; std::map<uint256, TxStatsInfo> mapMemPoolTxs;
/** Classes to track historical data on transaction confirmations */ /** Classes to track historical data on transaction confirmations */
TxConfirmStats feeStats, priStats; TxConfirmStats feeStats;
/** Breakpoints to help determine whether a transaction was confirmed by priority or Fee */
CFeeRate feeLikely, feeUnlikely;
double priLikely, priUnlikely;
}; };
class FeeFilterRounder class FeeFilterRounder

4
src/rpc/mining.cpp

@ -810,7 +810,7 @@ UniValue estimatepriority(const JSONRPCRequest& request)
if (request.fHelp || request.params.size() != 1) if (request.fHelp || request.params.size() != 1)
throw runtime_error( throw runtime_error(
"estimatepriority nblocks\n" "estimatepriority nblocks\n"
"\nEstimates the approximate priority a zero-fee transaction needs to begin\n" "\nDEPRECATED. Estimates the approximate priority a zero-fee transaction needs to begin\n"
"confirmation within nblocks blocks.\n" "confirmation within nblocks blocks.\n"
"\nArguments:\n" "\nArguments:\n"
"1. nblocks (numeric)\n" "1. nblocks (numeric)\n"
@ -873,7 +873,7 @@ UniValue estimatesmartpriority(const JSONRPCRequest& request)
if (request.fHelp || request.params.size() != 1) if (request.fHelp || request.params.size() != 1)
throw runtime_error( throw runtime_error(
"estimatesmartpriority nblocks\n" "estimatesmartpriority nblocks\n"
"\nWARNING: This interface is unstable and may disappear or change!\n" "\nDEPRECATED. WARNING: This interface is unstable and may disappear or change!\n"
"\nEstimates the approximate priority a zero-fee transaction needs to begin\n" "\nEstimates the approximate priority a zero-fee transaction needs to begin\n"
"confirmation within nblocks blocks if possible and return the number of blocks\n" "confirmation within nblocks blocks if possible and return the number of blocks\n"
"for which the estimate is valid.\n" "for which the estimate is valid.\n"

60
src/test/policyestimator_tests.cpp

@ -19,26 +19,18 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
CTxMemPool mpool(CFeeRate(1000)); CTxMemPool mpool(CFeeRate(1000));
TestMemPoolEntryHelper entry; TestMemPoolEntryHelper entry;
CAmount basefee(2000); CAmount basefee(2000);
double basepri = 1e6;
CAmount deltaFee(100); CAmount deltaFee(100);
double deltaPri=5e5; std::vector<CAmount> feeV;
std::vector<CAmount> feeV[2];
std::vector<double> priV[2];
// Populate vectors of increasing fees or priorities // Populate vectors of increasing fees
for (int j = 0; j < 10; j++) { for (int j = 0; j < 10; j++) {
//V[0] is for fee transactions feeV.push_back(basefee * (j+1));
feeV[0].push_back(basefee * (j+1));
priV[0].push_back(0);
//V[1] is for priority transactions
feeV[1].push_back(CAmount(0));
priV[1].push_back(basepri * pow(10, j+1));
} }
// Store the hashes of transactions that have been // Store the hashes of transactions that have been
// added to the mempool by their associate fee/pri // added to the mempool by their associate fee
// txHashes[j] is populated with transactions either of // txHashes[j] is populated with transactions either of
// fee = basefee * (j+1) OR pri = 10^6 * 10^(j+1) // fee = basefee * (j+1)
std::vector<uint256> txHashes[10]; std::vector<uint256> txHashes[10];
// Create a transaction template // Create a transaction template
@ -60,19 +52,19 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
// At a decay .998 and 4 fee transactions per block // At a decay .998 and 4 fee transactions per block
// This makes the tx count about 1.33 per bucket, above the 1 threshold // This makes the tx count about 1.33 per bucket, above the 1 threshold
while (blocknum < 200) { while (blocknum < 200) {
for (int j = 0; j < 10; j++) { // For each fee/pri multiple for (int j = 0; j < 10; j++) { // For each fee
for (int k = 0; k < 5; k++) { // add 4 fee txs for every priority tx for (int k = 0; k < 4; k++) { // add 4 fee txs
tx.vin[0].prevout.n = 10000*blocknum+100*j+k; // make transaction unique tx.vin[0].prevout.n = 10000*blocknum+100*j+k; // make transaction unique
uint256 hash = tx.GetHash(); uint256 hash = tx.GetHash();
mpool.addUnchecked(hash, entry.Fee(feeV[k/4][j]).Time(GetTime()).Priority(priV[k/4][j]).Height(blocknum).FromTx(tx, &mpool)); mpool.addUnchecked(hash, entry.Fee(feeV[j]).Time(GetTime()).Priority(0).Height(blocknum).FromTx(tx, &mpool));
txHashes[j].push_back(hash); txHashes[j].push_back(hash);
} }
} }
//Create blocks where higher fee/pri txs are included more often //Create blocks where higher fee txs are included more often
for (int h = 0; h <= blocknum%10; h++) { for (int h = 0; h <= blocknum%10; h++) {
// 10/10 blocks add highest fee/pri transactions // 10/10 blocks add highest fee transactions
// 9/10 blocks add 2nd highest and so on until ... // 9/10 blocks add 2nd highest and so on until ...
// 1/10 blocks add lowest fee/pri transactions // 1/10 blocks add lowest fee transactions
while (txHashes[9-h].size()) { while (txHashes[9-h].size()) {
std::shared_ptr<const CTransaction> ptx = mpool.get(txHashes[9-h].back()); std::shared_ptr<const CTransaction> ptx = mpool.get(txHashes[9-h].back());
if (ptx) if (ptx)
@ -100,7 +92,6 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
} }
std::vector<CAmount> origFeeEst; std::vector<CAmount> origFeeEst;
std::vector<double> origPriEst;
// Highest feerate is 10*baseRate and gets in all blocks, // Highest feerate is 10*baseRate and gets in all blocks,
// second highest feerate is 9*baseRate and gets in 9/10 blocks = 90%, // second highest feerate is 9*baseRate and gets in 9/10 blocks = 90%,
// third highest feerate is 8*base rate, and gets in 8/10 blocks = 80%, // third highest feerate is 8*base rate, and gets in 8/10 blocks = 80%,
@ -109,16 +100,12 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
// so estimateFee(2) should return 9*baseRate etc... // so estimateFee(2) should return 9*baseRate etc...
for (int i = 1; i < 10;i++) { for (int i = 1; i < 10;i++) {
origFeeEst.push_back(mpool.estimateFee(i).GetFeePerK()); origFeeEst.push_back(mpool.estimateFee(i).GetFeePerK());
origPriEst.push_back(mpool.estimatePriority(i));
if (i > 1) { // Fee estimates should be monotonically decreasing if (i > 1) { // Fee estimates should be monotonically decreasing
BOOST_CHECK(origFeeEst[i-1] <= origFeeEst[i-2]); BOOST_CHECK(origFeeEst[i-1] <= origFeeEst[i-2]);
BOOST_CHECK(origPriEst[i-1] <= origPriEst[i-2]);
} }
int mult = 11-i; int mult = 11-i;
BOOST_CHECK(origFeeEst[i-1] < mult*baseRate.GetFeePerK() + deltaFee); BOOST_CHECK(origFeeEst[i-1] < mult*baseRate.GetFeePerK() + deltaFee);
BOOST_CHECK(origFeeEst[i-1] > mult*baseRate.GetFeePerK() - deltaFee); BOOST_CHECK(origFeeEst[i-1] > mult*baseRate.GetFeePerK() - deltaFee);
BOOST_CHECK(origPriEst[i-1] < pow(10,mult) * basepri + deltaPri);
BOOST_CHECK(origPriEst[i-1] > pow(10,mult) * basepri - deltaPri);
} }
// Mine 50 more blocks with no transactions happening, estimates shouldn't change // Mine 50 more blocks with no transactions happening, estimates shouldn't change
@ -129,19 +116,17 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
for (int i = 1; i < 10;i++) { for (int i = 1; i < 10;i++) {
BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() < origFeeEst[i-1] + deltaFee); BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() < origFeeEst[i-1] + deltaFee);
BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee); BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
BOOST_CHECK(mpool.estimatePriority(i) < origPriEst[i-1] + deltaPri);
BOOST_CHECK(mpool.estimatePriority(i) > origPriEst[i-1] - deltaPri);
} }
// Mine 15 more blocks with lots of transactions happening and not getting mined // Mine 15 more blocks with lots of transactions happening and not getting mined
// Estimates should go up // Estimates should go up
while (blocknum < 265) { while (blocknum < 265) {
for (int j = 0; j < 10; j++) { // For each fee/pri multiple for (int j = 0; j < 10; j++) { // For each fee multiple
for (int k = 0; k < 5; k++) { // add 4 fee txs for every priority tx for (int k = 0; k < 4; k++) { // add 4 fee txs
tx.vin[0].prevout.n = 10000*blocknum+100*j+k; tx.vin[0].prevout.n = 10000*blocknum+100*j+k;
uint256 hash = tx.GetHash(); uint256 hash = tx.GetHash();
mpool.addUnchecked(hash, entry.Fee(feeV[k/4][j]).Time(GetTime()).Priority(priV[k/4][j]).Height(blocknum).FromTx(tx, &mpool)); mpool.addUnchecked(hash, entry.Fee(feeV[j]).Time(GetTime()).Priority(0).Height(blocknum).FromTx(tx, &mpool));
txHashes[j].push_back(hash); txHashes[j].push_back(hash);
} }
} }
@ -152,8 +137,6 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
for (int i = 1; i < 10;i++) { for (int i = 1; i < 10;i++) {
BOOST_CHECK(mpool.estimateFee(i) == CFeeRate(0) || mpool.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee); BOOST_CHECK(mpool.estimateFee(i) == CFeeRate(0) || mpool.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
BOOST_CHECK(mpool.estimateSmartFee(i, &answerFound).GetFeePerK() > origFeeEst[answerFound-1] - deltaFee); BOOST_CHECK(mpool.estimateSmartFee(i, &answerFound).GetFeePerK() > origFeeEst[answerFound-1] - deltaFee);
BOOST_CHECK(mpool.estimatePriority(i) == -1 || mpool.estimatePriority(i) > origPriEst[i-1] - deltaPri);
BOOST_CHECK(mpool.estimateSmartPriority(i, &answerFound) > origPriEst[answerFound-1] - deltaPri);
} }
// Mine all those transactions // Mine all those transactions
@ -170,20 +153,20 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
block.clear(); block.clear();
for (int i = 1; i < 10;i++) { for (int i = 1; i < 10;i++) {
BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee); BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
BOOST_CHECK(mpool.estimatePriority(i) > origPriEst[i-1] - deltaPri);
} }
// Mine 200 more blocks where everything is mined every block // Mine 200 more blocks where everything is mined every block
// Estimates should be below original estimates // Estimates should be below original estimates
while (blocknum < 465) { while (blocknum < 465) {
for (int j = 0; j < 10; j++) { // For each fee/pri multiple for (int j = 0; j < 10; j++) { // For each fee multiple
for (int k = 0; k < 5; k++) { // add 4 fee txs for every priority tx for (int k = 0; k < 4; k++) { // add 4 fee txs
tx.vin[0].prevout.n = 10000*blocknum+100*j+k; tx.vin[0].prevout.n = 10000*blocknum+100*j+k;
uint256 hash = tx.GetHash(); uint256 hash = tx.GetHash();
mpool.addUnchecked(hash, entry.Fee(feeV[k/4][j]).Time(GetTime()).Priority(priV[k/4][j]).Height(blocknum).FromTx(tx, &mpool)); mpool.addUnchecked(hash, entry.Fee(feeV[j]).Time(GetTime()).Priority(0).Height(blocknum).FromTx(tx, &mpool));
std::shared_ptr<const CTransaction> ptx = mpool.get(hash); std::shared_ptr<const CTransaction> ptx = mpool.get(hash);
if (ptx) if (ptx)
block.push_back(*ptx); block.push_back(*ptx);
} }
} }
mpool.removeForBlock(block, ++blocknum); mpool.removeForBlock(block, ++blocknum);
@ -191,15 +174,14 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
} }
for (int i = 1; i < 10; i++) { for (int i = 1; i < 10; i++) {
BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee); BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee);
BOOST_CHECK(mpool.estimatePriority(i) < origPriEst[i-1] - deltaPri);
} }
// Test that if the mempool is limited, estimateSmartFee won't return a value below the mempool min fee // Test that if the mempool is limited, estimateSmartFee won't return a value below the mempool min fee
// and that estimateSmartPriority returns essentially an infinite value // and that estimateSmartPriority returns essentially an infinite value
mpool.addUnchecked(tx.GetHash(), entry.Fee(feeV[0][5]).Time(GetTime()).Priority(priV[1][5]).Height(blocknum).FromTx(tx, &mpool)); mpool.addUnchecked(tx.GetHash(), entry.Fee(feeV[5]).Time(GetTime()).Priority(0).Height(blocknum).FromTx(tx, &mpool));
// evict that transaction which should set a mempool min fee of minRelayTxFee + feeV[0][5] // evict that transaction which should set a mempool min fee of minRelayTxFee + feeV[5]
mpool.TrimToSize(1); mpool.TrimToSize(1);
BOOST_CHECK(mpool.GetMinFee(1).GetFeePerK() > feeV[0][5]); BOOST_CHECK(mpool.GetMinFee(1).GetFeePerK() > feeV[5]);
for (int i = 1; i < 10; i++) { for (int i = 1; i < 10; i++) {
BOOST_CHECK(mpool.estimateSmartFee(i).GetFeePerK() >= mpool.estimateFee(i).GetFeePerK()); BOOST_CHECK(mpool.estimateSmartFee(i).GetFeePerK() >= mpool.estimateFee(i).GetFeePerK());
BOOST_CHECK(mpool.estimateSmartFee(i).GetFeePerK() >= mpool.GetMinFee(1).GetFeePerK()); BOOST_CHECK(mpool.estimateSmartFee(i).GetFeePerK() >= mpool.GetMinFee(1).GetFeePerK());

5
src/txmempool.cpp

@ -895,7 +895,7 @@ CTxMemPool::WriteFeeEstimates(CAutoFile& fileout) const
{ {
try { try {
LOCK(cs); LOCK(cs);
fileout << 109900; // version required to read: 0.10.99 or later fileout << 139900; // version required to read: 0.13.99 or later
fileout << CLIENT_VERSION; // version that wrote the file fileout << CLIENT_VERSION; // version that wrote the file
minerPolicyEstimator->Write(fileout); minerPolicyEstimator->Write(fileout);
} }
@ -914,9 +914,8 @@ CTxMemPool::ReadFeeEstimates(CAutoFile& filein)
filein >> nVersionRequired >> nVersionThatWrote; filein >> nVersionRequired >> nVersionThatWrote;
if (nVersionRequired > CLIENT_VERSION) if (nVersionRequired > CLIENT_VERSION)
return error("CTxMemPool::ReadFeeEstimates(): up-version (%d) fee estimate file", nVersionRequired); return error("CTxMemPool::ReadFeeEstimates(): up-version (%d) fee estimate file", nVersionRequired);
LOCK(cs); LOCK(cs);
minerPolicyEstimator->Read(filein); minerPolicyEstimator->Read(filein, nVersionThatWrote);
} }
catch (const std::exception&) { catch (const std::exception&) {
LogPrintf("CTxMemPool::ReadFeeEstimates(): unable to read policy estimator data (non-fatal)\n"); LogPrintf("CTxMemPool::ReadFeeEstimates(): unable to read policy estimator data (non-fatal)\n");

Loading…
Cancel
Save