From 5f1f0c64905491fea4ba50c0a8eb68fa59d65411 Mon Sep 17 00:00:00 2001 From: Alex Morcos Date: Fri, 10 Mar 2017 16:57:40 -0500 Subject: [PATCH] Historical block span Store in fee estimate file the block span for which we were tracking estimates, so we know what targets we can successfully evaluate with the data in the file. When restarting use either this historical block span to set valid range of targets until our current span of tracking estimates is just as long. --- src/policy/fees.cpp | 57 ++++++++++++++++++++++++++++++++++++++------- src/policy/fees.h | 5 ++++ 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index 1b8ef85282..74ed002724 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -477,7 +477,7 @@ bool CBlockPolicyEstimator::removeTx(uint256 hash, bool inBlock) } CBlockPolicyEstimator::CBlockPolicyEstimator() - : nBestSeenHeight(0), firstRecordedHeight(0), trackedTxs(0), untrackedTxs(0) + : nBestSeenHeight(0), firstRecordedHeight(0), historicalFirst(0), historicalBest(0), trackedTxs(0), untrackedTxs(0) { static_assert(MIN_BUCKET_FEERATE > 0, "Min feerate must be nonzero"); minTrackedFee = CFeeRate(MIN_BUCKET_FEERATE); @@ -609,8 +609,9 @@ void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight, } - 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()); + LogPrint(BCLog::ESTIMATEFEE, "Blockpolicy estimates updated by %u of %u block txs, since last block %u of %u tracked, mempool map size %u, max target %u from %s\n", + countedTxs, entries.size(), trackedTxs, trackedTxs + untrackedTxs, mapMemPoolTxs.size(), + MaxUsableEstimate(), HistoricalBlockSpan() > BlockSpan() ? "historical" : "current"); trackedTxs = 0; untrackedTxs = 0; @@ -663,6 +664,29 @@ CFeeRate CBlockPolicyEstimator::estimateRawFee(int confTarget, double successThr return CFeeRate(median); } +unsigned int CBlockPolicyEstimator::BlockSpan() const +{ + if (firstRecordedHeight == 0) return 0; + assert(nBestSeenHeight >= firstRecordedHeight); + + return nBestSeenHeight - firstRecordedHeight; +} + +unsigned int CBlockPolicyEstimator::HistoricalBlockSpan() const +{ + if (historicalFirst == 0) return 0; + assert(historicalBest >= historicalFirst); + + if (nBestSeenHeight - historicalBest > OLDEST_ESTIMATE_HISTORY) return 0; + + return historicalBest - historicalFirst; +} + +unsigned int CBlockPolicyEstimator::MaxUsableEstimate() const +{ + // Block spans are divided by 2 to make sure there are enough potential failing data points for the estimate + return std::min(longStats->GetMaxConfirms(), std::max(BlockSpan(), HistoricalBlockSpan()) / 2); +} /** Return a fee estimate at the required successThreshold from the shortest * time horizon which tracks confirmations up to the desired target. If @@ -731,6 +755,14 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, int *answerFoun if (confTarget == 1) confTarget = 2; + unsigned int maxUsableEstimate = MaxUsableEstimate(); + if (maxUsableEstimate <= 1) + return CFeeRate(0); + + if ((unsigned int)confTarget > maxUsableEstimate) { + confTarget = maxUsableEstimate; + } + assert(confTarget > 0); //estimateCombinedFee and estimateConservativeFee take unsigned ints /** true is passed to estimateCombined fee for target/2 and target so @@ -784,8 +816,12 @@ bool CBlockPolicyEstimator::Write(CAutoFile& fileout) const fileout << 149900; // version required to read: 0.14.99 or later fileout << CLIENT_VERSION; // version that wrote the file fileout << nBestSeenHeight; - unsigned int future1 = 0, future2 = 0; - fileout << future1 << future2; + if (BlockSpan() > HistoricalBlockSpan()/2) { + fileout << firstRecordedHeight << nBestSeenHeight; + } + else { + fileout << historicalFirst << historicalBest; + } fileout << buckets; feeStats->Write(fileout); shortStats->Write(fileout); @@ -803,7 +839,7 @@ bool CBlockPolicyEstimator::Read(CAutoFile& filein) try { LOCK(cs_feeEstimator); int nVersionRequired, nVersionThatWrote; - unsigned int nFileBestSeenHeight; + unsigned int nFileBestSeenHeight, nFileHistoricalFirst, nFileHistoricalBest; filein >> nVersionRequired >> nVersionThatWrote; if (nVersionRequired > CLIENT_VERSION) return error("CBlockPolicyEstimator::Read(): up-version (%d) fee estimate file", nVersionRequired); @@ -838,9 +874,10 @@ bool CBlockPolicyEstimator::Read(CAutoFile& filein) } } else { // nVersionThatWrote >= 149900 - unsigned int future1, future2; - filein >> future1 >> future2; - + filein >> nFileHistoricalFirst >> nFileHistoricalBest; + if (nFileHistoricalFirst > nFileHistoricalBest || nFileHistoricalBest > nFileBestSeenHeight) { + throw std::runtime_error("Corrupt estimates file. Historical block range for estimates is invalid"); + } std::vector fileBuckets; filein >> fileBuckets; size_t numBuckets = fileBuckets.size(); @@ -871,6 +908,8 @@ bool CBlockPolicyEstimator::Read(CAutoFile& filein) longStats = fileLongStats.release(); nBestSeenHeight = nFileBestSeenHeight; + historicalFirst = nFileHistoricalFirst; + historicalBest = nFileHistoricalBest; } } catch (const std::exception& e) { diff --git a/src/policy/fees.h b/src/policy/fees.h index 3184aa08ab..1f752415e7 100644 --- a/src/policy/fees.h +++ b/src/policy/fees.h @@ -98,6 +98,8 @@ private: static constexpr unsigned int MED_BLOCK_CONFIRMS = 48; /** Track confirm delays up to 1008 blocks for longer decay */ static constexpr unsigned int LONG_BLOCK_CONFIRMS = 1008; + /** Historical estimates that are older than this aren't valid */ + static const unsigned int OLDEST_ESTIMATE_HISTORY = 6 * 1008; /** Decay of .962 is a half-life of 18 blocks or about 3 hours */ static constexpr double SHORT_DECAY = .962; @@ -205,6 +207,9 @@ private: double estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon) const; double estimateConservativeFee(unsigned int doubleTarget) const; + unsigned int BlockSpan() const; + unsigned int HistoricalBlockSpan() const; + unsigned int MaxUsableEstimate() const; }; class FeeFilterRounder