Skip to content

Commit 5dd1f1e

Browse files
committed
Merge dustdynamic-28+knots
2 parents 84eff59 + 3b8df1d commit 5dd1f1e

File tree

10 files changed

+224
-1
lines changed

10 files changed

+224
-1
lines changed

src/init.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,11 @@ void SetupServerArgs(ArgsManager& argsman)
671671

672672
argsman.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (default: %u)", DEFAULT_ACCEPT_NON_STD_TXN), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
673673
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);
674-
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 | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
674+
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);
675+
argsman.AddArg("-dustdynamic=off|[<multiplier>*]target:<blocks>|[<multiplier>*]mempool:<kvB>",
676+
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. If unspecified, multiplier is %s. (default: %s)",
677+
DEFAULT_DUST_RELAY_MULTIPLIER / 1000.,
678+
DEFAULT_DUST_DYNAMIC), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
675679
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);
676680
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);
677681
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);
@@ -1639,6 +1643,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
16391643
assert(!node.chainman);
16401644

16411645
CTxMemPool::Options mempool_opts{
1646+
.estimator = node.fee_estimator.get(),
1647+
.scheduler = &*node.scheduler,
16421648
.check_ratio = chainparams.DefaultConsistencyChecks() ? 1 : 0,
16431649
.signals = &validation_signals,
16441650
};

src/kernel/mempool_options.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include <cstdint>
1414
#include <optional>
1515

16+
class CBlockPolicyEstimator;
17+
class CScheduler;
1618
class ValidationSignals;
1719

1820
enum class RBFPolicy { Never, OptIn, Always };
@@ -42,6 +44,9 @@ namespace kernel {
4244
* Most of the time, this struct should be referenced as CTxMemPool::Options.
4345
*/
4446
struct MemPoolOptions {
47+
/* Used to estimate appropriate transaction fees. */
48+
CBlockPolicyEstimator* estimator{nullptr};
49+
CScheduler* scheduler{nullptr};
4550
/* The ratio used to determine how often sanity checks will run. */
4651
int check_ratio{0};
4752
int64_t max_size_bytes{DEFAULT_MAX_MEMPOOL_SIZE_MB * 1'000'000};
@@ -50,6 +55,11 @@ struct MemPoolOptions {
5055
/** A fee rate smaller than this is considered zero fee (for relaying, mining and transaction creation) */
5156
CFeeRate min_relay_feerate{DEFAULT_MIN_RELAY_TX_FEE};
5257
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};
61+
/** Multiplier for dustdynamic assignments, in thousandths. */
62+
int dust_relay_multiplier{DEFAULT_DUST_RELAY_MULTIPLIER};
5363
/**
5464
* A data carrying output is an unspendable output containing data. The script
5565
* type is designated as TxoutType::NULL_DATA.

src/node/mempool_args.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,20 @@
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/result.h>
22+
#include <util/strencodings.h>
2023
#include <util/string.h>
2124
#include <util/translation.h>
2225

2326
#include <chrono>
27+
#include <cstdint>
2428
#include <memory>
29+
#include <string_view>
30+
#include <utility>
2531

2632
using common::AmountErrMsg;
2733
using kernel::MemPoolLimits;
@@ -40,6 +46,51 @@ void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolLimits& mempool_limi
4046
}
4147
}
4248

49+
util::Result<std::pair<int32_t, int>> ParseDustDynamicOpt(std::string_view optstr, const unsigned int max_fee_estimate_blocks)
50+
{
51+
if (optstr == "0" || optstr == "off") {
52+
return std::pair<int32_t, int>(0, DEFAULT_DUST_RELAY_MULTIPLIER);
53+
}
54+
55+
int multiplier{DEFAULT_DUST_RELAY_MULTIPLIER};
56+
if (auto pos = optstr.find('*'); pos != optstr.npos) {
57+
int64_t parsed;
58+
if ((!ParseFixedPoint(optstr.substr(0, pos), 3, &parsed)) || parsed > std::numeric_limits<int>::max() || parsed < 1) {
59+
return util::Error{_("failed to parse multiplier")};
60+
}
61+
multiplier = parsed;
62+
optstr.remove_prefix(pos + 1);
63+
}
64+
65+
if (optstr.rfind("target:", 0) == 0) {
66+
if (!max_fee_estimate_blocks) {
67+
return util::Error{_("\"target\" mode requires fee estimator (disabled)")};
68+
}
69+
const auto val = ToIntegral<uint16_t>(optstr.substr(7));
70+
if (!val) {
71+
return util::Error{_("failed to parse target block count")};
72+
}
73+
if (*val < 2) {
74+
return util::Error{_("target must be at least 2 blocks")};
75+
}
76+
if (*val > max_fee_estimate_blocks) {
77+
return util::Error{strprintf(_("target can only be at most %s blocks"), max_fee_estimate_blocks)};
78+
}
79+
return std::pair<int32_t, int>(-*val, multiplier);
80+
} else if (optstr.rfind("mempool:", 0) == 0) {
81+
const auto val = ToIntegral<int32_t>(optstr.substr(8));
82+
if (!val) {
83+
return util::Error{_("failed to parse mempool position")};
84+
}
85+
if (*val < 1) {
86+
return util::Error{_("mempool position must be at least 1 kB")};
87+
}
88+
return std::pair<int32_t, int>(*val, multiplier);
89+
} else {
90+
return util::Error{strprintf(_("\"%s\""), optstr)};
91+
}
92+
}
93+
4394
util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainParams& chainparams, MemPoolOptions& mempool_opts)
4495
{
4596
mempool_opts.check_ratio = argsman.GetIntArg("-checkmempool", mempool_opts.check_ratio);
@@ -80,6 +131,16 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainP
80131
return util::Error{AmountErrMsg("dustrelayfee", argsman.GetArg("-dustrelayfee", ""))};
81132
}
82133
}
134+
if (argsman.IsArgSet("-dustdynamic")) {
135+
const auto optstr = argsman.GetArg("-dustdynamic", DEFAULT_DUST_DYNAMIC);
136+
const auto max_fee_estimate_blocks = mempool_opts.estimator ? mempool_opts.estimator->HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE) : (unsigned int)0;
137+
const auto parsed = ParseDustDynamicOpt(optstr, max_fee_estimate_blocks);
138+
if (!parsed) {
139+
return util::Error{strprintf(_("Invalid mode for dustdynamic: %s"), util::ErrorString(parsed))};
140+
}
141+
mempool_opts.dust_relay_target = parsed->first;
142+
mempool_opts.dust_relay_multiplier = parsed->second;
143+
}
83144

84145
mempool_opts.permit_bare_pubkey = argsman.GetBoolArg("-permitbarepubkey", DEFAULT_PERMIT_BAREPUBKEY);
85146

src/node/mempool_args.h

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

88
#include <util/result.h>
99

10+
#include <cstdint>
11+
#include <string_view>
12+
#include <utility>
13+
1014
class ArgsManager;
1115
class CChainParams;
1216
struct bilingual_str;
1317
namespace kernel {
1418
struct MemPoolOptions;
1519
};
1620

21+
[[nodiscard]] util::Result<std::pair<int32_t, int>> ParseDustDynamicOpt(std::string_view optstr, unsigned int max_fee_estimate_blocks);
22+
1723
/**
1824
* Overlay the options set in \p argsman on top of corresponding members in \p mempool_opts.
1925
* Returns an error if one was encountered.

src/policy/feerate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class CFeeRate
7474
std::string SatsToString() const;
7575
friend CFeeRate operator*(const CFeeRate& f, int a) { return CFeeRate(a * f.nSatoshisPerK); }
7676
friend CFeeRate operator*(int a, const CFeeRate& f) { return CFeeRate(a * f.nSatoshisPerK); }
77+
friend CFeeRate operator/(const CFeeRate& f, int a) { return CFeeRate(f.nSatoshisPerK / a); }
7778

7879
SERIALIZE_METHODS(CFeeRate, obj) { READWRITE(obj.nSatoshisPerK); }
7980
};

src/policy/policy.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ static constexpr unsigned int MAX_STANDARD_SCRIPTSIG_SIZE{1650};
6767
* only increase the dust limit after prior releases were already not creating
6868
* outputs below the new threshold */
6969
static constexpr unsigned int DUST_RELAY_TX_FEE{3000};
70+
static const std::string DEFAULT_DUST_DYNAMIC{"off"};
71+
static const int DEFAULT_DUST_RELAY_MULTIPLIER{3'000};
7072
static const std::string DEFAULT_SPKREUSE{"allow"};
7173
/** Default for -minrelaytxfee, minimum relay fee for transactions */
7274
static constexpr unsigned int DEFAULT_MIN_RELAY_TX_FEE{1000};

src/rpc/mempool.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,22 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool, const std::optional<MempoolHi
891891
ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(), pool.m_opts.min_relay_feerate).GetFeePerK()));
892892
ret.pushKV("minrelaytxfee", ValueFromAmount(pool.m_opts.min_relay_feerate.GetFeePerK()));
893893
ret.pushKV("incrementalrelayfee", ValueFromAmount(pool.m_opts.incremental_relay_feerate.GetFeePerK()));
894+
ret.pushKV("dustrelayfee", ValueFromAmount(pool.m_opts.dust_relay_feerate.GetFeePerK()));
895+
ret.pushKV("dustrelayfeefloor", ValueFromAmount(pool.m_opts.dust_relay_feerate_floor.GetFeePerK()));
896+
if (pool.m_opts.dust_relay_target == 0) {
897+
ret.pushKV("dustdynamic", "off");
898+
} else {
899+
std::string multiplier_str = strprintf("%u", pool.m_opts.dust_relay_multiplier / 1000);
900+
if (pool.m_opts.dust_relay_multiplier % 1000) {
901+
multiplier_str += strprintf(".%03u", pool.m_opts.dust_relay_multiplier % 1000);
902+
while (multiplier_str.back() == '0') multiplier_str.pop_back();
903+
}
904+
if (pool.m_opts.dust_relay_target < 0) {
905+
ret.pushKV("dustdynamic", strprintf("%s*target:%u", multiplier_str, -pool.m_opts.dust_relay_target));
906+
} else { // pool.m_opts.dust_relay_target > 0
907+
ret.pushKV("dustdynamic", strprintf("%s*mempool:%u", multiplier_str, pool.m_opts.dust_relay_target));
908+
}
909+
}
894910
ret.pushKV("unbroadcastcount", uint64_t{pool.GetUnbroadcastTxs().size()});
895911
ret.pushKV("fullrbf", (pool.m_opts.rbf_policy == RBFPolicy::Always));
896912
switch (pool.m_opts.rbf_policy) {
@@ -984,6 +1000,9 @@ static RPCHelpMan getmempoolinfo()
9841000
{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"},
9851001
{RPCResult::Type::STR_AMOUNT, "minrelaytxfee", "Current minimum relay fee for transactions"},
9861002
{RPCResult::Type::NUM, "incrementalrelayfee", "minimum fee rate increment for mempool limiting or replacement in " + CURRENCY_UNIT + "/kvB"},
1003+
{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"},
1004+
{RPCResult::Type::NUM, "dustrelayfeefloor", "Minimum fee rate used to define dust in " + CURRENCY_UNIT + "/kvB"},
1005+
{RPCResult::Type::STR, "dustdynamic", "Method for automatic adjustments to dustrelayfee (one of: off, target:<blocks>, or mempool:<kB>)"},
9871006
{RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"},
9881007
{RPCResult::Type::BOOL, "fullrbf", "True if the mempool accepts RBF without replaceability signaling inspection"},
9891008
{RPCResult::Type::STR, "rbf_policy", "Policy used for replacing conflicting transactions by fee (one of: never, optin, always)"},

src/txmempool.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
#include <crypto/ripemd160.h>
1515
#include <logging.h>
1616
#include <policy/coin_age_priority.h>
17+
#include <policy/fees.h>
1718
#include <policy/policy.h>
1819
#include <policy/settings.h>
1920
#include <random.h>
21+
#include <scheduler.h>
2022
#include <tinyformat.h>
2123
#include <script/script.h>
2224
#include <util/check.h>
@@ -421,6 +423,13 @@ static CTxMemPool::Options&& Flatten(CTxMemPool::Options&& opts, bilingual_str&
421423
CTxMemPool::CTxMemPool(Options opts, bilingual_str& error)
422424
: m_opts{Flatten(std::move(opts), error)}
423425
{
426+
Assert(m_opts.scheduler || !m_opts.dust_relay_target);
427+
m_opts.dust_relay_feerate_floor = m_opts.dust_relay_feerate;
428+
if (m_opts.scheduler) {
429+
m_opts.scheduler->scheduleEvery([this]{
430+
UpdateDynamicDustFeerate();
431+
}, DYNAMIC_DUST_FEERATE_UPDATE_INTERVAL);
432+
}
424433
}
425434

426435
bool CTxMemPool::isSpent(const COutPoint& outpoint) const
@@ -691,6 +700,36 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
691700
blockSinceLastRollingFeeBump = true;
692701
}
693702

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

src/txmempool.h

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

4848
struct bilingual_str;
4949

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

@@ -214,6 +216,8 @@ struct entry_time {};
214216
struct ancestor_score {};
215217
struct index_by_wtxid {};
216218

219+
class CBlockPolicyEstimator;
220+
217221
/**
218222
* Information about a mempool transaction.
219223
*/
@@ -502,6 +506,8 @@ class CTxMemPool
502506
*/
503507
void UpdateDependentPriorities(const CTransaction &tx, unsigned int nBlockHeight, bool addToChain);
504508

509+
void UpdateDynamicDustFeerate();
510+
505511
/** Affect CreateNewBlock prioritisation of transactions */
506512
void PrioritiseTransaction(const uint256& hash, double dPriorityDelta, const CAmount& nFeeDelta);
507513
void PrioritiseTransaction(const uint256& hash, const CAmount& nFeeDelta) { PrioritiseTransaction(hash, 0., nFeeDelta); }

0 commit comments

Comments
 (0)