23#include <validation.h>
31#include <system_error>
37struct MinedBlockStatsFormatter {
38 template <
typename Stream>
43 template <
typename Stream>
50void AddMinedBlockStats(std::vector<MinedBlockStats>& mined_blocks,
MinedBlockStats stats)
52 const auto stale_begin{std::find_if(mined_blocks.begin(), mined_blocks.end(), [&](
const MinedBlockStats& block) {
53 return block.m_height >= stats.m_height;
55 const auto stale_count{std::distance(stale_begin, mined_blocks.end())};
56 if (stale_count > 0) {
58 "%s: connected block height=%s discards tracked mined-block stats "
59 "from height=%s to height=%s; stale_stats=%s",
62 stale_begin->m_height,
63 mined_blocks.back().m_height,
66 mined_blocks.erase(stale_begin, mined_blocks.end());
67 if (!mined_blocks.empty() && mined_blocks.back().m_height + 1 != stats.
m_height) {
69 "%s: clearing mined-block stats after height gap; tracked_stats=%s "
70 "expected_height=%s received_height=%s",
73 mined_blocks.back().m_height + 1,
79 mined_blocks.push_back(stats);
91 if (!tip)
return std::nullopt;
92 return ActiveTip{tip->
nHeight, tip->GetBlockHash()};
98 Assume(std::is_sorted(chunk_feerates.begin(), chunk_feerates.end(), [](
const auto& a,
const auto& b) { return ByRatio{a} >
ByRatio{b}; }));
100 const int64_t p50_weight{total_weight / 2};
101 const int64_t p75_weight{total_weight * 3 / 4};
102 Percentiles percentiles{};
103 int64_t accumulated_weight{0};
104 for (
const auto& curr_feerate : chunk_feerates) {
106 if (accumulated_weight >= p50_weight && percentiles.p50.IsEmpty()) {
107 percentiles.p50 = curr_feerate;
109 if (accumulated_weight >= p75_weight && percentiles.p75.IsEmpty()) {
110 percentiles.p75 = curr_feerate;
122std::optional<MemPoolFeeRateEstimatorCache::FeeRateEstimate>
153 return "Not enough recent block data for fee rate estimation";
155 return "Mempool is unreliable for fee rate estimation";
166 : m_mempool(mempool),
167 m_chainman(chainman),
168 m_mempool_estimator_file_path(
std::move(mempool_estimator_file_path))
192 int version_required;
193 file >> version_required;
195 LogWarning(
"%s: file version not supported; continuing anyway",
200 std::vector<MinedBlockStats> blocks;
201 file >> Using<VectorFormatter<MinedBlockStatsFormatter>>(blocks);
205 LogWarning(
"%s: Number of previously mined blocks read exceeds the maximum of %s; ignoring file",
210 for (
size_t i = 1; i < blocks.size(); ++i) {
211 const uint64_t expected_height{
SaturatingAdd(blocks[i - 1].m_height, uint64_t{1})};
212 if (blocks[i].m_height != expected_height) {
213 LogWarning(
"%s: Non-consecutive block heights read, expected height %s but found %s; ignoring file",
215 expected_height, blocks[i].m_height);
219 if (!blocks.empty()) {
220 const auto& last_block{blocks.back()};
221 const std::optional<ActiveTip> active_tip{GetActiveTip(
m_chainman)};
223 LogWarning(
"%s: Mined-block stats read end at height %s block %s, but there is no active chain tip; ignoring file",
225 last_block.m_height, tip_hash.ToString());
228 if (last_block.m_height !=
static_cast<uint64_t
>(active_tip->height) || tip_hash != active_tip->hash) {
229 LogWarning(
"%s: Mined-block stats read end at height %s block %s, but the active chain tip is height %s block %s; ignoring file",
231 last_block.m_height, tip_hash.ToString(),
232 active_tip->height, active_tip->hash.ToString());
237 m_prev_mined_blocks = std::move(blocks);
238 m_mined_blocks_tip_hash = tip_hash;
240 }
catch (
const std::exception&) {
241 LogWarning(
"%s: Unable to read mined-block stats from stream (non-fatal)",
253 file << Using<VectorFormatter<MinedBlockStatsFormatter>>(m_prev_mined_blocks);
254 file << m_mined_blocks_tip_hash;
255 }
catch (
const std::exception&) {
264 std::error_code error;
267 LogWarning(
"%s: failed to create mempool policy estimator directory %s: %s. Continuing anyway",
275 LogWarning(
"%s: unable to open %s for writing. Continuing anyway",
281 LogWarning(
"%s: Unable to write mined-block stats to %s (non-fatal)",
285 if (file.fclose() != 0) {
286 LogWarning(
"Failed to close mempool policy estimator file %s: %s. Continuing anyway.",
297 const std::vector<RemovedMempoolTransactionInfo>& txs_removed_for_block,
298 unsigned int block_height)
301 Assert(!block->vtx.empty());
307 const uint64_t block_weight = std::accumulate(std::next(block->vtx.begin()), block->vtx.end(), uint64_t{0},
309 return acc + get_tx_weight(tx);
311 const uint64_t removed_weight = std::accumulate(
312 txs_removed_for_block.begin(), txs_removed_for_block.end(), uint64_t{0},
314 return acc + get_tx_weight(tx.info.m_tx);
316 AddMinedBlockStats(m_prev_mined_blocks, {block_height, removed_weight, block_weight});
317 m_mined_blocks_tip_hash = block->GetHash();
334 uint64_t total_block_weight{0};
335 uint64_t total_removed_weight{0};
336 uint64_t expected_height{m_prev_mined_blocks.front().m_height};
337 for (
const auto& block : m_prev_mined_blocks) {
338 Assume(block.m_height == expected_height);
340 total_block_weight += block.m_block_weight;
341 total_removed_weight += block.m_removed_block_txs_weight;
349 const double representation_ratio =
static_cast<double>(total_removed_weight) / total_block_weight;
351 "%s: mempool health check %s; removed_weight=%s total_block_weight=%s "
352 "coverage=%.2f required_coverage=%.2f",
355 total_removed_weight,
357 representation_ratio,
383 const auto cached_estimate = m_cache.GetCachedEstimate(tip_hash);
384 if (cached_estimate) {
385 const auto cached_feerate{
386 conservative ? cached_estimate->m_conservative : cached_estimate->m_economical};
395 std::sort(blocktemplate->m_package_feerates.begin(), blocktemplate->m_package_feerates.end(), [](
const auto& a,
const auto& b) { return ByRatio{a} >
ByRatio{b}; });
396 const auto percentiles = CalculateMaxWeightPercentiles(blocktemplate->m_package_feerates);
399 const FeePerVSize floor{std::max(m_mempool.m_opts.min_relay_feerate, m_mempool.GetMinFee()).GetFeePerVSize()};
402 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)
T SaturatingAdd(const T i, const T j) noexcept
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)