22#include <validation.h>
30#include <system_error>
36struct MinedBlockStatsFormatter {
37 template <
typename Stream>
42 template <
typename Stream>
49void AddMinedBlockStats(std::vector<MinedBlockStats>& mined_blocks,
MinedBlockStats stats)
51 const auto stale_begin{std::find_if(mined_blocks.begin(), mined_blocks.end(), [&](
const MinedBlockStats& block) {
52 return block.m_height >= stats.m_height;
54 const auto stale_count{std::distance(stale_begin, mined_blocks.end())};
55 if (stale_count > 0) {
57 "%s: connected block height=%s discards tracked mined-block stats "
58 "from height=%s to height=%s; stale_stats=%s",
61 stale_begin->m_height,
62 mined_blocks.back().m_height,
65 mined_blocks.erase(stale_begin, mined_blocks.end());
66 if (!mined_blocks.empty() && mined_blocks.back().m_height + 1 != stats.
m_height) {
68 "%s: clearing mined-block stats after height gap; tracked_stats=%s "
69 "expected_height=%s received_height=%s",
72 mined_blocks.back().m_height + 1,
78 mined_blocks.push_back(stats);
90 if (!tip)
return std::nullopt;
91 return ActiveTip{tip->
nHeight, tip->GetBlockHash()};
97 Assume(std::is_sorted(chunk_feerates.begin(), chunk_feerates.end(), [](
const auto& a,
const auto& b) { return ByRatio{a} >
ByRatio{b}; }));
99 const int64_t p50_weight{total_weight / 2};
100 const int64_t p75_weight{total_weight * 3 / 4};
101 Percentiles percentiles{};
102 int64_t accumulated_weight{0};
103 for (
const auto& curr_feerate : chunk_feerates) {
105 if (accumulated_weight >= p50_weight && percentiles.p50.IsEmpty()) {
106 percentiles.p50 = curr_feerate;
108 if (accumulated_weight >= p75_weight && percentiles.p75.IsEmpty()) {
109 percentiles.p75 = curr_feerate;
121std::optional<MemPoolFeeRateEstimatorCache::FeeRateEstimate>
152 return "Not enough recent block data for fee rate estimation";
154 return "Mempool is unreliable for fee rate estimation";
165 : m_mempool(mempool),
166 m_chainman(chainman),
167 m_mempool_estimator_file_path(
std::move(mempool_estimator_file_path))
191 int version_required;
192 file >> version_required;
194 LogWarning(
"%s: file version not supported; continuing anyway",
199 std::vector<MinedBlockStats> blocks;
200 file >> Using<VectorFormatter<MinedBlockStatsFormatter>>(blocks);
204 LogWarning(
"%s: Number of previously mined blocks read exceeds the maximum of %s; ignoring file",
209 for (
size_t i = 1; i < blocks.size(); ++i) {
210 if (blocks[i].m_height != blocks[i - 1].m_height + 1) {
211 LogWarning(
"%s: Non-consecutive block heights read, expected height %s but found %s; ignoring file",
213 blocks[i - 1].m_height + 1, blocks[i].m_height);
217 if (!blocks.empty()) {
218 const auto& last_block{blocks.back()};
219 const std::optional<ActiveTip> active_tip{GetActiveTip(
m_chainman)};
221 LogWarning(
"%s: Mined-block stats read end at height %s block %s, but there is no active chain tip; ignoring file",
223 last_block.m_height, tip_hash.ToString());
226 if (last_block.m_height !=
static_cast<uint64_t
>(active_tip->height) || tip_hash != active_tip->hash) {
227 LogWarning(
"%s: Mined-block stats read end at height %s block %s, but the active chain tip is height %s block %s; ignoring file",
229 last_block.m_height, tip_hash.ToString(),
230 active_tip->height, active_tip->hash.ToString());
235 m_prev_mined_blocks = std::move(blocks);
236 m_mined_blocks_tip_hash = tip_hash;
238 }
catch (
const std::exception&) {
239 LogWarning(
"%s: Unable to read mined-block stats from stream (non-fatal)",
251 file << Using<VectorFormatter<MinedBlockStatsFormatter>>(m_prev_mined_blocks);
252 file << m_mined_blocks_tip_hash;
253 }
catch (
const std::exception&) {
262 std::error_code error;
265 LogWarning(
"%s: failed to create mempool policy estimator directory %s: %s. Continuing anyway",
273 LogWarning(
"%s: unable to open %s for writing. Continuing anyway",
279 LogWarning(
"%s: Unable to write mined-block stats to %s (non-fatal)",
283 if (file.fclose() != 0) {
284 LogWarning(
"Failed to close mempool policy estimator file %s: %s. Continuing anyway.",
295 const std::vector<RemovedMempoolTransactionInfo>& txs_removed_for_block,
296 unsigned int block_height)
299 Assert(!block->vtx.empty());
305 const uint64_t block_weight = std::accumulate(std::next(block->vtx.begin()), block->vtx.end(), uint64_t{0},
307 return acc + get_tx_weight(tx);
309 const uint64_t removed_weight = std::accumulate(
310 txs_removed_for_block.begin(), txs_removed_for_block.end(), uint64_t{0},
312 return acc + get_tx_weight(tx.info.m_tx);
314 AddMinedBlockStats(m_prev_mined_blocks, {block_height, removed_weight, block_weight});
315 m_mined_blocks_tip_hash = block->GetHash();
332 uint64_t total_block_weight{0};
333 uint64_t total_removed_weight{0};
334 uint64_t expected_height{m_prev_mined_blocks.front().m_height};
335 for (
const auto& block : m_prev_mined_blocks) {
336 Assume(block.m_height == expected_height);
338 total_block_weight += block.m_block_weight;
339 total_removed_weight += block.m_removed_block_txs_weight;
347 const double representation_ratio =
static_cast<double>(total_removed_weight) / total_block_weight;
349 "%s: mempool health check %s; removed_weight=%s total_block_weight=%s "
350 "coverage=%.2f required_coverage=%.2f",
353 total_removed_weight,
355 representation_ratio,
381 const auto cached_estimate = m_cache.GetCachedEstimate(tip_hash);
382 if (cached_estimate) {
383 const auto cached_feerate{
384 conservative ? cached_estimate->m_conservative : cached_estimate->m_economical};
393 std::sort(blocktemplate->m_package_feerates.begin(), blocktemplate->m_package_feerates.end(), [](
const auto& a,
const auto& b) { return ByRatio{a} >
ByRatio{b}; });
394 const auto percentiles = CalculateMaxWeightPercentiles(blocktemplate->m_package_feerates);
397 const FeePerVSize floor{std::max(m_mempool.m_opts.min_relay_feerate, m_mempool.GetMinFee()).GetFeePerVSize()};
400 WITH_LOCK(
cs, m_cache.Update(p50, p75, blocktemplate->block.hashPrevBlock));
#define Assert(val)
Identity function.
#define Assume(val)
Assume is the identity function.
Non-refcounted RAII wrapper for FILE*.
Wrapper around FeeFrac & derived types, which adds a feerate-based ordering which treats equal-feerat...
The block chain is a tree shaped structure starting with the genesis block at the root,...
int nHeight
height of the entry in the chain. The genesis block has height 0
CBlockIndex * Tip() const
Returns the index entry for the tip of this chain, or nullptr if none.
Fee rate in satoshis per virtualbyte: CAmount / vB the feerate is represented internally as FeeFrac.
CTxMemPool stores valid-according-to-the-current-best-chain transactions that may be included in the ...
bool GetLoadTried() const
CChain m_chain
The current chain of blockheaders we consult and build on.
Interface for managing multiple Chainstate objects, where each chainstate is associated with chainsta...
CBlockIndex * ActiveTip() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex())
SnapshotCompletionResult MaybeValidateSnapshot(Chainstate &validated_cs, Chainstate &unvalidated_cs) EXCLUSIVE_LOCKS_REQUIRED(Chainstate & CurrentChainstate() const EXCLUSIVE_LOCKS_REQUIRED(GetMutex())
Try to validate an assumeutxo snapshot by using a validated historical chainstate targeted at the sna...
std::optional< FeeRateEstimate > GetCachedEstimate(const uint256 &tip_hash) const
Returns cached estimates if not stale and computed on tip_hash, nullopt otherwise.
NodeClock::time_point m_last_updated
void Clear()
Clear cached fee rate estimates.
bool IsStale() const
Returns true if the cache is empty or older than CACHE_LIFE.
std::optional< FeeRateEstimate > m_fee_rate_estimation
void Update(FeePerVSize conservative, FeePerVSize economical, const uint256 &tip_hash)
Update the cache with new estimates computed on tip_hash.
void ReadFromDisk() EXCLUSIVE_LOCKS_REQUIRED(!cs)
bool Write(AutoFile &file) const EXCLUSIVE_LOCKS_REQUIRED(!cs)
Serialize mined-block stats without taking ownership of file.
MempoolHealth GetMempoolHealth() const EXCLUSIVE_LOCKS_REQUIRED(!cs)
const CTxMemPool & m_mempool
ChainstateManager & m_chainman
void MempoolTxsRemovedForBlock(const std::shared_ptr< const CBlock > &block, const std::vector< RemovedMempoolTransactionInfo > &txs_removed_for_block, unsigned int block_height) EXCLUSIVE_LOCKS_REQUIRED(!cs)
void FlushMinedBlockStats() EXCLUSIVE_LOCKS_REQUIRED(!cs)
static Percentiles CalculateMaxWeightPercentiles(std::span< const FeePerVSize > chunk_feerates)
Calculate the 50th and 75th percentile fee rates from block template chunks, sorted in descending min...
const fs::path m_mempool_estimator_file_path
bool Read(AutoFile &file) EXCLUSIVE_LOCKS_REQUIRED(!cs)
Deserialize mined-block stats without taking ownership of file.
util::Expected< FeeRateEstimation, FeeRateEstimationError > EstimateFeeRate(bool conservative) const EXCLUSIVE_LOCKS_REQUIRED(!cs)
MemPoolFeeRateEstimator(fs::path mempool_estimator_file_path, const CTxMemPool &mempool, ChainstateManager &chainman)
MempoolHealth
Health of the recent mined-block window for fee rate estimation.
@ LOW_COVERAGE
Recent blocks include too few mempool transactions to estimate a fee rate.
@ HEALTHY
Recent blocks represent the mempool well enough to estimate a fee rate.
@ INSUFFICIENT_DATA
Too few recent mined blocks to estimate a fee rate.
Generate a new block, without valid proof-of-work.
The util::Expected class provides a standard way for low-level functions to return either error value...
The util::Unexpected class represents an unexpected value stored in util::Expected.
static int32_t GetTransactionWeight(const CTransaction &tx)
constexpr int WITNESS_SCALE_FACTOR
RecursiveMutex cs_main
Mutex to guard access to validation specific variables, such as reading or changing the chainstate.
const std::string CURRENCY_ATOM
static std::string PathToString(const path &path)
Convert path object to a byte string.
#define LogDebug(category,...)
constexpr int CURRENT_MEMPOOL_ESTIMATOR_VERSION
static constexpr uint64_t MIN_REPRESENTATIVE_WINDOW_WEIGHT
static std::optional< std::string_view > MempoolHealthError(MemPoolFeeRateEstimator::MempoolHealth health)
static util::Unexpected< FeeRateEstimationError > EstimationError(std::string error)
Build the error result for a failed mempool fee rate estimation.
constexpr int MEMPOOL_FEE_ESTIMATOR_MAX_TARGET
constexpr std::chrono::seconds CACHE_LIFE
constexpr double MEMPOOL_REPRESENTATION_THRESHOLD
constexpr size_t MEMPOOL_HEALTH_WINDOW_BLOCKS
FILE * fopen(const fs::path &p, const char *mode)
constexpr unsigned int DEFAULT_BLOCK_MAX_WEIGHT
Default for -blockmaxweight, which controls the range of block weights the mining code will create.
std::shared_ptr< const CTransaction > CTransactionRef
bool IsEmpty() const noexcept
Check if this is empty (size and fee are 0).
A successful fee rate estimate returned by a fee rate estimator.
Weight statistics for a recently mined block, used to assess mempool coverage.
uint64_t m_block_weight
Total non-coinbase transaction weight in the block.
uint64_t m_height
Block height.
uint64_t m_removed_block_txs_weight
Weight of mempool transactions removed for this block (excluding coinbase).
static time_point now() noexcept
Return current system time or mocked time, if set.
Block template creation options.
bool test_block_validity
Whether to call TestBlockValidity() at the end of CreateNewBlock().
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
std::string SysErrorString(int err)
Return system error string from errno value.
std::string_view FeeRateEstimatorTypeToString(FeeRateEstimatorType feerate_estimator_type)