mempool_util.py raw

   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
 134