mempool_util.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2024-present 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 """Helpful routines for mempool testing.""" 6 import random 7 8 from .blocktools import ( 9 COINBASE_MATURITY, 10 ) 11 from .messages import ( 12 COutPoint, 13 CTransaction, 14 CTxIn, 15 CTxInWitness, 16 CTxOut, 17 ) 18 from .script import ( 19 CScript, 20 OP_RETURN, 21 ) 22 from .util import ( 23 assert_equal, 24 assert_greater_than, 25 create_lots_of_big_transactions, 26 gen_return_txouts, 27 ) 28 from .wallet import ( 29 MiniWallet, 30 ) 31 32 # Default for -minrelaytxfee in sat/kvB 33 DEFAULT_MIN_RELAY_TX_FEE = 100 34 # Default for -incrementalrelayfee in sat/kvB 35 DEFAULT_INCREMENTAL_RELAY_FEE = 100 36 DEFAULT_CLUSTER_LIMIT = 64 37 DEFAULT_CLUSTER_SIZE_LIMIT_KVB = 101 38 39 TRUC_MAX_VSIZE = 10000 40 TRUC_CHILD_MAX_VSIZE = 1000 41 42 def assert_mempool_contents(test_framework, node, expected=None, sync=True): 43 """Assert that all transactions in expected are in the mempool, 44 and no additional ones exist. 'expected' is an array of 45 CTransaction objects 46 """ 47 if sync: 48 test_framework.sync_mempools() 49 if not expected: 50 expected = [] 51 assert_equal(len(expected), len(set(expected))) 52 mempool = node.getrawmempool(verbose=False) 53 assert_equal(len(mempool), len(expected)) 54 for tx in expected: 55 assert tx.txid_hex in mempool 56 57 58 def fill_mempool(test_framework, node, *, tx_sync_fun=None): 59 """Fill mempool until eviction. 60 61 Allows for simpler testing of scenarios with floating mempoolminfee > minrelay 62 Requires -maxmempool=5. 63 To avoid unintentional tx dependencies, the mempool filling txs are created with a 64 tagged ephemeral miniwallet instance. 65 """ 66 test_framework.log.info("Fill the mempool until eviction is triggered and the mempoolminfee rises") 67 txouts = gen_return_txouts() 68 minrelayfee = node.getnetworkinfo()['relayfee'] 69 70 tx_batch_size = 1 71 num_of_batches = 75 72 # Generate UTXOs to flood the mempool 73 # 1 to create a tx initially that will be evicted from the mempool later 74 # 75 transactions each with a fee rate higher than the previous one 75 ephemeral_miniwallet = MiniWallet(node, tag_name="fill_mempool_ephemeral_wallet") 76 test_framework.generate(ephemeral_miniwallet, 1 + num_of_batches * tx_batch_size) 77 78 # Mine enough blocks so that the UTXOs are allowed to be spent 79 test_framework.generate(node, COINBASE_MATURITY - 1) 80 81 # Get all UTXOs up front to ensure none of the transactions spend from each other, as that may 82 # change their effective feerate and thus the order in which they are selected for eviction. 83 confirmed_utxos = [ephemeral_miniwallet.get_utxo(confirmed_only=True) for _ in range(num_of_batches * tx_batch_size + 1)] 84 assert_equal(len(confirmed_utxos), num_of_batches * tx_batch_size + 1) 85 86 test_framework.log.debug("Create a mempool tx that will be evicted") 87 tx_to_be_evicted_id = ephemeral_miniwallet.send_self_transfer( 88 from_node=node, utxo_to_spend=confirmed_utxos.pop(0), fee_rate=minrelayfee)["txid"] 89 90 def send_batch(fee): 91 utxos = confirmed_utxos[:tx_batch_size] 92 create_lots_of_big_transactions(ephemeral_miniwallet, node, fee, tx_batch_size, txouts, utxos) 93 del confirmed_utxos[:tx_batch_size] 94 95 # Increase the tx fee rate to give the subsequent transactions a higher priority in the mempool 96 # The tx has an approx. vsize of 65k, i.e. multiplying the previous fee rate (in sats/kvB) 97 # by 130 should result in a fee that corresponds to 2x of that fee rate 98 base_fee = minrelayfee * 130 99 batch_fees = [(i + 1) * base_fee for i in range(num_of_batches)] 100 101 test_framework.log.debug("Fill up the mempool with txs with higher fee rate") 102 for fee in batch_fees[:-3]: 103 send_batch(fee) 104 tx_sync_fun() if tx_sync_fun else test_framework.sync_mempools() # sync before any eviction 105 assert_equal(node.getmempoolinfo()["mempoolminfee"], minrelayfee) 106 for fee in batch_fees[-3:]: 107 send_batch(fee) 108 tx_sync_fun() if tx_sync_fun else test_framework.sync_mempools() # sync after all evictions 109 110 test_framework.log.debug("The tx should be evicted by now") 111 # The number of transactions created should be greater than the ones present in the mempool 112 assert_greater_than(tx_batch_size * num_of_batches, len(node.getrawmempool())) 113 # Initial tx created should not be present in the mempool anymore as it had a lower fee rate 114 assert tx_to_be_evicted_id not in node.getrawmempool() 115 116 test_framework.log.debug("Check that mempoolminfee is larger than minrelaytxfee") 117 assert_equal(node.getmempoolinfo()['minrelaytxfee'], minrelayfee) 118 assert_greater_than(node.getmempoolinfo()['mempoolminfee'], minrelayfee) 119 120 def tx_in_orphanage(node, tx: CTransaction) -> bool: 121 """Returns true if the transaction is in the orphanage.""" 122 found = [o for o in node.getorphantxs(verbosity=1) if o["txid"] == tx.txid_hex and o["wtxid"] == tx.wtxid_hex] 123 return len(found) == 1 124 125 def create_large_orphan(): 126 """Create huge orphan transaction""" 127 tx = CTransaction() 128 # Nonexistent UTXO 129 tx.vin = [CTxIn(COutPoint(random.randrange(1 << 256), random.randrange(1, 100)))] 130 tx.wit.vtxinwit = [CTxInWitness()] 131 tx.wit.vtxinwit[0].scriptWitness.stack = [CScript(b'X' * 390000)] 132 tx.vout = [CTxOut(100, CScript([OP_RETURN, b'a' * 20]))] 133 return tx