/ test / functional / test_framework / mempool_util.py
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