Skip to content

Commit f646275

Browse files
committed
Merge #9138: Improve fee estimation
44b64b9 Fix edge case with stale fee estimates (Alex Morcos) 78ae62d Add clarifying comments to fee estimation (Alex Morcos) 5fe0f47 Add extra logging to processBlock in fee estimation. (Alex Morcos) dc008c4 Add IsCurrentForFeeEstimatation (Alex Morcos) ebafdca Pass pointers to existing CTxMemPoolEntries to fee estimation (Alex Morcos) d825838 Always update fee estimates on new blocks. (Alex Morcos) 6f06b26 rename bool to validFeeEstimate (Alex Morcos) 84f7ab0 Remove member variable hadNoDependencies from CTxMemPoolEntry (Alex Morcos) 60ac00d Don't track transactions at all during IBD. (Alex Morcos) 4df4479 Remove extraneous LogPrint from fee estimation (Alex Morcos)
2 parents c252685 + 44b64b9 commit f646275

File tree

11 files changed

+94
-76
lines changed

11 files changed

+94
-76
lines changed

src/bench/mempool_eviction.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ static void AddTx(const CTransaction& tx, const CAmount& nFee, CTxMemPool& pool)
1818
unsigned int sigOpCost = 4;
1919
LockPoints lp;
2020
pool.addUnchecked(tx.GetHash(), CTxMemPoolEntry(
21-
MakeTransactionRef(tx), nFee, nTime, dPriority, nHeight, pool.HasNoInputsOf(tx),
21+
MakeTransactionRef(tx), nFee, nTime, dPriority, nHeight,
2222
tx.GetValueOut(), spendsCoinbase, sigOpCost, lp));
2323
}
2424

src/policy/fees.cpp

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -281,23 +281,25 @@ void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHe
281281
}
282282
}
283283

284-
void CBlockPolicyEstimator::removeTx(uint256 hash)
284+
// This function is called from CTxMemPool::removeUnchecked to ensure
285+
// txs removed from the mempool for any reason are no longer
286+
// tracked. Txs that were part of a block have already been removed in
287+
// processBlockTx to ensure they are never double tracked, but it is
288+
// of no harm to try to remove them again.
289+
bool CBlockPolicyEstimator::removeTx(uint256 hash)
285290
{
286291
std::map<uint256, TxStatsInfo>::iterator pos = mapMemPoolTxs.find(hash);
287-
if (pos == mapMemPoolTxs.end()) {
288-
LogPrint("estimatefee", "Blockpolicy error mempool tx %s not found for removeTx\n",
289-
hash.ToString().c_str());
290-
return;
292+
if (pos != mapMemPoolTxs.end()) {
293+
feeStats.removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex);
294+
mapMemPoolTxs.erase(hash);
295+
return true;
296+
} else {
297+
return false;
291298
}
292-
unsigned int entryHeight = pos->second.blockHeight;
293-
unsigned int bucketIndex = pos->second.bucketIndex;
294-
295-
feeStats.removeTx(entryHeight, nBestSeenHeight, bucketIndex);
296-
mapMemPoolTxs.erase(hash);
297299
}
298300

299301
CBlockPolicyEstimator::CBlockPolicyEstimator(const CFeeRate& _minRelayFee)
300-
: nBestSeenHeight(0)
302+
: nBestSeenHeight(0), trackedTxs(0), untrackedTxs(0)
301303
{
302304
static_assert(MIN_FEERATE > 0, "Min feerate must be nonzero");
303305
minTrackedFee = _minRelayFee < CFeeRate(MIN_FEERATE) ? CFeeRate(MIN_FEERATE) : _minRelayFee;
@@ -309,7 +311,7 @@ CBlockPolicyEstimator::CBlockPolicyEstimator(const CFeeRate& _minRelayFee)
309311
feeStats.Initialize(vfeelist, MAX_BLOCK_CONFIRMS, DEFAULT_DECAY);
310312
}
311313

312-
void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool fCurrentEstimate)
314+
void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate)
313315
{
314316
unsigned int txHeight = entry.GetHeight();
315317
uint256 hash = entry.GetTx().GetHash();
@@ -319,23 +321,21 @@ void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, boo
319321
return;
320322
}
321323

322-
if (txHeight < nBestSeenHeight) {
324+
if (txHeight != nBestSeenHeight) {
323325
// Ignore side chains and re-orgs; assuming they are random they don't
324326
// affect the estimate. We'll potentially double count transactions in 1-block reorgs.
327+
// Ignore txs if BlockPolicyEstimator is not in sync with chainActive.Tip().
328+
// It will be synced next time a block is processed.
325329
return;
326330
}
327331

328332
// Only want to be updating estimates when our blockchain is synced,
329333
// otherwise we'll miscalculate how many blocks its taking to get included.
330-
if (!fCurrentEstimate)
331-
return;
332-
333-
if (!entry.WasClearAtEntry()) {
334-
// This transaction depends on other transactions in the mempool to
335-
// be included in a block before it will be able to be included, so
336-
// we shouldn't include it in our calculations
334+
if (!validFeeEstimate) {
335+
untrackedTxs++;
337336
return;
338337
}
338+
trackedTxs++;
339339

340340
// Feerates are stored and reported as BTC-per-kb:
341341
CFeeRate feeRate(entry.GetFee(), entry.GetTxSize());
@@ -344,34 +344,33 @@ void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, boo
344344
mapMemPoolTxs[hash].bucketIndex = feeStats.NewTx(txHeight, (double)feeRate.GetFeePerK());
345345
}
346346

347-
void CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry& entry)
347+
bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry)
348348
{
349-
if (!entry.WasClearAtEntry()) {
350-
// This transaction depended on other transactions in the mempool to
351-
// be included in a block before it was able to be included, so
352-
// we shouldn't include it in our calculations
353-
return;
349+
if (!removeTx(entry->GetTx().GetHash())) {
350+
// This transaction wasn't being tracked for fee estimation
351+
return false;
354352
}
355353

356354
// How many blocks did it take for miners to include this transaction?
357355
// blocksToConfirm is 1-based, so a transaction included in the earliest
358356
// possible block has confirmation count of 1
359-
int blocksToConfirm = nBlockHeight - entry.GetHeight();
357+
int blocksToConfirm = nBlockHeight - entry->GetHeight();
360358
if (blocksToConfirm <= 0) {
361359
// This can't happen because we don't process transactions from a block with a height
362360
// lower than our greatest seen height
363361
LogPrint("estimatefee", "Blockpolicy error Transaction had negative blocksToConfirm\n");
364-
return;
362+
return false;
365363
}
366364

367365
// Feerates are stored and reported as BTC-per-kb:
368-
CFeeRate feeRate(entry.GetFee(), entry.GetTxSize());
366+
CFeeRate feeRate(entry->GetFee(), entry->GetTxSize());
369367

370368
feeStats.Record(blocksToConfirm, (double)feeRate.GetFeePerK());
369+
return true;
371370
}
372371

373372
void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
374-
std::vector<CTxMemPoolEntry>& entries, bool fCurrentEstimate)
373+
std::vector<const CTxMemPoolEntry*>& entries)
375374
{
376375
if (nBlockHeight <= nBestSeenHeight) {
377376
// Ignore side chains and re-orgs; assuming they are random
@@ -381,25 +380,30 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight,
381380
// transaction fees."
382381
return;
383382
}
384-
nBestSeenHeight = nBlockHeight;
385383

386-
// Only want to be updating estimates when our blockchain is synced,
387-
// otherwise we'll miscalculate how many blocks its taking to get included.
388-
if (!fCurrentEstimate)
389-
return;
384+
// Must update nBestSeenHeight in sync with ClearCurrent so that
385+
// calls to removeTx (via processBlockTx) correctly calculate age
386+
// of unconfirmed txs to remove from tracking.
387+
nBestSeenHeight = nBlockHeight;
390388

391-
// Clear the current block state
389+
// Clear the current block state and update unconfirmed circular buffer
392390
feeStats.ClearCurrent(nBlockHeight);
393391

392+
unsigned int countedTxs = 0;
394393
// Repopulate the current block states
395-
for (unsigned int i = 0; i < entries.size(); i++)
396-
processBlockTx(nBlockHeight, entries[i]);
394+
for (unsigned int i = 0; i < entries.size(); i++) {
395+
if (processBlockTx(nBlockHeight, entries[i]))
396+
countedTxs++;
397+
}
397398

398399
// Update all exponential averages with the current block state
399400
feeStats.UpdateMovingAverages();
400401

401-
LogPrint("estimatefee", "Blockpolicy after updating estimates for %u confirmed entries, new mempool map size %u\n",
402-
entries.size(), mapMemPoolTxs.size());
402+
LogPrint("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",
403+
countedTxs, entries.size(), trackedTxs, trackedTxs + untrackedTxs, mapMemPoolTxs.size());
404+
405+
trackedTxs = 0;
406+
untrackedTxs = 0;
403407
}
404408

405409
CFeeRate CBlockPolicyEstimator::estimateFee(int confTarget)

src/policy/fees.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,16 +203,16 @@ class CBlockPolicyEstimator
203203

204204
/** Process all the transactions that have been included in a block */
205205
void processBlock(unsigned int nBlockHeight,
206-
std::vector<CTxMemPoolEntry>& entries, bool fCurrentEstimate);
206+
std::vector<const CTxMemPoolEntry*>& entries);
207207

208208
/** Process a transaction confirmed in a block*/
209-
void processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry& entry);
209+
bool processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry);
210210

211211
/** Process a transaction accepted to the mempool*/
212-
void processTransaction(const CTxMemPoolEntry& entry, bool fCurrentEstimate);
212+
void processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate);
213213

214214
/** Remove a transaction from the mempool tracking stats*/
215-
void removeTx(uint256 hash);
215+
bool removeTx(uint256 hash);
216216

217217
/** Return a feerate estimate */
218218
CFeeRate estimateFee(int confTarget);
@@ -258,6 +258,9 @@ class CBlockPolicyEstimator
258258

259259
/** Classes to track historical data on transaction confirmations */
260260
TxConfirmStats feeStats;
261+
262+
unsigned int trackedTxs;
263+
unsigned int untrackedTxs;
261264
};
262265

263266
class FeeFilterRounder

src/test/mempool_tests.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ BOOST_AUTO_TEST_CASE(MempoolIndexingTest)
120120
{
121121
CTxMemPool pool(CFeeRate(0));
122122
TestMemPoolEntryHelper entry;
123-
entry.hadNoDependencies = true;
124123

125124
/* 3rd highest fee */
126125
CMutableTransaction tx1 = CMutableTransaction();
@@ -323,7 +322,6 @@ BOOST_AUTO_TEST_CASE(MempoolAncestorIndexingTest)
323322
{
324323
CTxMemPool pool(CFeeRate(0));
325324
TestMemPoolEntryHelper entry;
326-
entry.hadNoDependencies = true;
327325

328326
/* 3rd highest fee */
329327
CMutableTransaction tx1 = CMutableTransaction();

src/test/test_bitcoin.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,11 @@ CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction &tx, CT
147147
}
148148

149149
CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransaction &txn, CTxMemPool *pool) {
150-
bool hasNoDependencies = pool ? pool->HasNoInputsOf(txn) : hadNoDependencies;
151150
// Hack to assume either its completely dependent on other mempool txs or not at all
152-
CAmount inChainValue = hasNoDependencies ? txn.GetValueOut() : 0;
151+
CAmount inChainValue = pool && pool->HasNoInputsOf(txn) ? txn.GetValueOut() : 0;
153152

154153
return CTxMemPoolEntry(MakeTransactionRef(txn), nFee, nTime, dPriority, nHeight,
155-
hasNoDependencies, inChainValue, spendsCoinbase, sigOpCost, lp);
154+
inChainValue, spendsCoinbase, sigOpCost, lp);
156155
}
157156

158157
void Shutdown(void* parg)

src/test/test_bitcoin.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,13 @@ struct TestMemPoolEntryHelper
7070
int64_t nTime;
7171
double dPriority;
7272
unsigned int nHeight;
73-
bool hadNoDependencies;
7473
bool spendsCoinbase;
7574
unsigned int sigOpCost;
7675
LockPoints lp;
7776

7877
TestMemPoolEntryHelper() :
7978
nFee(0), nTime(0), dPriority(0.0), nHeight(1),
80-
hadNoDependencies(false), spendsCoinbase(false), sigOpCost(4) { }
79+
spendsCoinbase(false), sigOpCost(4) { }
8180

8281
CTxMemPoolEntry FromTx(const CMutableTransaction &tx, CTxMemPool *pool = NULL);
8382
CTxMemPoolEntry FromTx(const CTransaction &tx, CTxMemPool *pool = NULL);
@@ -87,7 +86,6 @@ struct TestMemPoolEntryHelper
8786
TestMemPoolEntryHelper &Time(int64_t _time) { nTime = _time; return *this; }
8887
TestMemPoolEntryHelper &Priority(double _priority) { dPriority = _priority; return *this; }
8988
TestMemPoolEntryHelper &Height(unsigned int _height) { nHeight = _height; return *this; }
90-
TestMemPoolEntryHelper &HadNoDependencies(bool _hnd) { hadNoDependencies = _hnd; return *this; }
9189
TestMemPoolEntryHelper &SpendsCoinbase(bool _flag) { spendsCoinbase = _flag; return *this; }
9290
TestMemPoolEntryHelper &SigOpsCost(unsigned int _sigopsCost) { sigOpCost = _sigopsCost; return *this; }
9391
};

src/txmempool.cpp

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ using namespace std;
2222

2323
CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee,
2424
int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
25-
bool poolHasNoInputsOf, CAmount _inChainInputValue,
25+
CAmount _inChainInputValue,
2626
bool _spendsCoinbase, int64_t _sigOpsCost, LockPoints lp):
2727
tx(_tx), nFee(_nFee), nTime(_nTime), entryPriority(_entryPriority), entryHeight(_entryHeight),
28-
hadNoDependencies(poolHasNoInputsOf), inChainInputValue(_inChainInputValue),
28+
inChainInputValue(_inChainInputValue),
2929
spendsCoinbase(_spendsCoinbase), sigOpCost(_sigOpsCost), lockPoints(lp)
3030
{
3131
nTxWeight = GetTransactionWeight(*tx);
@@ -392,7 +392,7 @@ void CTxMemPool::AddTransactionsUpdated(unsigned int n)
392392
nTransactionsUpdated += n;
393393
}
394394

395-
bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, setEntries &setAncestors, bool fCurrentEstimate)
395+
bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, setEntries &setAncestors, bool validFeeEstimate)
396396
{
397397
// Add to memory pool without checking anything.
398398
// Used by main.cpp AcceptToMemoryPool(), which DOES do
@@ -442,7 +442,7 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
442442

443443
nTransactionsUpdated++;
444444
totalTxSize += entry.GetTxSize();
445-
minerPolicyEstimator->processTransaction(entry, fCurrentEstimate);
445+
minerPolicyEstimator->processTransaction(entry, validFeeEstimate);
446446

447447
vTxHashes.emplace_back(tx.GetWitnessHash(), newit);
448448
newit->vTxHashesIdx = vTxHashes.size() - 1;
@@ -591,19 +591,20 @@ void CTxMemPool::removeConflicts(const CTransaction &tx)
591591
/**
592592
* Called when a block is connected. Removes from mempool and updates the miner fee estimator.
593593
*/
594-
void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight,
595-
bool fCurrentEstimate)
594+
void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight)
596595
{
597596
LOCK(cs);
598-
std::vector<CTxMemPoolEntry> entries;
597+
std::vector<const CTxMemPoolEntry*> entries;
599598
for (const auto& tx : vtx)
600599
{
601600
uint256 hash = tx->GetHash();
602601

603602
indexed_transaction_set::iterator i = mapTx.find(hash);
604603
if (i != mapTx.end())
605-
entries.push_back(*i);
604+
entries.push_back(&*i);
606605
}
606+
// Before the txs in the new block have been removed from the mempool, update policy estimates
607+
minerPolicyEstimator->processBlock(nBlockHeight, entries);
607608
for (const auto& tx : vtx)
608609
{
609610
txiter it = mapTx.find(tx->GetHash());
@@ -615,8 +616,6 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
615616
removeConflicts(*tx);
616617
ClearPrioritisation(tx->GetHash());
617618
}
618-
// After the txs in the new block have been removed from the mempool, update policy estimates
619-
minerPolicyEstimator->processBlock(nBlockHeight, entries, fCurrentEstimate);
620619
lastRollingFeeUpdate = GetTime();
621620
blockSinceLastRollingFeeBump = true;
622621
}
@@ -1015,14 +1014,14 @@ int CTxMemPool::Expire(int64_t time) {
10151014
return stage.size();
10161015
}
10171016

1018-
bool CTxMemPool::addUnchecked(const uint256&hash, const CTxMemPoolEntry &entry, bool fCurrentEstimate)
1017+
bool CTxMemPool::addUnchecked(const uint256&hash, const CTxMemPoolEntry &entry, bool validFeeEstimate)
10191018
{
10201019
LOCK(cs);
10211020
setEntries setAncestors;
10221021
uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();
10231022
std::string dummy;
10241023
CalculateMemPoolAncestors(entry, setAncestors, nNoLimit, nNoLimit, nNoLimit, nNoLimit, dummy);
1025-
return addUnchecked(hash, entry, setAncestors, fCurrentEstimate);
1024+
return addUnchecked(hash, entry, setAncestors, validFeeEstimate);
10261025
}
10271026

10281027
void CTxMemPool::UpdateChild(txiter entry, txiter child, bool add)

src/txmempool.h

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ class CTxMemPoolEntry
8888
int64_t nTime; //!< Local time when entering the mempool
8989
double entryPriority; //!< Priority when entering the mempool
9090
unsigned int entryHeight; //!< Chain height when entering the mempool
91-
bool hadNoDependencies; //!< Not dependent on any other txs when it entered the mempool
9291
CAmount inChainInputValue; //!< Sum of all txin values that are already in blockchain
9392
bool spendsCoinbase; //!< keep track of transactions that spend a coinbase
9493
int64_t sigOpCost; //!< Total sigop cost
@@ -113,7 +112,7 @@ class CTxMemPoolEntry
113112
public:
114113
CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee,
115114
int64_t _nTime, double _entryPriority, unsigned int _entryHeight,
116-
bool poolHasNoInputsOf, CAmount _inChainInputValue, bool spendsCoinbase,
115+
CAmount _inChainInputValue, bool spendsCoinbase,
117116
int64_t nSigOpsCost, LockPoints lp);
118117

119118
CTxMemPoolEntry(const CTxMemPoolEntry& other);
@@ -130,7 +129,6 @@ class CTxMemPoolEntry
130129
size_t GetTxWeight() const { return nTxWeight; }
131130
int64_t GetTime() const { return nTime; }
132131
unsigned int GetHeight() const { return entryHeight; }
133-
bool WasClearAtEntry() const { return hadNoDependencies; }
134132
int64_t GetSigOpCost() const { return sigOpCost; }
135133
int64_t GetModifiedFee() const { return nFee + feeDelta; }
136134
size_t DynamicMemoryUsage() const { return nUsageSize; }
@@ -525,14 +523,13 @@ class CTxMemPool
525523
// to track size/count of descendant transactions. First version of
526524
// addUnchecked can be used to have it call CalculateMemPoolAncestors(), and
527525
// then invoke the second version.
528-
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool fCurrentEstimate = true);
529-
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, setEntries &setAncestors, bool fCurrentEstimate = true);
526+
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool validFeeEstimate = true);
527+
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, setEntries &setAncestors, bool validFeeEstimate = true);
530528

531529
void removeRecursive(const CTransaction &tx);
532530
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags);
533531
void removeConflicts(const CTransaction &tx);
534-
void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight,
535-
bool fCurrentEstimate = true);
532+
void removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight);
536533
void clear();
537534
void _clear(); //lock free
538535
bool CompareDepthAndScore(const uint256& hasha, const uint256& hashb);

0 commit comments

Comments
 (0)