mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04:00
Merge 75e6bb5ada
into c5e44a0435
This commit is contained in:
commit
02176f4899
37 changed files with 767 additions and 84 deletions
|
@ -167,6 +167,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL
|
||||||
netbase.cpp
|
netbase.cpp
|
||||||
outputtype.cpp
|
outputtype.cpp
|
||||||
policy/feerate.cpp
|
policy/feerate.cpp
|
||||||
|
policy/fees/forecaster_util.cpp
|
||||||
policy/policy.cpp
|
policy/policy.cpp
|
||||||
pow.cpp
|
pow.cpp
|
||||||
protocol.cpp
|
protocol.cpp
|
||||||
|
@ -281,8 +282,10 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL
|
||||||
node/warnings.cpp
|
node/warnings.cpp
|
||||||
noui.cpp
|
noui.cpp
|
||||||
policy/ephemeral_policy.cpp
|
policy/ephemeral_policy.cpp
|
||||||
policy/fees.cpp
|
policy/fees/block_policy_estimator.cpp
|
||||||
policy/fees_args.cpp
|
policy/fees/block_policy_estimator_args.cpp
|
||||||
|
policy/fees/forecaster_man.cpp
|
||||||
|
policy/fees/mempool_forecaster.cpp
|
||||||
policy/packages.cpp
|
policy/packages.cpp
|
||||||
policy/rbf.cpp
|
policy/rbf.cpp
|
||||||
policy/settings.cpp
|
policy/settings.cpp
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include <common/messages.h>
|
#include <common/messages.h>
|
||||||
|
|
||||||
#include <common/types.h>
|
#include <common/types.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <node/types.h>
|
#include <node/types.h>
|
||||||
#include <tinyformat.h>
|
#include <tinyformat.h>
|
||||||
#include <util/strencodings.h>
|
#include <util/strencodings.h>
|
||||||
|
|
54
src/init.cpp
54
src/init.cpp
|
@ -57,8 +57,10 @@
|
||||||
#include <node/miner.h>
|
#include <node/miner.h>
|
||||||
#include <node/peerman_args.h>
|
#include <node/peerman_args.h>
|
||||||
#include <policy/feerate.h>
|
#include <policy/feerate.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <policy/fees_args.h>
|
#include <policy/fees/block_policy_estimator_args.h>
|
||||||
|
#include <policy/fees/forecaster_man.h>
|
||||||
|
#include <policy/fees/mempool_forecaster.h>
|
||||||
#include <policy/policy.h>
|
#include <policy/policy.h>
|
||||||
#include <policy/settings.h>
|
#include <policy/settings.h>
|
||||||
#include <protocol.h>
|
#include <protocol.h>
|
||||||
|
@ -328,13 +330,9 @@ void Shutdown(NodeContext& node)
|
||||||
DumpMempool(*node.mempool, MempoolPath(*node.args));
|
DumpMempool(*node.mempool, MempoolPath(*node.args));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop transactions we were still watching, record fee estimations and unregister
|
// Drop transactions we were still watching, record fee estimations.
|
||||||
// fee estimator from validation interface.
|
if (node.forecasterman) {
|
||||||
if (node.fee_estimator) {
|
node.forecasterman->GetBlockPolicyEstimator()->Flush();
|
||||||
node.fee_estimator->Flush();
|
|
||||||
if (node.validation_signals) {
|
|
||||||
node.validation_signals->UnregisterValidationInterface(node.fee_estimator.get());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing
|
// FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing
|
||||||
|
@ -389,7 +387,7 @@ void Shutdown(NodeContext& node)
|
||||||
node.validation_signals->UnregisterAllValidationInterfaces();
|
node.validation_signals->UnregisterAllValidationInterfaces();
|
||||||
}
|
}
|
||||||
node.mempool.reset();
|
node.mempool.reset();
|
||||||
node.fee_estimator.reset();
|
node.forecasterman.reset();
|
||||||
node.chainman.reset();
|
node.chainman.reset();
|
||||||
node.validation_signals.reset();
|
node.validation_signals.reset();
|
||||||
node.scheduler.reset();
|
node.scheduler.reset();
|
||||||
|
@ -1497,22 +1495,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
|
||||||
rng.rand64(),
|
rng.rand64(),
|
||||||
*node.addrman, *node.netgroupman, chainparams, args.GetBoolArg("-networkactive", true));
|
*node.addrman, *node.netgroupman, chainparams, args.GetBoolArg("-networkactive", true));
|
||||||
|
|
||||||
assert(!node.fee_estimator);
|
|
||||||
// Don't initialize fee estimation with old data if we don't relay transactions,
|
|
||||||
// as they would never get updated.
|
|
||||||
if (!peerman_opts.ignore_incoming_txs) {
|
|
||||||
bool read_stale_estimates = args.GetBoolArg("-acceptstalefeeestimates", DEFAULT_ACCEPT_STALE_FEE_ESTIMATES);
|
|
||||||
if (read_stale_estimates && (chainparams.GetChainType() != ChainType::REGTEST)) {
|
|
||||||
return InitError(strprintf(_("acceptstalefeeestimates is not supported on %s chain."), chainparams.GetChainTypeString()));
|
|
||||||
}
|
|
||||||
node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(args), read_stale_estimates);
|
|
||||||
|
|
||||||
// Flush estimates to disk periodically
|
|
||||||
CBlockPolicyEstimator* fee_estimator = node.fee_estimator.get();
|
|
||||||
scheduler.scheduleEvery([fee_estimator] { fee_estimator->FlushFeeEstimates(); }, FEE_FLUSH_INTERVAL);
|
|
||||||
validation_signals.RegisterValidationInterface(fee_estimator);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const std::string& socket_addr : args.GetArgs("-bind")) {
|
for (const std::string& socket_addr : args.GetArgs("-bind")) {
|
||||||
std::string host_out;
|
std::string host_out;
|
||||||
uint16_t port_out{0};
|
uint16_t port_out{0};
|
||||||
|
@ -1727,6 +1709,26 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
|
||||||
|
|
||||||
ChainstateManager& chainman = *Assert(node.chainman);
|
ChainstateManager& chainman = *Assert(node.chainman);
|
||||||
|
|
||||||
|
assert(!node.forecasterman);
|
||||||
|
// Don't initialize fee estimation with old data if we don't relay transactions,
|
||||||
|
// as they would never get updated.
|
||||||
|
if (!peerman_opts.ignore_incoming_txs) {
|
||||||
|
bool read_stale_estimates = args.GetBoolArg("-acceptstalefeeestimates", DEFAULT_ACCEPT_STALE_FEE_ESTIMATES);
|
||||||
|
if (read_stale_estimates && (chainparams.GetChainType() != ChainType::REGTEST)) {
|
||||||
|
return InitError(strprintf(_("acceptstalefeeestimates is not supported on %s chain."), chainparams.GetChainTypeString()));
|
||||||
|
}
|
||||||
|
node.forecasterman = std::make_unique<FeeRateForecasterManager>();
|
||||||
|
auto mempool_forecaster = std::make_shared<MemPoolForecaster>(node.mempool.get(), &(chainman.ActiveChainstate()));
|
||||||
|
node.forecasterman->RegisterForecaster(mempool_forecaster);
|
||||||
|
auto block_policy_estimator = std::make_shared<CBlockPolicyEstimator>(FeeestPath(args), read_stale_estimates);
|
||||||
|
validation_signals.RegisterSharedValidationInterface(block_policy_estimator);
|
||||||
|
// Flush block policy estimates to disk periodically
|
||||||
|
scheduler.scheduleEvery([block_policy_estimator] { block_policy_estimator->FlushFeeEstimates(); }, FEE_FLUSH_INTERVAL);
|
||||||
|
|
||||||
|
// Register block policy estimator to forecaster manager
|
||||||
|
node.forecasterman->RegisterForecaster(block_policy_estimator);
|
||||||
|
}
|
||||||
|
|
||||||
assert(!node.peerman);
|
assert(!node.peerman);
|
||||||
node.peerman = PeerManager::make(*node.connman, *node.addrman,
|
node.peerman = PeerManager::make(*node.connman, *node.addrman,
|
||||||
node.banman.get(), chainman,
|
node.banman.get(), chainman,
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
#include <node/txreconciliation.h>
|
#include <node/txreconciliation.h>
|
||||||
#include <node/warnings.h>
|
#include <node/warnings.h>
|
||||||
#include <policy/feerate.h>
|
#include <policy/feerate.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <policy/packages.h>
|
#include <policy/packages.h>
|
||||||
#include <policy/policy.h>
|
#include <policy/policy.h>
|
||||||
#include <primitives/block.h>
|
#include <primitives/block.h>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#include <netgroup.h>
|
#include <netgroup.h>
|
||||||
#include <node/kernel_notifications.h>
|
#include <node/kernel_notifications.h>
|
||||||
#include <node/warnings.h>
|
#include <node/warnings.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/forecaster_man.h>
|
||||||
#include <scheduler.h>
|
#include <scheduler.h>
|
||||||
#include <txmempool.h>
|
#include <txmempool.h>
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
|
|
|
@ -16,7 +16,7 @@ class ArgsManager;
|
||||||
class AddrMan;
|
class AddrMan;
|
||||||
class BanMan;
|
class BanMan;
|
||||||
class BaseIndex;
|
class BaseIndex;
|
||||||
class CBlockPolicyEstimator;
|
class FeeRateForecasterManager;
|
||||||
class CConnman;
|
class CConnman;
|
||||||
class ValidationSignals;
|
class ValidationSignals;
|
||||||
class CScheduler;
|
class CScheduler;
|
||||||
|
@ -67,7 +67,7 @@ struct NodeContext {
|
||||||
std::unique_ptr<CConnman> connman;
|
std::unique_ptr<CConnman> connman;
|
||||||
std::unique_ptr<CTxMemPool> mempool;
|
std::unique_ptr<CTxMemPool> mempool;
|
||||||
std::unique_ptr<const NetGroupManager> netgroupman;
|
std::unique_ptr<const NetGroupManager> netgroupman;
|
||||||
std::unique_ptr<CBlockPolicyEstimator> fee_estimator;
|
std::unique_ptr<FeeRateForecasterManager> forecasterman;
|
||||||
std::unique_ptr<PeerManager> peerman;
|
std::unique_ptr<PeerManager> peerman;
|
||||||
std::unique_ptr<ChainstateManager> chainman;
|
std::unique_ptr<ChainstateManager> chainman;
|
||||||
std::unique_ptr<BanMan> banman;
|
std::unique_ptr<BanMan> banman;
|
||||||
|
|
|
@ -40,7 +40,8 @@
|
||||||
#include <node/types.h>
|
#include <node/types.h>
|
||||||
#include <node/warnings.h>
|
#include <node/warnings.h>
|
||||||
#include <policy/feerate.h>
|
#include <policy/feerate.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
|
#include <policy/fees/forecaster_man.h>
|
||||||
#include <policy/policy.h>
|
#include <policy/policy.h>
|
||||||
#include <policy/rbf.h>
|
#include <policy/rbf.h>
|
||||||
#include <policy/settings.h>
|
#include <policy/settings.h>
|
||||||
|
@ -739,13 +740,13 @@ public:
|
||||||
}
|
}
|
||||||
CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override
|
CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override
|
||||||
{
|
{
|
||||||
if (!m_node.fee_estimator) return {};
|
if (!m_node.forecasterman) return {};
|
||||||
return m_node.fee_estimator->estimateSmartFee(num_blocks, calc, conservative);
|
return m_node.forecasterman->GetBlockPolicyEstimator()->estimateSmartFee(num_blocks, calc, conservative);
|
||||||
}
|
}
|
||||||
unsigned int estimateMaxBlocks() override
|
unsigned int estimateMaxBlocks() override
|
||||||
{
|
{
|
||||||
if (!m_node.fee_estimator) return 0;
|
if (!m_node.forecasterman) return 0;
|
||||||
return m_node.fee_estimator->HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
|
return m_node.forecasterman->GetBlockPolicyEstimator()->MaximumTarget();
|
||||||
}
|
}
|
||||||
CFeeRate mempoolMinFee() override
|
CFeeRate mempoolMinFee() override
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,13 +3,14 @@
|
||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
|
|
||||||
#include <common/system.h>
|
#include <common/system.h>
|
||||||
#include <consensus/amount.h>
|
#include <consensus/amount.h>
|
||||||
#include <kernel/mempool_entry.h>
|
#include <kernel/mempool_entry.h>
|
||||||
#include <logging.h>
|
#include <logging.h>
|
||||||
#include <policy/feerate.h>
|
#include <policy/feerate.h>
|
||||||
|
#include <policy/fees/forecaster_util.h>
|
||||||
#include <primitives/transaction.h>
|
#include <primitives/transaction.h>
|
||||||
#include <random.h>
|
#include <random.h>
|
||||||
#include <serialize.h>
|
#include <serialize.h>
|
||||||
|
@ -540,7 +541,7 @@ bool CBlockPolicyEstimator::_removeTx(const uint256& hash, bool inBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
CBlockPolicyEstimator::CBlockPolicyEstimator(const fs::path& estimation_filepath, const bool read_stale_estimates)
|
CBlockPolicyEstimator::CBlockPolicyEstimator(const fs::path& estimation_filepath, const bool read_stale_estimates)
|
||||||
: m_estimation_filepath{estimation_filepath}
|
: Forecaster(ForecastType::BLOCK_POLICY), m_estimation_filepath{estimation_filepath}
|
||||||
{
|
{
|
||||||
static_assert(MIN_BUCKET_FEERATE > 0, "Min feerate must be nonzero");
|
static_assert(MIN_BUCKET_FEERATE > 0, "Min feerate must be nonzero");
|
||||||
size_t bucketIndex = 0;
|
size_t bucketIndex = 0;
|
||||||
|
@ -723,6 +724,29 @@ CFeeRate CBlockPolicyEstimator::estimateFee(int confTarget) const
|
||||||
return estimateRawFee(confTarget, DOUBLE_SUCCESS_PCT, FeeEstimateHorizon::MED_HALFLIFE);
|
return estimateRawFee(confTarget, DOUBLE_SUCCESS_PCT, FeeEstimateHorizon::MED_HALFLIFE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ForecastResult CBlockPolicyEstimator::ForecastFeeRate(int target, bool conservative) const
|
||||||
|
{
|
||||||
|
ForecastResult result;
|
||||||
|
result.forecaster = ForecastType::BLOCK_POLICY;
|
||||||
|
FeeCalculation feeCalcConservative;
|
||||||
|
CFeeRate feerate{estimateSmartFee(target, &feeCalcConservative, conservative)};
|
||||||
|
result.current_block_height = feeCalcConservative.bestheight;
|
||||||
|
if (feerate == CFeeRate(0)) {
|
||||||
|
result.m_error = "Insufficient data or no feerate found";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// Note: size can be any positive non-zero integer; the evaluated fee/size will result in the same fee rate,
|
||||||
|
// and we only care that the fee rate remains consistent.
|
||||||
|
int32_t size = 1000;
|
||||||
|
result.feerate = FeeFrac(feerate.GetFee(size), size);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int CBlockPolicyEstimator::MaximumTarget() const
|
||||||
|
{
|
||||||
|
return HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
|
||||||
|
}
|
||||||
|
|
||||||
CFeeRate CBlockPolicyEstimator::estimateRawFee(int confTarget, double successThreshold, FeeEstimateHorizon horizon, EstimationResult* result) const
|
CFeeRate CBlockPolicyEstimator::estimateRawFee(int confTarget, double successThreshold, FeeEstimateHorizon horizon, EstimationResult* result) const
|
||||||
{
|
{
|
||||||
TxConfirmStats* stats = nullptr;
|
TxConfirmStats* stats = nullptr;
|
||||||
|
@ -874,6 +898,7 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation
|
||||||
if (feeCalc) {
|
if (feeCalc) {
|
||||||
feeCalc->desiredTarget = confTarget;
|
feeCalc->desiredTarget = confTarget;
|
||||||
feeCalc->returnedTarget = confTarget;
|
feeCalc->returnedTarget = confTarget;
|
||||||
|
feeCalc->bestheight = nBestSeenHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
double median = -1;
|
double median = -1;
|
|
@ -2,11 +2,12 @@
|
||||||
// Copyright (c) 2009-2022 The Bitcoin Core developers
|
// Copyright (c) 2009-2022 The Bitcoin Core developers
|
||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
#ifndef BITCOIN_POLICY_FEES_H
|
#ifndef BITCOIN_POLICY_FEES_BLOCK_POLICY_ESTIMATOR_H
|
||||||
#define BITCOIN_POLICY_FEES_H
|
#define BITCOIN_POLICY_FEES_BLOCK_POLICY_ESTIMATOR_H
|
||||||
|
|
||||||
#include <consensus/amount.h>
|
#include <consensus/amount.h>
|
||||||
#include <policy/feerate.h>
|
#include <policy/feerate.h>
|
||||||
|
#include <policy/fees/forecaster.h>
|
||||||
#include <random.h>
|
#include <random.h>
|
||||||
#include <sync.h>
|
#include <sync.h>
|
||||||
#include <threadsafety.h>
|
#include <threadsafety.h>
|
||||||
|
@ -37,9 +38,12 @@ static constexpr bool DEFAULT_ACCEPT_STALE_FEE_ESTIMATES{false};
|
||||||
|
|
||||||
class AutoFile;
|
class AutoFile;
|
||||||
class TxConfirmStats;
|
class TxConfirmStats;
|
||||||
|
|
||||||
|
struct ForecastResult;
|
||||||
struct RemovedMempoolTransactionInfo;
|
struct RemovedMempoolTransactionInfo;
|
||||||
struct NewMempoolTransactionInfo;
|
struct NewMempoolTransactionInfo;
|
||||||
|
|
||||||
|
|
||||||
/* Identifier for each of the 3 different TxConfirmStats which will track
|
/* Identifier for each of the 3 different TxConfirmStats which will track
|
||||||
* history over different time horizons. */
|
* history over different time horizons. */
|
||||||
enum class FeeEstimateHorizon {
|
enum class FeeEstimateHorizon {
|
||||||
|
@ -95,6 +99,7 @@ struct FeeCalculation
|
||||||
FeeReason reason = FeeReason::NONE;
|
FeeReason reason = FeeReason::NONE;
|
||||||
int desiredTarget = 0;
|
int desiredTarget = 0;
|
||||||
int returnedTarget = 0;
|
int returnedTarget = 0;
|
||||||
|
unsigned int bestheight{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** \class CBlockPolicyEstimator
|
/** \class CBlockPolicyEstimator
|
||||||
|
@ -145,7 +150,7 @@ struct FeeCalculation
|
||||||
* a certain number of blocks. Every time a block is added to the best chain, this class records
|
* a certain number of blocks. Every time a block is added to the best chain, this class records
|
||||||
* stats on the transactions included in that block
|
* stats on the transactions included in that block
|
||||||
*/
|
*/
|
||||||
class CBlockPolicyEstimator : public CValidationInterface
|
class CBlockPolicyEstimator : public Forecaster, public CValidationInterface
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
/** Track confirm delays up to 12 blocks for short horizon */
|
/** Track confirm delays up to 12 blocks for short horizon */
|
||||||
|
@ -262,6 +267,10 @@ public:
|
||||||
/** Calculates the age of the file, since last modified */
|
/** Calculates the age of the file, since last modified */
|
||||||
std::chrono::hours GetFeeEstimatorFileAge();
|
std::chrono::hours GetFeeEstimatorFileAge();
|
||||||
|
|
||||||
|
/** Overridden from Forecaster. */
|
||||||
|
unsigned int MaximumTarget() const override
|
||||||
|
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/** Overridden from CValidationInterface. */
|
/** Overridden from CValidationInterface. */
|
||||||
void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /*unused*/) override
|
void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /*unused*/) override
|
||||||
|
@ -271,6 +280,10 @@ protected:
|
||||||
void MempoolTransactionsRemovedForBlock(const std::vector<RemovedMempoolTransactionInfo>& txs_removed_for_block, unsigned int nBlockHeight) override
|
void MempoolTransactionsRemovedForBlock(const std::vector<RemovedMempoolTransactionInfo>& txs_removed_for_block, unsigned int nBlockHeight) override
|
||||||
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
|
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
|
||||||
|
|
||||||
|
/** Overridden from Forecaster. */
|
||||||
|
ForecastResult ForecastFeeRate(int target, bool conservative) const override
|
||||||
|
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mutable Mutex m_cs_fee_estimator;
|
mutable Mutex m_cs_fee_estimator;
|
||||||
|
|
||||||
|
@ -342,4 +355,4 @@ private:
|
||||||
FastRandomContext& insecure_rand GUARDED_BY(m_insecure_rand_mutex);
|
FastRandomContext& insecure_rand GUARDED_BY(m_insecure_rand_mutex);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // BITCOIN_POLICY_FEES_H
|
#endif // BITCOIN_POLICY_FEES_BLOCK_POLICY_ESTIMATOR_H
|
|
@ -2,7 +2,7 @@
|
||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
#include <policy/fees_args.h>
|
#include <policy/fees/block_policy_estimator_args.h>
|
||||||
|
|
||||||
#include <common/args.h>
|
#include <common/args.h>
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
#ifndef BITCOIN_POLICY_FEES_ARGS_H
|
#ifndef BITCOIN_POLICY_FEES_BLOCK_POLICY_ESTIMATOR_ARGS_H
|
||||||
#define BITCOIN_POLICY_FEES_ARGS_H
|
#define BITCOIN_POLICY_FEES_BLOCK_POLICY_ESTIMATOR_ARGS_H
|
||||||
|
|
||||||
#include <util/fs.h>
|
#include <util/fs.h>
|
||||||
|
|
||||||
|
@ -12,4 +12,4 @@ class ArgsManager;
|
||||||
/** @return The fee estimates data file path. */
|
/** @return The fee estimates data file path. */
|
||||||
fs::path FeeestPath(const ArgsManager& argsman);
|
fs::path FeeestPath(const ArgsManager& argsman);
|
||||||
|
|
||||||
#endif // BITCOIN_POLICY_FEES_ARGS_H
|
#endif // BITCOIN_POLICY_FEES_BLOCK_POLICY_ESTIMATOR_ARGS_H
|
39
src/policy/fees/forecaster.h
Normal file
39
src/policy/fees/forecaster.h
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright (c) 2025 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_FEES_FORECASTER_H
|
||||||
|
#define BITCOIN_POLICY_FEES_FORECASTER_H
|
||||||
|
|
||||||
|
#include <policy/fees/forecaster_util.h>
|
||||||
|
|
||||||
|
/** \class Forecaster
|
||||||
|
* @brief Abstract base class for fee rate forecasters.
|
||||||
|
*/
|
||||||
|
class Forecaster
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
const ForecastType m_forecastType;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Forecaster(ForecastType forecastType) : m_forecastType(forecastType) {}
|
||||||
|
|
||||||
|
ForecastType GetForecastType() const { return m_forecastType; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the estimated fee rate for a package to confirm within the given target.
|
||||||
|
* @param target Confirmation target in blocks.
|
||||||
|
* @param conservative True if the package cannot be fee bumped later.
|
||||||
|
* @return Predicted fee rate.
|
||||||
|
*/
|
||||||
|
virtual ForecastResult ForecastFeeRate(int target, bool conservative) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the maximum supported confirmation target.
|
||||||
|
*/
|
||||||
|
virtual unsigned int MaximumTarget() const = 0;
|
||||||
|
|
||||||
|
virtual ~Forecaster() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BITCOIN_POLICY_FEES_FORECASTER_H
|
74
src/policy/fees/forecaster_man.cpp
Normal file
74
src/policy/fees/forecaster_man.cpp
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright (c) 2025 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 <logging.h>
|
||||||
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
|
#include <policy/fees/forecaster.h>
|
||||||
|
#include <policy/fees/forecaster_man.h>
|
||||||
|
#include <policy/fees/forecaster_util.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
void FeeRateForecasterManager::RegisterForecaster(std::shared_ptr<Forecaster> forecaster)
|
||||||
|
{
|
||||||
|
forecasters.emplace(forecaster->GetForecastType(), forecaster);
|
||||||
|
}
|
||||||
|
|
||||||
|
CBlockPolicyEstimator* FeeRateForecasterManager::GetBlockPolicyEstimator()
|
||||||
|
{
|
||||||
|
Assert(forecasters.contains(ForecastType::BLOCK_POLICY));
|
||||||
|
Forecaster* block_policy_estimator = forecasters.find(ForecastType::BLOCK_POLICY)->second.get();
|
||||||
|
return dynamic_cast<CBlockPolicyEstimator*>(block_policy_estimator);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<ForecastResult, std::vector<std::string>> FeeRateForecasterManager::ForecastFeeRateFromForecasters(
|
||||||
|
int target, bool conservative) const
|
||||||
|
{
|
||||||
|
std::vector<std::string> err_messages;
|
||||||
|
ForecastResult feerate_forecast;
|
||||||
|
|
||||||
|
for (const auto& forecaster : forecasters) {
|
||||||
|
auto curr_forecast = forecaster.second->ForecastFeeRate(target, conservative);
|
||||||
|
|
||||||
|
if (curr_forecast.m_error.has_value()) {
|
||||||
|
err_messages.emplace_back(
|
||||||
|
strprintf("%s: %s", forecastTypeToString(forecaster.first), curr_forecast.m_error.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle case where the block policy forecaster does not have enough data.
|
||||||
|
if (forecaster.first == ForecastType::BLOCK_POLICY && curr_forecast.feerate.IsEmpty()) {
|
||||||
|
return {ForecastResult(), err_messages};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!curr_forecast.feerate.IsEmpty()) {
|
||||||
|
if (feerate_forecast.feerate.IsEmpty()) {
|
||||||
|
// If there's no selected forecast, choose curr_forecast as feerate_forecast.
|
||||||
|
feerate_forecast = curr_forecast;
|
||||||
|
} else {
|
||||||
|
// Otherwise, choose the smaller as feerate_forecast.
|
||||||
|
feerate_forecast = std::min(feerate_forecast, curr_forecast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!feerate_forecast.feerate.IsEmpty()) {
|
||||||
|
LogInfo("Fee rate Forecaster %s: Block height %s, fee rate %s %s/kvB.\n",
|
||||||
|
forecastTypeToString(feerate_forecast.forecaster),
|
||||||
|
feerate_forecast.current_block_height,
|
||||||
|
CFeeRate(feerate_forecast.feerate.fee, feerate_forecast.feerate.size).GetFeePerK(),
|
||||||
|
CURRENCY_ATOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {feerate_forecast, err_messages};
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int FeeRateForecasterManager::MaximumTarget() const
|
||||||
|
{
|
||||||
|
unsigned int maximum_target{0};
|
||||||
|
for (const auto& forecaster : forecasters) {
|
||||||
|
maximum_target = std::max(maximum_target, forecaster.second->MaximumTarget());
|
||||||
|
}
|
||||||
|
return maximum_target;
|
||||||
|
}
|
55
src/policy/fees/forecaster_man.h
Normal file
55
src/policy/fees/forecaster_man.h
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright (c) 2025 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_FEES_FORECASTER_MAN_H
|
||||||
|
#define BITCOIN_POLICY_FEES_FORECASTER_MAN_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
class CBlockPolicyEstimator;
|
||||||
|
class Forecaster;
|
||||||
|
struct ForecastResult;
|
||||||
|
|
||||||
|
enum class ForecastType;
|
||||||
|
|
||||||
|
/** \class FeeRateForecasterManager
|
||||||
|
* Manages multiple fee rate forecasters.
|
||||||
|
*/
|
||||||
|
class FeeRateForecasterManager
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
//! Map of all registered forecasters to their shared pointers.
|
||||||
|
std::unordered_map<ForecastType, std::shared_ptr<Forecaster>> forecasters;
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Register a forecaster.
|
||||||
|
* @param[in] forecaster shared pointer to a Forecaster instance.
|
||||||
|
*/
|
||||||
|
void RegisterForecaster(std::shared_ptr<Forecaster> forecaster);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the pointer to block policy estimator.
|
||||||
|
*/
|
||||||
|
CBlockPolicyEstimator* GetBlockPolicyEstimator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a fee rate forecast from all registered forecasters for a given confirmation target.
|
||||||
|
*
|
||||||
|
* Polls all registered forecasters and selects the lowest fee rate.
|
||||||
|
*
|
||||||
|
* @param[in] target The target within which the transaction should be confirmed.
|
||||||
|
* @param[in] conservative True if the package cannot be fee bumped later.
|
||||||
|
* @return A pair consisting of the forecast result and a vector of forecaster names.
|
||||||
|
*/
|
||||||
|
std::pair<ForecastResult, std::vector<std::string>> ForecastFeeRateFromForecasters(int target, bool conservative) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the maximum supported confirmation target from all forecasters.
|
||||||
|
*/
|
||||||
|
unsigned int MaximumTarget() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // BITCOIN_POLICY_FEES_FORECASTER_MAN_H
|
57
src/policy/fees/forecaster_util.cpp
Normal file
57
src/policy/fees/forecaster_util.cpp
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright (c) 2025 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 <policy/fees/forecaster_util.h>
|
||||||
|
#include <policy/policy.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
Percentiles CalculatePercentiles(const std::vector<FeeFrac>& package_feerates, const int32_t total_weight)
|
||||||
|
{
|
||||||
|
if (package_feerates.empty()) return Percentiles{};
|
||||||
|
int32_t accumulated_weight{0};
|
||||||
|
const int32_t p25_weight = 0.25 * total_weight;
|
||||||
|
const int32_t p50_weight = 0.50 * total_weight;
|
||||||
|
const int32_t p75_weight = 0.75 * total_weight;
|
||||||
|
const int32_t p95_weight = 0.95 * total_weight;
|
||||||
|
auto last_tracked_feerate = package_feerates.front();
|
||||||
|
auto percentiles = Percentiles{};
|
||||||
|
|
||||||
|
// Process histogram entries while maintaining monotonicity
|
||||||
|
for (const auto& curr_feerate : package_feerates) {
|
||||||
|
accumulated_weight += curr_feerate.size * WITNESS_SCALE_FACTOR;
|
||||||
|
// Maintain monotonicity by taking the minimum between the current and last tracked fee rate
|
||||||
|
last_tracked_feerate = std::min(last_tracked_feerate, curr_feerate, [](const FeeFrac& a, const FeeFrac& b) {
|
||||||
|
return std::is_lt(FeeRateCompare(a, b));
|
||||||
|
});
|
||||||
|
if (accumulated_weight >= p25_weight && percentiles.p25.IsEmpty()) {
|
||||||
|
percentiles.p25 = last_tracked_feerate;
|
||||||
|
}
|
||||||
|
if (accumulated_weight >= p50_weight && percentiles.p50.IsEmpty()) {
|
||||||
|
percentiles.p50 = last_tracked_feerate;
|
||||||
|
}
|
||||||
|
if (accumulated_weight >= p75_weight && percentiles.p75.IsEmpty()) {
|
||||||
|
percentiles.p75 = last_tracked_feerate;
|
||||||
|
}
|
||||||
|
if (accumulated_weight >= p95_weight && percentiles.p95.IsEmpty()) {
|
||||||
|
percentiles.p95 = last_tracked_feerate;
|
||||||
|
break; // Early exit once all percentiles are calculated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return empty percentiles if we couldn't calculate the 95th percentile.
|
||||||
|
return percentiles.p95.IsEmpty() ? Percentiles{} : percentiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string forecastTypeToString(ForecastType forecastType)
|
||||||
|
{
|
||||||
|
switch (forecastType) {
|
||||||
|
case ForecastType::MEMPOOL_FORECAST:
|
||||||
|
return std::string("Mempool Forecast");
|
||||||
|
case ForecastType::BLOCK_POLICY:
|
||||||
|
return std::string("Block Policy Estimator");
|
||||||
|
}
|
||||||
|
// no default case, so the compiler can warn about missing cases
|
||||||
|
assert(false);
|
||||||
|
}
|
82
src/policy/fees/forecaster_util.h
Normal file
82
src/policy/fees/forecaster_util.h
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright (c) 2025 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_FEES_FORECASTER_UTIL_H
|
||||||
|
#define BITCOIN_POLICY_FEES_FORECASTER_UTIL_H
|
||||||
|
|
||||||
|
#include <util/feefrac.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum ForecastType
|
||||||
|
* Identifier for fee rate forecasters.
|
||||||
|
*/
|
||||||
|
enum class ForecastType {
|
||||||
|
BLOCK_POLICY,
|
||||||
|
MEMPOOL_FORECAST,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @struct ForecastResult
|
||||||
|
* Represents the response returned by a fee rate forecaster.
|
||||||
|
*/
|
||||||
|
struct ForecastResult {
|
||||||
|
//! This identifies which forecaster is providing this feerate forecast
|
||||||
|
ForecastType forecaster;
|
||||||
|
|
||||||
|
//! Fee rate sufficient to confirm a package within target.
|
||||||
|
FeeFrac feerate;
|
||||||
|
|
||||||
|
//! The chain tip at which the forecast was made.
|
||||||
|
unsigned int current_block_height{0};
|
||||||
|
|
||||||
|
std::optional<std::string> m_error; ///< Optional error message.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overloaded less than operator which compares two ForecastResult objects based on
|
||||||
|
* their fee rate.
|
||||||
|
* @param other The other ForecastResult object to compare with.
|
||||||
|
* @return true if the current object's fee rate is less than the other, false otherwise.
|
||||||
|
*/
|
||||||
|
bool operator<(const ForecastResult& other) const
|
||||||
|
{
|
||||||
|
return feerate << other.feerate;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Block percentiles fee rate (in BTC/vB).
|
||||||
|
struct Percentiles {
|
||||||
|
FeeFrac p25; // 25th percentile
|
||||||
|
FeeFrac p50; // 50th percentile
|
||||||
|
FeeFrac p75; // 75th percentile
|
||||||
|
FeeFrac p95; // 5th percentile
|
||||||
|
|
||||||
|
Percentiles() = default;
|
||||||
|
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return p25.IsEmpty() && p50.IsEmpty() && p75.IsEmpty() && p95.IsEmpty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the percentile fee rates from a given vector of fee rates.
|
||||||
|
*
|
||||||
|
* This function assumes that the fee rates in the input vector are sorted in descending order
|
||||||
|
* based on mining score priority. It ensures that the calculated percentile fee rates
|
||||||
|
* are monotonically decreasing by filtering out outliers. Outliers can occur when
|
||||||
|
* the mining score of a transaction increases due to the inclusion of its ancestors
|
||||||
|
* in different transaction packages.
|
||||||
|
*
|
||||||
|
* @param[in] package_feerates A vector containing packages fee rates.
|
||||||
|
* @param[in] total_weight The total weight units to use for percentile fee rate calculations.
|
||||||
|
* @return Percentiles object containing the calculated percentile fee rates.
|
||||||
|
*/
|
||||||
|
Percentiles CalculatePercentiles(const std::vector<FeeFrac>& package_feerates, const int32_t total_weight);
|
||||||
|
|
||||||
|
std::string forecastTypeToString(ForecastType forecastType);
|
||||||
|
|
||||||
|
#endif // BITCOIN_POLICY_FEES_FORECASTER_UTIL_H
|
63
src/policy/fees/mempool_forecaster.cpp
Normal file
63
src/policy/fees/mempool_forecaster.cpp
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright (c) 2024 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 <logging.h>
|
||||||
|
#include <node/miner.h>
|
||||||
|
#include <policy/fees/forecaster.h>
|
||||||
|
#include <policy/fees/forecaster_util.h>
|
||||||
|
#include <policy/fees/mempool_forecaster.h>
|
||||||
|
#include <policy/policy.h>
|
||||||
|
#include <validation.h>
|
||||||
|
|
||||||
|
ForecastResult MemPoolForecaster::ForecastFeeRate(int target, bool conservative) const
|
||||||
|
{
|
||||||
|
ForecastResult result;
|
||||||
|
result.forecaster = m_forecastType;
|
||||||
|
LOCK2(cs_main, m_mempool->cs);
|
||||||
|
auto activeTip = m_chainstate->m_chainman.ActiveTip();
|
||||||
|
if (!activeTip) {
|
||||||
|
result.m_error = "No active chainstate available";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
result.current_block_height = static_cast<unsigned int>(activeTip->nHeight);
|
||||||
|
|
||||||
|
if (target > MEMPOOL_FORECAST_MAX_TARGET) {
|
||||||
|
result.m_error = strprintf("Confirmation target %s exceeds the maximum limit of %s. mempool conditions might change",
|
||||||
|
target, MEMPOOL_FORECAST_MAX_TARGET);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto cached_estimate = cache.get_cached_forecast();
|
||||||
|
const auto known_chain_tip_hash = cache.get_chain_tip_hash();
|
||||||
|
if (cached_estimate && *activeTip->phashBlock == known_chain_tip_hash) {
|
||||||
|
result.feerate = conservative ? cached_estimate->p50 : cached_estimate->p75;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
node::BlockAssembler::Options options;
|
||||||
|
options.test_block_validity = false;
|
||||||
|
node::BlockAssembler assembler(*m_chainstate, m_mempool, options);
|
||||||
|
|
||||||
|
const auto pblocktemplate = assembler.CreateNewBlock();
|
||||||
|
const auto& m_package_feerates = pblocktemplate->m_package_feerates;
|
||||||
|
const auto percentiles = CalculatePercentiles(m_package_feerates, DEFAULT_BLOCK_MAX_WEIGHT);
|
||||||
|
if (percentiles.empty()) {
|
||||||
|
result.m_error = "Forecaster unable a fee rate due to insufficient data";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogDebug(BCLog::MEMPOOL,
|
||||||
|
"%s: Block height %s, Block template 25th percentile fee rate: %s %s/kvB, "
|
||||||
|
"50th percentile fee rate: %s %s/kvB, 75th percentile fee rate: %s %s/kvB, "
|
||||||
|
"95th percentile fee rate: %s %s/kvB\n",
|
||||||
|
forecastTypeToString(m_forecastType), result.current_block_height,
|
||||||
|
CFeeRate(percentiles.p25.fee, percentiles.p25.size).GetFeePerK(), CURRENCY_ATOM,
|
||||||
|
CFeeRate(percentiles.p50.fee, percentiles.p50.size).GetFeePerK(), CURRENCY_ATOM,
|
||||||
|
CFeeRate(percentiles.p75.fee, percentiles.p75.size).GetFeePerK(), CURRENCY_ATOM,
|
||||||
|
CFeeRate(percentiles.p95.fee, percentiles.p95.size).GetFeePerK(), CURRENCY_ATOM);
|
||||||
|
|
||||||
|
cache.update(percentiles, *activeTip->phashBlock);
|
||||||
|
result.feerate = conservative ? percentiles.p50 : percentiles.p75;
|
||||||
|
return result;
|
||||||
|
}
|
100
src/policy/fees/mempool_forecaster.h
Normal file
100
src/policy/fees/mempool_forecaster.h
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright (c) 2024 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_FEES_MEMPOOL_FORECASTER_H
|
||||||
|
#define BITCOIN_POLICY_FEES_MEMPOOL_FORECASTER_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <logging.h>
|
||||||
|
#include <policy/fees/forecaster.h>
|
||||||
|
#include <policy/fees/forecaster_util.h>
|
||||||
|
#include <sync.h>
|
||||||
|
#include <uint256.h>
|
||||||
|
#include <util/time.h>
|
||||||
|
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
class Chainstate;
|
||||||
|
class CTxMemPool;
|
||||||
|
|
||||||
|
// Fee rate forecasts above this confirmation target are not reliable,
|
||||||
|
// as mempool conditions are likely to change.
|
||||||
|
constexpr int MEMPOOL_FORECAST_MAX_TARGET{2};
|
||||||
|
constexpr std::chrono::seconds CACHE_LIFE{30};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CachedMempoolForecast holds a cache of recent fee rate forecast.
|
||||||
|
* We only provide fresh fee rate if the last saved cache ages more than CACHE_LIFE.
|
||||||
|
*/
|
||||||
|
struct CachedMempoolForecast {
|
||||||
|
private:
|
||||||
|
mutable Mutex cache_mutex;
|
||||||
|
uint256 chain_tip_hash GUARDED_BY(cache_mutex);
|
||||||
|
Percentiles fee_rate GUARDED_BY(cache_mutex);
|
||||||
|
NodeClock::time_point last_updated GUARDED_BY(cache_mutex){NodeClock::now() - CACHE_LIFE - std::chrono::seconds(1)};
|
||||||
|
|
||||||
|
public:
|
||||||
|
CachedMempoolForecast() = default;
|
||||||
|
CachedMempoolForecast(const CachedMempoolForecast&) = delete;
|
||||||
|
CachedMempoolForecast& operator=(const CachedMempoolForecast&) = delete;
|
||||||
|
|
||||||
|
bool isStale() const EXCLUSIVE_LOCKS_REQUIRED(cache_mutex)
|
||||||
|
{
|
||||||
|
AssertLockHeld(cache_mutex);
|
||||||
|
return (last_updated + CACHE_LIFE) < NodeClock::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Percentiles> get_cached_forecast() const EXCLUSIVE_LOCKS_REQUIRED(!cache_mutex)
|
||||||
|
{
|
||||||
|
LOCK(cache_mutex);
|
||||||
|
if (isStale()) return std::nullopt;
|
||||||
|
LogDebug(BCLog::ESTIMATEFEE, "%s: cache is not stale, using cached value\n", forecastTypeToString(ForecastType::MEMPOOL_FORECAST));
|
||||||
|
return fee_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint256 get_chain_tip_hash() const EXCLUSIVE_LOCKS_REQUIRED(!cache_mutex)
|
||||||
|
{
|
||||||
|
LOCK(cache_mutex);
|
||||||
|
return chain_tip_hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(const Percentiles& new_fee_rate, uint256 current_tip_hash) EXCLUSIVE_LOCKS_REQUIRED(!cache_mutex)
|
||||||
|
{
|
||||||
|
LOCK(cache_mutex);
|
||||||
|
fee_rate = new_fee_rate;
|
||||||
|
last_updated = NodeClock::now();
|
||||||
|
chain_tip_hash = current_tip_hash;
|
||||||
|
LogDebug(BCLog::ESTIMATEFEE, "%s: updated cache\n", forecastTypeToString(ForecastType::MEMPOOL_FORECAST));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** \class MemPoolForecaster
|
||||||
|
* @brief Forecasts the fee rate required for a transaction to be included in the next block.
|
||||||
|
*
|
||||||
|
* This forecaster uses Bitcoin Core's block-building algorithm to generate the block
|
||||||
|
* template that will likely be mined from unconfirmed transactions in the mempool. It calculates percentile
|
||||||
|
* fee rates from the selected packages of the template: the 75th percentile fee rate is used as the economical
|
||||||
|
* forecast, and the 50th fee rate percentile as the conservative forecast.
|
||||||
|
*/
|
||||||
|
class MemPoolForecaster : public Forecaster
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MemPoolForecaster(const CTxMemPool* mempool, Chainstate* chainstate)
|
||||||
|
: Forecaster(ForecastType::MEMPOOL_FORECAST), m_mempool(mempool), m_chainstate(chainstate) {};
|
||||||
|
~MemPoolForecaster() = default;
|
||||||
|
|
||||||
|
/** Overridden from Forecaster. */
|
||||||
|
ForecastResult ForecastFeeRate(int target, bool conservative) const override;
|
||||||
|
unsigned int MaximumTarget() const override
|
||||||
|
{
|
||||||
|
return MEMPOOL_FORECAST_MAX_TARGET;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
const CTxMemPool* m_mempool;
|
||||||
|
Chainstate* m_chainstate;
|
||||||
|
mutable CachedMempoolForecast cache;
|
||||||
|
};
|
||||||
|
#endif // BITCOIN_POLICY_FEES_MEMPOOL_FORECASTER_H
|
|
@ -21,7 +21,7 @@
|
||||||
#include <key_io.h>
|
#include <key_io.h>
|
||||||
#include <node/interface_ui.h>
|
#include <node/interface_ui.h>
|
||||||
#include <node/types.h>
|
#include <node/types.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <txmempool.h>
|
#include <txmempool.h>
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
#include <wallet/coincontrol.h>
|
#include <wallet/coincontrol.h>
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
#include <core_io.h>
|
#include <core_io.h>
|
||||||
#include <node/context.h>
|
#include <node/context.h>
|
||||||
#include <policy/feerate.h>
|
#include <policy/feerate.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
|
#include <policy/fees/forecaster_man.h>
|
||||||
#include <rpc/protocol.h>
|
#include <rpc/protocol.h>
|
||||||
#include <rpc/request.h>
|
#include <rpc/request.h>
|
||||||
#include <rpc/server.h>
|
#include <rpc/server.h>
|
||||||
|
@ -59,12 +60,12 @@ static RPCHelpMan estimatesmartfee()
|
||||||
},
|
},
|
||||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
{
|
{
|
||||||
CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
|
CBlockPolicyEstimator& block_policy_fee_estimator = *(EnsureAnyForecasterMan(request.context).GetBlockPolicyEstimator());
|
||||||
const NodeContext& node = EnsureAnyNodeContext(request.context);
|
const NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||||
const CTxMemPool& mempool = EnsureMemPool(node);
|
const CTxMemPool& mempool = EnsureMemPool(node);
|
||||||
|
|
||||||
CHECK_NONFATAL(mempool.m_opts.signals)->SyncWithValidationInterfaceQueue();
|
CHECK_NONFATAL(mempool.m_opts.signals)->SyncWithValidationInterfaceQueue();
|
||||||
unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
|
unsigned int max_target = block_policy_fee_estimator.MaximumTarget();
|
||||||
unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
|
unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
|
||||||
bool conservative = false;
|
bool conservative = false;
|
||||||
if (!request.params[1].isNull()) {
|
if (!request.params[1].isNull()) {
|
||||||
|
@ -78,7 +79,7 @@ static RPCHelpMan estimatesmartfee()
|
||||||
UniValue result(UniValue::VOBJ);
|
UniValue result(UniValue::VOBJ);
|
||||||
UniValue errors(UniValue::VARR);
|
UniValue errors(UniValue::VARR);
|
||||||
FeeCalculation feeCalc;
|
FeeCalculation feeCalc;
|
||||||
CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)};
|
CFeeRate feeRate{block_policy_fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)};
|
||||||
if (feeRate != CFeeRate(0)) {
|
if (feeRate != CFeeRate(0)) {
|
||||||
CFeeRate min_mempool_feerate{mempool.GetMinFee()};
|
CFeeRate min_mempool_feerate{mempool.GetMinFee()};
|
||||||
CFeeRate min_relay_feerate{mempool.m_opts.min_relay_feerate};
|
CFeeRate min_relay_feerate{mempool.m_opts.min_relay_feerate};
|
||||||
|
@ -150,11 +151,11 @@ static RPCHelpMan estimaterawfee()
|
||||||
},
|
},
|
||||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
{
|
{
|
||||||
CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
|
CBlockPolicyEstimator& block_policy_fee_estimator = *(EnsureAnyForecasterMan(request.context).GetBlockPolicyEstimator());
|
||||||
const NodeContext& node = EnsureAnyNodeContext(request.context);
|
const NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||||
|
|
||||||
CHECK_NONFATAL(node.validation_signals)->SyncWithValidationInterfaceQueue();
|
CHECK_NONFATAL(node.validation_signals)->SyncWithValidationInterfaceQueue();
|
||||||
unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
|
unsigned int max_target = block_policy_fee_estimator.MaximumTarget();
|
||||||
unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
|
unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
|
||||||
double threshold = 0.95;
|
double threshold = 0.95;
|
||||||
if (!request.params[1].isNull()) {
|
if (!request.params[1].isNull()) {
|
||||||
|
@ -171,9 +172,9 @@ static RPCHelpMan estimaterawfee()
|
||||||
EstimationResult buckets;
|
EstimationResult buckets;
|
||||||
|
|
||||||
// Only output results for horizons which track the target
|
// Only output results for horizons which track the target
|
||||||
if (conf_target > fee_estimator.HighestTargetTracked(horizon)) continue;
|
if (conf_target > block_policy_fee_estimator.HighestTargetTracked(horizon)) continue;
|
||||||
|
|
||||||
feeRate = fee_estimator.estimateRawFee(conf_target, threshold, horizon, &buckets);
|
feeRate = block_policy_fee_estimator.estimateRawFee(conf_target, threshold, horizon, &buckets);
|
||||||
UniValue horizon_result(UniValue::VOBJ);
|
UniValue horizon_result(UniValue::VOBJ);
|
||||||
UniValue errors(UniValue::VARR);
|
UniValue errors(UniValue::VARR);
|
||||||
UniValue passbucket(UniValue::VOBJ);
|
UniValue passbucket(UniValue::VOBJ);
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
#include <net_processing.h>
|
#include <net_processing.h>
|
||||||
#include <node/context.h>
|
#include <node/context.h>
|
||||||
#include <node/miner.h>
|
#include <node/miner.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <pow.h>
|
#include <pow.h>
|
||||||
#include <rpc/protocol.h>
|
#include <rpc/protocol.h>
|
||||||
#include <rpc/request.h>
|
#include <rpc/request.h>
|
||||||
|
@ -84,19 +84,20 @@ ChainstateManager& EnsureAnyChainman(const std::any& context)
|
||||||
return EnsureChainman(EnsureAnyNodeContext(context));
|
return EnsureChainman(EnsureAnyNodeContext(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
CBlockPolicyEstimator& EnsureFeeEstimator(const NodeContext& node)
|
FeeRateForecasterManager& EnsureForecasterMan(const NodeContext& node)
|
||||||
{
|
{
|
||||||
if (!node.fee_estimator) {
|
if (!node.forecasterman) {
|
||||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Fee estimation disabled");
|
throw JSONRPCError(RPC_INTERNAL_ERROR, "Fee estimation disabled");
|
||||||
}
|
}
|
||||||
return *node.fee_estimator;
|
return *node.forecasterman.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
CBlockPolicyEstimator& EnsureAnyFeeEstimator(const std::any& context)
|
FeeRateForecasterManager& EnsureAnyForecasterMan(const std::any& context)
|
||||||
{
|
{
|
||||||
return EnsureFeeEstimator(EnsureAnyNodeContext(context));
|
return EnsureForecasterMan(EnsureAnyNodeContext(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CConnman& EnsureConnman(const NodeContext& node)
|
CConnman& EnsureConnman(const NodeContext& node)
|
||||||
{
|
{
|
||||||
if (!node.connman) {
|
if (!node.connman) {
|
||||||
|
|
|
@ -12,10 +12,10 @@
|
||||||
class AddrMan;
|
class AddrMan;
|
||||||
class ArgsManager;
|
class ArgsManager;
|
||||||
class CBlockIndex;
|
class CBlockIndex;
|
||||||
class CBlockPolicyEstimator;
|
|
||||||
class CConnman;
|
class CConnman;
|
||||||
class CTxMemPool;
|
class CTxMemPool;
|
||||||
class ChainstateManager;
|
class ChainstateManager;
|
||||||
|
class FeeRateForecasterManager;
|
||||||
class PeerManager;
|
class PeerManager;
|
||||||
class BanMan;
|
class BanMan;
|
||||||
namespace node {
|
namespace node {
|
||||||
|
@ -34,8 +34,8 @@ ArgsManager& EnsureArgsman(const node::NodeContext& node);
|
||||||
ArgsManager& EnsureAnyArgsman(const std::any& context);
|
ArgsManager& EnsureAnyArgsman(const std::any& context);
|
||||||
ChainstateManager& EnsureChainman(const node::NodeContext& node);
|
ChainstateManager& EnsureChainman(const node::NodeContext& node);
|
||||||
ChainstateManager& EnsureAnyChainman(const std::any& context);
|
ChainstateManager& EnsureAnyChainman(const std::any& context);
|
||||||
CBlockPolicyEstimator& EnsureFeeEstimator(const node::NodeContext& node);
|
FeeRateForecasterManager& EnsureForecasterMan(const node::NodeContext& node);
|
||||||
CBlockPolicyEstimator& EnsureAnyFeeEstimator(const std::any& context);
|
FeeRateForecasterManager& EnsureAnyForecasterMan(const std::any& context);
|
||||||
CConnman& EnsureConnman(const node::NodeContext& node);
|
CConnman& EnsureConnman(const node::NodeContext& node);
|
||||||
interfaces::Mining& EnsureMining(const node::NodeContext& node);
|
interfaces::Mining& EnsureMining(const node::NodeContext& node);
|
||||||
PeerManager& EnsurePeerman(const node::NodeContext& node);
|
PeerManager& EnsurePeerman(const node::NodeContext& node);
|
||||||
|
|
|
@ -40,6 +40,8 @@ add_executable(test_bitcoin
|
||||||
descriptor_tests.cpp
|
descriptor_tests.cpp
|
||||||
disconnected_transactions.cpp
|
disconnected_transactions.cpp
|
||||||
feefrac_tests.cpp
|
feefrac_tests.cpp
|
||||||
|
feerounder_tests.cpp
|
||||||
|
forecaster_util_tests.cpp
|
||||||
flatfile_tests.cpp
|
flatfile_tests.cpp
|
||||||
fs_tests.cpp
|
fs_tests.cpp
|
||||||
getarg_tests.cpp
|
getarg_tests.cpp
|
||||||
|
@ -52,6 +54,7 @@ add_executable(test_bitcoin
|
||||||
key_tests.cpp
|
key_tests.cpp
|
||||||
logging_tests.cpp
|
logging_tests.cpp
|
||||||
mempool_tests.cpp
|
mempool_tests.cpp
|
||||||
|
mempoolforecaster_tests.cpp
|
||||||
merkle_tests.cpp
|
merkle_tests.cpp
|
||||||
merkleblock_tests.cpp
|
merkleblock_tests.cpp
|
||||||
miner_tests.cpp
|
miner_tests.cpp
|
||||||
|
@ -68,7 +71,6 @@ add_executable(test_bitcoin
|
||||||
pcp_tests.cpp
|
pcp_tests.cpp
|
||||||
peerman_tests.cpp
|
peerman_tests.cpp
|
||||||
pmt_tests.cpp
|
pmt_tests.cpp
|
||||||
policy_fee_tests.cpp
|
|
||||||
policyestimator_tests.cpp
|
policyestimator_tests.cpp
|
||||||
pool_tests.cpp
|
pool_tests.cpp
|
||||||
pow_tests.cpp
|
pow_tests.cpp
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
#include <consensus/amount.h>
|
#include <consensus/amount.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
|
|
||||||
#include <boost/test/unit_test.hpp>
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE(policy_fee_tests)
|
BOOST_AUTO_TEST_SUITE(fee_rounder_tests)
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(FeeRounder)
|
BOOST_AUTO_TEST_CASE(FeeRounder)
|
||||||
{
|
{
|
65
src/test/forecaster_util_tests.cpp
Normal file
65
src/test/forecaster_util_tests.cpp
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright (c) 2024 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 <policy/fees/forecaster_util.h>
|
||||||
|
#include <policy/policy.h>
|
||||||
|
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE(forecaster_util_tests)
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(calculate_percentile_test)
|
||||||
|
{
|
||||||
|
// Test that CalculatePercentiles returns empty when vector is empty
|
||||||
|
BOOST_CHECK(CalculatePercentiles({}, DEFAULT_BLOCK_MAX_WEIGHT).empty());
|
||||||
|
|
||||||
|
const int32_t package_size{10};
|
||||||
|
const int32_t individual_tx_vsize = static_cast<int32_t>(DEFAULT_BLOCK_MAX_WEIGHT / WITNESS_SCALE_FACTOR) / package_size;
|
||||||
|
|
||||||
|
// Define fee rate categories with corresponding fee rates in satoshis per kvB
|
||||||
|
const FeeFrac superHighFeerate{500 * individual_tx_vsize, individual_tx_vsize}; // Super High fee: 500 sat/kvB
|
||||||
|
const FeeFrac highFeerate{100 * individual_tx_vsize, individual_tx_vsize}; // High fee: 100 sat/kvB
|
||||||
|
const FeeFrac mediumFeerate{50 * individual_tx_vsize, individual_tx_vsize}; // Medium fee: 50 sat/kvB
|
||||||
|
const FeeFrac lowFeerate{10 * individual_tx_vsize, individual_tx_vsize}; // Low fee: 10 sat/kvB
|
||||||
|
|
||||||
|
std::vector<FeeFrac> package_feerates;
|
||||||
|
package_feerates.reserve(package_size);
|
||||||
|
|
||||||
|
// Populate the feerate histogram based on specified index ranges.
|
||||||
|
for (int i = 0; i < package_size; ++i) {
|
||||||
|
if (i < 3) {
|
||||||
|
package_feerates.emplace_back(superHighFeerate); // Super High fee rate for top 3
|
||||||
|
} else if (i < 5) {
|
||||||
|
package_feerates.emplace_back(highFeerate); // High fee rate for next 2
|
||||||
|
} else if (i < 8) {
|
||||||
|
package_feerates.emplace_back(mediumFeerate); // Medium fee rate for next 3
|
||||||
|
BOOST_CHECK(CalculatePercentiles(package_feerates, DEFAULT_BLOCK_MAX_WEIGHT).empty()); // CalculatePercentiles should return empty until reaching the 95th percentile
|
||||||
|
} else {
|
||||||
|
package_feerates.emplace_back(lowFeerate); // Low fee rate for remaining 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test percentile calculation on a complete histogram
|
||||||
|
{
|
||||||
|
const auto percentiles = CalculatePercentiles(package_feerates, DEFAULT_BLOCK_MAX_WEIGHT);
|
||||||
|
BOOST_CHECK(percentiles.p25 == superHighFeerate);
|
||||||
|
BOOST_CHECK(percentiles.p50 == highFeerate);
|
||||||
|
BOOST_CHECK(percentiles.p75 == mediumFeerate);
|
||||||
|
BOOST_CHECK(percentiles.p95 == lowFeerate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that CalculatePercentiles maintains monotonicity across all percentiles
|
||||||
|
{
|
||||||
|
package_feerates[7] = superHighFeerate; // Increase 8th index to a high fee rate
|
||||||
|
const auto percentiles = CalculatePercentiles(package_feerates, DEFAULT_BLOCK_MAX_WEIGHT);
|
||||||
|
BOOST_CHECK(percentiles.p25 == superHighFeerate);
|
||||||
|
BOOST_CHECK(percentiles.p50 == highFeerate);
|
||||||
|
BOOST_CHECK(percentiles.p75 == mediumFeerate); // Should still reflect the previous medium rate
|
||||||
|
BOOST_CHECK(percentiles.p95 == lowFeerate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END()
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
#include <common/messages.h>
|
#include <common/messages.h>
|
||||||
#include <consensus/amount.h>
|
#include <consensus/amount.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <test/fuzz/FuzzedDataProvider.h>
|
#include <test/fuzz/FuzzedDataProvider.h>
|
||||||
#include <test/fuzz/fuzz.h>
|
#include <test/fuzz/fuzz.h>
|
||||||
#include <test/fuzz/util.h>
|
#include <test/fuzz/util.h>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#include <common/messages.h>
|
#include <common/messages.h>
|
||||||
#include <merkleblock.h>
|
#include <merkleblock.h>
|
||||||
#include <node/types.h>
|
#include <node/types.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <rpc/util.h>
|
#include <rpc/util.h>
|
||||||
#include <test/fuzz/FuzzedDataProvider.h>
|
#include <test/fuzz/FuzzedDataProvider.h>
|
||||||
#include <test/fuzz/fuzz.h>
|
#include <test/fuzz/fuzz.h>
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
#include <kernel/mempool_entry.h>
|
#include <kernel/mempool_entry.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <policy/fees_args.h>
|
#include <policy/fees/block_policy_estimator_args.h>
|
||||||
#include <primitives/transaction.h>
|
#include <primitives/transaction.h>
|
||||||
#include <streams.h>
|
#include <streams.h>
|
||||||
#include <test/fuzz/FuzzedDataProvider.h>
|
#include <test/fuzz/FuzzedDataProvider.h>
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <policy/fees_args.h>
|
#include <policy/fees/block_policy_estimator_args.h>
|
||||||
#include <streams.h>
|
#include <streams.h>
|
||||||
#include <test/fuzz/FuzzedDataProvider.h>
|
#include <test/fuzz/FuzzedDataProvider.h>
|
||||||
#include <test/fuzz/fuzz.h>
|
#include <test/fuzz/fuzz.h>
|
||||||
|
|
100
src/test/mempoolforecaster_tests.cpp
Normal file
100
src/test/mempoolforecaster_tests.cpp
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright (c) 2024 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 <policy/fees/forecaster.h>
|
||||||
|
#include <policy/fees/forecaster_util.h>
|
||||||
|
#include <policy/fees/mempool_forecaster.h>
|
||||||
|
#include <random.h>
|
||||||
|
#include <test/util/txmempool.h>
|
||||||
|
#include <txmempool.h>
|
||||||
|
#include <uint256.h>
|
||||||
|
#include <util/feefrac.h>
|
||||||
|
#include <util/strencodings.h>
|
||||||
|
#include <validation.h>
|
||||||
|
|
||||||
|
|
||||||
|
#include <test/util/setup_common.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_SUITE(mempoolforecaster_tests, TestChain100Setup)
|
||||||
|
|
||||||
|
static inline CTransactionRef make_random_tx()
|
||||||
|
{
|
||||||
|
auto rng = FastRandomContext();
|
||||||
|
auto tx = CMutableTransaction();
|
||||||
|
tx.vin.resize(1);
|
||||||
|
tx.vout.resize(1);
|
||||||
|
tx.vin[0].prevout.hash = Txid::FromUint256(rng.rand256());
|
||||||
|
tx.vin[0].prevout.n = 0;
|
||||||
|
tx.vin[0].scriptSig << OP_TRUE;
|
||||||
|
tx.vout[0].scriptPubKey = CScript() << OP_TRUE;
|
||||||
|
tx.vout[0].nValue = COIN;
|
||||||
|
return MakeTransactionRef(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(MempoolForecaster)
|
||||||
|
{
|
||||||
|
auto mempool_forecaster = std::make_unique<MemPoolForecaster>(m_node.mempool.get(), &(m_node.chainman->ActiveChainstate()));
|
||||||
|
int conf_target = MEMPOOL_FORECAST_MAX_TARGET + 1;
|
||||||
|
LOCK2(cs_main, m_node.mempool->cs);
|
||||||
|
{
|
||||||
|
// Test when targetBlocks > MEMPOOL_FORECAST_MAX_TARGET
|
||||||
|
const auto result = mempool_forecaster->ForecastFeeRate(conf_target, /*conservative=*/true);
|
||||||
|
BOOST_CHECK(result.feerate.IsEmpty());
|
||||||
|
BOOST_CHECK(*result.m_error == strprintf("Confirmation target %s exceeds the maximum limit of %s. mempool conditions might change",
|
||||||
|
conf_target, MEMPOOL_FORECAST_MAX_TARGET));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_CHECK(m_node.mempool->GetTotalTxSize() == 0);
|
||||||
|
TestMemPoolEntryHelper entry;
|
||||||
|
|
||||||
|
const CAmount low_fee{CENT / 3000};
|
||||||
|
const CAmount med_fee{CENT / 100};
|
||||||
|
const CAmount high_fee{CENT / 10};
|
||||||
|
std::string data_err = "Forecaster unable a fee rate due to insufficient data";
|
||||||
|
|
||||||
|
conf_target = MEMPOOL_FORECAST_MAX_TARGET;
|
||||||
|
// Test when there are not enough mempool transactions to get an accurate forecast
|
||||||
|
{
|
||||||
|
// Add transactions with high_fee fee until mempool transactions weight is more than 25th percent of DEFAULT_BLOCK_MAX_WEIGHT
|
||||||
|
while (static_cast<int>(m_node.mempool->GetTotalTxSize() * WITNESS_SCALE_FACTOR) <= static_cast<int>(0.25 * DEFAULT_BLOCK_MAX_WEIGHT)) {
|
||||||
|
AddToMempool(*m_node.mempool, entry.Fee(high_fee).FromTx(make_random_tx()));
|
||||||
|
}
|
||||||
|
const auto result = mempool_forecaster->ForecastFeeRate(conf_target, /*conservative=*/true);
|
||||||
|
BOOST_CHECK(result.feerate.IsEmpty());
|
||||||
|
BOOST_CHECK(*result.m_error == data_err);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Add transactions with med_fee fee until mempool transactions weight is more than 50th percent of DEFAULT_BLOCK_MAX_WEIGHT
|
||||||
|
while (static_cast<int>(m_node.mempool->GetTotalTxSize() * WITNESS_SCALE_FACTOR) <= static_cast<int>(0.5 * DEFAULT_BLOCK_MAX_WEIGHT)) {
|
||||||
|
AddToMempool(*m_node.mempool, entry.Fee(med_fee).FromTx(make_random_tx()));
|
||||||
|
}
|
||||||
|
const auto result = mempool_forecaster->ForecastFeeRate(conf_target, /*conservative=*/true);
|
||||||
|
BOOST_CHECK(result.feerate.IsEmpty());
|
||||||
|
BOOST_CHECK(*result.m_error == data_err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mempool transactions are enough to provide feerate forecast
|
||||||
|
{
|
||||||
|
// Add low_fee transactions until mempool transactions weight is more than 95th percent of DEFAULT_BLOCK_MAX_WEIGHT
|
||||||
|
while (static_cast<int>(m_node.mempool->GetTotalTxSize() * WITNESS_SCALE_FACTOR) <= static_cast<int>(0.95 * DEFAULT_BLOCK_MAX_WEIGHT)) {
|
||||||
|
const auto txref = make_random_tx();
|
||||||
|
AddToMempool(*m_node.mempool, entry.Fee(low_fee).FromTx(make_random_tx()));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto result_conservative = mempool_forecaster->ForecastFeeRate(conf_target, /*conservative=*/true);
|
||||||
|
const auto result_economical = mempool_forecaster->ForecastFeeRate(conf_target, /*conservative=*/false);
|
||||||
|
BOOST_CHECK(!result_conservative.feerate.IsEmpty() && !result_economical.feerate.IsEmpty());
|
||||||
|
const auto tx_vsize = entry.FromTx(make_random_tx()).GetTxSize();
|
||||||
|
BOOST_CHECK(result_economical.feerate == FeeFrac(low_fee, tx_vsize));
|
||||||
|
BOOST_CHECK(result_conservative.feerate == FeeFrac(med_fee, tx_vsize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END()
|
|
@ -2,8 +2,8 @@
|
||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <policy/fees_args.h>
|
#include <policy/fees/block_policy_estimator_args.h>
|
||||||
#include <policy/policy.h>
|
#include <policy/policy.h>
|
||||||
#include <test/util/txmempool.h>
|
#include <test/util/txmempool.h>
|
||||||
#include <txmempool.h>
|
#include <txmempool.h>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
#include <node/peerman_args.h>
|
#include <node/peerman_args.h>
|
||||||
#include <node/warnings.h>
|
#include <node/warnings.h>
|
||||||
#include <noui.h>
|
#include <noui.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <pow.h>
|
#include <pow.h>
|
||||||
#include <random.h>
|
#include <random.h>
|
||||||
#include <rpc/blockchain.h>
|
#include <rpc/blockchain.h>
|
||||||
|
@ -288,7 +288,7 @@ ChainTestingSetup::~ChainTestingSetup()
|
||||||
m_node.netgroupman.reset();
|
m_node.netgroupman.reset();
|
||||||
m_node.args = nullptr;
|
m_node.args = nullptr;
|
||||||
m_node.mempool.reset();
|
m_node.mempool.reset();
|
||||||
Assert(!m_node.fee_estimator); // Each test must create a local object, if they wish to use the fee_estimator
|
Assert(!m_node.forecasterman); // Each test must create a local object, if they wish to use the forecasterman
|
||||||
m_node.chainman.reset();
|
m_node.chainman.reset();
|
||||||
m_node.validation_signals.reset();
|
m_node.validation_signals.reset();
|
||||||
m_node.scheduler.reset();
|
m_node.scheduler.reset();
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
#include <outputtype.h>
|
#include <outputtype.h>
|
||||||
#include <policy/feerate.h>
|
#include <policy/feerate.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <primitives/transaction.h>
|
#include <primitives/transaction.h>
|
||||||
#include <script/keyorigin.h>
|
#include <script/keyorigin.h>
|
||||||
#include <script/signingprovider.h>
|
#include <script/signingprovider.h>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include <consensus/validation.h>
|
#include <consensus/validation.h>
|
||||||
#include <interfaces/chain.h>
|
#include <interfaces/chain.h>
|
||||||
#include <node/types.h>
|
#include <node/types.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <policy/policy.h>
|
#include <policy/policy.h>
|
||||||
#include <util/moneystr.h>
|
#include <util/moneystr.h>
|
||||||
#include <util/rbf.h>
|
#include <util/rbf.h>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
#include <interfaces/chain.h>
|
#include <interfaces/chain.h>
|
||||||
#include <interfaces/handler.h>
|
#include <interfaces/handler.h>
|
||||||
#include <node/types.h>
|
#include <node/types.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <primitives/transaction.h>
|
#include <primitives/transaction.h>
|
||||||
#include <rpc/server.h>
|
#include <rpc/server.h>
|
||||||
#include <scheduler.h>
|
#include <scheduler.h>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#define BITCOIN_WALLET_SPEND_H
|
#define BITCOIN_WALLET_SPEND_H
|
||||||
|
|
||||||
#include <consensus/amount.h>
|
#include <consensus/amount.h>
|
||||||
#include <policy/fees.h> // for FeeCalculation
|
#include <policy/fees/block_policy_estimator.h> // for FeeCalculation
|
||||||
#include <util/result.h>
|
#include <util/result.h>
|
||||||
#include <wallet/coinselection.h>
|
#include <wallet/coinselection.h>
|
||||||
#include <wallet/transaction.h>
|
#include <wallet/transaction.h>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
#include <consensus/amount.h>
|
#include <consensus/amount.h>
|
||||||
#include <policy/fees.h>
|
#include <policy/fees/block_policy_estimator.h>
|
||||||
#include <script/solver.h>
|
#include <script/solver.h>
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
#include <wallet/coincontrol.h>
|
#include <wallet/coincontrol.h>
|
||||||
|
|
Loading…
Add table
Reference in a new issue