txmempool: Add dustdynamic option supporting fee estimator or kvB into mempool

Currently targets 80% success threshold and updates every 15 minutes
This commit is contained in:
Luke Dashjr 2024-03-15 21:21:44 +00:00
parent 7d6aaa128e
commit 690811b90f
7 changed files with 96 additions and 0 deletions

View File

@ -640,6 +640,9 @@ void SetupServerArgs(ArgsManager& argsman)
argsman.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (test networks only; default: %u)", DEFAULT_ACCEPT_NON_STD_TXN), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kvB) used to define cost of relay, used for mempool limiting and replacement policy. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kvB) used to define dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-dustdynamic=off|target:<blocks>|mempool:<kvB>",
strprintf("Automatically raise dustrelayfee based on either the expected fee to be mined within <blocks> blocks, or to be within the best <kvB> kvB of this node's mempool. (default: %s)",
DEFAULT_DUST_DYNAMIC), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-acceptstalefeeestimates", strprintf("Read fee estimates even if they are stale (%sdefault: %u) fee estimates are considered stale if they are %s hours old", "regtest only; ", DEFAULT_ACCEPT_STALE_FEE_ESTIMATES, Ticks<std::chrono::hours>(MAX_FILE_AGE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-bytespersigop", strprintf("Equivalent bytes per sigop in transactions for relay and mining (default: %u)", DEFAULT_BYTES_PER_SIGOP), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);

View File

@ -55,6 +55,9 @@ struct MemPoolOptions {
/** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */
CFeeRate min_relay_feerate{DEFAULT_MIN_RELAY_TX_FEE};
CFeeRate dust_relay_feerate{DUST_RELAY_TX_FEE};
CFeeRate dust_relay_feerate_floor{DUST_RELAY_TX_FEE};
/** Negative for a target number of blocks, positive for target kB into current mempool. */
int32_t dust_relay_target{0};
/**
* A data carrying output is an unspendable output containing data. The script
* type is designated as TxoutType::NULL_DATA.

View File

@ -14,9 +14,11 @@
#include <logging.h>
#include <node/interface_ui.h>
#include <policy/feerate.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <tinyformat.h>
#include <util/moneystr.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/translation.h>
@ -40,6 +42,39 @@ void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolLimits& mempool_limi
}
}
util::Result<int32_t> ParseDustDynamicOpt(const std::string& optstr, const unsigned int max_fee_estimate_blocks)
{
if (optstr == "0" || optstr == "off") {
return 0;
} else if (optstr.rfind("target:", 0) == 0) {
if (!max_fee_estimate_blocks) {
return util::Error{_("\"target\" mode requires fee estimator (disabled)")};
}
const auto val = ToIntegral<uint16_t>(optstr.substr(7));
if (!val) {
return util::Error{_("failed to parse target block count")};
}
if (*val < 2) {
return util::Error{_("target must be at least 2 blocks")};
}
if (*val > max_fee_estimate_blocks) {
return util::Error{strprintf(_("target can only be at most %s blocks"), max_fee_estimate_blocks)};
}
return -*val;
} else if (optstr.rfind("mempool:", 0) == 0) {
const auto val = ToIntegral<int32_t>(optstr.substr(8));
if (!val) {
return util::Error{_("failed to parse mempool position")};
}
if (*val < 1) {
return util::Error{_("mempool position must be at least 1 kB")};
}
return *val;
} else {
return util::Error{strprintf(_("\"%s\""), optstr)};
}
}
util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainParams& chainparams, MemPoolOptions& mempool_opts)
{
mempool_opts.check_ratio = argsman.GetIntArg("-checkmempool", mempool_opts.check_ratio);
@ -80,6 +115,15 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainP
return util::Error{AmountErrMsg("dustrelayfee", argsman.GetArg("-dustrelayfee", ""))};
}
}
if (argsman.IsArgSet("-dustdynamic")) {
const auto optstr = argsman.GetArg("-dustdynamic", DEFAULT_DUST_DYNAMIC);
const auto max_fee_estimate_blocks = mempool_opts.estimator ? mempool_opts.estimator->HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE) : (unsigned int)0;
const auto parsed = ParseDustDynamicOpt(optstr, max_fee_estimate_blocks);
if (!parsed) {
return util::Error{strprintf(_("Invalid mode for dustdynamic: %s"), util::ErrorString(parsed))};
}
mempool_opts.dust_relay_target = *parsed;
}
mempool_opts.permit_bare_pubkey = argsman.GetBoolArg("-permitbarepubkey", DEFAULT_PERMIT_BAREPUBKEY);

View File

@ -7,6 +7,8 @@
#include <util/result.h>
#include <string>
class ArgsManager;
class CChainParams;
struct bilingual_str;
@ -14,6 +16,8 @@ namespace kernel {
struct MemPoolOptions;
};
[[nodiscard]] util::Result<int32_t> ParseDustDynamicOpt(const std::string& optstr, unsigned int max_fee_estimate_blocks);
/**
* Overlay the options set in \p argsman on top of corresponding members in \p mempool_opts.
* Returns an error if one was encountered.

View File

@ -59,6 +59,7 @@ static constexpr unsigned int MAX_STANDARD_SCRIPTSIG_SIZE{1650};
* only increase the dust limit after prior releases were already not creating
* outputs below the new threshold */
static constexpr unsigned int DUST_RELAY_TX_FEE{3000};
static const std::string DEFAULT_DUST_DYNAMIC{"off"};
static const std::string DEFAULT_SPKREUSE{"allow"};
/** Default for -minrelaytxfee, minimum relay fee for transactions */
static constexpr unsigned int DEFAULT_MIN_RELAY_TX_FEE{1000};

View File

@ -13,9 +13,11 @@
#include <consensus/validation.h>
#include <crypto/ripemd160.h>
#include <logging.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <policy/settings.h>
#include <random.h>
#include <scheduler.h>
#include <tinyformat.h>
#include <script/script.h>
#include <util/check.h>
@ -420,6 +422,13 @@ static CTxMemPool::Options&& Flatten(CTxMemPool::Options&& opts, bilingual_str&
CTxMemPool::CTxMemPool(Options opts, bilingual_str& error)
: m_opts{Flatten(std::move(opts), error)}
{
Assert(m_opts.scheduler || !m_opts.dust_relay_target);
m_opts.dust_relay_feerate_floor = m_opts.dust_relay_feerate;
if (m_opts.scheduler) {
m_opts.scheduler->scheduleEvery([this]{
UpdateDynamicDustFeerate();
}, DYNAMIC_DUST_FEERATE_UPDATE_INTERVAL);
}
}
bool CTxMemPool::isSpent(const COutPoint& outpoint) const
@ -687,6 +696,34 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
blockSinceLastRollingFeeBump = true;
}
void CTxMemPool::UpdateDynamicDustFeerate()
{
CFeeRate est_feerate{0};
if (m_opts.dust_relay_target < 0 && m_opts.estimator) {
static constexpr double target_success_threshold{0.8};
est_feerate = m_opts.estimator->estimateRawFee(-m_opts.dust_relay_target, target_success_threshold, FeeEstimateHorizon::LONG_HALFLIFE, nullptr);
} else if (m_opts.dust_relay_target > 0) {
auto bytes_remaining = int64_t{m_opts.dust_relay_target} * 1'000;
LOCK(cs);
for (auto mi = mapTx.get<ancestor_score>().begin(); mi != mapTx.get<ancestor_score>().end(); ++mi) {
bytes_remaining -= mi->GetTxSize();
if (bytes_remaining <= 0) {
est_feerate = CFeeRate(mi->GetFee(), mi->GetTxSize());
break;
}
}
}
if (est_feerate < m_opts.dust_relay_feerate_floor) {
est_feerate = m_opts.dust_relay_feerate_floor;
}
if (m_opts.dust_relay_feerate != est_feerate) {
LogPrint(BCLog::MEMPOOL, "Updating dust feerate to %s\n", est_feerate.ToString(FeeEstimateMode::SAT_VB));
m_opts.dust_relay_feerate = est_feerate;
}
}
void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) const
{
if (m_opts.check_ratio == 0) return;

View File

@ -46,6 +46,8 @@ class ValidationSignals;
struct bilingual_str;
static constexpr std::chrono::minutes DYNAMIC_DUST_FEERATE_UPDATE_INTERVAL{15};
/** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */
static const uint32_t MEMPOOL_HEIGHT = 0x7FFFFFFF;
@ -493,6 +495,8 @@ public:
*/
bool HasNoInputsOf(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs);
void UpdateDynamicDustFeerate();
/** Affect CreateNewBlock prioritisation of transactions */
void PrioritiseTransaction(const uint256& hash, const CAmount& nFeeDelta);
void ApplyDelta(const uint256& hash, CAmount &nFeeDelta) const EXCLUSIVE_LOCKS_REQUIRED(cs);