1 #!/usr/bin/env python3
2 # Copyright (c) 2014-2022 The Limenka developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """Test mempool limiting together/eviction with the wallet."""
6 7 from decimal import Decimal
8 import time
9 10 from test_framework.authproxy import JSONRPCException
11 from test_framework.mempool_util import (
12 fill_mempool,
13 )
14 from test_framework.p2p import P2PTxInvStore
15 from test_framework.test_framework import LimenkaTestFramework
16 from test_framework.util import (
17 assert_equal,
18 assert_fee_amount,
19 assert_greater_than,
20 assert_raises_rpc_error,
21 )
22 from test_framework.wallet import (
23 COIN,
24 DEFAULT_FEE,
25 MiniWallet,
26 )
27 28 29 class MempoolLimitTest(LimenkaTestFramework):
30 def set_test_params(self):
31 self.setup_clean_chain = True
32 self.num_nodes = 1
33 self.extra_args = [[
34 "-datacarriersize=100000",
35 "-maxmempool=5",
36 ]]
37 self.supports_cli = False
38 39 def test_rbf_carveout_disallowed(self):
40 node = self.nodes[0]
41 42 self.log.info("Check that individually-evaluated transactions in a package don't increase package limits for other subpackage parts")
43 44 # We set chain limits to 2 ancestors, 1 descendant, then try to get a parents-and-child chain of 2 in mempool
45 #
46 # A: Solo transaction to be RBF'd (to bump descendant limit for package later)
47 # B: First transaction in package, RBFs A by itself under individual evaluation, which would give it +1 descendant limit
48 # C: Second transaction in package, spends B. If the +1 descendant limit persisted, would make it into mempool
49 50 self.restart_node(0, extra_args=self.extra_args[0] + ["-limitancestorcount=2", "-limitdescendantcount=1"])
51 52 # Generate a confirmed utxo we will double-spend
53 rbf_utxo = self.wallet.send_self_transfer(
54 from_node=node,
55 confirmed_only=True
56 )["new_utxo"]
57 self.generate(node, 1)
58 59 # tx_A needs to be RBF'd, set minfee at set size
60 A_vsize = 250
61 mempoolmin_feerate = node.getmempoolinfo()["mempoolminfee"]
62 tx_A = self.wallet.send_self_transfer(
63 from_node=node,
64 fee_rate=mempoolmin_feerate,
65 target_vsize=A_vsize,
66 utxo_to_spend=rbf_utxo,
67 confirmed_only=True
68 )
69 70 # RBF's tx_A, is not yet submitted
71 tx_B = self.wallet.create_self_transfer(
72 fee=tx_A["fee"] * 4,
73 target_vsize=A_vsize,
74 utxo_to_spend=rbf_utxo,
75 confirmed_only=True
76 )
77 78 # Spends tx_B's output, too big for cpfp carveout (because that would also increase the descendant limit by 1)
79 non_cpfp_carveout_vsize = 10001 # EXTRA_DESCENDANT_TX_SIZE_LIMIT + 1
80 tx_C = self.wallet.create_self_transfer(
81 target_vsize=non_cpfp_carveout_vsize,
82 fee_rate=mempoolmin_feerate,
83 utxo_to_spend=tx_B["new_utxo"],
84 confirmed_only=True
85 )
86 res = node.submitpackage([tx_B["hex"], tx_C["hex"]])
87 assert_equal(res["package_msg"], "transaction failed")
88 assert "too-long-mempool-chain" in res["tx-results"][tx_C["wtxid"]]["error"]
89 90 def test_mid_package_eviction_success(self):
91 node = self.nodes[0]
92 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")
93 94 # Clear mempool so it can be filled with minrelay txns
95 self.restart_node(0, extra_args=self.extra_args[0] + ["-persistmempool=0"])
96 assert_equal(node.getrawmempool(), [])
97 98 # Restarting the node resets mempool minimum feerate
99 assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"])
100 101 fill_mempool(self, node)
102 current_info = node.getmempoolinfo()
103 mempoolmin_feerate = current_info["mempoolminfee"]
104 105 mempool_txids = node.getrawmempool()
106 mempool_entries = [node.getmempoolentry(entry) for entry in mempool_txids]
107 fees_btc_per_kvb = [entry["fees"]["base"] / (Decimal(entry["vsize"]) / 1000) for entry in mempool_entries]
108 mempool_entry_minrate = min(fees_btc_per_kvb)
109 mempool_entry_minrate = mempool_entry_minrate.quantize(Decimal("0.00000000"))
110 111 # There is a gap, our parents will be minrate, with child bringing up descendant fee sufficiently to avoid
112 # eviction even though parents cause eviction on their own
113 assert_greater_than(mempool_entry_minrate, mempoolmin_feerate)
114 115 package_hex = []
116 # UTXOs to be spent by the ultimate child transaction
117 parent_utxos = []
118 119 # Series of parents that don't need CPFP and are submitted individually. Each one is large
120 # which means in aggregate they could trigger eviction, but child submission should result
121 # in them not being evicted
122 parent_vsize = 25000
123 num_big_parents = 3
124 # Need to be large enough to trigger eviction
125 # (note that the mempool usage of a tx is about three times its vsize)
126 assert_greater_than(parent_vsize * num_big_parents * 3, current_info["maxmempool"] - current_info["usage"])
127 128 big_parent_txids = []
129 big_parent_wtxids = []
130 for i in range(num_big_parents):
131 # Last parent is higher feerate causing other parents to possibly
132 # be evicted if trimming was allowed, which would cause the package to end up failing
133 parent_feerate = mempoolmin_feerate + Decimal("0.00000001") if i == num_big_parents - 1 else mempoolmin_feerate
134 parent = self.wallet.create_self_transfer(fee_rate=parent_feerate, target_vsize=parent_vsize, confirmed_only=True)
135 parent_utxos.append(parent["new_utxo"])
136 package_hex.append(parent["hex"])
137 big_parent_txids.append(parent["txid"])
138 big_parent_wtxids.append(parent["wtxid"])
139 # There is room for each of these transactions independently
140 assert node.testmempoolaccept([parent["hex"]])[0]["allowed"]
141 142 # Create a child spending everything with an insane fee, bumping the package above mempool_entry_minrate
143 child = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=10000000)
144 package_hex.append(child["hex"])
145 146 # Package should be submitted, temporarily exceeding maxmempool, but not evicted.
147 package_res = None
148 with node.assert_debug_log(expected_msgs=["rolling minimum fee bumped"]):
149 package_res = node.submitpackage(package=package_hex, maxfeerate=0)
150 151 assert_equal(package_res["package_msg"], "success")
152 153 # Ensure that intra-package trimming is not happening.
154 # Each transaction separately satisfies the current
155 # minfee and shouldn't need package evaluation to
156 # be included. If trimming of a parent were to happen,
157 # package evaluation would happen to reintrodce the evicted
158 # parent.
159 assert_equal(len(package_res["tx-results"]), len(big_parent_wtxids) + 1)
160 for wtxid in big_parent_wtxids + [child["wtxid"]]:
161 assert_equal(len(package_res["tx-results"][wtxid]["fees"]["effective-includes"]), 1)
162 163 # Maximum size must never be exceeded.
164 assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["usage"])
165 166 # Package found in mempool still
167 resulting_mempool_txids = node.getrawmempool()
168 assert child["txid"] in resulting_mempool_txids
169 for txid in big_parent_txids:
170 assert txid in resulting_mempool_txids
171 172 # Check every evicted tx was higher feerate than parents which evicted it
173 eviction_set = set(mempool_txids) - set(resulting_mempool_txids) - set(big_parent_txids)
174 parent_entries = [node.getmempoolentry(entry) for entry in big_parent_txids]
175 max_parent_feerate = max([entry["fees"]["modified"] / (Decimal(entry["vsize"]) / 1000) for entry in parent_entries])
176 for eviction in eviction_set:
177 assert eviction in mempool_txids
178 for txid, entry in zip(mempool_txids, mempool_entries):
179 if txid == eviction:
180 evicted_feerate_btc_per_kvb = entry["fees"]["modified"] / (Decimal(entry["vsize"]) / 1000)
181 assert_greater_than(evicted_feerate_btc_per_kvb, max_parent_feerate)
182 183 def test_mid_package_eviction(self):
184 node = self.nodes[0]
185 self.log.info("Check a package where each parent passes the current mempoolminfee but would cause eviction before package submission terminates")
186 187 self.restart_node(0, extra_args=self.extra_args[0])
188 189 # Restarting the node resets mempool minimum feerate
190 assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00000100'))
191 assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00000100'))
192 193 fill_mempool(self, node)
194 current_info = node.getmempoolinfo()
195 mempoolmin_feerate = current_info["mempoolminfee"]
196 197 package_hex = []
198 # UTXOs to be spent by the ultimate child transaction
199 parent_utxos = []
200 201 evicted_vsize = 2000
202 # Mempool transaction which is evicted due to being at the "bottom" of the mempool when the
203 # mempool overflows and evicts by descendant score. It's important that the eviction doesn't
204 # happen in the middle of package evaluation, as it can invalidate the coins cache.
205 #
206 # NOTE: On 32-bit systems (i686), there's a race condition where concurrent transaction additions
207 # can cause the mempool to repeatedly exceed the limit, causing immediate eviction of low-fee
208 # transactions. We retry with exponential backoff to handle this scenario.
209 mempool_evicted_tx = None
210 max_retries = 20
211 for attempt in range(max_retries):
212 try:
213 # Brief backoff on retries to let concurrent operations settle
214 if attempt > 0:
215 backoff = min(0.05 * (2 ** (attempt - 1)), 2.0) # Exponential backoff, max 2 seconds
216 self.log.info(f"Retry attempt {attempt + 1}/{max_retries} after {backoff:.2f}s backoff...")
217 time.sleep(backoff)
218 # Rescan UTXOs to recover any that failed to be added
219 self.wallet.rescan_utxos()
220 # Update minimum feerate as it may have increased during retries
221 mempoolmin_feerate = node.getmempoolinfo()["mempoolminfee"]
222 223 mempool_evicted_tx = self.wallet.send_self_transfer(
224 from_node=node,
225 fee_rate=mempoolmin_feerate,
226 target_vsize=evicted_vsize,
227 confirmed_only=True
228 )
229 if attempt > 0:
230 self.log.info(f"Successfully added transaction on attempt {attempt + 1}")
231 break
232 except JSONRPCException as e:
233 if e.error['code'] == -26: # mempool full or min fee not met
234 if attempt < max_retries - 1:
235 continue
236 else:
237 self.log.error(f"Failed to add transaction after {max_retries} attempts due to race condition")
238 raise
239 240 assert mempool_evicted_tx is not None, "Failed to add transaction after retries"
241 # Already in mempool when package is submitted.
242 assert mempool_evicted_tx["txid"] in node.getrawmempool()
243 244 # This parent spends the above mempool transaction that exists when its inputs are first
245 # looked up, but disappears later. It is rejected for being too low fee (but eligible for
246 # reconsideration), and its inputs are cached. When the mempool transaction is evicted, its
247 # coin is no longer available, but the cache could still contains the tx.
248 cpfp_parent = self.wallet.create_self_transfer(
249 utxo_to_spend=mempool_evicted_tx["new_utxo"],
250 fee_rate=mempoolmin_feerate / 2,
251 confirmed_only=True)
252 package_hex.append(cpfp_parent["hex"])
253 parent_utxos.append(cpfp_parent["new_utxo"])
254 assert_equal(node.testmempoolaccept([cpfp_parent["hex"]])[0]["reject-reason"], "mempool min fee not met")
255 256 self.wallet.rescan_utxos()
257 258 # Series of parents that don't need CPFP and are submitted individually. Each one is large and
259 # high feerate, which means they should trigger eviction but not be evicted.
260 parent_vsize = 25000
261 num_big_parents = 3
262 # Need to be large enough to trigger eviction
263 # (note that the mempool usage of a tx is about three times its vsize)
264 assert_greater_than(parent_vsize * num_big_parents * 3, current_info["maxmempool"] - current_info["usage"])
265 parent_feerate = 10 * mempoolmin_feerate
266 267 big_parent_txids = []
268 for i in range(num_big_parents):
269 parent = self.wallet.create_self_transfer(fee_rate=parent_feerate, target_vsize=parent_vsize, confirmed_only=True)
270 parent_utxos.append(parent["new_utxo"])
271 package_hex.append(parent["hex"])
272 big_parent_txids.append(parent["txid"])
273 # There is room for each of these transactions independently
274 assert node.testmempoolaccept([parent["hex"]])[0]["allowed"]
275 276 # Create a child spending everything, bumping cpfp_parent just above mempool minimum
277 # feerate. It's important not to bump too much as otherwise mempool_evicted_tx would not be
278 # evicted, making this test much less meaningful.
279 approx_child_vsize = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos)["tx"].get_vsize()
280 cpfp_fee = (mempoolmin_feerate / 1000) * (cpfp_parent["tx"].get_vsize() + approx_child_vsize) - cpfp_parent["fee"]
281 # Specific number of satoshis to fit within a small window. The parent_cpfp + child package needs to be
282 # - When there is mid-package eviction, high enough feerate to meet the new mempoolminfee
283 # - When there is no mid-package eviction, low enough feerate to be evicted immediately after submission.
284 magic_satoshis = 120
285 cpfp_satoshis = int(cpfp_fee * COIN) + magic_satoshis
286 287 child = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=cpfp_satoshis)
288 package_hex.append(child["hex"])
289 290 # Package should be submitted, temporarily exceeding maxmempool, and then evicted.
291 with node.assert_debug_log(expected_msgs=["rolling minimum fee bumped"]):
292 assert_equal(node.submitpackage(package_hex)["package_msg"], "transaction failed")
293 294 # Maximum size must never be exceeded.
295 assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["usage"])
296 297 # Evicted transaction and its descendants must not be in mempool.
298 resulting_mempool_txids = node.getrawmempool()
299 assert mempool_evicted_tx["txid"] not in resulting_mempool_txids
300 assert cpfp_parent["txid"] not in resulting_mempool_txids
301 assert child["txid"] not in resulting_mempool_txids
302 for txid in big_parent_txids:
303 assert txid in resulting_mempool_txids
304 305 def test_mid_package_replacement(self):
306 node = self.nodes[0]
307 self.log.info("Check a package where an early tx depends on a later-replaced mempool tx")
308 309 self.restart_node(0, extra_args=self.extra_args[0])
310 311 # Restarting the node resets mempool minimum feerate
312 assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"])
313 314 fill_mempool(self, node)
315 current_info = node.getmempoolinfo()
316 mempoolmin_feerate = current_info["mempoolminfee"]
317 318 # Mempool transaction which is evicted due to being at the "bottom" of the mempool when the
319 # mempool overflows and evicts by descendant score. It's important that the eviction doesn't
320 # happen in the middle of package evaluation, as it can invalidate the coins cache.
321 double_spent_utxo = self.wallet.get_utxo(confirmed_only=True)
322 replaced_tx = self.wallet.send_self_transfer(
323 from_node=node,
324 utxo_to_spend=double_spent_utxo,
325 fee_rate=mempoolmin_feerate,
326 confirmed_only=True
327 )
328 # Already in mempool when package is submitted.
329 assert replaced_tx["txid"] in node.getrawmempool()
330 331 # This parent spends the above mempool transaction that exists when its inputs are first
332 # looked up, but disappears later. It is rejected for being too low fee (but eligible for
333 # reconsideration), and its inputs are cached. When the mempool transaction is evicted, its
334 # coin is no longer available, but the cache could still contain the tx.
335 cpfp_parent = self.wallet.create_self_transfer(
336 utxo_to_spend=replaced_tx["new_utxo"],
337 fee_rate=mempoolmin_feerate - Decimal('0.000001'),
338 confirmed_only=True)
339 340 self.wallet.rescan_utxos()
341 342 # Parent that replaces the parent of cpfp_parent.
343 replacement_tx = self.wallet.create_self_transfer(
344 utxo_to_spend=double_spent_utxo,
345 fee_rate=10*mempoolmin_feerate,
346 confirmed_only=True
347 )
348 parent_utxos = [cpfp_parent["new_utxo"], replacement_tx["new_utxo"]]
349 350 # Create a child spending everything, CPFPing the low-feerate parent.
351 approx_child_vsize = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos)["tx"].get_vsize()
352 cpfp_fee = (2 * mempoolmin_feerate / 1000) * (cpfp_parent["tx"].get_vsize() + approx_child_vsize) - cpfp_parent["fee"]
353 child = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=int(cpfp_fee * COIN))
354 # It's very important that the cpfp_parent is before replacement_tx so that its input (from
355 # replaced_tx) is first looked up *before* replacement_tx is submitted.
356 package_hex = [cpfp_parent["hex"], replacement_tx["hex"], child["hex"]]
357 358 # Package should be submitted, temporarily exceeding maxmempool, and then evicted.
359 res = node.submitpackage(package_hex)
360 assert_equal(res["package_msg"], "transaction failed")
361 assert len([tx_res for _, tx_res in res["tx-results"].items() if "error" in tx_res and tx_res["error"] == "bad-txns-inputs-missingorspent"])
362 363 # Maximum size must never be exceeded.
364 assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["usage"])
365 366 resulting_mempool_txids = node.getrawmempool()
367 # The replacement should be successful.
368 assert replacement_tx["txid"] in resulting_mempool_txids
369 # The replaced tx and all of its descendants must not be in mempool.
370 assert replaced_tx["txid"] not in resulting_mempool_txids
371 assert cpfp_parent["txid"] not in resulting_mempool_txids
372 assert child["txid"] not in resulting_mempool_txids
373 374 375 def run_test(self):
376 node = self.nodes[0]
377 self.wallet = MiniWallet(node)
378 miniwallet = self.wallet
379 380 # Generate coins needed to create transactions in the subtests (excluding coins used in fill_mempool).
381 self.generate(miniwallet, 20)
382 383 relayfee = node.getnetworkinfo()['relayfee']
384 self.log.info('Check that mempoolminfee is minrelaytxfee')
385 assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"])
386 387 fill_mempool(self, node)
388 389 # Deliberately try to create a tx with a fee less than the minimum mempool fee to assert that it does not get added to the mempool
390 self.log.info('Create a mempool tx that will not pass mempoolminfee')
391 assert_raises_rpc_error(-26, "mempool min fee not met", miniwallet.send_self_transfer, from_node=node, fee_rate=relayfee)
392 393 self.log.info("Check that submitpackage allows cpfp of a parent below mempool min feerate")
394 node = self.nodes[0]
395 peer = node.add_p2p_connection(P2PTxInvStore())
396 397 # Package with 2 parents and 1 child. One parent has a high feerate due to modified fees,
398 # another is below the mempool minimum feerate but bumped by the child.
399 tx_poor = miniwallet.create_self_transfer(fee_rate=relayfee)
400 tx_rich = miniwallet.create_self_transfer(fee=0, fee_rate=0)
401 node.prioritisetransaction(tx_rich["txid"], 0, int(DEFAULT_FEE * COIN))
402 package_txns = [tx_rich, tx_poor]
403 coins = [tx["new_utxo"] for tx in package_txns]
404 tx_child = miniwallet.create_self_transfer_multi(utxos_to_spend=coins, fee_per_output=10000) #DEFAULT_FEE
405 package_txns.append(tx_child)
406 407 submitpackage_result = node.submitpackage([tx["hex"] for tx in package_txns])
408 assert_equal(submitpackage_result["package_msg"], "success")
409 410 rich_parent_result = submitpackage_result["tx-results"][tx_rich["wtxid"]]
411 poor_parent_result = submitpackage_result["tx-results"][tx_poor["wtxid"]]
412 child_result = submitpackage_result["tx-results"][tx_child["tx"].getwtxid()]
413 assert_fee_amount(poor_parent_result["fees"]["base"], tx_poor["tx"].get_vsize(), relayfee)
414 assert_equal(rich_parent_result["fees"]["base"], 0)
415 assert_equal(child_result["fees"]["base"], DEFAULT_FEE)
416 # The "rich" parent does not require CPFP so its effective feerate is just its individual feerate.
417 assert_fee_amount(DEFAULT_FEE, tx_rich["tx"].get_vsize(), rich_parent_result["fees"]["effective-feerate"])
418 assert_equal(rich_parent_result["fees"]["effective-includes"], [tx_rich["wtxid"]])
419 # The "poor" parent and child's effective feerates are the same, composed of their total
420 # fees divided by their combined vsize.
421 package_fees = poor_parent_result["fees"]["base"] + child_result["fees"]["base"]
422 package_vsize = tx_poor["tx"].get_vsize() + tx_child["tx"].get_vsize()
423 assert_fee_amount(package_fees, package_vsize, poor_parent_result["fees"]["effective-feerate"])
424 assert_fee_amount(package_fees, package_vsize, child_result["fees"]["effective-feerate"])
425 assert_equal([tx_poor["wtxid"], tx_child["tx"].getwtxid()], poor_parent_result["fees"]["effective-includes"])
426 assert_equal([tx_poor["wtxid"], tx_child["tx"].getwtxid()], child_result["fees"]["effective-includes"])
427 428 # The node will broadcast each transaction, still abiding by its peer's fee filter
429 peer.wait_for_broadcast([tx["tx"].getwtxid() for tx in package_txns])
430 431 self.log.info("Check a package that passes mempoolminfee but is evicted immediately after submission")
432 mempoolmin_feerate = node.getmempoolinfo()["mempoolminfee"]
433 current_mempool = node.getrawmempool(verbose=False)
434 worst_feerate_btcvb = Decimal("21000000")
435 for txid in current_mempool:
436 entry = node.getmempoolentry(txid)
437 worst_feerate_btcvb = min(worst_feerate_btcvb, entry["fees"]["descendant"] / entry["descendantsize"])
438 # Needs to be large enough to trigger eviction
439 # (note that the mempool usage of a tx is about three times its vsize)
440 target_vsize_each = 50000
441 assert_greater_than(target_vsize_each * 2 * 3, node.getmempoolinfo()["maxmempool"] - node.getmempoolinfo()["usage"])
442 # Should be a true CPFP: parent's feerate is just below mempool min feerate
443 parent_feerate = mempoolmin_feerate - Decimal("0.0000001") # 0.01 sats/vbyte below min feerate
444 # Parent + child is above mempool minimum feerate
445 child_feerate = (worst_feerate_btcvb * 1000) - Decimal("0.0000001") # 0.01 sats/vbyte below worst feerate
446 # However, when eviction is triggered, these transactions should be at the bottom.
447 # This assertion assumes parent and child are the same size.
448 miniwallet.rescan_utxos()
449 tx_parent_just_below = miniwallet.create_self_transfer(fee_rate=parent_feerate, target_vsize=target_vsize_each)
450 tx_child_just_above = miniwallet.create_self_transfer(utxo_to_spend=tx_parent_just_below["new_utxo"], fee_rate=child_feerate, target_vsize=target_vsize_each)
451 # This package ranks below the lowest descendant package in the mempool
452 package_fee = tx_parent_just_below["fee"] + tx_child_just_above["fee"]
453 package_vsize = tx_parent_just_below["tx"].get_vsize() + tx_child_just_above["tx"].get_vsize()
454 assert_greater_than(worst_feerate_btcvb, package_fee / package_vsize)
455 assert_greater_than(mempoolmin_feerate, tx_parent_just_below["fee"] / (tx_parent_just_below["tx"].get_vsize()))
456 assert_greater_than(package_fee / package_vsize, mempoolmin_feerate / 1000)
457 res = node.submitpackage([tx_parent_just_below["hex"], tx_child_just_above["hex"]])
458 for wtxid in [tx_parent_just_below["wtxid"], tx_child_just_above["wtxid"]]:
459 assert_equal(res["tx-results"][wtxid]["error"], "mempool full")
460 461 self.log.info('Test passing a value below the minimum (5 MB) to -maxmempool throws an error')
462 self.stop_node(0)
463 self.nodes[0].assert_start_raises_init_error(["-maxmempool=4"], "Error: -maxmempool must be at least 5 MB")
464 465 self.test_mid_package_eviction_success()
466 self.test_mid_package_replacement()
467 self.test_mid_package_eviction()
468 self.test_rbf_carveout_disallowed()
469 470 471 if __name__ == '__main__':
472 MempoolLimitTest(__file__).main()
473