Skip to content

Commit 690811b

Browse files
committed
txmempool: Add dustdynamic option supporting fee estimator or kvB into mempool
Currently targets 80% success threshold and updates every 15 minutes
1 parent 7d6aaa1 commit 690811b

File tree

7 files changed

+96
-0
lines changed

7 files changed

+96
-0
lines changed

src/init.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,9 @@ void SetupServerArgs(ArgsManager& argsman)
640640
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);
641641
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);
642642
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);
643+
argsman.AddArg("-dustdynamic=off|target:<blocks>|mempool:<kvB>",
644+
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)",
645+
DEFAULT_DUST_DYNAMIC), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
643646
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);
644647
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);
645648
argsman.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);

src/kernel/mempool_options.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ struct MemPoolOptions {
5555
/** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */
5656
CFeeRate min_relay_feerate{DEFAULT_MIN_RELAY_TX_FEE};
5757
CFeeRate dust_relay_feerate{DUST_RELAY_TX_FEE};
58+
CFeeRate dust_relay_feerate_floor{DUST_RELAY_TX_FEE};
59+
/** Negative for a target number of blocks, positive for target kB into current mempool. */
60+
int32_t dust_relay_target{0};
5861
/**
5962
* A data carrying output is an unspendable output containing data. The script
6063
* type is designated as TxoutType::NULL_DATA.

src/node/mempool_args.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
#include <logging.h>
1515
#include <node/interface_ui.h>
1616
#include <policy/feerate.h>
17+
#include <policy/fees.h>
1718
#include <policy/policy.h>
1819
#include <tinyformat.h>
1920
#include <util/moneystr.h>
21+
#include <util/strencodings.h>
2022
#include <util/string.h>
2123
#include <util/translation.h>
2224

@@ -40,6 +42,39 @@ void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolLimits& mempool_limi
4042
}
4143
}
4244

45+
util::Result<int32_t> ParseDustDynamicOpt(const std::string& optstr, const unsigned int max_fee_estimate_blocks)
46+
{
47+
if (optstr == "0" || optstr == "off") {
48+
return 0;
49+
} else if (optstr.rfind("target:", 0) == 0) {
50+
if (!max_fee_estimate_blocks) {
51+
return util::Error{_("\"target\" mode requires fee estimator (disabled)")};
52+
}
53+
const auto val = ToIntegral<uint16_t>(optstr.substr(7));
54+
if (!val) {
55+
return util::Error{_("failed to parse target block count")};
56+
}
57+
if (*val < 2) {
58+
return util::Error{_("target must be at least 2 blocks")};
59+
}
60+
if (*val > max_fee_estimate_blocks) {
61+
return util::Error{strprintf(_("target can only be at most %s blocks"), max_fee_estimate_blocks)};
62+
}
63+
return -*val;
64+
} else if (optstr.rfind("mempool:", 0) == 0) {
65+
const auto val = ToIntegral<int32_t>(optstr.substr(8));
66+
if (!val) {
67+
return util::Error{_("failed to parse mempool position")};
68+
}
69+
if (*val < 1) {
70+
return util::Error{_("mempool position must be at least 1 kB")};
71+
}
72+
return *val;
73+
} else {
74+
return util::Error{strprintf(_("\"%s\""), optstr)};
75+
}
76+
}
77+
4378
util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainParams& chainparams, MemPoolOptions& mempool_opts)
4479
{
4580
mempool_opts.check_ratio = argsman.GetIntArg("-checkmempool", mempool_opts.check_ratio);
@@ -80,6 +115,15 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainP
80115
return util::Error{AmountErrMsg("dustrelayfee", argsman.GetArg("-dustrelayfee", ""))};
81116
}
82117
}
118+
if (argsman.IsArgSet("-dustdynamic")) {
119+
const auto optstr = argsman.GetArg("-dustdynamic", DEFAULT_DUST_DYNAMIC);
120+
const auto max_fee_estimate_blocks = mempool_opts.estimator ? mempool_opts.estimator->HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE) : (unsigned int)0;
121+
const auto parsed = ParseDustDynamicOpt(optstr, max_fee_estimate_blocks);
122+
if (!parsed) {
123+
return util::Error{strprintf(_("Invalid mode for dustdynamic: %s"), util::ErrorString(parsed))};
124+
}
125+
mempool_opts.dust_relay_target = *parsed;
126+
}
83127

84128
mempool_opts.permit_bare_pubkey = argsman.GetBoolArg("-permitbarepubkey", DEFAULT_PERMIT_BAREPUBKEY);
85129

src/node/mempool_args.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77

88
#include <util/result.h>
99

10+
#include <string>
11+
1012
class ArgsManager;
1113
class CChainParams;
1214
struct bilingual_str;
1315
namespace kernel {
1416
struct MemPoolOptions;
1517
};
1618

19+
[[nodiscard]] util::Result<int32_t> ParseDustDynamicOpt(const std::string& optstr, unsigned int max_fee_estimate_blocks);
20+
1721
/**
1822
* Overlay the options set in \p argsman on top of corresponding members in \p mempool_opts.
1923
* Returns an error if one was encountered.

src/policy/policy.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ static constexpr unsigned int MAX_STANDARD_SCRIPTSIG_SIZE{1650};
5959
* only increase the dust limit after prior releases were already not creating
6060
* outputs below the new threshold */
6161
static constexpr unsigned int DUST_RELAY_TX_FEE{3000};
62+
static const std::string DEFAULT_DUST_DYNAMIC{"off"};
6263
static const std::string DEFAULT_SPKREUSE{"allow"};
6364
/** Default for -minrelaytxfee, minimum relay fee for transactions */
6465
static constexpr unsigned int DEFAULT_MIN_RELAY_TX_FEE{1000};

src/txmempool.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
#include <consensus/validation.h>
1414
#include <crypto/ripemd160.h>
1515
#include <logging.h>
16+
#include <policy/fees.h>
1617
#include <policy/policy.h>
1718
#include <policy/settings.h>
1819
#include <random.h>
20+
#include <scheduler.h>
1921
#include <tinyformat.h>
2022
#include <script/script.h>
2123
#include <util/check.h>
@@ -420,6 +422,13 @@ static CTxMemPool::Options&& Flatten(CTxMemPool::Options&& opts, bilingual_str&
420422
CTxMemPool::CTxMemPool(Options opts, bilingual_str& error)
421423
: m_opts{Flatten(std::move(opts), error)}
422424
{
425+
Assert(m_opts.scheduler || !m_opts.dust_relay_target);
426+
m_opts.dust_relay_feerate_floor = m_opts.dust_relay_feerate;
427+
if (m_opts.scheduler) {
428+
m_opts.scheduler->scheduleEvery([this]{
429+
UpdateDynamicDustFeerate();
430+
}, DYNAMIC_DUST_FEERATE_UPDATE_INTERVAL);
431+
}
423432
}
424433

425434
bool CTxMemPool::isSpent(const COutPoint& outpoint) const
@@ -687,6 +696,34 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
687696
blockSinceLastRollingFeeBump = true;
688697
}
689698

699+
void CTxMemPool::UpdateDynamicDustFeerate()
700+
{
701+
CFeeRate est_feerate{0};
702+
if (m_opts.dust_relay_target < 0 && m_opts.estimator) {
703+
static constexpr double target_success_threshold{0.8};
704+
est_feerate = m_opts.estimator->estimateRawFee(-m_opts.dust_relay_target, target_success_threshold, FeeEstimateHorizon::LONG_HALFLIFE, nullptr);
705+
} else if (m_opts.dust_relay_target > 0) {
706+
auto bytes_remaining = int64_t{m_opts.dust_relay_target} * 1'000;
707+
LOCK(cs);
708+
for (auto mi = mapTx.get<ancestor_score>().begin(); mi != mapTx.get<ancestor_score>().end(); ++mi) {
709+
bytes_remaining -= mi->GetTxSize();
710+
if (bytes_remaining <= 0) {
711+
est_feerate = CFeeRate(mi->GetFee(), mi->GetTxSize());
712+
break;
713+
}
714+
}
715+
}
716+
717+
if (est_feerate < m_opts.dust_relay_feerate_floor) {
718+
est_feerate = m_opts.dust_relay_feerate_floor;
719+
}
720+
721+
if (m_opts.dust_relay_feerate != est_feerate) {
722+
LogPrint(BCLog::MEMPOOL, "Updating dust feerate to %s\n", est_feerate.ToString(FeeEstimateMode::SAT_VB));
723+
m_opts.dust_relay_feerate = est_feerate;
724+
}
725+
}
726+
690727
void CTxMemPool::check(const CCoinsViewCache& active_coins_tip, int64_t spendheight) const
691728
{
692729
if (m_opts.check_ratio == 0) return;

src/txmempool.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ class ValidationSignals;
4646

4747
struct bilingual_str;
4848

49+
static constexpr std::chrono::minutes DYNAMIC_DUST_FEERATE_UPDATE_INTERVAL{15};
50+
4951
/** Fake height value used in Coin to signify they are only in the memory pool (since 0.8) */
5052
static const uint32_t MEMPOOL_HEIGHT = 0x7FFFFFFF;
5153

@@ -493,6 +495,8 @@ class CTxMemPool
493495
*/
494496
bool HasNoInputsOf(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs);
495497

498+
void UpdateDynamicDustFeerate();
499+
496500
/** Affect CreateNewBlock prioritisation of transactions */
497501
void PrioritiseTransaction(const uint256& hash, const CAmount& nFeeDelta);
498502
void ApplyDelta(const uint256& hash, CAmount &nFeeDelta) const EXCLUSIVE_LOCKS_REQUIRED(cs);

0 commit comments

Comments
 (0)