Skip to content

Commit

Permalink
Reapply "dustdynamic: Support specifying a multiplier (default to 3)"
Browse files Browse the repository at this point in the history
This reverts commit 0204dca.
  • Loading branch information
luke-jr committed May 4, 2024
1 parent c8cb391 commit 37fdfbf
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 38 deletions.
5 changes: 3 additions & 2 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -628,8 +628,9 @@ 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=<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)",
argsman.AddArg("-dustdynamic=off|[<multiplier>*]target:<blocks>|[<multiplier>*]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. 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<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);
Expand Down
2 changes: 2 additions & 0 deletions src/kernel/mempool_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ struct MemPoolOptions {
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. */
unsigned 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.
Expand Down
27 changes: 21 additions & 6 deletions src/node/mempool_args.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
#include <chrono>
#include <cstdint>
#include <memory>
#include <string_view>
#include <utility>

using kernel::MemPoolLimits;
using kernel::MemPoolOptions;
Expand All @@ -42,11 +44,23 @@ void ApplyArgsManOptions(const ArgsManager& argsman, MemPoolLimits& mempool_limi
}
}

util::Result<int32_t> ParseDustDynamicOpt(const std::string& optstr, const unsigned int max_fee_estimate_blocks)
util::Result<std::pair<int32_t, unsigned int>> ParseDustDynamicOpt(std::string_view optstr, const unsigned int max_fee_estimate_blocks)
{
if (optstr == "0" || optstr == "off") {
return 0;
} else if (optstr.rfind("target:", 0) == 0) {
return std::pair<int32_t, unsigned int>(0, DEFAULT_DUST_RELAY_MULTIPLIER);
}

unsigned 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<unsigned int>::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)")};
}
Expand All @@ -60,7 +74,7 @@ util::Result<int32_t> ParseDustDynamicOpt(const std::string& optstr, const unsig
if (*val > max_fee_estimate_blocks) {
return util::Error{strprintf(_("target can only be at most %s blocks"), max_fee_estimate_blocks)};
}
return -*val;
return std::pair<int32_t, unsigned int>(-*val, multiplier);
} else if (optstr.rfind("mempool:", 0) == 0) {
const auto val = ToIntegral<int32_t>(optstr.substr(8));
if (!val) {
Expand All @@ -69,7 +83,7 @@ util::Result<int32_t> ParseDustDynamicOpt(const std::string& optstr, const unsig
if (*val < 1) {
return util::Error{_("mempool position must be at least 1 kB")};
}
return *val;
return std::pair<int32_t, unsigned int>(*val, multiplier);
} else {
return util::Error{strprintf(_("\"%s\""), optstr)};
}
Expand Down Expand Up @@ -122,7 +136,8 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainP
if (!parsed) {
return util::Error{strprintf(_("Invalid mode for dustdynamic: %s"), util::ErrorString(parsed))};
}
mempool_opts.dust_relay_target = *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);
Expand Down
5 changes: 3 additions & 2 deletions src/node/mempool_args.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
#include <util/result.h>

#include <cstdint>
#include <string>
#include <string_view>
#include <utility>

class ArgsManager;
class CChainParams;
Expand All @@ -17,7 +18,7 @@ namespace kernel {
struct MemPoolOptions;
};

[[nodiscard]] util::Result<int32_t> ParseDustDynamicOpt(const std::string& optstr, unsigned int max_fee_estimate_blocks);
[[nodiscard]] util::Result<std::pair<int32_t, unsigned int>> 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.
Expand Down
2 changes: 2 additions & 0 deletions src/policy/feerate.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ class CFeeRate
friend bool operator<=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK <= b.nSatoshisPerK; }
friend bool operator>=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK >= b.nSatoshisPerK; }
friend bool operator!=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK != b.nSatoshisPerK; }
CFeeRate operator*(const unsigned int a) { return CFeeRate{nSatoshisPerK * a}; }
CFeeRate operator/(const unsigned int a) { return CFeeRate{nSatoshisPerK / a}; }
CFeeRate& operator+=(const CFeeRate& a) { nSatoshisPerK += a.nSatoshisPerK; return *this; }
/** Return the fee rate in sat/vB or BTC/kvB, with units, as a string. */
std::string ToString(const FeeEstimateMode& fee_estimate_mode = FeeEstimateMode::BTC_KVB) const;
Expand Down
3 changes: 2 additions & 1 deletion src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ static constexpr unsigned int MAX_STANDARD_SCRIPTSIG_SIZE{1650};
* standard and should be done with care and ideally rarely. It makes sense to
* 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 constexpr unsigned int DUST_RELAY_TX_FEE{3'000};
static const std::string DEFAULT_DUST_DYNAMIC{"off"};
static const unsigned int DEFAULT_DUST_RELAY_MULTIPLIER{3000};
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};
Expand Down
17 changes: 12 additions & 5 deletions src/rpc/mempool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -890,12 +890,19 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool, const std::optional<MempoolHi
ret.pushKV("incrementalrelayfee", ValueFromAmount(pool.m_incremental_relay_feerate.GetFeePerK()));
ret.pushKV("dustrelayfee", ValueFromAmount(pool.m_dust_relay_feerate.GetFeePerK()));
ret.pushKV("dustrelayfeefloor", ValueFromAmount(pool.m_dust_relay_feerate_floor.GetFeePerK()));
if (pool.m_dust_relay_target < 0) {
ret.pushKV("dustdynamic", strprintf("target:%u", -pool.m_dust_relay_target));
} else if (pool.m_dust_relay_target > 0) {
ret.pushKV("dustdynamic", strprintf("mempool:%u", pool.m_dust_relay_target));
} else {
if (pool.m_dust_relay_target == 0) {
ret.pushKV("dustdynamic", "off");
} else {
std::string multiplier_str = strprintf("%u", pool.m_dust_relay_multiplier / 1000);
if (pool.m_dust_relay_multiplier % 1000) {
multiplier_str += strprintf(".%03u", pool.m_dust_relay_multiplier % 1000);
while (multiplier_str.back() == '0') multiplier_str.pop_back();
}
if (pool.m_dust_relay_target < 0) {
ret.pushKV("dustdynamic", strprintf("%s*target:%u", multiplier_str, -pool.m_dust_relay_target));
} else { // pool.m_dust_relay_target > 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));
Expand Down
3 changes: 3 additions & 0 deletions src/txmempool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ CTxMemPool::CTxMemPool(const Options& opts)
m_dust_relay_feerate{opts.dust_relay_feerate},
m_dust_relay_feerate_floor{opts.dust_relay_feerate},
m_dust_relay_target{opts.dust_relay_target},
m_dust_relay_multiplier{opts.dust_relay_multiplier},
m_permit_bare_pubkey{opts.permit_bare_pubkey},
m_permit_bare_multisig{opts.permit_bare_multisig},
m_max_datacarrier_bytes{opts.max_datacarrier_bytes},
Expand Down Expand Up @@ -733,6 +734,8 @@ void CTxMemPool::UpdateDynamicDustFeerate()
}
}

est_feerate = (est_feerate * m_dust_relay_multiplier) / 1'000;

if (est_feerate < m_dust_relay_feerate_floor) {
est_feerate = m_dust_relay_feerate_floor;
}
Expand Down
1 change: 1 addition & 0 deletions src/txmempool.h
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ class CTxMemPool
CFeeRate m_dust_relay_feerate;
CFeeRate m_dust_relay_feerate_floor;
int32_t m_dust_relay_target;
unsigned int m_dust_relay_multiplier;
bool m_permit_bare_pubkey;
bool m_permit_bare_multisig;
std::optional<unsigned> m_max_datacarrier_bytes;
Expand Down
45 changes: 23 additions & 22 deletions test/functional/feature_fee_estimation.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,22 +263,32 @@ def sanity_check_estimates_range(self):
self.log.info("Final estimates after emptying mempools")
check_estimates(self.nodes[1], self.fees_per_kb)

def test_feerate_dustrelayfee_common(self, node, multiplier, dust_mode, desc, expected_base):
dust_parameter = f"-dustdynamic={dust_mode}".replace('=3*', '=')
self.log.info(f"Test dust limit setting {dust_parameter} (fee estimation for {desc})")
self.restart_node(0, extra_args=[dust_parameter])
assert_equal(node.getmempoolinfo()['dustdynamic'], dust_mode)
with node.wait_for_debug_log([b'Updating dust feerate']):
node.mockscheduler(SECONDS_PER_HOUR)
mempool_info = node.getmempoolinfo()
assert_equal(mempool_info['dustrelayfee'], satoshi_round(expected_base() * multiplier))
assert mempool_info['dustrelayfee'] > mempool_info['dustrelayfeefloor']

def test_feerate_dustrelayfee_target(self, node, multiplier, dustfee_target):
dust_mode = f"{multiplier}*target:{dustfee_target}"
self.test_feerate_dustrelayfee_common(node, multiplier, dust_mode, f'{dustfee_target} blocks', lambda: node.estimaterawfee(dustfee_target, target_success_threshold)['long']['feerate'])

def test_feerate_dustrelayfee_mempool(self, node, multiplier, dustfee_kB):
dust_mode = f"{multiplier}*mempool:{dustfee_kB}"
self.test_feerate_dustrelayfee_common(node, multiplier, dust_mode, f'{dustfee_kB} kB into mempool', lambda: get_feerate_into_mempool(node, dustfee_kB))

def test_feerate_dustrelayfee(self):
node = self.nodes[0]

# test dustdynamic=target:<blocks>
for dustfee_target in (2, 8, 1008):
dust_mode = f"target:{dustfee_target}"
dust_parameter = f"-dustdynamic={dust_mode}"
self.log.info(f"Test dust limit setting {dust_parameter} (fee estimation for {dustfee_target} blocks)")
self.restart_node(0, extra_args=[dust_parameter])
assert_equal(node.getmempoolinfo()['dustdynamic'], dust_mode)
with node.wait_for_debug_log([b'Updating dust feerate']):
node.mockscheduler(SECONDS_PER_HOUR)
fee_est = node.estimaterawfee(dustfee_target, target_success_threshold)['long']['feerate']
mempool_info = node.getmempoolinfo()
assert_equal(mempool_info['dustrelayfee'], fee_est)
assert mempool_info['dustrelayfee'] > mempool_info['dustrelayfeefloor']
for multiplier in (Decimal('0.5'), 1, 3, Decimal('3.3'), 10, Decimal('10.001')):
self.test_feerate_dustrelayfee_target(node, multiplier, dustfee_target)

# Fill mempool up
mempool_size = 0
Expand All @@ -300,17 +310,8 @@ def test_feerate_dustrelayfee(self):

# test dustdynamic=mempool:<kB>
for dustfee_kB in (1, 10, 50):
dust_mode = f"mempool:{dustfee_kB}"
dust_parameter = f"-dustdynamic={dust_mode}"
self.log.info(f"Test dust limit setting {dust_parameter} (fee estimation for {dustfee_kB} kB into mempool)")
self.restart_node(0, extra_args=[dust_parameter])
assert_equal(node.getmempoolinfo()['dustdynamic'], dust_mode)
with node.wait_for_debug_log([b'Updating dust feerate']):
node.mockscheduler(SECONDS_PER_HOUR)
feerate_into_mempool = get_feerate_into_mempool(node, dustfee_kB)
mempool_info = node.getmempoolinfo()
assert_equal(mempool_info['dustrelayfee'], feerate_into_mempool)
assert mempool_info['dustrelayfee'] > mempool_info['dustrelayfeefloor']
for multiplier in (Decimal('0.5'), 1, 3, Decimal('3.3'), 10, Decimal('10.001')):
self.test_feerate_dustrelayfee_mempool(node, multiplier, dustfee_kB)

# Restore nodes to a normal state, wiping the mempool
self.stop_node(0)
Expand Down

0 comments on commit 37fdfbf

Please sign in to comment.