Skip to content

Commit 2a52718

Browse files
committed
Merge bitcoin#31152: functional test: Additional package evaluation coverage
f32c34d functional test: Additional package evaluation coverage (Greg Sanders) Pull request description: Current test coverage doesn't ensure that mempool trimming doesn't appear prior to the entire package, and not just the subpackage, is finished being submitted. Add a scenario that covers this case, where package ancestors can make it in individually, but would be immadiately evicted if not for the package CPFP. in response to bitcoin#31122 (comment) where if applied onto that PR's old commit, the test fails due to package failure. ACKs for top commit: sdaftuar: re-ACK f32c34d rkrux: tACK f32c34d glozow: reACK f32c34d Tree-SHA512: 739fcc5e66878b3def9b25dc588d8cb5349aaaa0901b11475879a413a03f6ea0e87d19de5bc4fb44ddd0436fdc052cdc3ed564f7e2ad510269aab9732d5c24eb
2 parents 25dacae + f32c34d commit 2a52718

File tree

1 file changed

+95
-0
lines changed

1 file changed

+95
-0
lines changed

test/functional/mempool_limit.py

+95
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,100 @@ def test_rbf_carveout_disallowed(self):
8585
assert_equal(res["package_msg"], "transaction failed")
8686
assert "too-long-mempool-chain" in res["tx-results"][tx_C["wtxid"]]["error"]
8787

88+
def test_mid_package_eviction_success(self):
89+
node = self.nodes[0]
90+
self.log.info("Check a package where each parent passes the current mempoolminfee but a parent could be evicted before getting child's descendant feerate")
91+
92+
# Clear mempool so it can be filled with minrelay txns
93+
self.restart_node(0, extra_args=self.extra_args[0] + ["-persistmempool=0"])
94+
assert_equal(node.getrawmempool(), [])
95+
96+
# Restarting the node resets mempool minimum feerate
97+
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
98+
assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
99+
100+
fill_mempool(self, node)
101+
current_info = node.getmempoolinfo()
102+
mempoolmin_feerate = current_info["mempoolminfee"]
103+
104+
mempool_txids = node.getrawmempool()
105+
mempool_entries = [node.getmempoolentry(entry) for entry in mempool_txids]
106+
fees_btc_per_kvb = [entry["fees"]["base"] / (Decimal(entry["vsize"]) / 1000) for entry in mempool_entries]
107+
mempool_entry_minrate = min(fees_btc_per_kvb)
108+
mempool_entry_minrate = mempool_entry_minrate.quantize(Decimal("0.00000000"))
109+
110+
# There is a gap, our parents will be minrate, with child bringing up descendant fee sufficiently to avoid
111+
# eviction even though parents cause eviction on their own
112+
assert_greater_than(mempool_entry_minrate, mempoolmin_feerate)
113+
114+
package_hex = []
115+
# UTXOs to be spent by the ultimate child transaction
116+
parent_utxos = []
117+
118+
# Series of parents that don't need CPFP and are submitted individually. Each one is large
119+
# which means in aggregate they could trigger eviction, but child submission should result
120+
# in them not being evicted
121+
parent_vsize = 25000
122+
num_big_parents = 3
123+
# Need to be large enough to trigger eviction
124+
# (note that the mempool usage of a tx is about three times its vsize)
125+
assert_greater_than(parent_vsize * num_big_parents * 3, current_info["maxmempool"] - current_info["bytes"])
126+
127+
big_parent_txids = []
128+
big_parent_wtxids = []
129+
for i in range(num_big_parents):
130+
# Last parent is higher feerate causing other parents to possibly
131+
# be evicted if trimming was allowed, which would cause the package to end up failing
132+
parent_feerate = mempoolmin_feerate + Decimal("0.00000001") if i == num_big_parents - 1 else mempoolmin_feerate
133+
parent = self.wallet.create_self_transfer(fee_rate=parent_feerate, target_vsize=parent_vsize, confirmed_only=True)
134+
parent_utxos.append(parent["new_utxo"])
135+
package_hex.append(parent["hex"])
136+
big_parent_txids.append(parent["txid"])
137+
big_parent_wtxids.append(parent["wtxid"])
138+
# There is room for each of these transactions independently
139+
assert node.testmempoolaccept([parent["hex"]])[0]["allowed"]
140+
141+
# Create a child spending everything with an insane fee, bumping the package above mempool_entry_minrate
142+
child = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=10000000)
143+
package_hex.append(child["hex"])
144+
145+
# Package should be submitted, temporarily exceeding maxmempool, but not evicted.
146+
package_res = None
147+
with node.assert_debug_log(expected_msgs=["rolling minimum fee bumped"]):
148+
package_res = node.submitpackage(package=package_hex, maxfeerate=0)
149+
150+
assert_equal(package_res["package_msg"], "success")
151+
152+
# Ensure that intra-package trimming is not happening.
153+
# Each transaction separately satisfies the current
154+
# minfee and shouldn't need package evaluation to
155+
# be included. If trimming of a parent were to happen,
156+
# package evaluation would happen to reintrodce the evicted
157+
# parent.
158+
assert_equal(len(package_res["tx-results"]), len(big_parent_wtxids) + 1)
159+
for wtxid in big_parent_wtxids + [child["wtxid"]]:
160+
assert_equal(len(package_res["tx-results"][wtxid]["fees"]["effective-includes"]), 1)
161+
162+
# Maximum size must never be exceeded.
163+
assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["bytes"])
164+
165+
# Package found in mempool still
166+
resulting_mempool_txids = node.getrawmempool()
167+
assert child["txid"] in resulting_mempool_txids
168+
for txid in big_parent_txids:
169+
assert txid in resulting_mempool_txids
170+
171+
# Check every evicted tx was higher feerate than parents which evicted it
172+
eviction_set = set(mempool_txids) - set(resulting_mempool_txids) - set(big_parent_txids)
173+
parent_entries = [node.getmempoolentry(entry) for entry in big_parent_txids]
174+
max_parent_feerate = max([entry["fees"]["modified"] / (Decimal(entry["vsize"]) / 1000) for entry in parent_entries])
175+
for eviction in eviction_set:
176+
assert eviction in mempool_txids
177+
for txid, entry in zip(mempool_txids, mempool_entries):
178+
if txid == eviction:
179+
evicted_feerate_btc_per_kvb = entry["fees"]["modified"] / (Decimal(entry["vsize"]) / 1000)
180+
assert_greater_than(evicted_feerate_btc_per_kvb, max_parent_feerate)
181+
88182
def test_mid_package_eviction(self):
89183
node = self.nodes[0]
90184
self.log.info("Check a package where each parent passes the current mempoolminfee but would cause eviction before package submission terminates")
@@ -339,6 +433,7 @@ def run_test(self):
339433
self.stop_node(0)
340434
self.nodes[0].assert_start_raises_init_error(["-maxmempool=4"], "Error: -maxmempool must be at least 5 MB")
341435

436+
self.test_mid_package_eviction_success()
342437
self.test_mid_package_replacement()
343438
self.test_mid_package_eviction()
344439
self.test_rbf_carveout_disallowed()

0 commit comments

Comments
 (0)