mempool_util.py raw

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