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