Branch data Line data Source code
1 : : // Copyright (c) The Bitcoin Core developers
2 : : // Distributed under the MIT software license. See the accompanying
3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 : :
5 : : #include <policy/fees/mempool_estimator.h>
6 : :
7 : : #include <logging.h>
8 : : #include <node/miner.h>
9 : : #include <policy/feerate.h>
10 : : #include <policy/policy.h>
11 : : #include <primitives/block.h>
12 : : #include <serialize.h>
13 : : #include <streams.h>
14 : : #include <sync.h>
15 : : #include <tinyformat.h>
16 : : #include <txmempool.h>
17 : : #include <util/check.h>
18 : : #include <util/feefrac.h>
19 : : #include <util/fees.h>
20 : : #include <util/fs.h>
21 : : #include <util/syserror.h>
22 : : #include <validation.h>
23 : :
24 : : #include <algorithm>
25 : : #include <iterator>
26 : : #include <numeric>
27 : : #include <optional>
28 : : #include <string>
29 : : #include <string_view>
30 : : #include <system_error>
31 : : #include <utility>
32 : :
33 : : constexpr int CURRENT_MEMPOOL_ESTIMATOR_VERSION{1};
34 : :
35 : : namespace {
36 : : struct MinedBlockStatsFormatter {
37 : : template <typename Stream>
38 : 0 : void Ser(Stream& s, const MinedBlockStats& v)
39 : : {
40 : 0 : s << v.m_height << v.m_removed_block_txs_weight << v.m_block_weight;
41 : 0 : }
42 : : template <typename Stream>
43 : 0 : void Unser(Stream& s, MinedBlockStats& v)
44 : : {
45 : 0 : s >> v.m_height >> v.m_removed_block_txs_weight >> v.m_block_weight;
46 : 0 : }
47 : : };
48 : :
49 : 28 : void AddMinedBlockStats(std::vector<MinedBlockStats>& mined_blocks, MinedBlockStats stats)
50 : : {
51 : 28 : const auto stale_begin{std::find_if(mined_blocks.begin(), mined_blocks.end(), [&](const MinedBlockStats& block) {
52 [ + + ]: 109 : return block.m_height >= stats.m_height;
53 : : })};
54 [ + + ]: 28 : const auto stale_count{std::distance(stale_begin, mined_blocks.end())};
55 [ + + ]: 28 : if (stale_count > 0) {
56 [ + - ]: 2 : LogDebug(BCLog::ESTIMATEFEE,
57 : : "%s: connected block height=%s discards tracked mined-block stats "
58 : : "from height=%s to height=%s; stale_stats=%s",
59 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),
60 : : stats.m_height,
61 : : stale_begin->m_height,
62 : : mined_blocks.back().m_height,
63 : : stale_count);
64 : : }
65 : 28 : mined_blocks.erase(stale_begin, mined_blocks.end());
66 [ + + + + ]: 28 : if (!mined_blocks.empty() && mined_blocks.back().m_height + 1 != stats.m_height) {
67 [ + - - + ]: 1 : LogDebug(BCLog::ESTIMATEFEE,
68 : : "%s: clearing mined-block stats after height gap; tracked_stats=%s "
69 : : "expected_height=%s received_height=%s",
70 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),
71 : : mined_blocks.size(),
72 : : mined_blocks.back().m_height + 1,
73 : : stats.m_height);
74 [ + - ]: 1 : mined_blocks.clear();
75 : : }
76 : :
77 [ - + + + ]: 28 : if (mined_blocks.size() == MEMPOOL_HEALTH_WINDOW_BLOCKS) mined_blocks.erase(mined_blocks.begin());
78 : 28 : mined_blocks.push_back(stats);
79 : 28 : }
80 : :
81 : : struct ActiveTip {
82 : : int height;
83 : : uint256 hash;
84 : : };
85 : :
86 : 0 : std::optional<ActiveTip> GetActiveTip(const ChainstateManager& chainman)
87 : : {
88 : 0 : LOCK(::cs_main);
89 [ # # ]: 0 : const CBlockIndex* tip{chainman.ActiveTip()};
90 [ # # ]: 0 : if (!tip) return std::nullopt;
91 : 0 : return ActiveTip{tip->nHeight, tip->GetBlockHash()};
92 : 0 : }
93 : : } // namespace
94 : :
95 : 9 : MemPoolFeeRateEstimator::Percentiles MemPoolFeeRateEstimator::CalculateMaxWeightPercentiles(std::span<const FeePerVSize> chunk_feerates)
96 : : {
97 [ - + ]: 40316 : Assume(std::is_sorted(chunk_feerates.begin(), chunk_feerates.end(), [](const auto& a, const auto& b) { return ByRatio{a} > ByRatio{b}; }));
98 : 9 : constexpr int64_t total_weight{DEFAULT_BLOCK_MAX_WEIGHT};
99 : 9 : const int64_t p50_weight{total_weight / 2};
100 : 9 : const int64_t p75_weight{total_weight * 3 / 4};
101 : 9 : Percentiles percentiles{};
102 : 9 : int64_t accumulated_weight{0};
103 [ + + ]: 36319 : for (const auto& curr_feerate : chunk_feerates) {
104 : 36313 : accumulated_weight += int64_t{curr_feerate.size} * WITNESS_SCALE_FACTOR;
105 [ + + + + ]: 36313 : if (accumulated_weight >= p50_weight && percentiles.p50.IsEmpty()) {
106 : 6 : percentiles.p50 = curr_feerate;
107 : : }
108 [ + + ]: 36313 : if (accumulated_weight >= p75_weight && percentiles.p75.IsEmpty()) {
109 : 3 : percentiles.p75 = curr_feerate;
110 : 3 : break;
111 : : }
112 : : }
113 : 9 : return percentiles;
114 : : }
115 : :
116 : 18 : bool MemPoolFeeRateEstimatorCache::IsStale() const
117 : : {
118 [ + + + + ]: 18 : return !m_fee_rate_estimation || (m_last_updated + CACHE_LIFE) < NodeClock::now();
119 : : }
120 : :
121 : : std::optional<MemPoolFeeRateEstimatorCache::FeeRateEstimate>
122 : 15 : MemPoolFeeRateEstimatorCache::GetCachedEstimate(const uint256& tip_hash) const
123 : : {
124 [ + + + + ]: 15 : if (IsStale() || tip_hash != m_tip_hash) return std::nullopt;
125 : 7 : return m_fee_rate_estimation;
126 : : }
127 : :
128 : 6 : void MemPoolFeeRateEstimatorCache::Update(FeePerVSize conservative, FeePerVSize economical, const uint256& tip_hash)
129 : : {
130 [ + + ]: 6 : m_fee_rate_estimation = {conservative, economical};
131 : 6 : m_tip_hash = tip_hash;
132 : 6 : m_last_updated = NodeClock::now();
133 : 6 : }
134 : :
135 : 28 : void MemPoolFeeRateEstimatorCache::Clear()
136 : : {
137 [ - + ]: 28 : m_fee_rate_estimation.reset();
138 : 28 : m_tip_hash.SetNull();
139 : 28 : m_last_updated = {};
140 : 28 : }
141 : :
142 : : //! Build the error result for a failed mempool fee rate estimation.
143 : 2 : static util::Unexpected<FeeRateEstimationError> EstimationError(std::string error)
144 : : {
145 : 2 : return EstimationError(FeeRateEstimatorType::MEMPOOL_POLICY, MEMPOOL_FEE_ESTIMATOR_MAX_TARGET, std::move(error));
146 : : }
147 : :
148 : 12 : static std::optional<std::string_view> MempoolHealthError(MemPoolFeeRateEstimator::MempoolHealth health)
149 : : {
150 [ + - + - ]: 12 : switch (health) {
151 : 1 : case MemPoolFeeRateEstimator::MempoolHealth::INSUFFICIENT_DATA:
152 : 1 : return "Not enough recent block data for fee rate estimation";
153 : 0 : case MemPoolFeeRateEstimator::MempoolHealth::LOW_COVERAGE:
154 : 0 : return "Mempool is unreliable for fee rate estimation";
155 : 11 : case MemPoolFeeRateEstimator::MempoolHealth::HEALTHY:
156 : 11 : return std::nullopt;
157 : : }
158 : 0 : Assume(false);
159 : 0 : return std::nullopt;
160 : : }
161 : :
162 : 3 : MemPoolFeeRateEstimator::MemPoolFeeRateEstimator(fs::path mempool_estimator_file_path,
163 : : const CTxMemPool& mempool,
164 : 3 : ChainstateManager& chainman)
165 : 3 : : m_mempool(mempool),
166 : 3 : m_chainman(chainman),
167 : 3 : m_mempool_estimator_file_path(std::move(mempool_estimator_file_path))
168 : : {
169 [ + - ]: 3 : ReadFromDisk();
170 : 3 : }
171 : :
172 : 3 : void MemPoolFeeRateEstimator::ReadFromDisk()
173 : : {
174 : 6 : AutoFile file{fsbridge::fopen(m_mempool_estimator_file_path, "rb")};
175 [ + - ]: 3 : if (file.IsNull()) {
176 [ + - + - : 6 : LogDebug(BCLog::ESTIMATEFEE, "%s: %s does not exist. Continuing anyway",
- + + - +
- ]
177 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),
178 : : fs::PathToString(m_mempool_estimator_file_path));
179 : 3 : return;
180 : : }
181 [ # # # # ]: 0 : if (Read(file)) {
182 [ # # # # : 0 : LogDebug(BCLog::ESTIMATEFEE, "%s: mined-block stats successfully read from %s.",
# # # # #
# ]
183 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),
184 : : fs::PathToString(m_mempool_estimator_file_path));
185 : : }
186 : 3 : }
187 : :
188 : 0 : bool MemPoolFeeRateEstimator::Read(AutoFile& file)
189 : : {
190 : 0 : try {
191 : 0 : int version_required;
192 [ # # ]: 0 : file >> version_required;
193 [ # # ]: 0 : if (version_required != CURRENT_MEMPOOL_ESTIMATOR_VERSION) {
194 [ # # # # ]: 0 : LogWarning("%s: file version not supported; continuing anyway",
195 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY));
196 : : return false;
197 : : }
198 : : // Stage into a local buffer and commit to the member only after validation passes.
199 : 0 : std::vector<MinedBlockStats> blocks;
200 [ # # ]: 0 : file >> Using<VectorFormatter<MinedBlockStatsFormatter>>(blocks);
201 : 0 : uint256 tip_hash;
202 [ # # ]: 0 : file >> tip_hash;
203 [ # # # # ]: 0 : if (blocks.size() > MEMPOOL_HEALTH_WINDOW_BLOCKS) {
204 [ # # # # ]: 0 : LogWarning("%s: Number of previously mined blocks read exceeds the maximum of %s; ignoring file",
205 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),
206 : : MEMPOOL_HEALTH_WINDOW_BLOCKS);
207 : : return false;
208 : : }
209 [ # # ]: 0 : for (size_t i = 1; i < blocks.size(); ++i) {
210 [ # # ]: 0 : if (blocks[i].m_height != blocks[i - 1].m_height + 1) {
211 [ # # # # ]: 0 : LogWarning("%s: Non-consecutive block heights read, expected height %s but found %s; ignoring file",
212 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),
213 : : blocks[i - 1].m_height + 1, blocks[i].m_height);
214 : : return false;
215 : : }
216 : : }
217 [ # # ]: 0 : if (!blocks.empty()) {
218 : 0 : const auto& last_block{blocks.back()};
219 [ # # ]: 0 : const std::optional<ActiveTip> active_tip{GetActiveTip(m_chainman)};
220 [ # # ]: 0 : if (!active_tip) {
221 [ # # # # : 0 : LogWarning("%s: Mined-block stats read end at height %s block %s, but there is no active chain tip; ignoring file",
# # ]
222 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),
223 : : last_block.m_height, tip_hash.ToString());
224 : 0 : return false;
225 : : }
226 [ # # # # ]: 0 : if (last_block.m_height != static_cast<uint64_t>(active_tip->height) || tip_hash != active_tip->hash) {
227 [ # # # # : 0 : LogWarning("%s: Mined-block stats read end at height %s block %s, but the active chain tip is height %s block %s; ignoring file",
# # # # ]
228 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),
229 : : last_block.m_height, tip_hash.ToString(),
230 : : active_tip->height, active_tip->hash.ToString());
231 : 0 : return false;
232 : : }
233 : : }
234 [ # # ]: 0 : LOCK(cs);
235 : 0 : m_prev_mined_blocks = std::move(blocks);
236 : 0 : m_mined_blocks_tip_hash = tip_hash;
237 [ # # ]: 0 : m_cache.Clear();
238 [ # # ]: 0 : } catch (const std::exception&) {
239 [ - - - - ]: 0 : LogWarning("%s: Unable to read mined-block stats from stream (non-fatal)",
240 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY));
241 : 0 : return false;
242 : 0 : }
243 : 0 : return true;
244 : : }
245 : :
246 : 1 : bool MemPoolFeeRateEstimator::Write(AutoFile& file) const
247 : : {
248 : 1 : try {
249 [ + - ]: 1 : LOCK(cs);
250 [ + - ]: 1 : file << CURRENT_MEMPOOL_ESTIMATOR_VERSION;
251 [ + - ]: 1 : file << Using<VectorFormatter<MinedBlockStatsFormatter>>(m_prev_mined_blocks);
252 [ + - + - ]: 2 : file << m_mined_blocks_tip_hash;
253 [ - - ]: 0 : } catch (const std::exception&) {
254 : 0 : return false;
255 : 0 : }
256 : 1 : return true;
257 : : }
258 : :
259 : 1 : void MemPoolFeeRateEstimator::FlushMinedBlockStats()
260 : : {
261 [ + - ]: 2 : if (!m_mempool_estimator_file_path.parent_path().empty()) {
262 : 1 : std::error_code error;
263 [ + - ]: 1 : fs::create_directories(m_mempool_estimator_file_path.parent_path(), error);
264 [ - + ]: 1 : if (error) {
265 [ # # # # : 0 : LogWarning("%s: failed to create mempool policy estimator directory %s: %s. Continuing anyway",
# # # # ]
266 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),
267 : : fs::PathToString(m_mempool_estimator_file_path.parent_path()), error.message());
268 : 0 : return;
269 : : }
270 : : }
271 : 2 : AutoFile file{fsbridge::fopen(m_mempool_estimator_file_path, "wb")};
272 [ - + ]: 1 : if (file.IsNull()) {
273 [ # # # # : 0 : LogWarning("%s: unable to open %s for writing. Continuing anyway",
# # ]
274 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),
275 : : fs::PathToString(m_mempool_estimator_file_path));
276 : 0 : return;
277 : : }
278 [ + - - + ]: 1 : if (!Write(file)) {
279 [ # # # # : 0 : LogWarning("%s: Unable to write mined-block stats to %s (non-fatal)",
# # ]
280 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),
281 : : fs::PathToString(m_mempool_estimator_file_path));
282 : : }
283 [ + - - + ]: 2 : if (file.fclose() != 0) {
284 [ # # # # : 0 : LogWarning("Failed to close mempool policy estimator file %s: %s. Continuing anyway.",
# # ]
285 : : fs::PathToString(m_mempool_estimator_file_path), SysErrorString(errno));
286 : 0 : return;
287 : : }
288 [ + - + - : 2 : LogDebug(BCLog::ESTIMATEFEE, "%s: mined-block stats flushed to %s.",
- + + - +
- ]
289 : : FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),
290 : : fs::PathToString(m_mempool_estimator_file_path));
291 : 1 : }
292 : :
293 : :
294 : 28 : void MemPoolFeeRateEstimator::MempoolTxsRemovedForBlock(const std::shared_ptr<const CBlock>& block,
295 : : const std::vector<RemovedMempoolTransactionInfo>& txs_removed_for_block,
296 : : unsigned int block_height)
297 : : {
298 : 28 : LOCK(cs);
299 [ - + ]: 28 : Assert(!block->vtx.empty());
300 : : // Accumulate total block weight and removed mempool tx weight, both excluding the coinbase.
301 : 326626 : const auto get_tx_weight = [](const CTransactionRef& tx) {
302 : 653196 : return static_cast<uint64_t>(GetTransactionWeight(*tx));
303 : : };
304 : : // Skip vtx[0], which is the coinbase.
305 : 28 : const uint64_t block_weight = std::accumulate(std::next(block->vtx.begin()), block->vtx.end(), uint64_t{0},
306 : 169348 : [&](uint64_t acc, const CTransactionRef& tx) {
307 : 169348 : return acc + get_tx_weight(tx);
308 : : });
309 : 28 : const uint64_t removed_weight = std::accumulate(
310 : : txs_removed_for_block.begin(), txs_removed_for_block.end(), uint64_t{0},
311 : 157250 : [&](uint64_t acc, const RemovedMempoolTransactionInfo& tx) {
312 : 157250 : return acc + get_tx_weight(tx.info.m_tx);
313 : : });
314 [ + - ]: 28 : AddMinedBlockStats(m_prev_mined_blocks, {block_height, removed_weight, block_weight});
315 [ + - ]: 28 : m_mined_blocks_tip_hash = block->GetHash();
316 [ + - ]: 28 : m_cache.Clear();
317 : 28 : }
318 : :
319 : : // Require at least one block worth of activity across the window before using
320 : : // the coverage ratio as a representative mempool health signal.
321 : : static constexpr uint64_t MIN_REPRESENTATIVE_WINDOW_WEIGHT{DEFAULT_BLOCK_MAX_WEIGHT};
322 : :
323 : 42 : MemPoolFeeRateEstimator::MempoolHealth MemPoolFeeRateEstimator::GetMempoolHealth() const
324 : : {
325 : 42 : LOCK(cs);
326 [ + - ]: 42 : const auto estimator_name{FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY)};
327 [ - + + + ]: 42 : if (m_prev_mined_blocks.size() < MEMPOOL_HEALTH_WINDOW_BLOCKS) {
328 [ + - + - : 19 : LogDebug(BCLog::ESTIMATEFEE, "%s: mempool health check failed; tracked_blocks=%s required_blocks=%s",
- + + - ]
329 : : estimator_name, m_prev_mined_blocks.size(), MEMPOOL_HEALTH_WINDOW_BLOCKS);
330 : 19 : return MempoolHealth::INSUFFICIENT_DATA;
331 : : }
332 : 23 : uint64_t total_block_weight{0};
333 : 23 : uint64_t total_removed_weight{0};
334 : 23 : uint64_t expected_height{m_prev_mined_blocks.front().m_height};
335 [ + + ]: 161 : for (const auto& block : m_prev_mined_blocks) {
336 : 138 : Assume(block.m_height == expected_height);
337 : 138 : ++expected_height;
338 : 138 : total_block_weight += block.m_block_weight;
339 : 138 : total_removed_weight += block.m_removed_block_txs_weight;
340 : : }
341 : : // Too little block activity for the coverage ratio to be meaningful; skip it.
342 [ + + ]: 23 : if (total_block_weight < MIN_REPRESENTATIVE_WINDOW_WEIGHT) {
343 [ + - + - : 1 : LogDebug(BCLog::ESTIMATEFEE, "%s: mempool health check passed; low activity, total_block_weight=%s minimum=%s",
+ - ]
344 : : estimator_name, total_block_weight, MIN_REPRESENTATIVE_WINDOW_WEIGHT);
345 : 1 : return MempoolHealth::HEALTHY;
346 : : }
347 : 22 : const double representation_ratio = static_cast<double>(total_removed_weight) / total_block_weight;
348 [ + - + - : 25 : LogDebug(BCLog::ESTIMATEFEE,
+ + + - ]
349 : : "%s: mempool health check %s; removed_weight=%s total_block_weight=%s "
350 : : "coverage=%.2f required_coverage=%.2f",
351 : : estimator_name,
352 : : representation_ratio >= MEMPOOL_REPRESENTATION_THRESHOLD ? "passed" : "failed",
353 : : total_removed_weight,
354 : : total_block_weight,
355 : : representation_ratio,
356 : : MEMPOOL_REPRESENTATION_THRESHOLD);
357 [ + + ]: 22 : return representation_ratio >= MEMPOOL_REPRESENTATION_THRESHOLD ? MempoolHealth::HEALTHY : MempoolHealth::LOW_COVERAGE;
358 : 42 : }
359 : :
360 : 13 : util::Expected<FeeRateEstimation, FeeRateEstimationError> MemPoolFeeRateEstimator::EstimateFeeRate(bool conservative) const
361 : : {
362 : 13 : constexpr auto estimator_type{FeeRateEstimatorType::MEMPOOL_POLICY};
363 [ + + ]: 13 : if (!m_mempool.GetLoadTried()) {
364 : 1 : return EstimationError(strprintf("%s: Mempool not loaded yet, no fee rate estimate available", FeeRateEstimatorTypeToString(estimator_type)));
365 : : }
366 [ + + ]: 12 : if (auto error{MempoolHealthError(GetMempoolHealth())}) {
367 : 1 : return EstimationError(strprintf("%s: %s", FeeRateEstimatorTypeToString(estimator_type), *error));
368 : : }
369 : : // The estimator lock is not held while building a block template, so
370 : : // in a rare edge case concurrent callers may duplicate work.
371 : : //
372 : : // Cached fee rate estimates are tagged with the chain tip they were computed on
373 : : // and only served from the cache while that tip is current.
374 : : //
375 : : // The fee rate estimate returned directly below may still reflect a tip that went
376 : : // stale during the call; that is an accepted tradeoff of not holding
377 : : // locks across block assembly.
378 : 11 : {
379 [ - + + - ]: 33 : const uint256 tip_hash{WITH_LOCK(::cs_main, return Assume(m_chainman.CurrentChainstate().m_chain.Tip())->GetBlockHash())};
380 : 11 : LOCK(cs);
381 [ + - ]: 11 : const auto cached_estimate = m_cache.GetCachedEstimate(tip_hash);
382 [ + + ]: 11 : if (cached_estimate) {
383 : 6 : const auto cached_feerate{
384 [ + + ]: 6 : conservative ? cached_estimate->m_conservative : cached_estimate->m_economical};
385 [ + - ]: 6 : return FeeRateEstimation{estimator_type, cached_feerate, MEMPOOL_FEE_ESTIMATOR_MAX_TARGET};
386 : : }
387 : 6 : }
388 : 5 : node::BlockCreateOptions options;
389 : 5 : options.test_block_validity = false;
390 [ + - + - : 20 : const auto blocktemplate = WITH_LOCK(::cs_main, return (node::BlockAssembler{m_chainman.CurrentChainstate(), &m_mempool, options}).CreateNewBlock());
+ - + - ]
391 [ - + - - : 5 : if (!blocktemplate) return EstimationError(strprintf("%s: Failed to create block template for fee rate estimation", FeeRateEstimatorTypeToString(estimator_type)));
- - ]
392 : : // Sort again because the rounding up when converting from weight to vsize may cause slight misorder.
393 [ - - - - : 447178 : std::sort(blocktemplate->m_package_feerates.begin(), blocktemplate->m_package_feerates.end(), [](const auto& a, const auto& b) { return ByRatio{a} > ByRatio{b}; });
+ + + + +
+ + + + +
+ + + + -
+ - - -
+ ]
394 [ - + + - ]: 5 : const auto percentiles = CalculateMaxWeightPercentiles(blocktemplate->m_package_feerates);
395 : : // Fall back to a relayable floor (the higher of the min relay fee and the current
396 : : // mempool min fee) for any percentile the mempool was too sparse to fill.
397 [ + - + + ]: 5 : const FeePerVSize floor{std::max(m_mempool.m_opts.min_relay_feerate, m_mempool.GetMinFee()).GetFeePerVSize()};
398 [ + + ]: 5 : const FeePerVSize p50{percentiles.p50.IsEmpty() ? floor : percentiles.p50};
399 [ + + ]: 5 : const FeePerVSize p75{percentiles.p75.IsEmpty() ? floor : percentiles.p75};
400 [ + - + - ]: 15 : WITH_LOCK(cs, m_cache.Update(p50, p75, blocktemplate->block.hashPrevBlock));
401 [ + - + - : 15 : LogDebug(BCLog::ESTIMATEFEE, "%s: conservative/economical fee rate: %s/%s %s/kvB",
+ - + - +
- + - ]
402 : : FeeRateEstimatorTypeToString(estimator_type), CFeeRate(p50).GetFeePerK(),
403 : : CFeeRate(p75).GetFeePerK(), CURRENCY_ATOM);
404 [ + + ]: 6 : return FeeRateEstimation{estimator_type, conservative ? p50 : p75, MEMPOOL_FEE_ESTIMATOR_MAX_TARGET};
405 : 5 : }
|