Skip to content

Commit

Permalink
fees: add GetFeeRateForecast method
Browse files Browse the repository at this point in the history
- Fallback to Block policy estimator estimates whenever mempool forecaster
  estimates are higher than block policy estimator.
  • Loading branch information
ismaelsadeeq committed Jan 15, 2025
1 parent 02bfda8 commit ba0bede
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 19 deletions.
41 changes: 22 additions & 19 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@
#include <node/peerman_args.h>
#include <policy/feerate.h>
#include <policy/fees/block_policy_estimator.h>
#include <policy/fees/forecaster_man.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/settings.h>
#include <protocol.h>
Expand Down Expand Up @@ -1429,24 +1430,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
rng.rand64(),
*node.addrman, *node.netgroupman, chainparams, args.GetBoolArg("-networkactive", true));

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 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);
}

for (const std::string& socket_addr : args.GetArgs("-bind")) {
std::string host_out;
uint16_t port_out{0};
Expand Down Expand Up @@ -1660,6 +1643,26 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)

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);
node.peerman = PeerManager::make(*node.connman, *node.addrman,
node.banman.get(), chainman,
Expand Down
45 changes: 45 additions & 0 deletions src/policy/fees/forecaster_man.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
// 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)
Expand All @@ -20,3 +22,46 @@ CBlockPolicyEstimator* FeeRateForecasterManager::GetBlockPolicyEstimator()
Forecaster* block_policy_estimator = forecasters.find(ForecastType::BLOCK_POLICY)->second.get();
return dynamic_cast<CBlockPolicyEstimator*>(block_policy_estimator);
}

std::pair<std::optional<ForecastResult>, std::vector<std::string>> FeeRateForecasterManager::GetFeeRateForecast(ConfirmationTarget& target)
{
std::vector<std::string> err_messages;
ForecastResult selected_forecast;

for (const auto& forecaster : forecasters) {
auto curr_forecast = forecaster.second->EstimateFee(target);

if (curr_forecast.GetError().has_value()) {
err_messages.emplace_back(
strprintf("%s: %s", forecastTypeToString(forecaster.first), curr_forecast.GetError().value_or("")));
}

// Handle case where the block policy estimator does not have enough data for fee estimates.
if (curr_forecast.Empty() && forecaster.first == ForecastType::BLOCK_POLICY) {
return {std::nullopt, err_messages};
}

if (!curr_forecast.Empty()) {
if (selected_forecast.Empty()) {
// If there's no selected forecast, choose curr_forecast as selected_forecast.
selected_forecast = curr_forecast;
} else {
// Otherwise, choose the smaller as selected_forecast.
selected_forecast = std::min(selected_forecast, curr_forecast);
}
}
}

if (!selected_forecast.Empty()) {
LogDebug(BCLog::ESTIMATEFEE,
"FeeEst %s: Block height %s, low priority fee rate %s %s/kvB, high priority fee rate %s %s/kvB.\n",
forecastTypeToString(selected_forecast.GetResponse().forecaster),
selected_forecast.GetResponse().current_block_height,
CFeeRate(selected_forecast.GetResponse().low_priority.fee, selected_forecast.GetResponse().low_priority.size).GetFeePerK(),
CURRENCY_ATOM,
CFeeRate(selected_forecast.GetResponse().high_priority.fee, selected_forecast.GetResponse().high_priority.size).GetFeePerK(),
CURRENCY_ATOM);
}

return {selected_forecast, err_messages};
}
14 changes: 14 additions & 0 deletions src/policy/fees/forecaster_man.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
#define BITCOIN_POLICY_FEES_FORECASTER_MAN_H

#include <memory>
#include <optional>
#include <unordered_map>

class CBlockPolicyEstimator;
class Forecaster;
class ForecastResult;

struct ConfirmationTarget;

enum class ForecastType;

/** \class FeeRateForecasterManager
Expand All @@ -38,6 +41,17 @@ class FeeRateForecasterManager
* Return the pointer to block policy estimator.
*/
CBlockPolicyEstimator* GetBlockPolicyEstimator();

/**
* Get a fee rate estimate from all registered forecasters for a given confirmation target.
*
* Polls all registered forecasters and selects the lowest fee rate
* estimate with acceptable confidence.
*
* @param[in] target The target within which the transaction should be confirmed.
* @return A pair consisting of the forecast result and a vector of forecaster names.
*/
std::pair<std::optional<ForecastResult>, std::vector<std::string>> GetFeeRateForecast(ConfirmationTarget& target);
};

#endif // BITCOIN_POLICY_FEES_FORECASTER_MAN_H

0 comments on commit ba0bede

Please sign in to comment.