Browse Source

Merge #9942: Refactor CBlockPolicyEstimator

68af651 MOVEONLY: move TxConfirmStats to cpp (Alex Morcos)
2332f19 Initialize TxConfirmStats in constructor (Alex Morcos)
5ba81e5 Read and Write fee estimate file directly from CBlockPolicyEstimator (Alex Morcos)
14e10aa Call estimate(Smart)Fee directly from CBlockPolicyEstimator (Alex Morcos)
dbb9e36 Give CBlockPolicyEstimator it's own lock (Alex Morcos)
f6187d6 Make processBlockTx private. (Alex Morcos)
ae7327b Make feeEstimator its own global instance of CBlockPolicyEstimator (Alex Morcos)

Tree-SHA512: dbf3bd2b30822e609a35f3da519b62d23f8a50e564750695ddebd08553b4c01874ae3e07d792c6cc78cc377d2db33b951ffedc46ac7edaf5793f9ebb931713af
0.15
Wladimir J. van der Laan 8 years ago
parent
commit
14c948987f
No known key found for this signature in database
GPG Key ID: 74810B012346C9A6
  1. 5
      src/init.cpp
  2. 209
      src/policy/fees.cpp
  3. 129
      src/policy/fees.h
  4. 5
      src/qt/coincontroldialog.cpp
  5. 3
      src/qt/sendcoinsdialog.cpp
  6. 5
      src/rpc/mining.cpp
  7. 45
      src/test/policyestimator_tests.cpp
  8. 64
      src/txmempool.cpp
  9. 16
      src/txmempool.h
  10. 3
      src/validation.cpp
  11. 2
      src/validation.h
  12. 5
      src/wallet/feebumper.cpp
  13. 1
      src/wallet/rpcwallet.cpp
  14. 11
      src/wallet/wallet.cpp
  15. 5
      src/wallet/wallet.h

5
src/init.cpp

@ -25,6 +25,7 @@
#include "netbase.h" #include "netbase.h"
#include "net.h" #include "net.h"
#include "net_processing.h" #include "net_processing.h"
#include "policy/fees.h"
#include "policy/policy.h" #include "policy/policy.h"
#include "rpc/server.h" #include "rpc/server.h"
#include "rpc/register.h" #include "rpc/register.h"
@ -215,7 +216,7 @@ void Shutdown()
fs::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME; fs::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME;
CAutoFile est_fileout(fsbridge::fopen(est_path, "wb"), SER_DISK, CLIENT_VERSION); CAutoFile est_fileout(fsbridge::fopen(est_path, "wb"), SER_DISK, CLIENT_VERSION);
if (!est_fileout.IsNull()) if (!est_fileout.IsNull())
mempool.WriteFeeEstimates(est_fileout); ::feeEstimator.Write(est_fileout);
else else
LogPrintf("%s: Failed to write fee estimates to %s\n", __func__, est_path.string()); LogPrintf("%s: Failed to write fee estimates to %s\n", __func__, est_path.string());
fFeeEstimatesInitialized = false; fFeeEstimatesInitialized = false;
@ -1550,7 +1551,7 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
CAutoFile est_filein(fsbridge::fopen(est_path, "rb"), SER_DISK, CLIENT_VERSION); CAutoFile est_filein(fsbridge::fopen(est_path, "rb"), SER_DISK, CLIENT_VERSION);
// Allowed to fail as this file IS missing on first startup. // Allowed to fail as this file IS missing on first startup.
if (!est_filein.IsNull()) if (!est_filein.IsNull())
mempool.ReadFeeEstimates(est_filein); ::feeEstimator.Read(est_filein);
fFeeEstimatesInitialized = true; fFeeEstimatesInitialized = true;
// ********************************************************* Step 8: load wallet // ********************************************************* Step 8: load wallet

209
src/policy/fees.cpp

@ -7,14 +7,121 @@
#include "policy/policy.h" #include "policy/policy.h"
#include "amount.h" #include "amount.h"
#include "clientversion.h"
#include "primitives/transaction.h" #include "primitives/transaction.h"
#include "random.h" #include "random.h"
#include "streams.h" #include "streams.h"
#include "txmempool.h" #include "txmempool.h"
#include "util.h" #include "util.h"
void TxConfirmStats::Initialize(std::vector<double>& defaultBuckets, /**
unsigned int maxConfirms, double _decay) * We will instantiate an instance of this class to track transactions that were
* included in a block. We will lump transactions into a bucket according to their
* approximate feerate 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
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 calculate 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 calculate the totals for the current block to update the moving averages
std::vector<std::vector<int> > curBlockConf; // curBlockConf[Y][X]
// Sum the total feerate 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 feerate per bucket
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:
/**
* Create new TxConfirmStats. This is called by BlockPolicyEstimator's
* constructor with default values.
* @param defaultBuckets contains the upper limits for the bucket boundaries
* @param maxConfirms max number of confirms to track
* @param decay how much to decay the historical moving average per block
*/
TxConfirmStats(const std::vector<double>& defaultBuckets, unsigned int maxConfirms, double decay);
/** 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 the feerate 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 feerate estimate. Find the lowest value bucket (or range of buckets
* to make sure we have enough data points) whose transactions still have sufficient likelihood
* 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 feerate such that all higher values pass minSuccess OR
* return the highest feerate 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) const;
/** Return the max number of confirms we're tracking */
unsigned int GetMaxConfirms() const { return confAvg.size(); }
/** Write state of estimation data to a file*/
void Write(CAutoFile& fileout) const;
/**
* Read saved state of estimation data from a file and replace all internal data structures and
* variables with this state.
*/
void Read(CAutoFile& filein);
};
TxConfirmStats::TxConfirmStats(const std::vector<double>& defaultBuckets,
unsigned int maxConfirms, double _decay)
{ {
decay = _decay; decay = _decay;
for (unsigned int i = 0; i < defaultBuckets.size(); i++) { for (unsigned int i = 0; i < defaultBuckets.size(); i++) {
@ -77,7 +184,7 @@ void TxConfirmStats::UpdateMovingAverages()
// returns -1 on error conditions // returns -1 on error conditions
double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal, double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
double successBreakPoint, bool requireGreater, double successBreakPoint, bool requireGreater,
unsigned int nBlockHeight) unsigned int nBlockHeight) const
{ {
// Counters for a bucket (or range of buckets) // Counters for a bucket (or range of buckets)
double nConf = 0; // Number of tx's confirmed within the confTarget double nConf = 0; // Number of tx's confirmed within the confTarget
@ -173,7 +280,7 @@ double TxConfirmStats::EstimateMedianVal(int confTarget, double sufficientTxVal,
return median; return median;
} }
void TxConfirmStats::Write(CAutoFile& fileout) void TxConfirmStats::Write(CAutoFile& fileout) const
{ {
fileout << decay; fileout << decay;
fileout << buckets; fileout << buckets;
@ -290,9 +397,10 @@ void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHe
// of no harm to try to remove them again. // of no harm to try to remove them again.
bool CBlockPolicyEstimator::removeTx(uint256 hash) bool CBlockPolicyEstimator::removeTx(uint256 hash)
{ {
LOCK(cs_feeEstimator);
std::map<uint256, TxStatsInfo>::iterator pos = mapMemPoolTxs.find(hash); std::map<uint256, TxStatsInfo>::iterator pos = mapMemPoolTxs.find(hash);
if (pos != mapMemPoolTxs.end()) { if (pos != mapMemPoolTxs.end()) {
feeStats.removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex); feeStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex);
mapMemPoolTxs.erase(hash); mapMemPoolTxs.erase(hash);
return true; return true;
} else { } else {
@ -310,11 +418,17 @@ CBlockPolicyEstimator::CBlockPolicyEstimator()
vfeelist.push_back(bucketBoundary); vfeelist.push_back(bucketBoundary);
} }
vfeelist.push_back(INF_FEERATE); vfeelist.push_back(INF_FEERATE);
feeStats.Initialize(vfeelist, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY); feeStats = new TxConfirmStats(vfeelist, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
}
CBlockPolicyEstimator::~CBlockPolicyEstimator()
{
delete feeStats;
} }
void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate) void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate)
{ {
LOCK(cs_feeEstimator);
unsigned int txHeight = entry.GetHeight(); unsigned int txHeight = entry.GetHeight();
uint256 hash = entry.GetTx().GetHash(); uint256 hash = entry.GetTx().GetHash();
if (mapMemPoolTxs.count(hash)) { if (mapMemPoolTxs.count(hash)) {
@ -343,7 +457,7 @@ void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, boo
CFeeRate feeRate(entry.GetFee(), entry.GetTxSize()); CFeeRate feeRate(entry.GetFee(), entry.GetTxSize());
mapMemPoolTxs[hash].blockHeight = txHeight; mapMemPoolTxs[hash].blockHeight = txHeight;
mapMemPoolTxs[hash].bucketIndex = feeStats.NewTx(txHeight, (double)feeRate.GetFeePerK()); mapMemPoolTxs[hash].bucketIndex = feeStats->NewTx(txHeight, (double)feeRate.GetFeePerK());
} }
bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry) bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry)
@ -367,13 +481,14 @@ bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxM
// Feerates are stored and reported as BTC-per-kb: // Feerates are stored and reported as BTC-per-kb:
CFeeRate feeRate(entry->GetFee(), entry->GetTxSize()); CFeeRate feeRate(entry->GetFee(), entry->GetTxSize());
feeStats.Record(blocksToConfirm, (double)feeRate.GetFeePerK()); feeStats->Record(blocksToConfirm, (double)feeRate.GetFeePerK());
return true; return true;
} }
void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight, void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
std::vector<const CTxMemPoolEntry*>& entries) std::vector<const CTxMemPoolEntry*>& entries)
{ {
LOCK(cs_feeEstimator);
if (nBlockHeight <= nBestSeenHeight) { if (nBlockHeight <= nBestSeenHeight) {
// Ignore side chains and re-orgs; assuming they are random // Ignore side chains and re-orgs; assuming they are random
// they don't affect the estimate. // they don't affect the estimate.
@ -389,7 +504,7 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
nBestSeenHeight = nBlockHeight; nBestSeenHeight = nBlockHeight;
// Clear the current block state and update unconfirmed circular buffer // Clear the current block state and update unconfirmed circular buffer
feeStats.ClearCurrent(nBlockHeight); feeStats->ClearCurrent(nBlockHeight);
unsigned int countedTxs = 0; unsigned int countedTxs = 0;
// Repopulate the current block states // Repopulate the current block states
@ -399,7 +514,7 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
} }
// Update all exponential averages with the current block state // Update all exponential averages with the current block state
feeStats.UpdateMovingAverages(); feeStats->UpdateMovingAverages();
LogPrint(BCLog::ESTIMATEFEE, "Blockpolicy after updating estimates for %u of %u txs in block, since last block %u of %u tracked, new mempool map size %u\n", LogPrint(BCLog::ESTIMATEFEE, "Blockpolicy after updating estimates for %u of %u txs in block, since last block %u of %u tracked, new mempool map size %u\n",
countedTxs, entries.size(), trackedTxs, trackedTxs + untrackedTxs, mapMemPoolTxs.size()); countedTxs, entries.size(), trackedTxs, trackedTxs + untrackedTxs, mapMemPoolTxs.size());
@ -408,14 +523,15 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
untrackedTxs = 0; untrackedTxs = 0;
} }
CFeeRate CBlockPolicyEstimator::estimateFee(int confTarget) CFeeRate CBlockPolicyEstimator::estimateFee(int confTarget) const
{ {
LOCK(cs_feeEstimator);
// Return failure if trying to analyze a target we're not tracking // Return failure if trying to analyze a target we're not tracking
// It's not possible to get reasonable estimates for confTarget of 1 // It's not possible to get reasonable estimates for confTarget of 1
if (confTarget <= 1 || (unsigned int)confTarget > feeStats.GetMaxConfirms()) if (confTarget <= 1 || (unsigned int)confTarget > feeStats->GetMaxConfirms())
return CFeeRate(0); return CFeeRate(0);
double median = feeStats.EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight); double median = feeStats->EstimateMedianVal(confTarget, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
if (median < 0) if (median < 0)
return CFeeRate(0); return CFeeRate(0);
@ -423,22 +539,28 @@ CFeeRate CBlockPolicyEstimator::estimateFee(int confTarget)
return CFeeRate(median); return CFeeRate(median);
} }
CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool) CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool) const
{ {
if (answerFoundAtTarget) if (answerFoundAtTarget)
*answerFoundAtTarget = confTarget; *answerFoundAtTarget = confTarget;
// Return failure if trying to analyze a target we're not tracking
if (confTarget <= 0 || (unsigned int)confTarget > feeStats.GetMaxConfirms())
return CFeeRate(0);
// It's not possible to get reasonable estimates for confTarget of 1
if (confTarget == 1)
confTarget = 2;
double median = -1; double median = -1;
while (median < 0 && (unsigned int)confTarget <= feeStats.GetMaxConfirms()) {
median = feeStats.EstimateMedianVal(confTarget++, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight); {
} LOCK(cs_feeEstimator);
// Return failure if trying to analyze a target we're not tracking
if (confTarget <= 0 || (unsigned int)confTarget > feeStats->GetMaxConfirms())
return CFeeRate(0);
// It's not possible to get reasonable estimates for confTarget of 1
if (confTarget == 1)
confTarget = 2;
while (median < 0 && (unsigned int)confTarget <= feeStats->GetMaxConfirms()) {
median = feeStats->EstimateMedianVal(confTarget++, SUFFICIENT_FEETXS, MIN_SUCCESS_PCT, true, nBestSeenHeight);
}
} // Must unlock cs_feeEstimator before taking mempool locks
if (answerFoundAtTarget) if (answerFoundAtTarget)
*answerFoundAtTarget = confTarget - 1; *answerFoundAtTarget = confTarget - 1;
@ -454,19 +576,40 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoun
return CFeeRate(median); return CFeeRate(median);
} }
void CBlockPolicyEstimator::Write(CAutoFile& fileout) bool CBlockPolicyEstimator::Write(CAutoFile& fileout) const
{ {
fileout << nBestSeenHeight; try {
feeStats.Write(fileout); LOCK(cs_feeEstimator);
fileout << 139900; // version required to read: 0.13.99 or later
fileout << CLIENT_VERSION; // version that wrote the file
fileout << nBestSeenHeight;
feeStats->Write(fileout);
}
catch (const std::exception&) {
LogPrintf("CBlockPolicyEstimator::Write(): unable to read policy estimator data (non-fatal)\n");
return false;
}
return true;
} }
void CBlockPolicyEstimator::Read(CAutoFile& filein, int nFileVersion) bool CBlockPolicyEstimator::Read(CAutoFile& filein)
{ {
int nFileBestSeenHeight; try {
filein >> nFileBestSeenHeight; LOCK(cs_feeEstimator);
feeStats.Read(filein); int nVersionRequired, nVersionThatWrote, nFileBestSeenHeight;
nBestSeenHeight = nFileBestSeenHeight; filein >> nVersionRequired >> nVersionThatWrote;
// if nVersionThatWrote < 139900 then another TxConfirmStats (for priority) follows but can be ignored. if (nVersionRequired > CLIENT_VERSION)
return error("CBlockPolicyEstimator::Read(): up-version (%d) fee estimate file", nVersionRequired);
filein >> nFileBestSeenHeight;
feeStats->Read(filein);
nBestSeenHeight = nFileBestSeenHeight;
// if nVersionThatWrote < 139900 then another TxConfirmStats (for priority) follows but can be ignored.
}
catch (const std::exception&) {
LogPrintf("CBlockPolicyEstimator::Read(): unable to read policy estimator data (non-fatal)\n");
return false;
}
return true;
} }
FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee) FeeFilterRounder::FeeFilterRounder(const CFeeRate& minIncrementalFee)

129
src/policy/fees.h

@ -8,6 +8,7 @@
#include "amount.h" #include "amount.h"
#include "uint256.h" #include "uint256.h"
#include "random.h" #include "random.h"
#include "sync.h"
#include <map> #include <map>
#include <string> #include <string>
@ -17,6 +18,7 @@ class CAutoFile;
class CFeeRate; class CFeeRate;
class CTxMemPoolEntry; class CTxMemPoolEntry;
class CTxMemPool; class CTxMemPool;
class TxConfirmStats;
/** \class CBlockPolicyEstimator /** \class CBlockPolicyEstimator
* The BlockPolicyEstimator is used for estimating the feerate needed * The BlockPolicyEstimator is used for estimating the feerate needed
@ -59,113 +61,6 @@ class CTxMemPool;
* they've been outstanding. * they've been outstanding.
*/ */
/**
* We will instantiate an instance of this class to track transactions that were
* included in a block. We will lump transactions into a bucket according to their
* approximate feerate 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
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 calculate 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 calculate the totals for the current block to update the moving averages
std::vector<std::vector<int> > curBlockConf; // curBlockConf[Y][X]
// Sum the total feerate 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 feerate per bucket
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 boundaries
* @param maxConfirms max number of confirms to track
* @param decay how much to decay the historical moving average per block
*/
void Initialize(std::vector<double>& defaultBuckets, unsigned int maxConfirms, double decay);
/** Clear the state of the curBlock variables to start counting for the new block */
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 the feerate 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 feerate estimate. Find the lowest value bucket (or range of buckets
* to make sure we have enough data points) whose transactions still have sufficient likelihood
* 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 feerate such that all higher values pass minSuccess OR
* return the highest feerate 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 */ /** Track confirm delays up to 25 blocks, can't estimate beyond that */
static const unsigned int MAX_BLOCK_CONFIRMS = 25; static const unsigned int MAX_BLOCK_CONFIRMS = 25;
@ -204,14 +99,12 @@ class CBlockPolicyEstimator
public: public:
/** Create new BlockPolicyEstimator and initialize stats tracking classes with default values */ /** Create new BlockPolicyEstimator and initialize stats tracking classes with default values */
CBlockPolicyEstimator(); CBlockPolicyEstimator();
~CBlockPolicyEstimator();
/** Process all the transactions that have been included in a block */ /** Process all the transactions that have been included in a block */
void processBlock(unsigned int nBlockHeight, void processBlock(unsigned int nBlockHeight,
std::vector<const CTxMemPoolEntry*>& entries); std::vector<const CTxMemPoolEntry*>& entries);
/** Process a transaction confirmed in a block*/
bool processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry);
/** Process a transaction accepted to the mempool*/ /** Process a transaction accepted to the mempool*/
void processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate); void processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate);
@ -219,19 +112,19 @@ public:
bool removeTx(uint256 hash); bool removeTx(uint256 hash);
/** Return a feerate estimate */ /** Return a feerate estimate */
CFeeRate estimateFee(int confTarget); CFeeRate estimateFee(int confTarget) const;
/** Estimate feerate needed to get be included in a block within /** Estimate feerate needed to get be included in a block within
* confTarget blocks. If no answer can be given at confTarget, return an * confTarget blocks. If no answer can be given at confTarget, return an
* estimate at the lowest target where one can be given. * estimate at the lowest target where one can be given.
*/ */
CFeeRate estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool); CFeeRate estimateSmartFee(int confTarget, int *answerFoundAtTarget, const CTxMemPool& pool) const;
/** Write estimation data to a file */ /** Write estimation data to a file */
void Write(CAutoFile& fileout); bool Write(CAutoFile& fileout) const;
/** Read estimation data from a file */ /** Read estimation data from a file */
void Read(CAutoFile& filein, int nFileVersion); bool Read(CAutoFile& filein);
private: private:
CFeeRate minTrackedFee; //!< Passed to constructor to avoid dependency on main CFeeRate minTrackedFee; //!< Passed to constructor to avoid dependency on main
@ -247,10 +140,16 @@ private:
std::map<uint256, TxStatsInfo> mapMemPoolTxs; std::map<uint256, TxStatsInfo> mapMemPoolTxs;
/** Classes to track historical data on transaction confirmations */ /** Classes to track historical data on transaction confirmations */
TxConfirmStats feeStats; TxConfirmStats* feeStats;
unsigned int trackedTxs; unsigned int trackedTxs;
unsigned int untrackedTxs; unsigned int untrackedTxs;
mutable CCriticalSection cs_feeEstimator;
/** Process a transaction confirmed in a block*/
bool processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry);
}; };
class FeeFilterRounder class FeeFilterRounder

5
src/qt/coincontroldialog.cpp

@ -15,6 +15,7 @@
#include "wallet/coincontrol.h" #include "wallet/coincontrol.h"
#include "init.h" #include "init.h"
#include "policy/fees.h"
#include "policy/policy.h" #include "policy/policy.h"
#include "validation.h" // For mempool #include "validation.h" // For mempool
#include "wallet/wallet.h" #include "wallet/wallet.h"
@ -512,7 +513,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
nBytes -= 34; nBytes -= 34;
// Fee // Fee
nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, mempool); nPayFee = CWallet::GetMinimumFee(nBytes, nTxConfirmTarget, ::mempool, ::feeEstimator);
if (nPayFee > 0 && coinControl->nMinimumTotalFee > nPayFee) if (nPayFee > 0 && coinControl->nMinimumTotalFee > nPayFee)
nPayFee = coinControl->nMinimumTotalFee; nPayFee = coinControl->nMinimumTotalFee;
@ -592,7 +593,7 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog)
if (payTxFee.GetFeePerK() > 0) if (payTxFee.GetFeePerK() > 0)
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), payTxFee.GetFeePerK()) / 1000; dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), payTxFee.GetFeePerK()) / 1000;
else { else {
dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), mempool.estimateSmartFee(nTxConfirmTarget).GetFeePerK()) / 1000; dFeeVary = (double)std::max(CWallet::GetRequiredFee(1000), ::feeEstimator.estimateSmartFee(nTxConfirmTarget, NULL, ::mempool).GetFeePerK()) / 1000;
} }
QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary); QString toolTip4 = tr("Can vary +/- %1 satoshi(s) per input.").arg(dFeeVary);

3
src/qt/sendcoinsdialog.cpp

@ -21,6 +21,7 @@
#include "validation.h" // mempool and minRelayTxFee #include "validation.h" // mempool and minRelayTxFee
#include "ui_interface.h" #include "ui_interface.h"
#include "txmempool.h" #include "txmempool.h"
#include "policy/fees.h"
#include "wallet/wallet.h" #include "wallet/wallet.h"
#include <QFontMetrics> #include <QFontMetrics>
@ -660,7 +661,7 @@ void SendCoinsDialog::updateSmartFeeLabel()
int nBlocksToConfirm = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2; int nBlocksToConfirm = ui->sliderSmartFee->maximum() - ui->sliderSmartFee->value() + 2;
int estimateFoundAtBlocks = nBlocksToConfirm; int estimateFoundAtBlocks = nBlocksToConfirm;
CFeeRate feeRate = mempool.estimateSmartFee(nBlocksToConfirm, &estimateFoundAtBlocks); CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocksToConfirm, &estimateFoundAtBlocks, ::mempool);
if (feeRate <= CFeeRate(0)) // not enough data => minfee if (feeRate <= CFeeRate(0)) // not enough data => minfee
{ {
ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(),

5
src/rpc/mining.cpp

@ -15,6 +15,7 @@
#include "validation.h" #include "validation.h"
#include "miner.h" #include "miner.h"
#include "net.h" #include "net.h"
#include "policy/fees.h"
#include "pow.h" #include "pow.h"
#include "rpc/blockchain.h" #include "rpc/blockchain.h"
#include "rpc/server.h" #include "rpc/server.h"
@ -818,7 +819,7 @@ UniValue estimatefee(const JSONRPCRequest& request)
if (nBlocks < 1) if (nBlocks < 1)
nBlocks = 1; nBlocks = 1;
CFeeRate feeRate = mempool.estimateFee(nBlocks); CFeeRate feeRate = ::feeEstimator.estimateFee(nBlocks);
if (feeRate == CFeeRate(0)) if (feeRate == CFeeRate(0))
return -1.0; return -1.0;
@ -856,7 +857,7 @@ UniValue estimatesmartfee(const JSONRPCRequest& request)
UniValue result(UniValue::VOBJ); UniValue result(UniValue::VOBJ);
int answerFound; int answerFound;
CFeeRate feeRate = mempool.estimateSmartFee(nBlocks, &answerFound); CFeeRate feeRate = ::feeEstimator.estimateSmartFee(nBlocks, &answerFound, ::mempool);
result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK()))); result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK())));
result.push_back(Pair("blocks", answerFound)); result.push_back(Pair("blocks", answerFound));
return result; return result;

45
src/test/policyestimator_tests.cpp

@ -16,7 +16,8 @@ BOOST_FIXTURE_TEST_SUITE(policyestimator_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(BlockPolicyEstimates) BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
{ {
CTxMemPool mpool; CBlockPolicyEstimator feeEst;
CTxMemPool mpool(&feeEst);
TestMemPoolEntryHelper entry; TestMemPoolEntryHelper entry;
CAmount basefee(2000); CAmount basefee(2000);
CAmount deltaFee(100); CAmount deltaFee(100);
@ -78,16 +79,16 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
// At this point we should need to combine 5 buckets to get enough data points // At this point we should need to combine 5 buckets to get enough data points
// So estimateFee(1,2,3) should fail and estimateFee(4) should return somewhere around // So estimateFee(1,2,3) should fail and estimateFee(4) should return somewhere around
// 8*baserate. estimateFee(4) %'s are 100,100,100,100,90 = average 98% // 8*baserate. estimateFee(4) %'s are 100,100,100,100,90 = average 98%
BOOST_CHECK(mpool.estimateFee(1) == CFeeRate(0)); BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
BOOST_CHECK(mpool.estimateFee(2) == CFeeRate(0)); BOOST_CHECK(feeEst.estimateFee(2) == CFeeRate(0));
BOOST_CHECK(mpool.estimateFee(3) == CFeeRate(0)); BOOST_CHECK(feeEst.estimateFee(3) == CFeeRate(0));
BOOST_CHECK(mpool.estimateFee(4).GetFeePerK() < 8*baseRate.GetFeePerK() + deltaFee); BOOST_CHECK(feeEst.estimateFee(4).GetFeePerK() < 8*baseRate.GetFeePerK() + deltaFee);
BOOST_CHECK(mpool.estimateFee(4).GetFeePerK() > 8*baseRate.GetFeePerK() - deltaFee); BOOST_CHECK(feeEst.estimateFee(4).GetFeePerK() > 8*baseRate.GetFeePerK() - deltaFee);
int answerFound; int answerFound;
BOOST_CHECK(mpool.estimateSmartFee(1, &answerFound) == mpool.estimateFee(4) && answerFound == 4); BOOST_CHECK(feeEst.estimateSmartFee(1, &answerFound, mpool) == feeEst.estimateFee(4) && answerFound == 4);
BOOST_CHECK(mpool.estimateSmartFee(3, &answerFound) == mpool.estimateFee(4) && answerFound == 4); BOOST_CHECK(feeEst.estimateSmartFee(3, &answerFound, mpool) == feeEst.estimateFee(4) && answerFound == 4);
BOOST_CHECK(mpool.estimateSmartFee(4, &answerFound) == mpool.estimateFee(4) && answerFound == 4); BOOST_CHECK(feeEst.estimateSmartFee(4, &answerFound, mpool) == feeEst.estimateFee(4) && answerFound == 4);
BOOST_CHECK(mpool.estimateSmartFee(8, &answerFound) == mpool.estimateFee(8) && answerFound == 8); BOOST_CHECK(feeEst.estimateSmartFee(8, &answerFound, mpool) == feeEst.estimateFee(8) && answerFound == 8);
} }
} }
@ -99,7 +100,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
// Second highest feerate has 100% chance of being included by 2 blocks, // Second highest feerate has 100% chance of being included by 2 blocks,
// so estimateFee(2) should return 9*baseRate etc... // so estimateFee(2) should return 9*baseRate etc...
for (int i = 1; i < 10;i++) { for (int i = 1; i < 10;i++) {
origFeeEst.push_back(mpool.estimateFee(i).GetFeePerK()); origFeeEst.push_back(feeEst.estimateFee(i).GetFeePerK());
if (i > 2) { // Fee estimates should be monotonically decreasing if (i > 2) { // Fee estimates should be monotonically decreasing
BOOST_CHECK(origFeeEst[i-1] <= origFeeEst[i-2]); BOOST_CHECK(origFeeEst[i-1] <= origFeeEst[i-2]);
} }
@ -118,10 +119,10 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
while (blocknum < 250) while (blocknum < 250)
mpool.removeForBlock(block, ++blocknum); mpool.removeForBlock(block, ++blocknum);
BOOST_CHECK(mpool.estimateFee(1) == CFeeRate(0)); BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
for (int i = 2; i < 10;i++) { for (int i = 2; i < 10;i++) {
BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() < origFeeEst[i-1] + deltaFee); BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] + deltaFee);
BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee); BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
} }
@ -141,8 +142,8 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
int answerFound; int answerFound;
for (int i = 1; i < 10;i++) { for (int i = 1; i < 10;i++) {
BOOST_CHECK(mpool.estimateFee(i) == CFeeRate(0) || mpool.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee); BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
BOOST_CHECK(mpool.estimateSmartFee(i, &answerFound).GetFeePerK() > origFeeEst[answerFound-1] - deltaFee); BOOST_CHECK(feeEst.estimateSmartFee(i, &answerFound, mpool).GetFeePerK() > origFeeEst[answerFound-1] - deltaFee);
} }
// Mine all those transactions // Mine all those transactions
@ -157,9 +158,9 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
} }
mpool.removeForBlock(block, 265); mpool.removeForBlock(block, 265);
block.clear(); block.clear();
BOOST_CHECK(mpool.estimateFee(1) == CFeeRate(0)); BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
for (int i = 2; i < 10;i++) { for (int i = 2; i < 10;i++) {
BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee); BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
} }
// Mine 200 more blocks where everything is mined every block // Mine 200 more blocks where everything is mined every block
@ -179,9 +180,9 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
mpool.removeForBlock(block, ++blocknum); mpool.removeForBlock(block, ++blocknum);
block.clear(); block.clear();
} }
BOOST_CHECK(mpool.estimateFee(1) == CFeeRate(0)); BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
for (int i = 2; i < 10; i++) { for (int i = 2; i < 10; i++) {
BOOST_CHECK(mpool.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee); BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee);
} }
// Test that if the mempool is limited, estimateSmartFee won't return a value below the mempool min fee // Test that if the mempool is limited, estimateSmartFee won't return a value below the mempool min fee
@ -190,8 +191,8 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
mpool.TrimToSize(1); mpool.TrimToSize(1);
BOOST_CHECK(mpool.GetMinFee(1).GetFeePerK() > feeV[5]); BOOST_CHECK(mpool.GetMinFee(1).GetFeePerK() > feeV[5]);
for (int i = 1; i < 10; i++) { for (int i = 1; i < 10; i++) {
BOOST_CHECK(mpool.estimateSmartFee(i).GetFeePerK() >= mpool.estimateFee(i).GetFeePerK()); BOOST_CHECK(feeEst.estimateSmartFee(i, NULL, mpool).GetFeePerK() >= feeEst.estimateFee(i).GetFeePerK());
BOOST_CHECK(mpool.estimateSmartFee(i).GetFeePerK() >= mpool.GetMinFee(1).GetFeePerK()); BOOST_CHECK(feeEst.estimateSmartFee(i, NULL, mpool).GetFeePerK() >= mpool.GetMinFee(1).GetFeePerK());
} }
} }

64
src/txmempool.cpp

@ -5,7 +5,6 @@
#include "txmempool.h" #include "txmempool.h"
#include "clientversion.h"
#include "consensus/consensus.h" #include "consensus/consensus.h"
#include "consensus/validation.h" #include "consensus/validation.h"
#include "validation.h" #include "validation.h"
@ -16,7 +15,6 @@
#include "util.h" #include "util.h"
#include "utilmoneystr.h" #include "utilmoneystr.h"
#include "utiltime.h" #include "utiltime.h"
#include "version.h"
CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee, CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee,
int64_t _nTime, unsigned int _entryHeight, int64_t _nTime, unsigned int _entryHeight,
@ -333,8 +331,8 @@ void CTxMemPoolEntry::UpdateAncestorState(int64_t modifySize, CAmount modifyFee,
assert(int(nSigOpCostWithAncestors) >= 0); assert(int(nSigOpCostWithAncestors) >= 0);
} }
CTxMemPool::CTxMemPool() : CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator) :
nTransactionsUpdated(0) nTransactionsUpdated(0), minerPolicyEstimator(estimator)
{ {
_clear(); //lock free clear _clear(); //lock free clear
@ -342,13 +340,6 @@ CTxMemPool::CTxMemPool() :
// accepting transactions becomes O(N^2) where N is the number // accepting transactions becomes O(N^2) where N is the number
// of transactions in the pool // of transactions in the pool
nCheckFrequency = 0; nCheckFrequency = 0;
minerPolicyEstimator = new CBlockPolicyEstimator();
}
CTxMemPool::~CTxMemPool()
{
delete minerPolicyEstimator;
} }
void CTxMemPool::pruneSpent(const uint256 &hashTx, CCoins &coins) void CTxMemPool::pruneSpent(const uint256 &hashTx, CCoins &coins)
@ -427,7 +418,7 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
nTransactionsUpdated++; nTransactionsUpdated++;
totalTxSize += entry.GetTxSize(); totalTxSize += entry.GetTxSize();
minerPolicyEstimator->processTransaction(entry, validFeeEstimate); if (minerPolicyEstimator) {minerPolicyEstimator->processTransaction(entry, validFeeEstimate);}
vTxHashes.emplace_back(tx.GetWitnessHash(), newit); vTxHashes.emplace_back(tx.GetWitnessHash(), newit);
newit->vTxHashesIdx = vTxHashes.size() - 1; newit->vTxHashesIdx = vTxHashes.size() - 1;
@ -457,7 +448,7 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
mapLinks.erase(it); mapLinks.erase(it);
mapTx.erase(it); mapTx.erase(it);
nTransactionsUpdated++; nTransactionsUpdated++;
minerPolicyEstimator->removeTx(hash); if (minerPolicyEstimator) {minerPolicyEstimator->removeTx(hash);}
} }
// Calculates descendants of entry that are not already in setDescendants, and adds to // Calculates descendants of entry that are not already in setDescendants, and adds to
@ -591,7 +582,7 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
entries.push_back(&*i); entries.push_back(&*i);
} }
// Before the txs in the new block have been removed from the mempool, update policy estimates // Before the txs in the new block have been removed from the mempool, update policy estimates
minerPolicyEstimator->processBlock(nBlockHeight, entries); if (minerPolicyEstimator) {minerPolicyEstimator->processBlock(nBlockHeight, entries);}
for (const auto& tx : vtx) for (const auto& tx : vtx)
{ {
txiter it = mapTx.find(tx->GetHash()); txiter it = mapTx.find(tx->GetHash());
@ -850,51 +841,6 @@ TxMempoolInfo CTxMemPool::info(const uint256& hash) const
return GetInfo(i); return GetInfo(i);
} }
CFeeRate CTxMemPool::estimateFee(int nBlocks) const
{
LOCK(cs);
return minerPolicyEstimator->estimateFee(nBlocks);
}
CFeeRate CTxMemPool::estimateSmartFee(int nBlocks, int *answerFoundAtBlocks) const
{
LOCK(cs);
return minerPolicyEstimator->estimateSmartFee(nBlocks, answerFoundAtBlocks, *this);
}
bool
CTxMemPool::WriteFeeEstimates(CAutoFile& fileout) const
{
try {
LOCK(cs);
fileout << 139900; // version required to read: 0.13.99 or later
fileout << CLIENT_VERSION; // version that wrote the file
minerPolicyEstimator->Write(fileout);
}
catch (const std::exception&) {
LogPrintf("CTxMemPool::WriteFeeEstimates(): unable to write policy estimator data (non-fatal)\n");
return false;
}
return true;
}
bool
CTxMemPool::ReadFeeEstimates(CAutoFile& filein)
{
try {
int nVersionRequired, nVersionThatWrote;
filein >> nVersionRequired >> nVersionThatWrote;
if (nVersionRequired > CLIENT_VERSION)
return error("CTxMemPool::ReadFeeEstimates(): up-version (%d) fee estimate file", nVersionRequired);
LOCK(cs);
minerPolicyEstimator->Read(filein, nVersionThatWrote);
}
catch (const std::exception&) {
LogPrintf("CTxMemPool::ReadFeeEstimates(): unable to read policy estimator data (non-fatal)\n");
return false;
}
return true;
}
void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeDelta) void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeDelta)
{ {
{ {

16
src/txmempool.h

@ -496,8 +496,7 @@ public:
/** Create a new CTxMemPool. /** Create a new CTxMemPool.
*/ */
CTxMemPool(); CTxMemPool(CBlockPolicyEstimator* estimator = nullptr);
~CTxMemPool();
/** /**
* If sanity-checking is turned on, check makes sure the pool is * If sanity-checking is turned on, check makes sure the pool is
@ -618,19 +617,6 @@ public:
TxMempoolInfo info(const uint256& hash) const; TxMempoolInfo info(const uint256& hash) const;
std::vector<TxMempoolInfo> infoAll() const; std::vector<TxMempoolInfo> infoAll() const;
/** Estimate fee rate needed to get into the next nBlocks
* If no answer can be given at nBlocks, return an estimate
* at the lowest number of blocks where one can be given
*/
CFeeRate estimateSmartFee(int nBlocks, int *answerFoundAtBlocks = NULL) const;
/** Estimate fee rate needed to get into the next nBlocks */
CFeeRate estimateFee(int nBlocks) const;
/** Write/Read estimates to disk */
bool WriteFeeEstimates(CAutoFile& fileout) const;
bool ReadFeeEstimates(CAutoFile& filein);
size_t DynamicMemoryUsage() const; size_t DynamicMemoryUsage() const;
boost::signals2::signal<void (CTransactionRef)> NotifyEntryAdded; boost::signals2::signal<void (CTransactionRef)> NotifyEntryAdded;

3
src/validation.cpp

@ -80,7 +80,8 @@ uint256 hashAssumeValid;
CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE); CFeeRate minRelayTxFee = CFeeRate(DEFAULT_MIN_RELAY_TX_FEE);
CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE; CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
CTxMemPool mempool; CBlockPolicyEstimator feeEstimator;
CTxMemPool mempool(&feeEstimator);
static void CheckBlockIndex(const Consensus::Params& consensusParams); static void CheckBlockIndex(const Consensus::Params& consensusParams);

2
src/validation.h

@ -39,6 +39,7 @@ class CChainParams;
class CInv; class CInv;
class CConnman; class CConnman;
class CScriptCheck; class CScriptCheck;
class CBlockPolicyEstimator;
class CTxMemPool; class CTxMemPool;
class CValidationInterface; class CValidationInterface;
class CValidationState; class CValidationState;
@ -152,6 +153,7 @@ struct BlockHasher
extern CScript COINBASE_FLAGS; extern CScript COINBASE_FLAGS;
extern CCriticalSection cs_main; extern CCriticalSection cs_main;
extern CBlockPolicyEstimator feeEstimator;
extern CTxMemPool mempool; extern CTxMemPool mempool;
typedef boost::unordered_map<uint256, CBlockIndex*, BlockHasher> BlockMap; typedef boost::unordered_map<uint256, CBlockIndex*, BlockHasher> BlockMap;
extern BlockMap mapBlockIndex; extern BlockMap mapBlockIndex;

5
src/wallet/feebumper.cpp

@ -5,6 +5,7 @@
#include "consensus/validation.h" #include "consensus/validation.h"
#include "wallet/feebumper.h" #include "wallet/feebumper.h"
#include "wallet/wallet.h" #include "wallet/wallet.h"
#include "policy/fees.h"
#include "policy/policy.h" #include "policy/policy.h"
#include "policy/rbf.h" #include "policy/rbf.h"
#include "validation.h" //for mempool access #include "validation.h" //for mempool access
@ -159,11 +160,11 @@ CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConf
} else { } else {
// if user specified a confirm target then don't consider any global payTxFee // if user specified a confirm target then don't consider any global payTxFee
if (specifiedConfirmTarget) { if (specifiedConfirmTarget) {
nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, CAmount(0)); nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator, CAmount(0));
} }
// otherwise use the regular wallet logic to select payTxFee or default confirm target // otherwise use the regular wallet logic to select payTxFee or default confirm target
else { else {
nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool); nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator);
} }
nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize); nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize);

1
src/wallet/rpcwallet.cpp

@ -11,6 +11,7 @@
#include "init.h" #include "init.h"
#include "validation.h" #include "validation.h"
#include "net.h" #include "net.h"
#include "policy/fees.h"
#include "policy/policy.h" #include "policy/policy.h"
#include "policy/rbf.h" #include "policy/rbf.h"
#include "rpc/server.h" #include "rpc/server.h"

11
src/wallet/wallet.cpp

@ -16,6 +16,7 @@
#include "keystore.h" #include "keystore.h"
#include "validation.h" #include "validation.h"
#include "net.h" #include "net.h"
#include "policy/fees.h"
#include "policy/policy.h" #include "policy/policy.h"
#include "policy/rbf.h" #include "policy/rbf.h"
#include "primitives/block.h" #include "primitives/block.h"
@ -2612,7 +2613,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
if (coinControl && coinControl->nConfirmTarget > 0) if (coinControl && coinControl->nConfirmTarget > 0)
currentConfirmationTarget = coinControl->nConfirmTarget; currentConfirmationTarget = coinControl->nConfirmTarget;
CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, mempool); CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, ::mempool, ::feeEstimator);
if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) { if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) {
nFeeNeeded = coinControl->nMinimumTotalFee; nFeeNeeded = coinControl->nMinimumTotalFee;
} }
@ -2786,19 +2787,19 @@ CAmount CWallet::GetRequiredFee(unsigned int nTxBytes)
return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes)); return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes));
} }
CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool) CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator)
{ {
// payTxFee is the user-set global for desired feerate // payTxFee is the user-set global for desired feerate
return GetMinimumFee(nTxBytes, nConfirmTarget, pool, payTxFee.GetFee(nTxBytes)); return GetMinimumFee(nTxBytes, nConfirmTarget, pool, estimator, payTxFee.GetFee(nTxBytes));
} }
CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, CAmount targetFee) CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, CAmount targetFee)
{ {
CAmount nFeeNeeded = targetFee; CAmount nFeeNeeded = targetFee;
// User didn't set: use -txconfirmtarget to estimate... // User didn't set: use -txconfirmtarget to estimate...
if (nFeeNeeded == 0) { if (nFeeNeeded == 0) {
int estimateFoundTarget = nConfirmTarget; int estimateFoundTarget = nConfirmTarget;
nFeeNeeded = pool.estimateSmartFee(nConfirmTarget, &estimateFoundTarget).GetFee(nTxBytes); nFeeNeeded = estimator.estimateSmartFee(nConfirmTarget, &estimateFoundTarget, pool).GetFee(nTxBytes);
// ... unless we don't have enough mempool data for estimatefee, then use fallbackFee // ... unless we don't have enough mempool data for estimatefee, then use fallbackFee
if (nFeeNeeded == 0) if (nFeeNeeded == 0)
nFeeNeeded = fallbackFee.GetFee(nTxBytes); nFeeNeeded = fallbackFee.GetFee(nTxBytes);

5
src/wallet/wallet.h

@ -73,6 +73,7 @@ class CReserveKey;
class CScript; class CScript;
class CScheduler; class CScheduler;
class CTxMemPool; class CTxMemPool;
class CBlockPolicyEstimator;
class CWalletTx; class CWalletTx;
/** (client) version numbers for particular wallet features */ /** (client) version numbers for particular wallet features */
@ -932,12 +933,12 @@ public:
* Estimate the minimum fee considering user set parameters * Estimate the minimum fee considering user set parameters
* and the required fee * and the required fee
*/ */
static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool); static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator);
/** /**
* Estimate the minimum fee considering required fee and targetFee or if 0 * Estimate the minimum fee considering required fee and targetFee or if 0
* then fee estimation for nConfirmTarget * then fee estimation for nConfirmTarget
*/ */
static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, CAmount targetFee); static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, CAmount targetFee);
/** /**
* Return the minimum required fee taking into account the * Return the minimum required fee taking into account the
* floating relay fee and user set minimum transaction fee * floating relay fee and user set minimum transaction fee

Loading…
Cancel
Save