diff --git a/src/init.cpp b/src/init.cpp index 5bade3b053..960ead3876 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -651,7 +651,11 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (default: %u)", DEFAULT_ACCEPT_NON_STD_TXN), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-incrementalrelayfee=", 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=", 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 | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY); + argsman.AddArg("-dustrelayfee=", 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:|[*]mempool:", + strprintf("Automatically raise dustrelayfee based on either the expected fee to be mined within blocks, or to be within the best kvB of this node's mempool. If unspecified, multiplier is %s. (default: %s)", + DEFAULT_DUST_RELAY_MULTIPLIER, + 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(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("-bytespersigopstrict", strprintf("Minimum bytes per sigop in transactions we relay and mine (default: %u)", DEFAULT_BYTES_PER_SIGOP_STRICT), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); @@ -1582,6 +1586,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) assert(!node.chainman); CTxMemPool::Options mempool_opts{ + .estimator = node.fee_estimator.get(), + .scheduler = &*node.scheduler, .check_ratio = chainparams.DefaultConsistencyChecks() ? 1 : 0, }; auto result{ApplyArgsManOptions(args, chainparams, mempool_opts)}; diff --git a/src/kernel/mempool_options.h b/src/kernel/mempool_options.h index e4ca6a65c8..7f6e4371c8 100644 --- a/src/kernel/mempool_options.h +++ b/src/kernel/mempool_options.h @@ -13,6 +13,9 @@ #include #include +class CBlockPolicyEstimator; +class CScheduler; + enum class RBFPolicy { Never, OptIn, Always }; enum class TRUCPolicy { Reject, Accept, Enforce }; @@ -40,6 +43,9 @@ namespace kernel { * Most of the time, this struct should be referenced as CTxMemPool::Options. */ struct MemPoolOptions { + /* Used to estimate appropriate transaction fees. */ + CBlockPolicyEstimator* estimator{nullptr}; + CScheduler* scheduler; /* The ratio used to determine how often sanity checks will run. */ int check_ratio{0}; int64_t max_size_bytes{DEFAULT_MAX_MEMPOOL_SIZE_MB * 1'000'000}; @@ -48,6 +54,10 @@ 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}; + /** Negative for a target number of blocks, positive for target kB into current mempool. */ + int32_t dust_relay_target; + /** Multiplier for dustdynamic assignments, in thousandths. */ + int dust_relay_multiplier{DEFAULT_DUST_RELAY_MULTIPLIER}; /** * A data carrying output is an unspendable output containing data. The script * type is designated as TxoutType::NULL_DATA. diff --git a/src/node/mempool_args.cpp b/src/node/mempool_args.cpp index dd60946d81..f3c9819a7d 100644 --- a/src/node/mempool_args.cpp +++ b/src/node/mempool_args.cpp @@ -13,14 +13,20 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include +#include #include +#include +#include using kernel::MemPoolLimits; using kernel::MemPoolOptions; @@ -38,6 +44,51 @@ void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolLimits& mempool_limi } } +util::Result> ParseDustDynamicOpt(std::string_view optstr, const unsigned int max_fee_estimate_blocks) +{ + if (optstr == "0" || optstr == "off") { + return std::pair(0, DEFAULT_DUST_RELAY_MULTIPLIER); + } + + int multiplier{DEFAULT_DUST_RELAY_MULTIPLIER}; + if (auto pos = optstr.find('*'); pos != optstr.npos) { + int64_t parsed; + if ((!ParseFixedPoint(optstr.substr(0, pos), 3, &parsed)) || parsed > std::numeric_limits::max() || parsed < 1) { + return util::Error{_("failed to parse multiplier")}; + } + multiplier = parsed; + optstr.remove_prefix(pos + 1); + } + + if (optstr.rfind("target:", 0) == 0) { + if (!max_fee_estimate_blocks) { + return util::Error{_("\"target\" mode requires fee estimator (disabled)")}; + } + const auto val = ToIntegral(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 std::pair(-*val, multiplier); + } else if (optstr.rfind("mempool:", 0) == 0) { + const auto val = ToIntegral(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 std::pair(*val, multiplier); + } else { + return util::Error{strprintf(_("\"%s\""), optstr)}; + } +} + util::Result ApplyArgsManOptions(const ArgsManager& argsman, const CChainParams& chainparams, MemPoolOptions& mempool_opts) { mempool_opts.check_ratio = argsman.GetIntArg("-checkmempool", mempool_opts.check_ratio); @@ -78,6 +129,16 @@ util::Result 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->first; + mempool_opts.dust_relay_multiplier = parsed->second; + } mempool_opts.permit_bare_pubkey = argsman.GetBoolArg("-permitbarepubkey", DEFAULT_PERMIT_BAREPUBKEY); diff --git a/src/node/mempool_args.h b/src/node/mempool_args.h index 630fee6421..9da673d71a 100644 --- a/src/node/mempool_args.h +++ b/src/node/mempool_args.h @@ -7,6 +7,10 @@ #include +#include +#include +#include + class ArgsManager; class CChainParams; struct bilingual_str; @@ -14,6 +18,8 @@ namespace kernel { struct MemPoolOptions; }; +[[nodiscard]] util::Result> ParseDustDynamicOpt(std::string_view 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. diff --git a/src/policy/feerate.h b/src/policy/feerate.h index f11ac0225e..4ea71399a0 100644 --- a/src/policy/feerate.h +++ b/src/policy/feerate.h @@ -76,6 +76,7 @@ public: std::string SatsToString() const; friend CFeeRate operator*(const CFeeRate& f, int a) { return CFeeRate(a * f.nSatoshisPerK); } friend CFeeRate operator*(int a, const CFeeRate& f) { return CFeeRate(a * f.nSatoshisPerK); } + friend CFeeRate operator/(const CFeeRate& f, int a) { return CFeeRate(f.nSatoshisPerK / a); } SERIALIZE_METHODS(CFeeRate, obj) { READWRITE(obj.nSatoshisPerK); } }; diff --git a/src/policy/policy.h b/src/policy/policy.h index cb44ff4be7..276f6ccd79 100644 --- a/src/policy/policy.h +++ b/src/policy/policy.h @@ -64,6 +64,8 @@ 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 int DEFAULT_DUST_RELAY_MULTIPLIER{3'000}; 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}; diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index ebd7d8efa1..6143869812 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -884,6 +884,22 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool, const std::optional 0 + ret.pushKV("dustdynamic", strprintf("%s*mempool:%u", multiplier_str, pool.m_dust_relay_target)); + } + } ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()}); ret.pushKV("fullrbf", (pool.m_rbf_policy == RBFPolicy::Always)); switch (pool.m_rbf_policy) { @@ -977,6 +993,9 @@ static RPCHelpMan getmempoolinfo() {RPCResult::Type::STR_AMOUNT, "mempoolminfee", "Minimum fee rate in " + CURRENCY_UNIT + "/kvB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee"}, {RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"}, {RPCResult::Type::NUM, "incrementalrelayfee", "minimum fee rate increment for mempool limiting or replacement in " + CURRENCY_UNIT + "/kvB"}, + {RPCResult::Type::NUM, "dustrelayfee", "Current fee rate used to define dust, the value of an output so small it will cost more to spend than its value, in " + CURRENCY_UNIT + "/kvB"}, + {RPCResult::Type::NUM, "dustrelayfeefloor", "Minimum fee rate used to define dust in " + CURRENCY_UNIT + "/kvB"}, + {RPCResult::Type::STR, "dustdynamic", "Method for automatic adjustments to dustrelayfee (one of: off, target:, or mempool:)"}, {RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"}, {RPCResult::Type::BOOL, "fullrbf", "True if the mempool accepts RBF without replaceability signaling inspection"}, {RPCResult::Type::STR, "rbf_policy", "Policy used for replacing conflicting transactions by fee (one of: never, optin, always)"}, diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 2bea66e86b..0e044bd675 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -14,10 +14,12 @@ #include #include #include +#include #include #include #include #include +#include #include