diff --git a/src/Makefile.am b/src/Makefile.am index 8685f0dacfabae..94fef7f9bacb24 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -242,6 +242,7 @@ BITCOIN_CORE_H = \ policy/fees.h \ policy/fees_args.h \ policy/packages.h \ + policy/mempool_fees.h \ policy/policy.h \ policy/rbf.h \ policy/settings.h \ @@ -439,6 +440,7 @@ libbitcoin_node_a_SOURCES = \ noui.cpp \ policy/fees.cpp \ policy/fees_args.cpp \ + policy/mempool_fees.cpp \ policy/packages.cpp \ policy/rbf.cpp \ policy/settings.cpp \ @@ -692,6 +694,7 @@ libbitcoin_common_a_SOURCES = \ net_permissions.cpp \ outputtype.cpp \ policy/feerate.cpp \ + policy/mempool_fees.cpp \ policy/policy.cpp \ protocol.cpp \ psbt.cpp \ @@ -950,6 +953,8 @@ libbitcoinkernel_la_SOURCES = \ node/chainstate.cpp \ node/utxo_snapshot.cpp \ policy/feerate.cpp \ + policy/fees.cpp \ + policy/mempool_fees.cpp policy/packages.cpp \ policy/policy.cpp \ policy/rbf.cpp \ diff --git a/src/init.cpp b/src/init.cpp index b825c8ce217985..10e3bb0ab9f15c 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -375,6 +376,7 @@ void Shutdown(NodeContext& node) node.chain_clients.clear(); UnregisterAllValidationInterfaces(); GetMainSignals().UnregisterBackgroundSignalScheduler(); + node.kernel.reset(); node.mempool.reset(); node.fee_estimator.reset(); node.chainman.reset(); @@ -1267,6 +1269,10 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) RegisterValidationInterface(fee_estimator); } + assert(!node.mempool_fee_estimator); + + node.mempool_fee_estimator = std::make_unique(); + // Check port numbers for (const std::string port_option : { "-port", diff --git a/src/node/context.cpp b/src/node/context.cpp index ca56fa0b86624c..9f6cd3c5b80837 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include diff --git a/src/node/context.h b/src/node/context.h index 4f3b640b2d2752..dfbb507bd93ac3 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -23,6 +23,7 @@ class CConnman; class CScheduler; class CTxMemPool; class ChainstateManager; +class MemPoolPolicyEstimator; class NetGroupManager; class PeerManager; namespace interfaces { @@ -57,6 +58,7 @@ struct NodeContext { std::unique_ptr mempool; std::unique_ptr netgroupman; std::unique_ptr fee_estimator; + std::unique_ptr mempool_fee_estimator; std::unique_ptr peerman; std::unique_ptr chainman; std::unique_ptr banman; diff --git a/src/policy/mempool_fees.cpp b/src/policy/mempool_fees.cpp new file mode 100644 index 00000000000000..28085ffc63f9fe --- /dev/null +++ b/src/policy/mempool_fees.cpp @@ -0,0 +1,91 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2023 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include + +using node::GetCustomBlockFeeRateHistogram; + +MemPoolPolicyEstimator::MemPoolPolicyEstimator() {} + +CFeeRate MemPoolPolicyEstimator::EstimateFeeWithMemPool(Chainstate& chainstate, const CTxMemPool& mempool, unsigned int confTarget, std::string& err_message) const +{ + + if (confTarget > MAX_CONF_TARGET) { + err_message = strprintf("Confirmation target %s is above maximum limit of %s, mempool conditions might change and estimates above %s are unreliable.\n", confTarget, MAX_CONF_TARGET, MAX_CONF_TARGET); + return CFeeRate(0); + } + + if (!mempool.GetLoadTried()) { + err_message = "Mempool did not finish loading, can't get accurate fee rate estimate."; + return CFeeRate(0); + } + + std::map mempool_fee_stats; + size_t target_weight = confTarget * DEFAULT_BLOCK_MAX_WEIGHT; + { + mempool_fee_stats = GetCustomBlockFeeRateHistogram(chainstate, &mempool, target_weight); + } + + if (mempool_fee_stats.empty()) { + err_message = "No transactions available in the mempool yet."; + return CFeeRate(0); // Return an appropriate default value (no txs in the mempool) + } + auto fee_rate = EstimateBlockFeeRate(mempool_fee_stats, confTarget, target_weight); + + if (fee_rate == CFeeRate(0)) { + err_message = "Insufficient mempool transactions to perform an estimate."; + } + return fee_rate; // Return the estimated fee rate or a default value if transactions aren't +} + +CFeeRate MemPoolPolicyEstimator::EstimateBlockFeeRate( + std::map& mempool_fee_stats, unsigned int confTarget, size_t target_weight) const +{ + auto it = mempool_fee_stats.begin(); + auto end = mempool_fee_stats.end(); + if (confTarget == 1) { + return CalculateMedianFeeRate(it, end); + } + size_t total_weight{0}; + while (total_weight < (target_weight - DEFAULT_BLOCK_MAX_WEIGHT)) { + total_weight += it->second * WITNESS_SCALE_FACTOR; + ++it; + } + return CalculateMedianFeeRate(it, end); +} + +CFeeRate MemPoolPolicyEstimator::CalculateMedianFeeRate( + std::map::iterator& start_it, + std::map::iterator& end_it) const +{ + // Ensure we have enough txs in the target block template. + auto curr = start_it; + unsigned int block_weight{0}; + while (curr != end_it) { + block_weight = curr->second * WITNESS_SCALE_FACTOR; + ++curr; + } + if (block_weight < (DEFAULT_BLOCK_MAX_WEIGHT / 2)) { + return CFeeRate(0); + } + + std::size_t size = std::distance(start_it, end_it); + if (size % 2 == 0) { + // If the number of txs is even, average the two middle fee rates + auto first_mid_it = std::next(start_it, size / 2); + auto second_mid_it = std::next(start_it, (size / 2) + 1); + auto mid_fee = (first_mid_it->first.GetFeePerK() + second_mid_it->first.GetFeePerK()) / 2; + return CFeeRate(mid_fee); + } else { + // If the number of txs is odd, return the fee rate of the middle tx + auto mid_it = std::next(start_it, (size / 2) + 1); + return mid_it->first; + } +} + diff --git a/src/policy/mempool_fees.h b/src/policy/mempool_fees.h new file mode 100644 index 00000000000000..5ebf0675ad998e --- /dev/null +++ b/src/policy/mempool_fees.h @@ -0,0 +1,71 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2023 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_POLICY_MEMPOOL_FEES_H +#define BITCOIN_POLICY_MEMPOOL_FEES_H + +#include +#include +#include + +#include + +class Chainstate; +class CTxMemPool; + +// Fee rate estimates above this confirmation target are not reliable, +// mempool condition might likely change. +static const unsigned int MAX_CONF_TARGET{3}; + +/** + * MemPoolPolicyEstimator estimates the fee rate that a tx should pay + * to be included in a confirmation target based on the mempool + * txs and their fee rates. + * + * The estimator works by generating template block up to a given confirmation target and then calculate the median + * fee rate of the txs in the confirmation target block as the approximate fee rate that a tx will pay to + * likely be included in the block. + */ +class MemPoolPolicyEstimator +{ +public: + + MemPoolPolicyEstimator(); + + ~MemPoolPolicyEstimator() = default; + + /** + * Estimate the fee rate from mempool txs data given a confirmation target. + * + * @param[in] chainstate The reference to the active chainstate. + * @param[in] mempool The reference to the mempool from which we will estimate the fee rate. + * @param[in] confTarget The confirmation target of transactions. + * @param[out] err_message optional error message. + * @return The estimated fee rate. + */ + CFeeRate EstimateFeeWithMemPool(Chainstate& chainstate, const CTxMemPool& mempool, unsigned int confTarget, std::string& err_message) const; + +private: + /** + * Calculate the fee rate estimate for a block of txs. + * + * @param[in] mempool_fee_stats The mempool fee statistics (fee rate and size). + * @param[in] confTarget The next block we wish to a tx to be included in. + * @param[in] target_weight Calculated of the histogram. + * @return The fee rate estimate in satoshis per kilobyte. + */ + CFeeRate EstimateBlockFeeRate(std::map& mempool_fee_stats, unsigned int confTarget, size_t target_weight) const; + + /** + * Calculate the median fee rate for a range of txs in the mempool. + * + * @param[in] start_it The iterator pointing to the beginning of the range. + * @param[in] end_it The iterator pointing to the end of the range. + * @return The median fee rate. + */ + CFeeRate CalculateMedianFeeRate(std::map::iterator& start_it, std::map::iterator& end_it) const; + +}; +#endif // BITCOIN_POLICY_MEMPOOL_FEES_H \ No newline at end of file