Merge truc_opts-27+knots

This commit is contained in:
Luke Dashjr 2024-08-01 00:06:00 +00:00
commit 52ebfb386c
9 changed files with 72 additions and 8 deletions

View File

@ -662,6 +662,7 @@ void SetupServerArgs(ArgsManager& argsman)
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-mempoolfullrbf", strprintf("Accept transaction replace-by-fee without requiring replaceability signaling (default: %u)", (DEFAULT_MEMPOOL_RBF_POLICY == RBFPolicy::Always)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-mempoolfullrbf", strprintf("Accept transaction replace-by-fee without requiring replaceability signaling (default: %u)", (DEFAULT_MEMPOOL_RBF_POLICY == RBFPolicy::Always)), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-mempoolreplacement", strprintf("Set to 0 to disable RBF entirely, \"fee,optin\" to honour RBF opt-out signal, or \"fee,-optin\" to always RBF aka full RBF (default: %s)", "fee,optin"), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-mempoolreplacement", strprintf("Set to 0 to disable RBF entirely, \"fee,optin\" to honour RBF opt-out signal, or \"fee,-optin\" to always RBF aka full RBF (default: %s)", "fee,optin"), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-mempooltruc", strprintf("Behaviour for transactions requesting TRUC limits: \"reject\" the transactions entirely, \"accept\" them just like any other, or \"enforce\" to impose their requested restrictions (default: %s)", "reject"), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY, argsman.AddArg("-permitbaremultisig", strprintf("Relay non-P2SH multisig (default: %u)", DEFAULT_PERMIT_BAREMULTISIG), ArgsManager::ALLOW_ANY,
OptionsCategory::NODE_RELAY); OptionsCategory::NODE_RELAY);
argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)", argsman.AddArg("-minrelaytxfee=<amt>", strprintf("Fees (in %s/kvB) smaller than this are considered zero fee for relaying, mining and transaction creation (default: %s)",
@ -760,6 +761,10 @@ static bool AppInitServers(NodeContext& node)
// Parameter interaction based on rules // Parameter interaction based on rules
void InitParameterInteraction(ArgsManager& args) void InitParameterInteraction(ArgsManager& args)
{ {
if (args.GetBoolArg("-acceptnonstdtxn", DEFAULT_ACCEPT_NON_STD_TXN) && (!args.IsArgSet("-mempooltruc")) && DEFAULT_MEMPOOL_TRUC_POLICY == TRUCPolicy::Reject) {
args.SoftSetArg("-mempooltruc", "enforce");
}
// when specifying an explicit binding address, you want to listen on it // when specifying an explicit binding address, you want to listen on it
// even when -connect or -proxy is specified // even when -connect or -proxy is specified
if (args.IsArgSet("-bind")) { if (args.IsArgSet("-bind")) {

View File

@ -14,6 +14,7 @@
#include <optional> #include <optional>
enum class RBFPolicy { Never, OptIn, Always }; enum class RBFPolicy { Never, OptIn, Always };
enum class TRUCPolicy { Reject, Accept, Enforce };
/** Default for -maxmempool, maximum megabytes of mempool memory usage */ /** Default for -maxmempool, maximum megabytes of mempool memory usage */
static constexpr unsigned int DEFAULT_MAX_MEMPOOL_SIZE_MB{300}; static constexpr unsigned int DEFAULT_MAX_MEMPOOL_SIZE_MB{300};
@ -23,6 +24,8 @@ static constexpr unsigned int DEFAULT_BLOCKSONLY_MAX_MEMPOOL_SIZE_MB{5};
static constexpr unsigned int DEFAULT_MEMPOOL_EXPIRY_HOURS{336}; static constexpr unsigned int DEFAULT_MEMPOOL_EXPIRY_HOURS{336};
/** Default for -mempoolreplacement; must update docs in init.cpp manually */ /** Default for -mempoolreplacement; must update docs in init.cpp manually */
static constexpr RBFPolicy DEFAULT_MEMPOOL_RBF_POLICY{RBFPolicy::OptIn}; static constexpr RBFPolicy DEFAULT_MEMPOOL_RBF_POLICY{RBFPolicy::OptIn};
/** Default for -mempooltruc; must update docs in init.cpp manually */
static constexpr TRUCPolicy DEFAULT_MEMPOOL_TRUC_POLICY{TRUCPolicy::Reject};
/** Whether to fall back to legacy V1 serialization when writing mempool.dat */ /** Whether to fall back to legacy V1 serialization when writing mempool.dat */
static constexpr bool DEFAULT_PERSIST_V1_DAT{false}; static constexpr bool DEFAULT_PERSIST_V1_DAT{false};
/** Default for -acceptnonstdtxn */ /** Default for -acceptnonstdtxn */
@ -56,6 +59,7 @@ struct MemPoolOptions {
bool permit_bare_multisig{DEFAULT_PERMIT_BAREMULTISIG}; bool permit_bare_multisig{DEFAULT_PERMIT_BAREMULTISIG};
bool require_standard{true}; bool require_standard{true};
RBFPolicy rbf_policy{DEFAULT_MEMPOOL_RBF_POLICY}; RBFPolicy rbf_policy{DEFAULT_MEMPOOL_RBF_POLICY};
TRUCPolicy truc_policy{DEFAULT_MEMPOOL_TRUC_POLICY};
bool persist_v1_dat{DEFAULT_PERSIST_V1_DAT}; bool persist_v1_dat{DEFAULT_PERSIST_V1_DAT};
MemPoolLimits limits{}; MemPoolLimits limits{};
}; };

View File

@ -143,6 +143,34 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainP
} }
} }
if (argsman.IsArgSet("-mempooltruc")) {
std::optional<bool> accept_flag, enforce_flag;
if (argsman.GetBoolArg("-mempooltruc", false)) {
enforce_flag = true;
}
for (auto& opt : SplitString(argsman.GetArg("-mempooltruc", ""), ",+")) {
if (opt == "optin" || opt == "enforce") {
enforce_flag = true;
} else if (opt == "-optin" || opt == "-enforce") {
enforce_flag = false;
} else if (opt == "accept") {
accept_flag = true;
} else if (opt == "reject" || opt == "0") {
accept_flag = false;
}
}
if (accept_flag && !*accept_flag) { // reject
mempool_opts.truc_policy = TRUCPolicy::Reject;
} else if (enforce_flag && *enforce_flag) { // enforce
mempool_opts.truc_policy = TRUCPolicy::Enforce;
} else if ((!accept_flag) && !enforce_flag) {
// nothing specified, leave at default
} else { // accept or -enforce
mempool_opts.truc_policy = TRUCPolicy::Accept;
}
}
mempool_opts.persist_v1_dat = argsman.GetBoolArg("-persistmempoolv1", mempool_opts.persist_v1_dat); mempool_opts.persist_v1_dat = argsman.GetBoolArg("-persistmempoolv1", mempool_opts.persist_v1_dat);
ApplyArgsManOptions(argsman, mempool_opts.limits); ApplyArgsManOptions(argsman, mempool_opts.limits);

View File

@ -141,7 +141,7 @@ bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_
// Changing the default transaction version requires a two step process: first // Changing the default transaction version requires a two step process: first
// adapting relay policy by bumping TX_MAX_STANDARD_VERSION, and then later // adapting relay policy by bumping TX_MAX_STANDARD_VERSION, and then later
// allowing the new transaction version in the wallet/RPC. // allowing the new transaction version in the wallet/RPC.
static constexpr decltype(CTransaction::nVersion) TX_MAX_STANDARD_VERSION{2}; static constexpr decltype(CTransaction::nVersion) TX_MAX_STANDARD_VERSION{3};
/** /**
* Check for standard transaction types * Check for standard transaction types

View File

@ -891,6 +891,11 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool, const std::optional<MempoolHi
case RBFPolicy::OptIn : ret.pushKV("rbf_policy", "optin"); break; case RBFPolicy::OptIn : ret.pushKV("rbf_policy", "optin"); break;
case RBFPolicy::Always: ret.pushKV("rbf_policy", "always"); break; case RBFPolicy::Always: ret.pushKV("rbf_policy", "always"); break;
} }
switch (pool.m_truc_policy) {
case TRUCPolicy::Reject : ret.pushKV("truc_policy", "reject"); break;
case TRUCPolicy::Accept : ret.pushKV("truc_policy", "accept"); break;
case TRUCPolicy::Enforce: ret.pushKV("truc_policy", "enforce"); break;
}
if (histogram_floors) { if (histogram_floors) {
const MempoolHistogramFeeRates& floors{histogram_floors.value()}; const MempoolHistogramFeeRates& floors{histogram_floors.value()};
@ -975,6 +980,7 @@ static RPCHelpMan getmempoolinfo()
{RPCResult::Type::NUM, "unbroadcastcount", "Current number of transactions that haven't passed initial broadcast yet"}, {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::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)"}, {RPCResult::Type::STR, "rbf_policy", "Policy used for replacing conflicting transactions by fee (one of: never, optin, always)"},
{RPCResult::Type::STR, "truc_policy", "Behaviour for transactions requesting limits (one of: reject, accept, enforce)"},
{RPCResult::Type::OBJ_DYN, "fee_histogram", /*optional=*/true, "", {RPCResult::Type::OBJ_DYN, "fee_histogram", /*optional=*/true, "",
{ {
{RPCResult::Type::OBJ, "<fee_rate_group>", "Fee rate group named by its lower bound (in " + CURRENCY_ATOM + "/vB), identical to the \"from_feerate\" field below", {RPCResult::Type::OBJ, "<fee_rate_group>", "Fee rate group named by its lower bound (in " + CURRENCY_ATOM + "/vB), identical to the \"from_feerate\" field below",

View File

@ -406,6 +406,7 @@ CTxMemPool::CTxMemPool(const Options& opts)
m_max_datacarrier_bytes{opts.max_datacarrier_bytes}, m_max_datacarrier_bytes{opts.max_datacarrier_bytes},
m_require_standard{opts.require_standard}, m_require_standard{opts.require_standard},
m_rbf_policy{opts.rbf_policy}, m_rbf_policy{opts.rbf_policy},
m_truc_policy{opts.truc_policy},
m_persist_v1_dat{opts.persist_v1_dat}, m_persist_v1_dat{opts.persist_v1_dat},
m_limits{opts.limits} m_limits{opts.limits}
{ {

View File

@ -448,6 +448,7 @@ public:
const std::optional<unsigned> m_max_datacarrier_bytes; const std::optional<unsigned> m_max_datacarrier_bytes;
const bool m_require_standard; const bool m_require_standard;
const RBFPolicy m_rbf_policy; const RBFPolicy m_rbf_policy;
const TRUCPolicy m_truc_policy;
const bool m_persist_v1_dat; const bool m_persist_v1_dat;
const Limits m_limits; const Limits m_limits;

View File

@ -738,6 +738,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
if (tx.IsCoinBase()) if (tx.IsCoinBase())
return state.Invalid(TxValidationResult::TX_CONSENSUS, "coinbase"); return state.Invalid(TxValidationResult::TX_CONSENSUS, "coinbase");
if (tx.nVersion == 3 && m_pool.m_truc_policy == TRUCPolicy::Reject) {
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "version");
}
// Rather not work on nonstandard transactions (unless -testnet/-regtest) // Rather not work on nonstandard transactions (unless -testnet/-regtest)
std::string reason; std::string reason;
if (m_pool.m_require_standard && !IsStandardTx(tx, m_pool.m_max_datacarrier_bytes, m_pool.m_permit_bare_multisig, m_pool.m_dust_relay_feerate, reason, ignore_rejects)) { if (m_pool.m_require_standard && !IsStandardTx(tx, m_pool.m_max_datacarrier_bytes, m_pool.m_permit_bare_multisig, m_pool.m_dust_relay_feerate, reason, ignore_rejects)) {
@ -906,7 +910,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// method of ensuring the tx remains bumped. For example, the fee-bumping child could disappear // method of ensuring the tx remains bumped. For example, the fee-bumping child could disappear
// due to a replacement. // due to a replacement.
// The only exception is v3 transactions. // The only exception is v3 transactions.
if (ws.m_ptx->nVersion != 3 && ws.m_modified_fees < m_pool.m_min_relay_feerate.GetFee(ws.m_vsize) && !args.m_ignore_rejects.count(rejectmsg_mempoolfull)) { if ((ws.m_ptx->nVersion != 3 || m_pool.m_truc_policy != TRUCPolicy::Enforce) && ws.m_modified_fees < m_pool.m_min_relay_feerate.GetFee(ws.m_vsize) && !args.m_ignore_rejects.count(rejectmsg_mempoolfull)) {
// Even though this is a fee-related failure, this result is TX_MEMPOOL_POLICY, not // Even though this is a fee-related failure, this result is TX_MEMPOOL_POLICY, not
// TX_RECONSIDERABLE, because it cannot be bypassed using package validation. // TX_RECONSIDERABLE, because it cannot be bypassed using package validation.
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met", return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met",
@ -986,7 +990,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
.descendant_size_vbytes = maybe_rbf_limits.descendant_size_vbytes + EXTRA_DESCENDANT_TX_SIZE_LIMIT, .descendant_size_vbytes = maybe_rbf_limits.descendant_size_vbytes + EXTRA_DESCENDANT_TX_SIZE_LIMIT,
}; };
const auto error_message{util::ErrorString(ancestors).original}; const auto error_message{util::ErrorString(ancestors).original};
if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || ws.m_ptx->nVersion == 3) { if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || (ws.m_ptx->nVersion == 3 && m_pool.m_truc_policy == TRUCPolicy::Enforce)) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message); return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
} }
ancestors = m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits); ancestors = m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits);
@ -997,6 +1001,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Even though just checking direct mempool parents for inheritance would be sufficient, we // Even though just checking direct mempool parents for inheritance would be sufficient, we
// check using the full ancestor set here because it's more convenient to use what we have // check using the full ancestor set here because it's more convenient to use what we have
// already calculated. // already calculated.
if (m_pool.m_truc_policy == TRUCPolicy::Enforce) {
if (const auto err{SingleV3Checks(ws.m_ptx, "truc-", reason, ignore_rejects, ws.m_ancestors, ws.m_conflicts, ws.m_vsize)}) { if (const auto err{SingleV3Checks(ws.m_ptx, "truc-", reason, ignore_rejects, ws.m_ancestors, ws.m_conflicts, ws.m_vsize)}) {
// Disabled within package validation. // Disabled within package validation.
if (err->second != nullptr && args.m_allow_replacement) { if (err->second != nullptr && args.m_allow_replacement) {
@ -1014,7 +1019,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
} else { } else {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, reason, err->first); return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, reason, err->first);
} }
} }}
// A transaction that spends outputs that would be replaced by it is invalid. Now // A transaction that spends outputs that would be replaced by it is invalid. Now
// that we have the set of all ancestors we can detect this // that we have the set of all ancestors we can detect this
@ -1391,13 +1396,14 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
// At this point we have all in-mempool ancestors, and we know every transaction's vsize. // At this point we have all in-mempool ancestors, and we know every transaction's vsize.
// Run the v3 checks on the package. // Run the v3 checks on the package.
if (m_pool.m_truc_policy == TRUCPolicy::Enforce) {
std::string reason; std::string reason;
for (Workspace& ws : workspaces) { for (Workspace& ws : workspaces) {
if (auto err{PackageV3Checks(ws.m_ptx, ws.m_vsize, "truc-", reason, args.m_ignore_rejects, txns, ws.m_ancestors)}) { if (auto err{PackageV3Checks(ws.m_ptx, ws.m_vsize, "truc-", reason, args.m_ignore_rejects, txns, ws.m_ancestors)}) {
package_state.Invalid(PackageValidationResult::PCKG_POLICY, reason, err.value()); package_state.Invalid(PackageValidationResult::PCKG_POLICY, reason, err.value());
return PackageMempoolAcceptResult(package_state, {}); return PackageMempoolAcceptResult(package_state, {});
} }
} }}
// Transactions must meet two minimum feerates: the mempool minimum fee and min relay fee. // Transactions must meet two minimum feerates: the mempool minimum fee and min relay fee.
// For transactions consisting of exactly one child and its parents, it suffices to use the // For transactions consisting of exactly one child and its parents, it suffices to use the

View File

@ -33,6 +33,8 @@ class ReplaceByFeeTest(BitcoinTestFramework):
"-limitancestorsize=101", "-limitancestorsize=101",
"-limitdescendantcount=200", "-limitdescendantcount=200",
"-limitdescendantsize=101", "-limitdescendantsize=101",
"-mempooltruc=accept",
"-paytxfee=0.00001", # this test confuses the fee estimator into nearly 1 BTC fees
], ],
# second node has default mempool parameters # second node has default mempool parameters
[ [
@ -97,8 +99,10 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.log.info("Running test opt-in...") self.log.info("Running test opt-in...")
self.test_opt_in(fullrbf=False) self.test_opt_in(fullrbf=False)
self.test_opt_in(fullrbf=False, use_truc=True)
self.nodes[0], self.nodes[-1] = self.nodes[-1], self.nodes[0] self.nodes[0], self.nodes[-1] = self.nodes[-1], self.nodes[0]
self.test_opt_in(fullrbf=True) self.test_opt_in(fullrbf=True)
self.test_opt_in(fullrbf=True, use_truc=True)
self.nodes[0], self.nodes[-1] = self.nodes[-1], self.nodes[0] self.nodes[0], self.nodes[-1] = self.nodes[-1], self.nodes[0]
self.log.info("Running test RPC...") self.log.info("Running test RPC...")
@ -511,7 +515,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.generate(normal_node, 1) self.generate(normal_node, 1)
self.wallet.rescan_utxos() self.wallet.rescan_utxos()
def test_opt_in(self, fullrbf): def test_opt_in(self, fullrbf, use_truc=False):
"""Replacing should only work if orig tx opted in""" """Replacing should only work if orig tx opted in"""
tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN)) tx0_outpoint = self.make_utxo(self.nodes[0], int(1.1 * COIN))
@ -570,15 +574,24 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# opt-in on one of the inputs # opt-in on one of the inputs
# Transaction should be replaceable on either input # Transaction should be replaceable on either input
self.generate(self.nodes[0], 1) # clean mempool so parent txs don't trigger BIP125
if use_truc:
kwargs = {'sequence': SEQUENCE_FINAL, 'version': 3}
else:
kwargs = {'sequence': [SEQUENCE_FINAL, 0xfffffffd]}
tx3a_txid = self.wallet.send_self_transfer_multi( tx3a_txid = self.wallet.send_self_transfer_multi(
from_node=self.nodes[0], from_node=self.nodes[0],
utxos_to_spend=[tx1a_utxo, tx2a_utxo], utxos_to_spend=[tx1a_utxo, tx2a_utxo],
sequence=[SEQUENCE_FINAL, 0xfffffffd],
fee_per_output=int(0.1 * COIN), fee_per_output=int(0.1 * COIN),
**kwargs
)["txid"] )["txid"]
# This transaction is shown as replaceable # This transaction is shown as replaceable
assert_equal(self.nodes[0].getmempoolentry(tx3a_txid)['bip125-replaceable'], True) if use_truc:
assert_equal(self.nodes[0].getmempoolentry(tx3a_txid)['bip125-replaceable'], False)
else:
assert_equal(self.nodes[0].getmempoolentry(tx3a_txid)['bip125-replaceable'], True)
self.wallet.send_self_transfer( self.wallet.send_self_transfer(
from_node=self.nodes[0], from_node=self.nodes[0],