mempool_limit.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2014-2022 The Bitcoin Core 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 9 from test_framework.mempool_util import ( 10 fill_mempool, 11 ) 12 from test_framework.p2p import P2PTxInvStore 13 from test_framework.test_framework import BitcoinTestFramework 14 from test_framework.util import ( 15 assert_equal, 16 assert_fee_amount, 17 assert_greater_than, 18 assert_raises_rpc_error, 19 ) 20 from test_framework.wallet import ( 21 COIN, 22 DEFAULT_FEE, 23 MiniWallet, 24 ) 25 26 27 class MempoolLimitTest(BitcoinTestFramework): 28 def set_test_params(self): 29 self.setup_clean_chain = True 30 self.num_nodes = 1 31 self.extra_args = [[ 32 "-maxmempool=5", 33 ]] 34 35 def test_mid_package_eviction_success(self): 36 node = self.nodes[0] 37 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") 38 39 # Clear mempool so it can be filled with minrelay txns 40 self.restart_node(0, extra_args=self.extra_args[0] + ["-persistmempool=0"]) 41 assert_equal(node.getrawmempool(), []) 42 43 # Restarting the node resets mempool minimum feerate 44 assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"]) 45 46 fill_mempool(self, node) 47 current_info = node.getmempoolinfo() 48 mempoolmin_feerate = current_info["mempoolminfee"] 49 50 mempool_txids = node.getrawmempool() 51 mempool_entries = [node.getmempoolentry(entry) for entry in mempool_txids] 52 fees_btc_per_kvb = [entry["fees"]["base"] / (Decimal(entry["vsize"]) / 1000) for entry in mempool_entries] 53 mempool_entry_minrate = min(fees_btc_per_kvb) 54 mempool_entry_minrate = mempool_entry_minrate.quantize(Decimal("0.00000000")) 55 56 # There is a gap, our parents will be minrate, with child bringing up descendant fee sufficiently to avoid 57 # eviction even though parents cause eviction on their own 58 assert_greater_than(mempool_entry_minrate, mempoolmin_feerate) 59 60 package_hex = [] 61 # UTXOs to be spent by the ultimate child transaction 62 parent_utxos = [] 63 64 # Series of parents that don't need CPFP and are submitted individually. Each one is large 65 # which means in aggregate they could trigger eviction, but child submission should result 66 # in them not being evicted 67 parent_vsize = 25000 68 num_big_parents = 3 69 # Need to be large enough to trigger eviction 70 # (note that the mempool usage of a tx is about three times its vsize) 71 assert_greater_than(parent_vsize * num_big_parents * 3, current_info["maxmempool"] - current_info["bytes"]) 72 73 big_parent_txids = [] 74 big_parent_wtxids = [] 75 for i in range(num_big_parents): 76 # Last parent is higher feerate causing other parents to possibly 77 # be evicted if trimming was allowed, which would cause the package to end up failing 78 parent_feerate = mempoolmin_feerate + Decimal("0.00000001") if i == num_big_parents - 1 else mempoolmin_feerate 79 parent = self.wallet.create_self_transfer(fee_rate=parent_feerate, target_vsize=parent_vsize, confirmed_only=True) 80 parent_utxos.append(parent["new_utxo"]) 81 package_hex.append(parent["hex"]) 82 big_parent_txids.append(parent["txid"]) 83 big_parent_wtxids.append(parent["wtxid"]) 84 # There is room for each of these transactions independently 85 assert node.testmempoolaccept([parent["hex"]])[0]["allowed"] 86 87 # Create a child spending everything with an insane fee, bumping the package above mempool_entry_minrate 88 child = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=10000000) 89 package_hex.append(child["hex"]) 90 91 # Package should be submitted, temporarily exceeding maxmempool, but not evicted. 92 package_res = None 93 with node.assert_debug_log(expected_msgs=["rolling minimum fee bumped"]): 94 package_res = node.submitpackage(package=package_hex, maxfeerate=0) 95 96 assert_equal(package_res["package_msg"], "success") 97 98 # Ensure that intra-package trimming is not happening. 99 # Each transaction separately satisfies the current 100 # minfee and shouldn't need package evaluation to 101 # be included. If trimming of a parent were to happen, 102 # package evaluation would happen to reintrodce the evicted 103 # parent. 104 assert_equal(len(package_res["tx-results"]), len(big_parent_wtxids) + 1) 105 for wtxid in big_parent_wtxids + [child["wtxid"]]: 106 assert_equal(len(package_res["tx-results"][wtxid]["fees"]["effective-includes"]), 1) 107 108 # Maximum size must never be exceeded. 109 assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["bytes"]) 110 111 # Package found in mempool still 112 resulting_mempool_txids = node.getrawmempool() 113 assert child["txid"] in resulting_mempool_txids 114 for txid in big_parent_txids: 115 assert txid in resulting_mempool_txids 116 117 # Check every evicted tx was higher feerate than parents which evicted it 118 eviction_set = set(mempool_txids) - set(resulting_mempool_txids) - set(big_parent_txids) 119 parent_entries = [node.getmempoolentry(entry) for entry in big_parent_txids] 120 max_parent_feerate = max([entry["fees"]["modified"] / (Decimal(entry["vsize"]) / 1000) for entry in parent_entries]) 121 for eviction in eviction_set: 122 assert eviction in mempool_txids 123 for txid, entry in zip(mempool_txids, mempool_entries): 124 if txid == eviction: 125 evicted_feerate_btc_per_kvb = entry["fees"]["modified"] / (Decimal(entry["vsize"]) / 1000) 126 assert_greater_than(evicted_feerate_btc_per_kvb, max_parent_feerate) 127 128 def test_mid_package_replacement(self): 129 node = self.nodes[0] 130 self.log.info("Check a package where an early tx depends on a later-replaced mempool tx") 131 132 self.restart_node(0, extra_args=self.extra_args[0]) 133 134 # Restarting the node resets mempool minimum feerate 135 assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"]) 136 137 fill_mempool(self, node) 138 current_info = node.getmempoolinfo() 139 mempoolmin_feerate = current_info["mempoolminfee"] 140 141 # Mempool transaction is replaced by a package transaction. 142 double_spent_utxo = self.wallet.get_utxo(confirmed_only=True) 143 replaced_tx = self.wallet.send_self_transfer( 144 from_node=node, 145 utxo_to_spend=double_spent_utxo, 146 fee_rate=mempoolmin_feerate, 147 confirmed_only=True 148 ) 149 # Already in mempool when package is submitted. 150 assert replaced_tx["txid"] in node.getrawmempool() 151 152 # This parent spends the above mempool transaction that exists when its inputs are first 153 # looked up, but will disappear if the replacement occurs. It is rejected for being too low fee (but eligible for 154 # reconsideration), and its inputs are cached. When the mempool transaction is replaced, its 155 # coin is no longer available, but the cache could still contain the tx. 156 cpfp_parent = self.wallet.create_self_transfer( 157 utxo_to_spend=replaced_tx["new_utxo"], 158 fee_rate=mempoolmin_feerate - Decimal('0.000001'), 159 confirmed_only=True) 160 161 self.wallet.rescan_utxos() 162 163 # Parent that replaces the parent of cpfp_parent. 164 replacement_tx = self.wallet.create_self_transfer( 165 utxo_to_spend=double_spent_utxo, 166 fee_rate=10*mempoolmin_feerate, 167 confirmed_only=True 168 ) 169 parent_utxos = [cpfp_parent["new_utxo"], replacement_tx["new_utxo"]] 170 171 # Create a child spending everything, CPFPing the low-feerate parent. 172 approx_child_vsize = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos)["tx"].get_vsize() 173 cpfp_fee = (2 * mempoolmin_feerate / 1000) * (cpfp_parent["tx"].get_vsize() + approx_child_vsize) - cpfp_parent["fee"] 174 child = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=int(cpfp_fee * COIN)) 175 # It's very important that the cpfp_parent is before replacement_tx so that its input (from 176 # replaced_tx) is first looked up *before* replacement_tx is submitted. 177 package_hex = [cpfp_parent["hex"], replacement_tx["hex"], child["hex"]] 178 179 # Package should be submitted, temporarily exceeding maxmempool, and then evicted. 180 res = node.submitpackage(package_hex) 181 assert_equal(res["package_msg"], "transaction failed") 182 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"]) 183 184 # Maximum size must never be exceeded. 185 assert_greater_than(node.getmempoolinfo()["maxmempool"], node.getmempoolinfo()["bytes"]) 186 187 resulting_mempool_txids = node.getrawmempool() 188 # The replacement should be successful. 189 assert replacement_tx["txid"] in resulting_mempool_txids 190 # The replaced tx and all of its descendants must not be in mempool. 191 assert replaced_tx["txid"] not in resulting_mempool_txids 192 assert cpfp_parent["txid"] not in resulting_mempool_txids 193 assert child["txid"] not in resulting_mempool_txids 194 195 196 def run_test(self): 197 node = self.nodes[0] 198 self.wallet = MiniWallet(node) 199 miniwallet = self.wallet 200 201 # Generate coins needed to create transactions in the subtests (excluding coins used in fill_mempool). 202 self.generate(miniwallet, 20) 203 204 relayfee = node.getnetworkinfo()['relayfee'] 205 self.log.info('Check that mempoolminfee is minrelaytxfee') 206 assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"]) 207 208 fill_mempool(self, node) 209 210 # 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 211 self.log.info('Create a mempool tx that will not pass mempoolminfee') 212 assert_raises_rpc_error(-26, "mempool min fee not met", miniwallet.send_self_transfer, from_node=node, fee_rate=relayfee) 213 214 self.log.info("Check that submitpackage allows cpfp of a parent below mempool min feerate") 215 node = self.nodes[0] 216 peer = node.add_p2p_connection(P2PTxInvStore()) 217 218 # Package with 2 parents and 1 child. One parent has a high feerate due to modified fees, 219 # another is below the mempool minimum feerate but bumped by the child. 220 tx_poor = miniwallet.create_self_transfer(fee_rate=relayfee) 221 tx_rich = miniwallet.create_self_transfer(fee=0, fee_rate=0) 222 node.prioritisetransaction(tx_rich["txid"], 0, int(DEFAULT_FEE * COIN)) 223 package_txns = [tx_rich, tx_poor] 224 coins = [tx["new_utxo"] for tx in package_txns] 225 tx_child = miniwallet.create_self_transfer_multi(utxos_to_spend=coins, fee_per_output=10000) #DEFAULT_FEE 226 package_txns.append(tx_child) 227 228 submitpackage_result = node.submitpackage([tx["hex"] for tx in package_txns]) 229 assert_equal(submitpackage_result["package_msg"], "success") 230 231 rich_parent_result = submitpackage_result["tx-results"][tx_rich["wtxid"]] 232 poor_parent_result = submitpackage_result["tx-results"][tx_poor["wtxid"]] 233 child_result = submitpackage_result["tx-results"][tx_child["tx"].wtxid_hex] 234 assert_fee_amount(poor_parent_result["fees"]["base"], tx_poor["tx"].get_vsize(), relayfee) 235 assert_equal(rich_parent_result["fees"]["base"], 0) 236 assert_equal(child_result["fees"]["base"], DEFAULT_FEE) 237 # The "rich" parent does not require CPFP so its effective feerate is just its individual feerate. 238 assert_fee_amount(DEFAULT_FEE, tx_rich["tx"].get_vsize(), rich_parent_result["fees"]["effective-feerate"]) 239 assert_equal(rich_parent_result["fees"]["effective-includes"], [tx_rich["wtxid"]]) 240 # The "poor" parent and child's effective feerates are the same, composed of their total 241 # fees divided by their combined vsize. 242 package_fees = poor_parent_result["fees"]["base"] + child_result["fees"]["base"] 243 package_vsize = tx_poor["tx"].get_vsize() + tx_child["tx"].get_vsize() 244 assert_fee_amount(package_fees, package_vsize, poor_parent_result["fees"]["effective-feerate"]) 245 assert_fee_amount(package_fees, package_vsize, child_result["fees"]["effective-feerate"]) 246 assert_equal([tx_poor["wtxid"], tx_child["tx"].wtxid_hex], poor_parent_result["fees"]["effective-includes"]) 247 assert_equal([tx_poor["wtxid"], tx_child["tx"].wtxid_hex], child_result["fees"]["effective-includes"]) 248 249 # The node will broadcast each transaction, still abiding by its peer's fee filter 250 peer.wait_for_broadcast([tx["tx"].wtxid_hex for tx in package_txns]) 251 252 self.log.info("Check a package that passes mempoolminfee but is evicted immediately after submission") 253 mempoolmin_feerate = node.getmempoolinfo()["mempoolminfee"] 254 current_mempool = node.getrawmempool(verbose=False) 255 worst_feerate_btcvb = Decimal("21000000") 256 for txid in current_mempool: 257 entry = node.getmempoolentry(txid) 258 worst_feerate_btcvb = min(worst_feerate_btcvb, entry["fees"]["descendant"] / entry["descendantsize"]) 259 # Needs to be large enough to trigger eviction 260 # (note that the mempool usage of a tx is about three times its vsize) 261 target_vsize_each = 50000 262 assert_greater_than(target_vsize_each * 2 * 3, node.getmempoolinfo()["maxmempool"] - node.getmempoolinfo()["bytes"]) 263 # Should be a true CPFP: parent's feerate is just below mempool min feerate 264 parent_feerate = mempoolmin_feerate - Decimal("0.0000001") # 0.01 sats/vbyte below min feerate 265 # Parent + child is above mempool minimum feerate 266 child_feerate = (worst_feerate_btcvb * 1000) - Decimal("0.0000001") # 0.01 sats/vbyte below worst feerate 267 # However, when eviction is triggered, these transactions should be at the bottom. 268 # This assertion assumes parent and child are the same size. 269 miniwallet.rescan_utxos() 270 tx_parent_just_below = miniwallet.create_self_transfer(fee_rate=parent_feerate, target_vsize=target_vsize_each) 271 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) 272 # This package ranks below the lowest descendant package in the mempool 273 package_fee = tx_parent_just_below["fee"] + tx_child_just_above["fee"] 274 package_vsize = tx_parent_just_below["tx"].get_vsize() + tx_child_just_above["tx"].get_vsize() 275 assert_greater_than(worst_feerate_btcvb, package_fee / package_vsize) 276 assert_greater_than(mempoolmin_feerate, tx_parent_just_below["fee"] / (tx_parent_just_below["tx"].get_vsize())) 277 assert_greater_than(package_fee / package_vsize, mempoolmin_feerate / 1000) 278 res = node.submitpackage([tx_parent_just_below["hex"], tx_child_just_above["hex"]]) 279 for wtxid in [tx_parent_just_below["wtxid"], tx_child_just_above["wtxid"]]: 280 assert_equal(res["tx-results"][wtxid]["error"], "mempool full") 281 282 self.log.info('Test passing a value below the minimum (5 MB) to -maxmempool throws an error') 283 self.stop_node(0) 284 self.nodes[0].assert_start_raises_init_error(["-maxmempool=4"], "Error: -maxmempool must be at least 5 MB") 285 286 self.test_mid_package_eviction_success() 287 self.test_mid_package_replacement() 288 289 290 if __name__ == '__main__': 291 MempoolLimitTest(__file__).main()