Wladimir J. van der Laan
10 years ago
10 changed files with 1267 additions and 424 deletions
@ -0,0 +1,529 @@
@@ -0,0 +1,529 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2015 The Bitcoin developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "policy/fees.h" |
||||
|
||||
#include "amount.h" |
||||
#include "primitives/transaction.h" |
||||
#include "streams.h" |
||||
#include "txmempool.h" |
||||
#include "util.h" |
||||
|
||||
void TxConfirmStats::Initialize(std::vector<double>& defaultBuckets, |
||||
unsigned int maxConfirms, double _decay, std::string _dataTypeString) |
||||
{ |
||||
decay = _decay; |
||||
dataTypeString = _dataTypeString; |
||||
for (unsigned int i = 0; i < defaultBuckets.size(); i++) { |
||||
buckets.push_back(defaultBuckets[i]); |
||||
bucketMap[defaultBuckets[i]] = i; |
||||
} |
||||
confAvg.resize(maxConfirms); |
||||
curBlockConf.resize(maxConfirms); |
||||
unconfTxs.resize(maxConfirms); |
||||
for (unsigned int i = 0; i < maxConfirms; i++) { |
||||
confAvg[i].resize(buckets.size()); |
||||
curBlockConf[i].resize(buckets.size()); |
||||
unconfTxs[i].resize(buckets.size()); |
||||
} |
||||
|
||||
oldUnconfTxs.resize(buckets.size()); |
||||
curBlockTxCt.resize(buckets.size()); |
||||
txCtAvg.resize(buckets.size()); |
||||
curBlockVal.resize(buckets.size()); |
||||
avg.resize(buckets.size()); |
||||
} |
||||
|
||||
// Zero out the data for the current block
|
||||
void TxConfirmStats::ClearCurrent(unsigned int nBlockHeight) |
||||
{ |
||||
for (unsigned int j = 0; j < buckets.size(); j++) { |
||||
oldUnconfTxs[j] += unconfTxs[nBlockHeight%unconfTxs.size()][j]; |
||||
unconfTxs[nBlockHeight%unconfTxs.size()][j] = 0; |
||||
for (unsigned int i = 0; i < curBlockConf.size(); i++) |
||||
curBlockConf[i][j] = 0; |
||||
curBlockTxCt[j] = 0; |
||||
curBlockVal[j] = 0; |
||||
} |
||||
} |
||||
|
||||
|
||||
void TxConfirmStats::Record(int blocksToConfirm, double val) |
||||
{ |
||||
// blocksToConfirm is 1-based
|
||||
if (blocksToConfirm < 1) |
||||
return; |
||||
unsigned int bucketindex = bucketMap.lower_bound(val)->second; |
||||
for (size_t i = blocksToConfirm; i <= curBlockConf.size(); i++) { |
||||
curBlockConf[i - 1][bucketindex]++; |
||||
} |
||||
curBlockTxCt[bucketindex]++; |
||||
curBlockVal[bucketindex] += val; |
||||
} |
||||
|
||||
void TxConfirmStats::UpdateMovingAverages() |
||||
{ |
||||
for (unsigned int j = 0; j < buckets.size(); j++) { |
||||
for (unsigned int i = 0; i < confAvg.size(); i++) |
||||
confAvg[i][j] = confAvg[i][j] * decay + curBlockConf[i][j]; |
||||
avg[j] = avg[j] * decay + curBlockVal[j]; |
||||
txCtAvg[j] = txCtAvg[j] * decay + curBlockTxCt[j]; |
||||
} |
||||
} |
||||
|
||||
// returns -1 on error conditions
|
||||
double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal, |
||||
double successBreakPoint, bool requireGreater, |
||||
unsigned int nBlockHeight) |
||||
{ |
||||
// Counters for a bucket (or range of buckets)
|
||||
double nConf = 0; // Number of tx's confirmed within the confTarget
|
||||
double totalNum = 0; // Total number of tx's that were ever confirmed
|
||||
int extraNum = 0; // Number of tx's still in mempool for confTarget or longer
|
||||
|
||||
int maxbucketindex = buckets.size() - 1; |
||||
|
||||
// requireGreater means we are looking for the lowest fee/priority such that all higher
|
||||
// values pass, so we start at maxbucketindex (highest fee) and look at succesively
|
||||
// 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.
|
||||
unsigned int startbucket = requireGreater ? maxbucketindex : 0; |
||||
int step = requireGreater ? -1 : 1; |
||||
|
||||
// We'll combine buckets until we have enough samples.
|
||||
// The near and far variables will define the range we've combined
|
||||
// The best variables are the last range we saw which still had a high
|
||||
// enough confirmation rate to count as success.
|
||||
// The cur variables are the current range we're counting.
|
||||
unsigned int curNearBucket = startbucket; |
||||
unsigned int bestNearBucket = startbucket; |
||||
unsigned int curFarBucket = startbucket; |
||||
unsigned int bestFarBucket = startbucket; |
||||
|
||||
bool foundAnswer = false; |
||||
unsigned int bins = unconfTxs.size(); |
||||
|
||||
// Start counting from highest(default) or lowest fee/pri transactions
|
||||
for (int bucket = startbucket; bucket >= 0 && bucket <= maxbucketindex; bucket += step) { |
||||
curFarBucket = bucket; |
||||
nConf += confAvg[confTarget - 1][bucket]; |
||||
totalNum += txCtAvg[bucket]; |
||||
for (unsigned int confct = confTarget; confct < GetMaxConfirms(); confct++) |
||||
extraNum += unconfTxs[(nBlockHeight - confct)%bins][bucket]; |
||||
extraNum += oldUnconfTxs[bucket]; |
||||
// If we have enough transaction data points in this range of buckets,
|
||||
// we can test for success
|
||||
// (Only count the confirmed data points, so that each confirmation count
|
||||
// will be looking at the same amount of data and same bucket breaks)
|
||||
if (totalNum >= sufficientTxVal / (1 - decay)) { |
||||
double curPct = nConf / (totalNum + extraNum); |
||||
|
||||
// Check to see if we are no longer getting confirmed at the success rate
|
||||
if (requireGreater && curPct < successBreakPoint) |
||||
break; |
||||
if (!requireGreater && curPct > successBreakPoint) |
||||
break; |
||||
|
||||
// Otherwise update the cumulative stats, and the bucket variables
|
||||
// and reset the counters
|
||||
else { |
||||
foundAnswer = true; |
||||
nConf = 0; |
||||
totalNum = 0; |
||||
extraNum = 0; |
||||
bestNearBucket = curNearBucket; |
||||
bestFarBucket = curFarBucket; |
||||
curNearBucket = bucket + step; |
||||
} |
||||
} |
||||
} |
||||
|
||||
double median = -1; |
||||
double txSum = 0; |
||||
|
||||
// Calculate the "average" fee 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
|
||||
// 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
|
||||
unsigned int minBucket = bestNearBucket < bestFarBucket ? bestNearBucket : bestFarBucket; |
||||
unsigned int maxBucket = bestNearBucket > bestFarBucket ? bestNearBucket : bestFarBucket; |
||||
for (unsigned int j = minBucket; j <= maxBucket; j++) { |
||||
txSum += txCtAvg[j]; |
||||
} |
||||
if (foundAnswer && txSum != 0) { |
||||
txSum = txSum / 2; |
||||
for (unsigned int j = minBucket; j <= maxBucket; j++) { |
||||
if (txCtAvg[j] < txSum) |
||||
txSum -= txCtAvg[j]; |
||||
else { // we're in the right bucket
|
||||
median = avg[j] / txCtAvg[j]; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
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", |
||||
confTarget, requireGreater ? ">" : "<", successBreakPoint, dataTypeString, |
||||
requireGreater ? ">" : "<", median, buckets[minBucket], buckets[maxBucket], |
||||
100 * nConf / (totalNum + extraNum), nConf, totalNum, extraNum); |
||||
|
||||
return median; |
||||
} |
||||
|
||||
void TxConfirmStats::Write(CAutoFile& fileout) |
||||
{ |
||||
fileout << decay; |
||||
fileout << buckets; |
||||
fileout << avg; |
||||
fileout << txCtAvg; |
||||
fileout << confAvg; |
||||
} |
||||
|
||||
void TxConfirmStats::Read(CAutoFile& filein) |
||||
{ |
||||
// Read data file into temporary variables and do some very basic sanity checking
|
||||
std::vector<double> fileBuckets; |
||||
std::vector<double> fileAvg; |
||||
std::vector<std::vector<double> > fileConfAvg; |
||||
std::vector<double> fileTxCtAvg; |
||||
double fileDecay; |
||||
size_t maxConfirms; |
||||
size_t numBuckets; |
||||
|
||||
filein >> fileDecay; |
||||
if (fileDecay <= 0 || fileDecay >= 1) |
||||
throw std::runtime_error("Corrupt estimates file. Decay must be between 0 and 1 (non-inclusive)"); |
||||
filein >> fileBuckets; |
||||
numBuckets = fileBuckets.size(); |
||||
if (numBuckets <= 1 || numBuckets > 1000) |
||||
throw std::runtime_error("Corrupt estimates file. Must have between 2 and 1000 fee/pri buckets"); |
||||
filein >> fileAvg; |
||||
if (fileAvg.size() != numBuckets) |
||||
throw std::runtime_error("Corrupt estimates file. Mismatch in fee/pri average bucket count"); |
||||
filein >> fileTxCtAvg; |
||||
if (fileTxCtAvg.size() != numBuckets) |
||||
throw std::runtime_error("Corrupt estimates file. Mismatch in tx count bucket count"); |
||||
filein >> fileConfAvg; |
||||
maxConfirms = fileConfAvg.size(); |
||||
if (maxConfirms <= 0 || maxConfirms > 6 * 24 * 7) // one week
|
||||
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++) { |
||||
if (fileConfAvg[i].size() != numBuckets) |
||||
throw std::runtime_error("Corrupt estimates file. Mismatch in fee/pri conf average bucket count"); |
||||
} |
||||
// Now that we've processed the entire fee estimate data file and not
|
||||
// thrown any errors, we can copy it to our data structures
|
||||
decay = fileDecay; |
||||
buckets = fileBuckets; |
||||
avg = fileAvg; |
||||
confAvg = fileConfAvg; |
||||
txCtAvg = fileTxCtAvg; |
||||
bucketMap.clear(); |
||||
|
||||
// Resize the current block variables which aren't stored in the data file
|
||||
// to match the number of confirms and buckets
|
||||
curBlockConf.resize(maxConfirms); |
||||
for (unsigned int i = 0; i < maxConfirms; i++) { |
||||
curBlockConf[i].resize(buckets.size()); |
||||
} |
||||
curBlockTxCt.resize(buckets.size()); |
||||
curBlockVal.resize(buckets.size()); |
||||
|
||||
unconfTxs.resize(maxConfirms); |
||||
for (unsigned int i = 0; i < maxConfirms; i++) { |
||||
unconfTxs[i].resize(buckets.size()); |
||||
} |
||||
oldUnconfTxs.resize(buckets.size()); |
||||
|
||||
for (unsigned int i = 0; i < buckets.size(); i++) |
||||
bucketMap[buckets[i]] = i; |
||||
|
||||
LogPrint("estimatefee", "Reading estimates: %u %s buckets counting confirms up to %u blocks\n", |
||||
numBuckets, dataTypeString, maxConfirms); |
||||
} |
||||
|
||||
unsigned int TxConfirmStats::NewTx(unsigned int nBlockHeight, double val) |
||||
{ |
||||
unsigned int bucketindex = bucketMap.lower_bound(val)->second; |
||||
unsigned int blockIndex = nBlockHeight % unconfTxs.size(); |
||||
unconfTxs[blockIndex][bucketindex]++; |
||||
LogPrint("estimatefee", "adding to %s\n", dataTypeString); |
||||
return bucketindex; |
||||
} |
||||
|
||||
void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHeight, unsigned int bucketindex) |
||||
{ |
||||
//nBestSeenHeight is not updated yet for the new block
|
||||
int blocksAgo = nBestSeenHeight - entryHeight; |
||||
if (nBestSeenHeight == 0) // the BlockPolicyEstimator hasn't seen any blocks yet
|
||||
blocksAgo = 0; |
||||
if (blocksAgo < 0) { |
||||
LogPrint("estimatefee", "Blockpolicy error, blocks ago is negative for mempool tx\n"); |
||||
return; //This can't happen becasue we call this with our best seen height, no entries can have higher
|
||||
} |
||||
|
||||
if (blocksAgo >= (int)unconfTxs.size()) { |
||||
if (oldUnconfTxs[bucketindex] > 0) |
||||
oldUnconfTxs[bucketindex]--; |
||||
else |
||||
LogPrint("estimatefee", "Blockpolicy error, mempool tx removed from >25 blocks,bucketIndex=%u already\n", |
||||
bucketindex); |
||||
} |
||||
else { |
||||
unsigned int blockIndex = entryHeight % unconfTxs.size(); |
||||
if (unconfTxs[blockIndex][bucketindex] > 0) |
||||
unconfTxs[blockIndex][bucketindex]--; |
||||
else |
||||
LogPrint("estimatefee", "Blockpolicy error, mempool tx removed from blockIndex=%u,bucketIndex=%u already\n", |
||||
blockIndex, bucketindex); |
||||
} |
||||
} |
||||
|
||||
void CBlockPolicyEstimator::removeTx(uint256 hash) |
||||
{ |
||||
std::map<uint256, TxStatsInfo>::iterator pos = mapMemPoolTxs.find(hash); |
||||
if (pos == mapMemPoolTxs.end()) { |
||||
LogPrint("estimatefee", "Blockpolicy error mempool tx %s not found for removeTx\n", |
||||
hash.ToString().c_str()); |
||||
return; |
||||
} |
||||
TxConfirmStats *stats = pos->second.stats; |
||||
unsigned int entryHeight = pos->second.blockHeight; |
||||
unsigned int bucketIndex = pos->second.bucketIndex; |
||||
|
||||
if (stats != NULL) |
||||
stats->removeTx(entryHeight, nBestSeenHeight, bucketIndex); |
||||
mapMemPoolTxs.erase(hash); |
||||
} |
||||
|
||||
CBlockPolicyEstimator::CBlockPolicyEstimator(const CFeeRate& _minRelayFee) |
||||
: nBestSeenHeight(0) |
||||
{ |
||||
minTrackedFee = _minRelayFee < CFeeRate(MIN_FEERATE) ? CFeeRate(MIN_FEERATE) : _minRelayFee; |
||||
std::vector<double> vfeelist; |
||||
for (double bucketBoundary = minTrackedFee.GetFeePerK(); bucketBoundary <= MAX_FEERATE; bucketBoundary *= FEE_SPACING) { |
||||
vfeelist.push_back(bucketBoundary); |
||||
} |
||||
vfeelist.push_back(INF_FEERATE); |
||||
feeStats.Initialize(vfeelist, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY, "FeeRate"); |
||||
|
||||
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) |
||||
{ |
||||
unsigned int txHeight = entry.GetHeight(); |
||||
uint256 hash = entry.GetTx().GetHash(); |
||||
if (mapMemPoolTxs[hash].stats != NULL) { |
||||
LogPrint("estimatefee", "Blockpolicy error mempool tx %s already being tracked\n", |
||||
hash.ToString().c_str()); |
||||
return; |
||||
} |
||||
|
||||
if (txHeight < nBestSeenHeight) { |
||||
// Ignore side chains and re-orgs; assuming they are random they don't
|
||||
// affect the estimate. We'll potentially double count transactions in 1-block reorgs.
|
||||
return; |
||||
} |
||||
|
||||
// Only want to be updating estimates when our blockchain is synced,
|
||||
// otherwise we'll miscalculate how many blocks its taking to get included.
|
||||
if (!fCurrentEstimate) |
||||
return; |
||||
|
||||
if (!entry.WasClearAtEntry()) { |
||||
// This transaction depends on other transactions in the mempool to
|
||||
// be included in a block before it will be able to be included, so
|
||||
// we shouldn't include it in our calculations
|
||||
return; |
||||
} |
||||
|
||||
// Fees are stored and reported as BTC-per-kb:
|
||||
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; |
||||
|
||||
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\n"); |
||||
} |
||||
} |
||||
|
||||
void CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry& entry) |
||||
{ |
||||
if (!entry.WasClearAtEntry()) { |
||||
// This transaction depended on other transactions in the mempool to
|
||||
// be included in a block before it was able to be included, so
|
||||
// we shouldn't include it in our calculations
|
||||
return; |
||||
} |
||||
|
||||
// How many blocks did it take for miners to include this transaction?
|
||||
// blocksToConfirm is 1-based, so a transaction included in the earliest
|
||||
// possible block has confirmation count of 1
|
||||
int blocksToConfirm = nBlockHeight - entry.GetHeight(); |
||||
if (blocksToConfirm <= 0) { |
||||
// This can't happen because we don't process transactions from a block with a height
|
||||
// lower than our greatest seen height
|
||||
LogPrint("estimatefee", "Blockpolicy error Transaction had negative blocksToConfirm\n"); |
||||
return; |
||||
} |
||||
|
||||
// Fees are stored and reported as BTC-per-kb:
|
||||
CFeeRate feeRate(entry.GetFee(), entry.GetTxSize()); |
||||
|
||||
// Want the priority of the tx at confirmation. The priority when it
|
||||
// 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, |
||||
std::vector<CTxMemPoolEntry>& entries, bool fCurrentEstimate) |
||||
{ |
||||
if (nBlockHeight <= nBestSeenHeight) { |
||||
// Ignore side chains and re-orgs; assuming they are random
|
||||
// they don't affect the estimate.
|
||||
// And if an attacker can re-org the chain at will, then
|
||||
// you've got much bigger problems than "attacker can influence
|
||||
// transaction fees."
|
||||
return; |
||||
} |
||||
nBestSeenHeight = nBlockHeight; |
||||
|
||||
// Only want to be updating estimates when our blockchain is synced,
|
||||
// otherwise we'll miscalculate how many blocks its taking to get included.
|
||||
if (!fCurrentEstimate) |
||||
return; |
||||
|
||||
// Update the dynamic cutoffs
|
||||
// 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); |
||||
priStats.ClearCurrent(nBlockHeight); |
||||
|
||||
// Repopulate the current block states
|
||||
for (unsigned int i = 0; i < entries.size(); i++) |
||||
processBlockTx(nBlockHeight, entries[i]); |
||||
|
||||
// Update all exponential averages with the current block states
|
||||
feeStats.UpdateMovingAverages(); |
||||
priStats.UpdateMovingAverages(); |
||||
|
||||
LogPrint("estimatefee", "Blockpolicy after updating estimates for %u confirmed entries, new mempool map size %u\n", |
||||
entries.size(), mapMemPoolTxs.size()); |
||||
} |
||||
|
||||
CFeeRate CBlockPolicyEstimator::estimateFee(int confTarget) |
||||
{ |
||||
// Return failure if trying to analyze a target we're not tracking
|
||||
if (confTarget <= 0 || (unsigned int)confTarget > feeStats.GetMaxConfirms()) |
||||
return CFeeRate(0); |
||||
|
||||
double median = feeStats.EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight); |
||||
|
||||
if (median < 0) |
||||
return CFeeRate(0); |
||||
|
||||
return CFeeRate(median); |
||||
} |
||||
|
||||
double CBlockPolicyEstimator::estimatePriority(int confTarget) |
||||
{ |
||||
// Return failure if trying to analyze a target we're not tracking
|
||||
if (confTarget <= 0 || (unsigned int)confTarget > priStats.GetMaxConfirms()) |
||||
return -1; |
||||
|
||||
return priStats.EstimateMedianVal(confTarget, SUFFICIENT_PRITXS, MIN_SUCCESS_PCT, true, nBestSeenHeight); |
||||
} |
||||
|
||||
void CBlockPolicyEstimator::Write(CAutoFile& fileout) |
||||
{ |
||||
fileout << nBestSeenHeight; |
||||
feeStats.Write(fileout); |
||||
priStats.Write(fileout); |
||||
} |
||||
|
||||
void CBlockPolicyEstimator::Read(CAutoFile& filein) |
||||
{ |
||||
int nFileBestSeenHeight; |
||||
filein >> nFileBestSeenHeight; |
||||
feeStats.Read(filein); |
||||
priStats.Read(filein); |
||||
nBestSeenHeight = nFileBestSeenHeight; |
||||
} |
@ -0,0 +1,276 @@
@@ -0,0 +1,276 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2015 The Bitcoin developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#ifndef BITCOIN_POLICYESTIMATOR_H |
||||
#define BITCOIN_POLICYESTIMATOR_H |
||||
|
||||
#include "amount.h" |
||||
#include "uint256.h" |
||||
|
||||
#include <map> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
class CAutoFile; |
||||
class CFeeRate; |
||||
class CTxMemPoolEntry; |
||||
|
||||
/** \class CBlockPolicyEstimator
|
||||
* The BlockPolicyEstimator is used for estimating the fee or priority needed |
||||
* for a transaction to be included in a block within a certain number of |
||||
* blocks. |
||||
* |
||||
* 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 |
||||
* takes transactions in the various buckets to be mined. It operates under |
||||
* the assumption that in general transactions of higher fee/priority will be |
||||
* included in blocks before transactions of lower fee/priority. So for |
||||
* example if you wanted to know what fee you should put on a transaction to |
||||
* be included in a block within the next 5 blocks, you would start by looking |
||||
* at the bucket with with the highest fee transactions and verifying that a |
||||
* 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 |
||||
* the last bucket to pass the test. The average fee of transactions in this |
||||
* bucket will give you an indication of the lowest fee you can put on a |
||||
* transaction and still have a sufficiently high chance of being confirmed |
||||
* within your desired 5 blocks. |
||||
* |
||||
* When a transaction enters the mempool or is included within a block we |
||||
* decide whether it can be used as a data point for fee estimation, priority |
||||
* 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, |
||||
* we count the number of transactions in each bucket and the total amount of fee |
||||
* 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 |
||||
* 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 |
||||
* 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 |
||||
* counter of the total number of transactions that happened in a given fee |
||||
* 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 |
||||
* decaying moving average of each one of these stats. Furthermore we also |
||||
* 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 |
||||
* the number of transactions we've seen in that fee bucket when calculating |
||||
* an estimate for any number of confirmations below the number of blocks |
||||
* they've been outstanding. |
||||
*/ |
||||
|
||||
/**
|
||||
* We will instantiate two instances of this class, one to track transactions |
||||
* that were included in a block due to fee, and one for tx's included due to |
||||
* priority. We will lump transactions into a bucket according to their approximate |
||||
* 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 |
||||
* historical tracking of transactions that have been confirmed in a block. |
||||
*/ |
||||
class TxConfirmStats |
||||
{ |
||||
private: |
||||
//Define the buckets we will group transactions into (both fee buckets and priority buckets)
|
||||
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
|
||||
|
||||
// For each bucket X:
|
||||
// Count the total # of txs in each bucket
|
||||
// Track the historical moving average of this total over blocks
|
||||
std::vector<double> txCtAvg; |
||||
// and calcuate the total for the current block to update the moving average
|
||||
std::vector<int> curBlockTxCt; |
||||
|
||||
// Count the total # of txs confirmed within Y blocks in each bucket
|
||||
// Track the historical moving average of theses totals over blocks
|
||||
std::vector<std::vector<double> > confAvg; // confAvg[Y][X]
|
||||
// and calcuate the totals for the current block to update the moving averages
|
||||
std::vector<std::vector<int> > curBlockConf; // curBlockConf[Y][X]
|
||||
|
||||
// Sum the total priority/fee of all tx's in each bucket
|
||||
// Track the historical moving average of this total over blocks
|
||||
std::vector<double> avg; |
||||
// and calculate the total for the current block to update the moving average
|
||||
std::vector<double> curBlockVal; |
||||
|
||||
// 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
|
||||
|
||||
std::string dataTypeString; |
||||
double decay; |
||||
|
||||
// Mempool counts of outstanding transactions
|
||||
// For each bucket X, track the number of transactions in the mempool
|
||||
// that are unconfirmed for each possible confirmation value Y
|
||||
std::vector<std::vector<int> > unconfTxs; //unconfTxs[Y][X]
|
||||
// transactions still unconfirmed after MAX_CONFIRMS for each bucket
|
||||
std::vector<int> oldUnconfTxs; |
||||
|
||||
public: |
||||
/**
|
||||
* Initialize the data structures. This is called by BlockPolicyEstimator's |
||||
* constructor with default values. |
||||
* @param defaultBuckets contains the upper limits for the bucket boundries |
||||
* @param maxConfirms max number of confirms to track |
||||
* @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); |
||||
|
||||
/** Clear the state of the curBlock variables to start counting for the new block */ |
||||
void ClearCurrent(unsigned int nBlockHeight); |
||||
|
||||
/**
|
||||
* Record a new transaction data point in the current block stats |
||||
* @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 |
||||
* @warning blocksToConfirm is 1-based and has to be >= 1 |
||||
*/ |
||||
void Record(int blocksToConfirm, double val); |
||||
|
||||
/** Record a new transaction entering the mempool*/ |
||||
unsigned int NewTx(unsigned int nBlockHeight, double val); |
||||
|
||||
/** Remove a transaction from mempool tracking stats*/ |
||||
void removeTx(unsigned int entryHeight, unsigned int nBestSeenHeight, |
||||
unsigned int bucketIndex); |
||||
|
||||
/** Update our estimates by decaying our historical moving average and updating
|
||||
with the data gathered from the current block */ |
||||
void UpdateMovingAverages(); |
||||
|
||||
/**
|
||||
* Calculate a fee or priority estimate. Find the lowest value bucket (or range of buckets |
||||
* to make sure we have enough data points) whose transactions still have sufficient likelihood |
||||
* of being confirmed within the target number of confirmations |
||||
* @param confTarget target number of confirmations |
||||
* @param sufficientTxVal required average number of transactions per block in a bucket range |
||||
* @param minSuccess the success probability we require |
||||
* @param requireGreater return the lowest fee/pri such that all higher values pass minSuccess OR |
||||
* return the highest fee/pri such that all lower values fail minSuccess |
||||
* @param nBlockHeight the current block height |
||||
*/ |
||||
double EstimateMedianVal(int confTarget, double sufficientTxVal, |
||||
double minSuccess, bool requireGreater, unsigned int nBlockHeight); |
||||
|
||||
/** Return the max number of confirms we're tracking */ |
||||
unsigned int GetMaxConfirms() { return confAvg.size(); } |
||||
|
||||
/** Write state of estimation data to a file*/ |
||||
void Write(CAutoFile& fileout); |
||||
|
||||
/**
|
||||
* Read saved state of estimation data from a file and replace all internal data structures and |
||||
* variables with this state. |
||||
*/ |
||||
void Read(CAutoFile& filein); |
||||
}; |
||||
|
||||
|
||||
|
||||
/** Track confirm delays up to 25 blocks, can't estimate beyond that */ |
||||
static const unsigned int MAX_BLOCK_CONFIRMS = 25; |
||||
|
||||
/** Decay of .998 is a half-life of 346 blocks or about 2.4 days */ |
||||
static const double DEFAULT_DECAY = .998; |
||||
|
||||
/** Require greater than 85% of X fee transactions to be confirmed within Y blocks for X to be big enough */ |
||||
static const double MIN_SUCCESS_PCT = .85; |
||||
static const double UNLIKELY_PCT = .5; |
||||
|
||||
/** Require an avg of 1 tx in the combined fee bucket per block to have stat significance */ |
||||
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) */ |
||||
static const double SUFFICIENT_PRITXS = .2; |
||||
|
||||
// Minimum and Maximum values for tracking fees and priorities
|
||||
static const double MIN_FEERATE = 10; |
||||
static const double MAX_FEERATE = 1e7; |
||||
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; |
||||
|
||||
// We have to lump transactions into buckets based on fee or priority, but we want to be able
|
||||
// to give accurate estimates over a large range of potential fees and priorities
|
||||
// Therefore it makes sense to exponentially space the buckets
|
||||
/** Spacing of FeeRate buckets */ |
||||
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 |
||||
* 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 |
||||
*/ |
||||
class CBlockPolicyEstimator |
||||
{ |
||||
public: |
||||
/** Create new BlockPolicyEstimator and initialize stats tracking classes with default values */ |
||||
CBlockPolicyEstimator(const CFeeRate& minRelayFee); |
||||
|
||||
/** Process all the transactions that have been included in a block */ |
||||
void processBlock(unsigned int nBlockHeight, |
||||
std::vector<CTxMemPoolEntry>& entries, bool fCurrentEstimate); |
||||
|
||||
/** Process a transaction confirmed in a block*/ |
||||
void processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry& entry); |
||||
|
||||
/** Process a transaction accepted to the mempool*/ |
||||
void processTransaction(const CTxMemPoolEntry& entry, bool fCurrentEstimate); |
||||
|
||||
/** Remove a transaction from the mempool tracking stats*/ |
||||
void removeTx(uint256 hash); |
||||
|
||||
/** Is this transaction likely included in a block because of its fee?*/ |
||||
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); |
||||
|
||||
/** Return a priority estimate */ |
||||
double estimatePriority(int confTarget); |
||||
|
||||
/** Write estimation data to a file */ |
||||
void Write(CAutoFile& fileout); |
||||
|
||||
/** Read estimation data from a file */ |
||||
void Read(CAutoFile& filein); |
||||
|
||||
private: |
||||
CFeeRate minTrackedFee; //! Passed to constructor to avoid dependency on main
|
||||
double minTrackedPriority; //! Set to AllowFreeThreshold
|
||||
unsigned int nBestSeenHeight; |
||||
struct TxStatsInfo |
||||
{ |
||||
TxConfirmStats *stats; |
||||
unsigned int blockHeight; |
||||
unsigned int bucketIndex; |
||||
TxStatsInfo() : stats(NULL), blockHeight(0), bucketIndex(0) {} |
||||
}; |
||||
|
||||
// map of txids to information about that transaction
|
||||
std::map<uint256, TxStatsInfo> mapMemPoolTxs; |
||||
|
||||
/** Classes to track historical data on transaction confirmations */ |
||||
TxConfirmStats feeStats, priStats; |
||||
|
||||
/** Breakpoints to help determine whether a transaction was confirmed by priority or Fee */ |
||||
CFeeRate feeLikely, feeUnlikely; |
||||
double priLikely, priUnlikely; |
||||
}; |
||||
#endif /*BITCOIN_POLICYESTIMATOR_H */ |
@ -0,0 +1,186 @@
@@ -0,0 +1,186 @@
|
||||
// Copyright (c) 2011-2015 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "policy/fees.h" |
||||
#include "txmempool.h" |
||||
#include "uint256.h" |
||||
#include "util.h" |
||||
|
||||
#include "test/test_bitcoin.h" |
||||
|
||||
#include <boost/test/unit_test.hpp> |
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(policyestimator_tests, BasicTestingSetup) |
||||
|
||||
BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) |
||||
{ |
||||
CTxMemPool mpool(CFeeRate(1000)); |
||||
CAmount basefee(2000); |
||||
double basepri = 1e6; |
||||
CAmount deltaFee(100); |
||||
double deltaPri=5e5; |
||||
std::vector<CAmount> feeV[2]; |
||||
std::vector<double> priV[2]; |
||||
|
||||
// Populate vectors of increasing fees or priorities
|
||||
for (int j = 0; j < 10; j++) { |
||||
//V[0] is for fee transactions
|
||||
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
|
||||
// added to the mempool by their associate fee/pri
|
||||
// txHashes[j] is populated with transactions either of
|
||||
// fee = basefee * (j+1) OR pri = 10^6 * 10^(j+1)
|
||||
std::vector<uint256> txHashes[10]; |
||||
|
||||
// Create a transaction template
|
||||
CScript garbage; |
||||
for (unsigned int i = 0; i < 128; i++) |
||||
garbage.push_back('X'); |
||||
CMutableTransaction tx; |
||||
std::list<CTransaction> dummyConflicted; |
||||
tx.vin.resize(1); |
||||
tx.vin[0].scriptSig = garbage; |
||||
tx.vout.resize(1); |
||||
tx.vout[0].nValue=0LL; |
||||
CFeeRate baseRate(basefee, ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION)); |
||||
|
||||
// Create a fake block
|
||||
std::vector<CTransaction> block; |
||||
int blocknum = 0; |
||||
|
||||
// Loop through 200 blocks
|
||||
// At a decay .998 and 4 fee transactions per block
|
||||
// This makes the tx count about 1.33 per bucket, above the 1 threshold
|
||||
while (blocknum < 200) { |
||||
for (int j = 0; j < 10; j++) { // For each fee/pri multiple
|
||||
for (int k = 0; k < 5; k++) { // add 4 fee txs for every priority tx
|
||||
tx.vin[0].prevout.n = 10000*blocknum+100*j+k; // make transaction unique
|
||||
uint256 hash = tx.GetHash(); |
||||
mpool.addUnchecked(hash, CTxMemPoolEntry(tx, feeV[k/4][j], GetTime(), priV[k/4][j], blocknum, mpool.HasNoInputsOf(tx))); |
||||
txHashes[j].push_back(hash); |
||||
} |
||||
} |
||||
//Create blocks where higher fee/pri txs are included more often
|
||||
for (int h = 0; h <= blocknum%10; h++) { |
||||
// 10/10 blocks add highest fee/pri transactions
|
||||
// 9/10 blocks add 2nd highest and so on until ...
|
||||
// 1/10 blocks add lowest fee/pri transactions
|
||||
while (txHashes[9-h].size()) { |
||||
CTransaction btx; |
||||
if (mpool.lookup(txHashes[9-h].back(), btx)) |
||||
block.push_back(btx); |
||||
txHashes[9-h].pop_back(); |
||||
} |
||||
} |
||||
mpool.removeForBlock(block, ++blocknum, dummyConflicted); |
||||
block.clear(); |
||||
if (blocknum == 30) { |
||||
// At this point we should need to combine 5 buckets to get enough data points
|
||||
// So estimateFee(1) should fail and estimateFee(2) should return somewhere around
|
||||
// 8*baserate
|
||||
BOOST_CHECK(mpool.estimateFee(1) == CFeeRate(0)); |
||||
BOOST_CHECK(mpool.estimateFee(2).GetFeePerK() < 8*baseRate.GetFeePerK() + deltaFee); |
||||
BOOST_CHECK(mpool.estimateFee(2).GetFeePerK() > 8*baseRate.GetFeePerK() - deltaFee); |
||||
} |
||||
} |
||||
|
||||
std::vector<CAmount> origFeeEst; |
||||
std::vector<double> origPriEst; |
||||
// Highest feerate is 10*baseRate and gets in all blocks,
|
||||
// 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%,
|
||||
// so estimateFee(1) should return 9*baseRate.
|
||||
// Third highest feerate has 90% chance of being included by 2 blocks,
|
||||
// so estimateFee(2) should return 8*baseRate etc...
|
||||
for (int i = 1; i < 10;i++) { |
||||
origFeeEst.push_back(mpool.estimateFee(i).GetFeePerK()); |
||||
origPriEst.push_back(mpool.estimatePriority(i)); |
||||
if (i > 1) { // Fee estimates should be monotonically decreasing
|
||||
BOOST_CHECK(origFeeEst[i-1] <= origFeeEst[i-2]); |
||||
BOOST_CHECK(origPriEst[i-1] <= origPriEst[i-2]); |
||||
} |
||||
BOOST_CHECK(origFeeEst[i-1] < (10-i)*baseRate.GetFeePerK() + deltaFee); |
||||
BOOST_CHECK(origFeeEst[i-1] > (10-i)*baseRate.GetFeePerK() - deltaFee); |
||||
BOOST_CHECK(origPriEst[i-1] < pow(10,10-i) * basepri + deltaPri); |
||||
BOOST_CHECK(origPriEst[i-1] > pow(10,10-i) * basepri - deltaPri); |
||||
} |
||||
|
||||
// Mine 50 more blocks with no transactions happening, estimates shouldn't change
|
||||
// We haven't decayed the moving average enough so we still have enough data points in every bucket
|
||||
while (blocknum < 250) |
||||
mpool.removeForBlock(block, ++blocknum, dummyConflicted); |
||||
|
||||
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); |
||||
BOOST_CHECK(mpool.estimatePriority(i) > origPriEst[i-1] - deltaPri); |
||||
} |
||||
|
||||
|
||||
// Mine 15 more blocks with lots of transactions happening and not getting mined
|
||||
// Estimates should go up
|
||||
while (blocknum < 265) { |
||||
for (int j = 0; j < 10; j++) { // For each fee/pri multiple
|
||||
for (int k = 0; k < 5; k++) { // add 4 fee txs for every priority tx
|
||||
tx.vin[0].prevout.n = 10000*blocknum+100*j+k; |
||||
uint256 hash = tx.GetHash(); |
||||
mpool.addUnchecked(hash, CTxMemPoolEntry(tx, feeV[k/4][j], GetTime(), priV[k/4][j], blocknum, mpool.HasNoInputsOf(tx))); |
||||
txHashes[j].push_back(hash); |
||||
} |
||||
} |
||||
mpool.removeForBlock(block, ++blocknum, dummyConflicted); |
||||
} |
||||
|
||||
for (int i = 1; i < 10;i++) { |
||||
BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee); |
||||
BOOST_CHECK(mpool.estimatePriority(i) > origPriEst[i-1] - deltaPri); |
||||
} |
||||
|
||||
// Mine all those transactions
|
||||
// Estimates should still not be below original
|
||||
for (int j = 0; j < 10; j++) { |
||||
while(txHashes[j].size()) { |
||||
CTransaction btx; |
||||
if (mpool.lookup(txHashes[j].back(), btx)) |
||||
block.push_back(btx); |
||||
txHashes[j].pop_back(); |
||||
} |
||||
} |
||||
mpool.removeForBlock(block, 265, dummyConflicted); |
||||
block.clear(); |
||||
for (int i = 1; i < 10;i++) { |
||||
BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee); |
||||
BOOST_CHECK(mpool.estimatePriority(i) > origPriEst[i-1] - deltaPri); |
||||
} |
||||
|
||||
// Mine 100 more blocks where everything is mined every block
|
||||
// Estimates should be below original estimates (not possible for last estimate)
|
||||
while (blocknum < 365) { |
||||
for (int j = 0; j < 10; j++) { // For each fee/pri multiple
|
||||
for (int k = 0; k < 5; k++) { // add 4 fee txs for every priority tx
|
||||
tx.vin[0].prevout.n = 10000*blocknum+100*j+k; |
||||
uint256 hash = tx.GetHash(); |
||||
mpool.addUnchecked(hash, CTxMemPoolEntry(tx, feeV[k/4][j], GetTime(), priV[k/4][j], blocknum, mpool.HasNoInputsOf(tx))); |
||||
CTransaction btx; |
||||
if (mpool.lookup(hash, btx)) |
||||
block.push_back(btx); |
||||
} |
||||
} |
||||
mpool.removeForBlock(block, ++blocknum, dummyConflicted); |
||||
block.clear(); |
||||
} |
||||
for (int i = 1; i < 9; i++) { |
||||
BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee); |
||||
BOOST_CHECK(mpool.estimatePriority(i) < origPriEst[i-1] - deltaPri); |
||||
} |
||||
} |
||||
|
||||
BOOST_AUTO_TEST_SUITE_END() |
Loading…
Reference in new issue