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