Skip to content

Commit ec4ff99

Browse files
committed
Merge bitcoin/bitcoin#33892: policy: allow <minrelay txns in package context if paid for by cpfp
e44dec0 add release note about supporing non-TRUC <minrelay txns (Greg Sanders) 1488315 policy: Allow any transaction version with < minrelay (Greg Sanders) Pull request description: Prior to cluster mempool, a policy was in place that disallowed non-TRUC transactions from being TX_RECONSIDERABLE in a package setting if it was below minrelay. This was meant to simplify reasoning about mempool trimming requirements with non-trivial transaction topologies in the mempool. This is no longer a concern post-cluster mempool, so this is relaxed. In effect, this makes 0-value parent transactions relayable through the network without the TRUC restrictions and thus the anti-pinning protections. ACKs for top commit: ajtowns: ACK e44dec0 - lgtm ismaelsadeeq: ACK e44dec0 Tree-SHA512: 6fd1a2429c55ca844d9bd669ea797e29eca3f544f0b5d3484743d3c1cdf4364f7c7a058aaf707bcfd94b84c621bea03228cb39487cbc23912b9e0980a1e5b451
2 parents 48c9ba1 + e44dec0 commit ec4ff99

File tree

7 files changed

+34
-53
lines changed

7 files changed

+34
-53
lines changed

doc/policy/packages.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,6 @@ submitted as a package.
9999
transaction (i.e. in which a replacement transaction with a higher fee cannot be signed) being
100100
rejected from the mempool when transaction volume is high and the mempool minimum feerate rises.
101101

102-
Note: Package feerate cannot be used to meet the minimum relay feerate (`-minrelaytxfee`)
103-
requirement. For example, if the mempool minimum feerate is 5sat/vB and the minimum relay feerate is
104-
set to 5sat/vB, a 1sat/vB parent transaction with a high-feerate child will not be accepted, even if
105-
submitted as a package. Note that this rule does not apply to
106-
[TRUC transactions](https://github.com/bitcoin/bips/blob/master/bip-0431.mediawiki) as an individual
107-
TRUC transaction is permitted to be below the mempool min relay feerate, assuming it is considered within
108-
a package that meets the mempool's feerate requirements.
109-
110102
*Rationale*: Avoid situations in which the mempool contains non-bumped transactions below min relay
111103
feerate (which we consider to have pay 0 fees and thus receiving free relay). While package
112104
submission would ensure these transactions are bumped at the time of entry, it is not guaranteed

doc/release-notes-33892.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
P2P and network changes
2+
-----------------------
3+
4+
- Transactions participating in one-parent-one-child package relay can now have the parent
5+
with a feerate lower than the `-minrelaytxfee` feerate, even 0 fee. This expands the change
6+
from 28.0 to also cover packages of non-TRUC transactions. Note that in general the
7+
package child can have additional unconfirmed parents, but they must already be
8+
in-mempool for the new package to be relayed. (#33892)

src/test/txpackage_tests.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -908,10 +908,10 @@ BOOST_AUTO_TEST_CASE(package_cpfp_tests)
908908
} else {
909909
BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_state.GetResult(), PackageValidationResult::PCKG_TX);
910910
BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_tx_results.find(tx_parent->GetWitnessHash())->second.m_state.GetResult(),
911-
TxValidationResult::TX_MEMPOOL_POLICY);
911+
TxValidationResult::TX_RECONSIDERABLE);
912912
BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_tx_results.find(tx_child->GetWitnessHash())->second.m_state.GetResult(),
913-
TxValidationResult::TX_MISSING_INPUTS);
914-
BOOST_CHECK(submit_cpfp_deprio.m_tx_results.find(tx_parent->GetWitnessHash())->second.m_state.GetRejectReason() == "min relay fee not met");
913+
TxValidationResult::TX_RECONSIDERABLE);
914+
BOOST_CHECK_EQUAL(submit_cpfp_deprio.m_tx_results.find(tx_parent->GetWitnessHash())->second.m_state.GetRejectReason(), "mempool min fee not met");
915915
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
916916
}
917917
}
@@ -1067,8 +1067,8 @@ BOOST_AUTO_TEST_CASE(package_cpfp_tests)
10671067
strprintf("rich parent: expected fee %s, got %s", high_parent_fee, it_parent->second.m_base_fees.value()));
10681068
BOOST_CHECK(it_parent->second.m_effective_feerate == CFeeRate(high_parent_fee, GetVirtualTransactionSize(*tx_parent_rich)));
10691069
BOOST_CHECK_EQUAL(it_child->second.m_result_type, MempoolAcceptResult::ResultType::INVALID);
1070-
BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MEMPOOL_POLICY);
1071-
BOOST_CHECK(it_child->second.m_state.GetRejectReason() == "min relay fee not met");
1070+
BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_RECONSIDERABLE);
1071+
BOOST_CHECK_EQUAL(it_child->second.m_state.GetRejectReason(), "mempool min fee not met");
10721072
}
10731073
expected_pool_size += 1;
10741074
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);

src/validation.cpp

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -943,18 +943,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
943943
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
944944
strprintf("%d", nSigOpsCost));
945945

946-
// No individual transactions are allowed below the min relay feerate except from disconnected blocks.
947-
// This requirement, unlike CheckFeeRate, cannot be bypassed using m_package_feerates because,
948-
// while a tx could be package CPFP'd when entering the mempool, we do not have a DoS-resistant
949-
// method of ensuring the tx remains bumped. For example, the fee-bumping child could disappear
950-
// due to a replacement.
951-
// The only exception is TRUC transactions.
952-
if (!bypass_limits && ws.m_ptx->version != TRUC_VERSION && ws.m_modified_fees < m_pool.m_opts.min_relay_feerate.GetFee(ws.m_vsize)) {
953-
// Even though this is a fee-related failure, this result is TX_MEMPOOL_POLICY, not
954-
// TX_RECONSIDERABLE, because it cannot be bypassed using package validation.
955-
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met",
956-
strprintf("%d < %d", ws.m_modified_fees, m_pool.m_opts.min_relay_feerate.GetFee(ws.m_vsize)));
957-
}
958946
// No individual transactions are allowed below the mempool min feerate except from disconnected
959947
// blocks and transactions in a package. Package transactions will be checked using package
960948
// feerate later.

test/functional/mempool_ephemeral_dust.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,18 +216,16 @@ def test_nonzero_dust(self):
216216
self.connect_nodes(0, 1)
217217
assert_mempool_contents(self, self.nodes[0], expected=[])
218218

219-
# N.B. If individual minrelay requirement is dropped, this test can be dropped
220219
def test_non_truc(self):
221-
self.log.info("Test that v2 dust-having transaction is rejected even if spent, because of min relay requirement")
220+
self.log.info("Test that v2 dust-having transaction is also accepted if spent")
222221

223222
assert_equal(self.nodes[0].getrawmempool(), [])
224223
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=2)
225224

226225
res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])
227-
assert_equal(res["package_msg"], "transaction failed")
228-
assert_equal(res["tx-results"][dusty_tx["wtxid"]]["error"], "min relay fee not met, 0 < 15")
229-
230-
assert_equal(self.nodes[0].getrawmempool(), [])
226+
assert_equal(res["package_msg"], "success")
227+
assert_mempool_contents(self, self.nodes[0], expected=[dusty_tx["tx"], sweep_tx["tx"]])
228+
self.generate(self.nodes[0], 1)
231229

232230
def test_unspent_ephemeral(self):
233231
self.log.info("Test that spending from a tx with ephemeral outputs is only allowed if dust is spent as well")

test/functional/mempool_truc.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ def test_reorg_sibling_eviction_1p2c(self):
612612
@cleanup(extra_args=None)
613613
def test_minrelay_in_package_combos(self):
614614
node = self.nodes[0]
615-
self.log.info("Test that only TRUC transactions can be under minrelaytxfee for various settings...")
615+
self.log.info("Test that all transaction versions can be under minrelaytxfee for various settings...")
616616

617617
for minrelay_setting in (0, 5, 10, 100, 500, 1000, 5000, 333333, 2500000):
618618
self.log.info(f"-> Test -minrelaytxfee={minrelay_setting}sat/kvB...")
@@ -649,14 +649,8 @@ def test_minrelay_in_package_combos(self):
649649
assert_equal(result_truc["package_msg"], "success")
650650

651651
result_non_truc = node.submitpackage([tx_v2_0fee_parent["hex"], tx_v2_child["hex"]], maxfeerate=0)
652-
if minrelayfeerate > 0:
653-
assert_equal(result_non_truc["package_msg"], "transaction failed")
654-
min_fee_parent = int(get_fee(tx_v2_0fee_parent["tx"].get_vsize(), minrelayfeerate) * COIN)
655-
assert_equal(result_non_truc["tx-results"][tx_v2_0fee_parent["wtxid"]]["error"], f"min relay fee not met, 0 < {min_fee_parent}")
656-
self.check_mempool([tx_v3_0fee_parent["txid"], tx_v3_child["txid"]])
657-
else:
658-
assert_equal(result_non_truc["package_msg"], "success")
659-
self.check_mempool([tx_v2_0fee_parent["txid"], tx_v2_child["txid"], tx_v3_0fee_parent["txid"], tx_v3_child["txid"]])
652+
assert_equal(result_non_truc["package_msg"], "success")
653+
self.check_mempool([tx_v2_0fee_parent["txid"], tx_v2_child["txid"], tx_v3_0fee_parent["txid"], tx_v3_child["txid"]])
660654

661655

662656
def run_test(self):

test/functional/p2p_orphan_handling.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import time
77

8+
from test_framework.blocktools import MAX_STANDARD_TX_WEIGHT
89
from test_framework.mempool_util import (
910
create_large_orphan,
1011
tx_in_orphanage,
@@ -188,15 +189,15 @@ def test_orphan_rejected_parents_exceptions(self):
188189
peer2 = node.add_p2p_connection(PeerTxRelayer())
189190

190191
self.log.info("Test orphan handling when a nonsegwit parent is known to be invalid")
191-
parent_low_fee_nonsegwit = self.wallet_nonsegwit.create_self_transfer(fee_rate=0)
192-
assert_equal(parent_low_fee_nonsegwit["txid"], parent_low_fee_nonsegwit["tx"].wtxid_hex)
192+
parent_overly_large_nonsegwit = self.wallet_nonsegwit.create_self_transfer(target_vsize=int(MAX_STANDARD_TX_WEIGHT / 4) + 1)
193+
assert_equal(parent_overly_large_nonsegwit["txid"], parent_overly_large_nonsegwit["tx"].wtxid_hex)
193194
parent_other = self.wallet_nonsegwit.create_self_transfer()
194195
child_nonsegwit = self.wallet_nonsegwit.create_self_transfer_multi(
195-
utxos_to_spend=[parent_other["new_utxo"], parent_low_fee_nonsegwit["new_utxo"]])
196+
utxos_to_spend=[parent_other["new_utxo"], parent_overly_large_nonsegwit["new_utxo"]])
196197

197-
# Relay the parent. It should be rejected because it pays 0 fees.
198-
self.relay_transaction(peer1, parent_low_fee_nonsegwit["tx"])
199-
assert parent_low_fee_nonsegwit["txid"] not in node.getrawmempool()
198+
# Relay the parent. It should be rejected (and not reconsiderable) because it violated size limitations.
199+
self.relay_transaction(peer1, parent_overly_large_nonsegwit["tx"])
200+
assert parent_overly_large_nonsegwit["txid"] not in node.getrawmempool()
200201

201202
# Relay the child. It should not be accepted because it has missing inputs.
202203
# Its parent should not be requested because its hash (txid == wtxid) has been added to the rejection filter.
@@ -208,7 +209,7 @@ def test_orphan_rejected_parents_exceptions(self):
208209
self.nodes[0].bumpmocktime(GETDATA_TX_INTERVAL)
209210
peer1.assert_never_requested(int(parent_other["txid"], 16))
210211
peer2.assert_never_requested(int(parent_other["txid"], 16))
211-
peer2.assert_never_requested(int(parent_low_fee_nonsegwit["txid"], 16))
212+
peer2.assert_never_requested(int(parent_overly_large_nonsegwit["txid"], 16))
212213

213214
self.log.info("Test orphan handling when a segwit parent was invalid but may be retried with another witness")
214215
parent_low_fee = self.wallet.create_self_transfer(fee_rate=0)
@@ -391,23 +392,23 @@ def test_orphan_inherit_rejection(self):
391392
peer3 = node.add_p2p_connection(PeerTxRelayer(wtxidrelay=False))
392393

393394
self.log.info("Test that an orphan with rejected parents, along with any descendants, cannot be retried with an alternate witness")
394-
parent_low_fee_nonsegwit = self.wallet_nonsegwit.create_self_transfer(fee_rate=0)
395-
assert_equal(parent_low_fee_nonsegwit["txid"], parent_low_fee_nonsegwit["tx"].wtxid_hex)
396-
child = self.wallet.create_self_transfer(utxo_to_spend=parent_low_fee_nonsegwit["new_utxo"])
395+
parent_overly_large_nonsegwit = self.wallet_nonsegwit.create_self_transfer(target_vsize=int(MAX_STANDARD_TX_WEIGHT / 4) + 1)
396+
assert_equal(parent_overly_large_nonsegwit["txid"], parent_overly_large_nonsegwit["tx"].wtxid_hex)
397+
child = self.wallet.create_self_transfer(utxo_to_spend=parent_overly_large_nonsegwit["new_utxo"])
397398
grandchild = self.wallet.create_self_transfer(utxo_to_spend=child["new_utxo"])
398399
assert_not_equal(child["txid"], child["tx"].wtxid_hex)
399400
assert_not_equal(grandchild["txid"], grandchild["tx"].wtxid_hex)
400401

401402
# Relay the parent. It should be rejected because it pays 0 fees.
402-
self.relay_transaction(peer1, parent_low_fee_nonsegwit["tx"])
403-
assert parent_low_fee_nonsegwit["txid"] not in node.getrawmempool()
403+
self.relay_transaction(peer1, parent_overly_large_nonsegwit["tx"])
404+
assert parent_overly_large_nonsegwit["txid"] not in node.getrawmempool()
404405

405406
# Relay the child. It should be rejected for having missing parents, and this rejection is
406407
# cached by txid and wtxid.
407408
self.relay_transaction(peer1, child["tx"])
408409
assert_equal(0, len(node.getrawmempool()))
409410
assert not tx_in_orphanage(node, child["tx"])
410-
peer1.assert_never_requested(parent_low_fee_nonsegwit["txid"])
411+
peer1.assert_never_requested(parent_overly_large_nonsegwit["txid"])
411412

412413
# Grandchild should also not be kept in orphanage because its parent has been rejected.
413414
self.relay_transaction(peer2, grandchild["tx"])

0 commit comments

Comments
 (0)