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);
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("-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,
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)",
@ -760,6 +761,10 @@ static bool AppInitServers(NodeContext& node)
// Parameter interaction based on rules
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
// even when -connect or -proxy is specified
if (args.IsArgSet("-bind")) {

View File

@ -14,6 +14,7 @@
#include <optional>
enum class RBFPolicy { Never, OptIn, Always };
enum class TRUCPolicy { Reject, Accept, Enforce };
/** Default for -maxmempool, maximum megabytes of mempool memory usage */
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};
/** Default for -mempoolreplacement; must update docs in init.cpp manually */
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 */
static constexpr bool DEFAULT_PERSIST_V1_DAT{false};
/** Default for -acceptnonstdtxn */
@ -56,6 +59,7 @@ struct MemPoolOptions {
bool permit_bare_multisig{DEFAULT_PERMIT_BAREMULTISIG};
bool require_standard{true};
RBFPolicy rbf_policy{DEFAULT_MEMPOOL_RBF_POLICY};
TRUCPolicy truc_policy{DEFAULT_MEMPOOL_TRUC_POLICY};
bool persist_v1_dat{DEFAULT_PERSIST_V1_DAT};
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);
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
// adapting relay policy by bumping TX_MAX_STANDARD_VERSION, and then later
// 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

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::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) {
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::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, "truc_policy", "Behaviour for transactions requesting limits (one of: reject, accept, enforce)"},
{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",

View File

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

View File

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

View File

@ -738,6 +738,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
if (tx.IsCoinBase())
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)
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)) {
@ -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
// due to a replacement.
// 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
// TX_RECONSIDERABLE, because it cannot be bypassed using package validation.
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,
};
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);
}
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
// check using the full ancestor set here because it's more convenient to use what we have
// 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)}) {
// Disabled within package validation.
if (err->second != nullptr && args.m_allow_replacement) {
@ -1014,7 +1019,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
} else {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, reason, err->first);
}
}
}}
// 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
@ -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.
// Run the v3 checks on the package.
if (m_pool.m_truc_policy == TRUCPolicy::Enforce) {
std::string reason;
for (Workspace& ws : workspaces) {
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());
return PackageMempoolAcceptResult(package_state, {});
}
}
}}
// 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

View File

@ -33,6 +33,8 @@ class ReplaceByFeeTest(BitcoinTestFramework):
"-limitancestorsize=101",
"-limitdescendantcount=200",
"-limitdescendantsize=101",
"-mempooltruc=accept",
"-paytxfee=0.00001", # this test confuses the fee estimator into nearly 1 BTC fees
],
# second node has default mempool parameters
[
@ -97,8 +99,10 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.log.info("Running test opt-in...")
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.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.log.info("Running test RPC...")
@ -511,7 +515,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.generate(normal_node, 1)
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"""
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
# 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(
from_node=self.nodes[0],
utxos_to_spend=[tx1a_utxo, tx2a_utxo],
sequence=[SEQUENCE_FINAL, 0xfffffffd],
fee_per_output=int(0.1 * COIN),
**kwargs
)["txid"]
# 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(
from_node=self.nodes[0],