[test] check bypass of minrelay for various minrelaytxfee settings
What changed, and why it matters
This commit only adds a new automated test to Bitcoin Core. It checks that a special new transaction type (TRUC, also known as version 3 transactions) can enter the mempool as a package even when one part pays no fee, while ordinary transactions cannot bypass the minimum relay fee. The change does not modify any production code, so it cannot directly affect live Bitcoin nodes or funds.
No action required; this is a test-only addition. Reviewers may optionally verify that the new test assertions correctly match intended TRUC package-submission behavior.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff adds test_minrelay_in_package_combos() to test/functional/mempool_truc.py. It restarts a regtest node with various -minrelaytxfee values, submits a parent+child package where the parent pays zero fee, and asserts that TRUC/v3 packages are accepted while non-TRUC/v2 packages are rejected unless minrelaytxfee is zero. It also increases the initial block generation from 120 to 200 to provide enough UTXOs/funds for the new test cases. No consensus, networking, mempool policy, or wallet logic is changed.
Changed components
test/functional/mempool_truc.pyInspect captured patch +48 / −1
diff --git a/test/functional/mempool_truc.py b/test/functional/mempool_truc.py
index 8a4f34e3..428bfb0b 100755
--- a/test/functional/mempool_truc.py
+++ b/test/functional/mempool_truc.py
@@ -11,6 +11,7 @@ from test_framework.util import (
assert_greater_than,
assert_greater_than_or_equal,
assert_raises_rpc_error,
+ get_fee,
)
from test_framework.wallet import (
COIN,
@@ -595,12 +596,57 @@ class MempoolTRUC(BitcoinTestFramework):
)
self.check_mempool([tx_with_multi_children["txid"], tx_with_sibling3_rbf["txid"], tx_with_sibling2["txid"]])
+ @cleanup(extra_args=None)
+ def test_minrelay_in_package_combos(self):
+ node = self.nodes[0]
+ self.log.info("Test that only TRUC transactions can be under minrelaytxfee for various settings...")
+
+ for minrelay_setting in (0, 5, 10, 100, 500, 1000, 5000, 333333, 2500000):
+ self.log.info(f"-> Test -minrelaytxfee={minrelay_setting}sat/kvB...")
+ setting_decimal = minrelay_setting / Decimal(COIN)
+ self.restart_node(0, extra_args=[f"-minrelaytxfee={setting_decimal:.8f}", "-persistmempool=0"])
+ minrelayfeerate = node.getmempoolinfo()["minrelaytxfee"]
+ high_feerate = minrelayfeerate * 50
+
+ tx_v3_0fee_parent = self.wallet.create_self_transfer(fee=0, fee_rate=0, confirmed_only=True, version=3)
+ tx_v3_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_0fee_parent["new_utxo"], fee_rate=high_feerate, version=3)
+ total_v3_fee = tx_v3_child["fee"] + tx_v3_0fee_parent["fee"]
+ total_v3_size = tx_v3_child["tx"].get_vsize() + tx_v3_0fee_parent["tx"].get_vsize()
+ assert_greater_than_or_equal(total_v3_fee, get_fee(total_v3_size, minrelayfeerate))
+ if minrelayfeerate > 0:
+ assert_greater_than(get_fee(tx_v3_0fee_parent["tx"].get_vsize(), minrelayfeerate), 0)
+ # Always need to pay at least 1 satoshi for entry, even if minimum feerate is very low
+ assert_greater_than(total_v3_fee, 0)
+
+ tx_v2_0fee_parent = self.wallet.create_self_transfer(fee=0, fee_rate=0, confirmed_only=True, version=2)
+ tx_v2_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v2_0fee_parent["new_utxo"], fee_rate=high_feerate, version=2)
+ total_v2_fee = tx_v2_child["fee"] + tx_v2_0fee_parent["fee"]
+ total_v2_size = tx_v2_child["tx"].get_vsize() + tx_v2_0fee_parent["tx"].get_vsize()
+ assert_greater_than_or_equal(total_v2_fee, get_fee(total_v2_size, minrelayfeerate))
+ if minrelayfeerate > 0:
+ assert_greater_than(get_fee(tx_v2_0fee_parent["tx"].get_vsize(), minrelayfeerate), 0)
+ # Always need to pay at least 1 satoshi for entry, even if minimum feerate is very low
+ assert_greater_than(total_v2_fee, 0)
+
+ result_truc = node.submitpackage([tx_v3_0fee_parent["hex"], tx_v3_child["hex"]], maxfeerate=0)
+ assert_equal(result_truc["package_msg"], "success")
+
+ result_non_truc = node.submitpackage([tx_v2_0fee_parent["hex"], tx_v2_child["hex"]], maxfeerate=0)
+ if minrelayfeerate > 0:
+ assert_equal(result_non_truc["package_msg"], "transaction failed")
+ min_fee_parent = int(get_fee(tx_v2_0fee_parent["tx"].get_vsize(), minrelayfeerate) * COIN)
+ assert_equal(result_non_truc["tx-results"][tx_v2_0fee_parent["wtxid"]]["error"], f"min relay fee not met, 0 < {min_fee_parent}")
+ self.check_mempool([tx_v3_0fee_parent["txid"], tx_v3_child["txid"]])
+ else:
+ assert_equal(result_non_truc["package_msg"], "success")
+ self.check_mempool([tx_v2_0fee_parent["txid"], tx_v2_child["txid"], tx_v3_0fee_parent["txid"], tx_v3_child["txid"]])
+
def run_test(self):
self.log.info("Generate blocks to create UTXOs")
node = self.nodes[0]
self.wallet = MiniWallet(node)
- self.generate(self.wallet, 120)
+ self.generate(self.wallet, 200)
self.test_truc_max_vsize()
self.test_truc_acceptance()
self.test_truc_replacement()
@@ -614,6 +660,7 @@ class MempoolTRUC(BitcoinTestFramework):
self.test_reorg_2child_rbf()
self.test_truc_sibling_eviction()
self.test_reorg_sibling_eviction_1p2c()
+ self.test_minrelay_in_package_combos()
if __name__ == "__main__":
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.