Skip to content

Commit c4554fe

Browse files
committed
[test] package cpfp bumps parents <mempoolminfee but >=minrelaytxfee
1 parent ac463e8 commit c4554fe

File tree

3 files changed

+77
-64
lines changed

3 files changed

+77
-64
lines changed

src/test/txpackage_tests.cpp

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
#include <boost/test/unit_test.hpp>
1717

1818
BOOST_AUTO_TEST_SUITE(txpackage_tests)
19+
// A fee amount that is above 1sat/vB but below 5sat/vB for most transactions created within these
20+
// unit tests.
21+
static const CAmount low_fee_amt{200};
1922

2023
// Create placeholder transactions that have no meaning.
2124
inline CTransactionRef create_placeholder_tx(size_t num_inputs, size_t num_outputs)
@@ -373,6 +376,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
373376
{
374377
// Mine blocks to mature coinbases.
375378
mineBlocks(5);
379+
MockMempoolMinFee(CFeeRate(5000));
376380
LOCK(cs_main);
377381

378382
// Transactions with a same-txid-different-witness transaction in the mempool should be ignored,
@@ -560,13 +564,15 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
560564
BOOST_CHECK(parent2_v2_result.m_result_type == MempoolAcceptResult::ResultType::VALID);
561565
package_mixed.push_back(ptx_parent2_v1);
562566

563-
// parent3 will be a new transaction. Put 0 fees on it to make it invalid on its own.
567+
// parent3 will be a new transaction. Put a low feerate to make it invalid on its own.
564568
auto mtx_parent3 = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[3], /*input_vout=*/0,
565569
/*input_height=*/0, /*input_signing_key=*/coinbaseKey,
566570
/*output_destination=*/acs_spk,
567-
/*output_amount=*/CAmount(50 * COIN), /*submit=*/false);
571+
/*output_amount=*/CAmount(50 * COIN - low_fee_amt), /*submit=*/false);
568572
CTransactionRef ptx_parent3 = MakeTransactionRef(mtx_parent3);
569573
package_mixed.push_back(ptx_parent3);
574+
BOOST_CHECK(m_node.mempool->GetMinFee().GetFee(GetVirtualTransactionSize(*ptx_parent3)) > low_fee_amt);
575+
BOOST_CHECK(m_node.mempool->m_min_relay_feerate.GetFee(GetVirtualTransactionSize(*ptx_parent3)) <= low_fee_amt);
570576

571577
// child spends parent1, parent2, and parent3
572578
CKey mixed_grandchild_key;
@@ -627,6 +633,7 @@ BOOST_FIXTURE_TEST_CASE(package_witness_swap_tests, TestChain100Setup)
627633
BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
628634
{
629635
mineBlocks(5);
636+
MockMempoolMinFee(CFeeRate(5000));
630637
LOCK(::cs_main);
631638
size_t expected_pool_size = m_node.mempool->size();
632639
CKey child_key;
@@ -636,9 +643,9 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
636643
grandchild_key.MakeNewKey(true);
637644
CScript child_spk = GetScriptForDestination(WitnessV0KeyHash(grandchild_key.GetPubKey()));
638645

639-
// zero-fee parent and high-fee child package
646+
// low-fee parent and high-fee child package
640647
const CAmount coinbase_value{50 * COIN};
641-
const CAmount parent_value{coinbase_value - 0};
648+
const CAmount parent_value{coinbase_value - low_fee_amt};
642649
const CAmount child_value{parent_value - COIN};
643650

644651
Package package_cpfp;
@@ -657,9 +664,9 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
657664
package_cpfp.push_back(tx_child);
658665

659666
// Package feerate is calculated using modified fees, and prioritisetransaction accepts negative
660-
// fee deltas. This should be taken into account. De-prioritise the parent transaction by -1BTC,
661-
// bringing the package feerate to 0.
662-
m_node.mempool->PrioritiseTransaction(tx_parent->GetHash(), -1 * COIN);
667+
// fee deltas. This should be taken into account. De-prioritise the parent transaction
668+
// to bring the package feerate to 0.
669+
m_node.mempool->PrioritiseTransaction(tx_parent->GetHash(), child_value - coinbase_value);
663670
{
664671
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
665672
const auto submit_cpfp_deprio = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
@@ -675,8 +682,8 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
675682
// Clear the prioritisation of the parent transaction.
676683
WITH_LOCK(m_node.mempool->cs, m_node.mempool->ClearPrioritisation(tx_parent->GetHash()));
677684

678-
// Package CPFP: Even though the parent pays 0 absolute fees, the child pays 1 BTC which is
679-
// enough for the package feerate to meet the threshold.
685+
// Package CPFP: Even though the parent's feerate is below the mempool minimum feerate, the
686+
// child pays enough for the package feerate to meet the threshold.
680687
{
681688
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
682689
const auto submit_cpfp = ProcessNewPackage(m_node.chainman->ActiveChainstate(), *m_node.mempool,
@@ -689,7 +696,7 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
689696
auto it_child = submit_cpfp.m_tx_results.find(tx_child->GetWitnessHash());
690697
BOOST_CHECK(it_parent != submit_cpfp.m_tx_results.end());
691698
BOOST_CHECK(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
692-
BOOST_CHECK(it_parent->second.m_base_fees.value() == 0);
699+
BOOST_CHECK(it_parent->second.m_base_fees.value() == coinbase_value - parent_value);
693700
BOOST_CHECK(it_child != submit_cpfp.m_tx_results.end());
694701
BOOST_CHECK(it_child->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
695702
BOOST_CHECK(it_child->second.m_base_fees.value() == COIN);
@@ -709,22 +716,28 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
709716
}
710717

711718
// Just because we allow low-fee parents doesn't mean we allow low-feerate packages.
712-
// This package just pays 200 satoshis total. This would be enough to pay for the child alone,
713-
// but isn't enough for the entire package to meet the 1sat/vbyte minimum.
719+
// The mempool minimum feerate is 5sat/vB, but this package just pays 800 satoshis total.
720+
// The child fees would be able to pay for itself, but isn't enough for the entire package.
714721
Package package_still_too_low;
722+
const CAmount parent_fee{200};
723+
const CAmount child_fee{600};
715724
auto mtx_parent_cheap = CreateValidMempoolTransaction(/*input_transaction=*/m_coinbase_txns[1], /*input_vout=*/0,
716725
/*input_height=*/0, /*input_signing_key=*/coinbaseKey,
717726
/*output_destination=*/parent_spk,
718-
/*output_amount=*/coinbase_value, /*submit=*/false);
727+
/*output_amount=*/coinbase_value - parent_fee, /*submit=*/false);
719728
CTransactionRef tx_parent_cheap = MakeTransactionRef(mtx_parent_cheap);
720729
package_still_too_low.push_back(tx_parent_cheap);
730+
BOOST_CHECK(m_node.mempool->GetMinFee().GetFee(GetVirtualTransactionSize(*tx_parent_cheap)) > parent_fee);
731+
BOOST_CHECK(m_node.mempool->m_min_relay_feerate.GetFee(GetVirtualTransactionSize(*tx_parent_cheap)) <= parent_fee);
721732

722733
auto mtx_child_cheap = CreateValidMempoolTransaction(/*input_transaction=*/tx_parent_cheap, /*input_vout=*/0,
723734
/*input_height=*/101, /*input_signing_key=*/child_key,
724735
/*output_destination=*/child_spk,
725-
/*output_amount=*/coinbase_value - 200, /*submit=*/false);
736+
/*output_amount=*/coinbase_value - parent_fee - child_fee, /*submit=*/false);
726737
CTransactionRef tx_child_cheap = MakeTransactionRef(mtx_child_cheap);
727738
package_still_too_low.push_back(tx_child_cheap);
739+
BOOST_CHECK(m_node.mempool->GetMinFee().GetFee(GetVirtualTransactionSize(*tx_child_cheap)) <= child_fee);
740+
BOOST_CHECK(m_node.mempool->GetMinFee().GetFee(GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap)) > parent_fee + child_fee);
728741

729742
// Cheap package should fail with package-fee-too-low.
730743
{
@@ -735,11 +748,6 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
735748
BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetResult(), PackageValidationResult::PCKG_POLICY);
736749
BOOST_CHECK_EQUAL(submit_package_too_low.m_state.GetRejectReason(), "package-fee-too-low");
737750
BOOST_CHECK_EQUAL(m_node.mempool->size(), expected_pool_size);
738-
const CFeeRate child_feerate(200, GetVirtualTransactionSize(*tx_child_cheap));
739-
BOOST_CHECK(child_feerate.GetFeePerK() > 1000);
740-
const CFeeRate expected_feerate(200,
741-
GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap));
742-
BOOST_CHECK(expected_feerate.GetFeePerK() < 1000);
743751
}
744752

745753
// Package feerate includes the modified fees of the transactions.
@@ -752,18 +760,18 @@ BOOST_FIXTURE_TEST_CASE(package_cpfp_tests, TestChain100Setup)
752760
expected_pool_size += 2;
753761
BOOST_CHECK_MESSAGE(submit_prioritised_package.m_state.IsValid(),
754762
"Package validation unexpectedly failed" << submit_prioritised_package.m_state.GetRejectReason());
755-
const CFeeRate expected_feerate(1 * COIN + 200,
763+
const CFeeRate expected_feerate(1 * COIN + parent_fee + child_fee,
756764
GetVirtualTransactionSize(*tx_parent_cheap) + GetVirtualTransactionSize(*tx_child_cheap));
757765
BOOST_CHECK_EQUAL(submit_prioritised_package.m_tx_results.size(), package_still_too_low.size());
758766
auto it_parent = submit_prioritised_package.m_tx_results.find(tx_parent_cheap->GetWitnessHash());
759767
auto it_child = submit_prioritised_package.m_tx_results.find(tx_child_cheap->GetWitnessHash());
760768
BOOST_CHECK(it_parent != submit_prioritised_package.m_tx_results.end());
761769
BOOST_CHECK(it_parent->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
762-
BOOST_CHECK(it_parent->second.m_base_fees.value() == 0);
770+
BOOST_CHECK(it_parent->second.m_base_fees.value() == parent_fee);
763771
BOOST_CHECK(it_parent->second.m_effective_feerate.value() == expected_feerate);
764772
BOOST_CHECK(it_child != submit_prioritised_package.m_tx_results.end());
765773
BOOST_CHECK(it_child->second.m_result_type == MempoolAcceptResult::ResultType::VALID);
766-
BOOST_CHECK(it_child->second.m_base_fees.value() == 200);
774+
BOOST_CHECK(it_child->second.m_base_fees.value() == child_fee);
767775
BOOST_CHECK(it_child->second.m_effective_feerate.value() == expected_feerate);
768776
std::vector<uint256> expected_wtxids({tx_parent_cheap->GetWitnessHash(), tx_child_cheap->GetWitnessHash()});
769777
BOOST_CHECK(it_parent->second.m_wtxids_fee_calculations.value() == expected_wtxids);

test/functional/mempool_limit.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,21 @@
77
from decimal import Decimal
88

99
from test_framework.blocktools import COINBASE_MATURITY
10+
from test_framework.p2p import P2PTxInvStore
1011
from test_framework.test_framework import BitcoinTestFramework
1112
from test_framework.util import (
1213
assert_equal,
14+
assert_fee_amount,
1315
assert_greater_than,
1416
assert_raises_rpc_error,
1517
create_lots_of_big_transactions,
1618
gen_return_txouts,
1719
)
18-
from test_framework.wallet import MiniWallet
20+
from test_framework.wallet import (
21+
COIN,
22+
DEFAULT_FEE,
23+
MiniWallet,
24+
)
1925

2026

2127
class MempoolLimitTest(BitcoinTestFramework):
@@ -44,7 +50,8 @@ def run_test(self):
4450
# 1 to create a tx initially that will be evicted from the mempool later
4551
# 3 batches of multiple transactions with a fee rate much higher than the previous UTXO
4652
# And 1 more to verify that this tx does not get added to the mempool with a fee rate less than the mempoolminfee
47-
self.generate(miniwallet, 1 + (num_of_batches * tx_batch_size) + 1)
53+
# And 2 more for the package cpfp test
54+
self.generate(miniwallet, 1 + (num_of_batches * tx_batch_size) + 1 + 2)
4855

4956
# Mine 99 blocks so that the UTXOs are allowed to be spent
5057
self.generate(node, COINBASE_MATURITY - 1)
@@ -76,6 +83,44 @@ def run_test(self):
7683
self.log.info('Create a mempool tx that will not pass mempoolminfee')
7784
assert_raises_rpc_error(-26, "mempool min fee not met", miniwallet.send_self_transfer, from_node=node, fee_rate=relayfee)
7885

86+
self.log.info("Check that submitpackage allows cpfp of a parent below mempool min feerate")
87+
node = self.nodes[0]
88+
peer = node.add_p2p_connection(P2PTxInvStore())
89+
90+
# Package with 2 parents and 1 child. One parent has a high feerate due to modified fees,
91+
# another is below the mempool minimum feerate but bumped by the child.
92+
tx_poor = miniwallet.create_self_transfer(fee_rate=relayfee)
93+
tx_rich = miniwallet.create_self_transfer(fee=0, fee_rate=0)
94+
node.prioritisetransaction(tx_rich["txid"], 0, int(DEFAULT_FEE * COIN))
95+
package_txns = [tx_rich, tx_poor]
96+
coins = [tx["new_utxo"] for tx in package_txns]
97+
tx_child = miniwallet.create_self_transfer_multi(utxos_to_spend=coins, fee_per_output=10000) #DEFAULT_FEE
98+
package_txns.append(tx_child)
99+
100+
submitpackage_result = node.submitpackage([tx["hex"] for tx in package_txns])
101+
102+
rich_parent_result = submitpackage_result["tx-results"][tx_rich["wtxid"]]
103+
poor_parent_result = submitpackage_result["tx-results"][tx_poor["wtxid"]]
104+
child_result = submitpackage_result["tx-results"][tx_child["tx"].getwtxid()]
105+
assert_fee_amount(poor_parent_result["fees"]["base"], tx_poor["tx"].get_vsize(), relayfee)
106+
assert_equal(rich_parent_result["fees"]["base"], 0)
107+
assert_equal(child_result["fees"]["base"], DEFAULT_FEE)
108+
# The "rich" parent does not require CPFP so its effective feerate is just its individual feerate.
109+
assert_fee_amount(DEFAULT_FEE, tx_rich["tx"].get_vsize(), rich_parent_result["fees"]["effective-feerate"])
110+
assert_equal(rich_parent_result["fees"]["effective-includes"], [tx_rich["wtxid"]])
111+
# The "poor" parent and child's effective feerates are the same, composed of their total
112+
# fees divided by their combined vsize.
113+
package_fees = poor_parent_result["fees"]["base"] + child_result["fees"]["base"]
114+
package_vsize = tx_poor["tx"].get_vsize() + tx_child["tx"].get_vsize()
115+
assert_fee_amount(package_fees, package_vsize, poor_parent_result["fees"]["effective-feerate"])
116+
assert_fee_amount(package_fees, package_vsize, child_result["fees"]["effective-feerate"])
117+
assert_equal([tx_poor["wtxid"], tx_child["tx"].getwtxid()], poor_parent_result["fees"]["effective-includes"])
118+
assert_equal([tx_poor["wtxid"], tx_child["tx"].getwtxid()], child_result["fees"]["effective-includes"])
119+
120+
# The node will broadcast each transaction, still abiding by its peer's fee filter
121+
peer.wait_for_broadcast([tx["tx"].getwtxid() for tx in package_txns])
122+
self.generate(node, 1)
123+
79124
self.log.info('Test passing a value below the minimum (5 MB) to -maxmempool throws an error')
80125
self.stop_node(0)
81126
self.nodes[0].assert_start_raises_init_error(["-maxmempool=4"], "Error: -maxmempool must be at least 5 MB")

test/functional/rpc_packages.py

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
assert_raises_rpc_error,
2121
)
2222
from test_framework.wallet import (
23-
COIN,
2423
DEFAULT_FEE,
2524
MiniWallet,
2625
)
@@ -325,42 +324,6 @@ def test_submit_child_with_parents(self, num_parents, partial_submit):
325324
peer.wait_for_broadcast([tx["tx"].getwtxid() for tx in package_txns])
326325
self.generate(node, 1)
327326

328-
def test_submit_cpfp(self):
329-
node = self.nodes[0]
330-
peer = node.add_p2p_connection(P2PTxInvStore())
331-
332-
# Package with 2 parents and 1 child. One parent pays for itself using modified fees, and
333-
# another has 0 fees but is bumped by child.
334-
tx_poor = self.wallet.create_self_transfer(fee=0, fee_rate=0)
335-
tx_rich = self.wallet.create_self_transfer(fee=0, fee_rate=0)
336-
node.prioritisetransaction(tx_rich["txid"], 0, int(DEFAULT_FEE * COIN))
337-
package_txns = [tx_rich, tx_poor]
338-
coins = [tx["new_utxo"] for tx in package_txns]
339-
tx_child = self.wallet.create_self_transfer_multi(utxos_to_spend=coins, fee_per_output=10000) #DEFAULT_FEE
340-
package_txns.append(tx_child)
341-
342-
submitpackage_result = node.submitpackage([tx["hex"] for tx in package_txns])
343-
344-
rich_parent_result = submitpackage_result["tx-results"][tx_rich["wtxid"]]
345-
poor_parent_result = submitpackage_result["tx-results"][tx_poor["wtxid"]]
346-
child_result = submitpackage_result["tx-results"][tx_child["tx"].getwtxid()]
347-
assert_equal(rich_parent_result["fees"]["base"], 0)
348-
assert_equal(poor_parent_result["fees"]["base"], 0)
349-
assert_equal(child_result["fees"]["base"], DEFAULT_FEE)
350-
# The "rich" parent does not require CPFP so its effective feerate.
351-
assert_fee_amount(DEFAULT_FEE, tx_rich["tx"].get_vsize(), rich_parent_result["fees"]["effective-feerate"])
352-
assert_equal(rich_parent_result["fees"]["effective-includes"], [tx_rich["wtxid"]])
353-
# The "poor" parent and child's effective feerates are the same, composed of the child's fee
354-
# divided by their combined vsize.
355-
assert_fee_amount(DEFAULT_FEE, tx_poor["tx"].get_vsize() + tx_child["tx"].get_vsize(), poor_parent_result["fees"]["effective-feerate"])
356-
assert_fee_amount(DEFAULT_FEE, tx_poor["tx"].get_vsize() + tx_child["tx"].get_vsize(), child_result["fees"]["effective-feerate"])
357-
assert_equal([tx_poor["wtxid"], tx_child["tx"].getwtxid()], poor_parent_result["fees"]["effective-includes"])
358-
assert_equal([tx_poor["wtxid"], tx_child["tx"].getwtxid()], child_result["fees"]["effective-includes"])
359-
360-
# The node will broadcast each transaction, still abiding by its peer's fee filter
361-
peer.wait_for_broadcast([tx["tx"].getwtxid() for tx in package_txns])
362-
self.generate(node, 1)
363-
364327
def test_submitpackage(self):
365328
node = self.nodes[0]
366329

@@ -369,9 +332,6 @@ def test_submitpackage(self):
369332
self.test_submit_child_with_parents(num_parents, False)
370333
self.test_submit_child_with_parents(num_parents, True)
371334

372-
self.log.info("Submitpackage valid packages with CPFP")
373-
self.test_submit_cpfp()
374-
375335
self.log.info("Submitpackage only allows packages of 1 child with its parents")
376336
# Chain of 3 transactions has too many generations
377337
chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=25)]

0 commit comments

Comments
 (0)