From d54ef1e1ae895c22ee9b8d9cb556c16cf8bcc42e Mon Sep 17 00:00:00 2001 From: connorslab <82235496+connorslab@users.noreply.github.com> Date: Wed, 7 Oct 2026 14:43:46 -0600 Subject: [PATCH] policy: Enforce TRUC restrictions by default --- doc/release-notes-447.md | 9 +++++++++ src/init.cpp | 3 +-- src/kernel/mempool_options.h | 2 +- test/functional/mempool_truc.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 doc/release-notes-447.md diff --git a/doc/release-notes-447.md b/doc/release-notes-447.md new file mode 100644 index 000000000000..8cec39733a04 --- /dev/null +++ b/doc/release-notes-447.md @@ -0,0 +1,9 @@ +Mempool policy +-------------- + +- The default for `mempooltruc` changes from `accept` to `enforce`, + applying the existing version-3 topology, inheritance, and size checks + by default. Explicit policy settings remain available. + This also selects the existing version-3 exemption from the individual + minimum relay fee check; package fee requirements still apply. + Ephemeral-dust rules, dust-penalty defaults, and mining policy are unchanged. diff --git a/src/init.cpp b/src/init.cpp index 50c4d691bf67..5eac81aae0e8 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -738,7 +738,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc) 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)", "accept"), 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)", "enforce"), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY); argsman.AddArg("-permitbareanchor", strprintf("Relay transactions that only have ephemeral anchor outputs (default: %u)", DEFAULT_PERMITBAREANCHOR), ArgsManager::ALLOW_ANY, @@ -880,7 +880,6 @@ void InitParameterInteraction(ArgsManager& args) args.SoftSetArg("-datacarrierfullcount", "0"); args.SoftSetArg("-maxtxlegacysigops", strprintf("%s", std::numeric_limits::max())); args.SoftSetArg("-maxscriptsize", strprintf("%s", std::numeric_limits::max())); - args.SoftSetArg("-mempooltruc", "enforce"); args.SoftSetArg("-permitephemeral", "anchor,send,dust"); args.SoftSetArg("-spkreuse", "allow"); args.SoftSetArg("-blockprioritysize", "0"); diff --git a/src/kernel/mempool_options.h b/src/kernel/mempool_options.h index 0ce9ac96b1e5..30c815ec1279 100644 --- a/src/kernel/mempool_options.h +++ b/src/kernel/mempool_options.h @@ -33,7 +33,7 @@ static constexpr int DEFAULT_MINRELAYMATURITY{0}; /** Default for -mempoolreplacement; must update docs in init.cpp manually */ static constexpr RBFPolicy DEFAULT_MEMPOOL_RBF_POLICY{RBFPolicy::Always}; /** Default for -mempooltruc; must update docs in init.cpp manually */ -static constexpr TRUCPolicy DEFAULT_MEMPOOL_TRUC_POLICY{TRUCPolicy::Accept}; +static constexpr TRUCPolicy DEFAULT_MEMPOOL_TRUC_POLICY{TRUCPolicy::Enforce}; /** Whether to fall back to legacy V1 serialization when writing mempool.dat */ static constexpr bool DEFAULT_PERSIST_V1_DAT{false}; /** Default for -acceptnonstddatacarrier */ diff --git a/test/functional/mempool_truc.py b/test/functional/mempool_truc.py index 28cb32a29d42..4bc5db4ab08c 100755 --- a/test/functional/mempool_truc.py +++ b/test/functional/mempool_truc.py @@ -682,6 +682,35 @@ def test_minrelay_in_package_combos(self): self.check_mempool([tx_v2_0fee_parent["txid"], tx_v2_child["txid"], tx_v3_0fee_parent["txid"], tx_v3_child["txid"]]) + def test_default_and_overrides(self): + self.log.info("Test native TRUC default and explicit overrides with both corepolicy settings") + node = self.nodes[0] + for corepolicy in (0, 1): + for mode in (None, "accept", "reject", "enforce"): + args = [f"-corepolicy={corepolicy}", "-acceptnonstdtxn=0", "-persistmempool=0"] + if mode is not None: + args.append(f"-mempooltruc={mode}") + self.restart_node(0, extra_args=args) + assert_equal(node.getmempoolinfo()["truc_policy"], mode or "enforce") + self.wallet.rescan_utxos() + parent = self.wallet.create_self_transfer(version=3, confirmed_only=True) + child = self.wallet.create_self_transfer(utxo_to_spend=parent["new_utxo"], version=2) + if mode == "reject": + result = node.testmempoolaccept([parent["hex"]])[0] + assert_equal(result["allowed"], False) + assert_equal(result["reject-reason"], "version") + continue + node.sendrawtransaction(parent["hex"]) + result = node.testmempoolaccept([child["hex"]])[0] + assert_equal(result["allowed"], mode == "accept") + if mode != "accept": + assert_equal(result["reject-reason"], "truc-spent-by-nontruc") + if mode in (None, "enforce"): + parent = self.wallet.create_self_transfer(version=3, confirmed_only=True, fee=0, fee_rate=0) + child = self.wallet.create_self_transfer(utxo_to_spend=parent["new_utxo"], version=3, fee_rate=DEFAULT_FEE*50) + assert_equal(node.submitpackage([parent["hex"], child["hex"]])["package_msg"], "success") + self.restart_node(0) + def run_test(self): self.log.info("Generate blocks to create UTXOs") node = self.nodes[0] @@ -702,6 +731,7 @@ def run_test(self): self.test_truc_sibling_eviction() self.test_reorg_sibling_eviction_1p2c() self.test_minrelay_in_package_combos() + self.test_default_and_overrides() if __name__ == "__main__":