Wladimir J. van der Laan
10 years ago
10 changed files with 1267 additions and 424 deletions
@ -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 @@ |
|||||||
|
// 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 @@ |
|||||||
|
// 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