Skip to content

Commit

Permalink
fees: detect and ignore flagged txs
Browse files Browse the repository at this point in the history
  • Loading branch information
ismaelsadeeq committed Mar 8, 2025
1 parent 6384ec2 commit 247df05
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 1 deletion.
47 changes: 47 additions & 0 deletions src/policy/fees/forecaster_man.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,25 @@ std::pair<std::optional<ForecastResult>, std::vector<std::string>> FeeRateForeca
err_messages.emplace_back("Mempool is unreliable for fee rate forecasting.");
}

<<<<<<< HEAD
for (const auto& [type, forecaster] : forecasters) {
if (!mempool_healthy && type == ForecastType::MEMPOOL_FORECAST) continue;

auto forecast = forecaster->EstimateFee(target);
if (forecast.GetError()) {
err_messages.emplace_back(strprintf("%s: %s", forecastTypeToString(type), *forecast.GetError()));
=======
for (const auto& forecaster : forecasters) {
auto forecaster_type = forecaster.first;
if (forecaster_type == ForecastType::MEMPOOL_FORECAST) {
if (!is_mempool_healthy) continue;
target.transactions_to_ignore = GetTransactionsToIgnore();
};
auto curr_forecast = forecaster.second->EstimateFee(target);
if (curr_forecast.GetError().has_value()) {
err_messages.emplace_back(
strprintf("%s: %s", forecastTypeToString(forecaster_type), curr_forecast.GetError().value_or("")));
>>>>>>> 88aaa407160 (fees: detect and ignore flagged txs)
}

// Select the lowest fee estimate among available (sane) forecasters
Expand Down Expand Up @@ -242,6 +255,40 @@ void FeeRateForecasterManager::removeTransaction(const Txid& txid)
}
}

std::set<Txid> FeeRateForecasterManager::GetTransactionsToIgnore()
{
std::set<Txid> flagged_txs;

// Iterate through sorted transactions (sorted by mining count in descending order)
for (auto tx = sorted_txs.begin(); tx != sorted_txs.end(); ++tx)
{
// Stop once we reach transactions below the ignore threshold
if (tx->first < NUMBER_OF_TIMES_BEFORE_IGNORING) break;

// Add transactions that have been seen multiple times without mining
flagged_txs.insert(tx->second);
}

return flagged_txs;
}

std::set<Txid> FeeRateForecasterManager::GetTransactionsToIgnore()
{
std::set<Txid> flagged_txs;

// Iterate through sorted transactions (sorted by mining count in descending order)
for (auto tx = sorted_txs.begin(); tx != sorted_txs.end(); ++tx)
{
// Stop once we reach transactions below the ignore threshold
if (tx->first < NUMBER_OF_TIMES_BEFORE_IGNORING) break;

// Add transactions that have been seen multiple times without mining
flagged_txs.insert(tx->second);
}

return flagged_txs;
}

void FeeRateForecasterManager::TransactionRemovedFromMempool(const CTransactionRef& tx,
MemPoolRemovalReason /*unused*/, uint64_t /*unused*/)
{
Expand Down
7 changes: 7 additions & 0 deletions src/policy/fees/forecaster_man.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ enum class ForecastType;

// Constants for mempool sanity checks.
constexpr size_t NUMBER_OF_BLOCKS = 6;
<<<<<<< HEAD
constexpr double HEALTHY_BLOCK_PERCENTILE = 0.75;
=======
constexpr double HEALTHY_BLOCK_PERCENTILE = 0.95;
constexpr size_t NUMBER_OF_TIMES_BEFORE_IGNORING = 5;
>>>>>>> 88aaa407160 (fees: detect and ignore flagged txs)

/**
* \class FeeRateForecasterManager
Expand Down Expand Up @@ -124,6 +129,8 @@ class FeeRateForecasterManager : public CValidationInterface
//! Retrieves block data as a human-readable string.
std::vector<std::string> GetPreviouslyMinedBlockDataStr() const EXCLUSIVE_LOCKS_REQUIRED(!cs);

std::set<Txid> GetTransactionsToIgnore();

/**
* Estimates a fee rate using registered forecasters for a given confirmation target.
*
Expand Down
3 changes: 3 additions & 0 deletions src/policy/fees/forecaster_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
#define BITCOIN_POLICY_FEES_FORECASTER_UTIL_H

#include <util/feefrac.h>
#include <util/transaction_identifier.h>

#include <optional>
#include <string>
#include <set>

/**
* @enum ForecastType
Expand Down Expand Up @@ -125,6 +127,7 @@ enum class ConfirmationTargetType {
struct ConfirmationTarget {
unsigned int value;
ConfirmationTargetType type;
std::set<Txid> transactions_to_ignore{};
};

// Block percentiles fee rate (in sat/kvB).
Expand Down
53 changes: 52 additions & 1 deletion src/policy/fees/mempool_forecaster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ ForecastResult MemPoolForecaster::EstimateFee(ConfirmationTarget& target)
node::BlockAssembler assembler(*m_chainstate, m_mempool, options);

const auto pblocktemplate = assembler.CreateNewBlock();
const auto& m_package_feerates = pblocktemplate->m_package_feerates;
auto& m_package_feerates = pblocktemplate->m_package_feerates;
if (m_package_feerates.empty()) {
return ForecastResult(response, "No enough transactions in the mempool to provide a fee rate forecast");
}

filterPackages(m_package_feerates, target.transactions_to_ignore, pblocktemplate->block.vtx);
const auto percentiles = CalculatePercentiles(m_package_feerates, DEFAULT_BLOCK_MAX_WEIGHT);
if (percentiles.empty()) {
return ForecastResult(response, "Forecaster unable to provide an estimate due to insufficient data");
Expand All @@ -69,3 +70,53 @@ ForecastResult MemPoolForecaster::EstimateFee(ConfirmationTarget& target)

return ForecastResult(response);
}

void MemPoolForecaster::filterPackages(
std::vector<FeeFrac>& package_feerates,
std::set<Txid>& flagged_txs,
const std::vector<CTransactionRef>& block_txs)
{
if (package_feerates.empty()) return;
Assume(!block_txs.empty());

std::set<size_t> flagged_indexes;
size_t index = 0, current_package_vsize = 0;

// Identify packages containing flagged transactions
for (const auto& tx : block_txs)
{
size_t tx_vsize = tx->GetTotalWeight() / WITNESS_SCALE_FACTOR;

// Move to the next package when needed
if (current_package_vsize + tx_vsize > (size_t)package_feerates[index].size)
{
++index;
current_package_vsize = tx_vsize;
}
else
{
current_package_vsize += tx_vsize;
}
Assume(index < package_feerates.size());

// Flag package for removal if it contains a flagged transaction
if (flagged_txs.count(tx->GetHash()))
{
flagged_indexes.insert(index);
}
}

// Build a new vector excluding flagged packages
std::vector<FeeFrac> filtered_packages;
filtered_packages.reserve(package_feerates.size() - flagged_indexes.size());

for (size_t i = 0; i < package_feerates.size(); ++i)
{
if (!flagged_indexes.count(i)) // Skip flagged packages
{
filtered_packages.push_back(std::move(package_feerates[i]));
}
}
// Swap to replace the old vector efficiently
package_feerates.swap(filtered_packages);
}
3 changes: 3 additions & 0 deletions src/policy/fees/mempool_forecaster.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <policy/fees/forecaster_util.h>
#include <sync.h>
#include <util/time.h>
#include <primitives/transaction.h>

#include <chrono>

Expand Down Expand Up @@ -88,5 +89,7 @@ class MemPoolForecaster : public Forecaster
const CTxMemPool* m_mempool;
Chainstate* m_chainstate;
mutable CachedMempoolEstimates cache;

void filterPackages(std::vector<FeeFrac>& package_feerates, std::set<Txid>& flagged_txs, const std::vector<CTransactionRef>& block_txs);
};
#endif // BITCOIN_POLICY_FEES_MEMPOOL_FORECASTER_H

0 comments on commit 247df05

Please sign in to comment.