mempool_expiry.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2020-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  """Tests that a mempool transaction expires after a given timeout and that its
   6  children are removed as well.
   7  
   8  Both the default expiry timeout defined by DEFAULT_MEMPOOL_EXPIRY_HOURS and a user
   9  definable expiry timeout via the '-mempoolexpiry=<n>' command line argument
  10  (<n> is the timeout in hours) are tested.
  11  """
  12  
  13  from datetime import timedelta
  14  
  15  from test_framework.messages import (
  16      COIN,
  17      DEFAULT_MEMPOOL_EXPIRY_HOURS,
  18  )
  19  from test_framework.test_framework import BitcoinTestFramework
  20  from test_framework.util import (
  21      assert_equal,
  22      assert_raises_rpc_error,
  23  )
  24  from test_framework.wallet import MiniWallet
  25  
  26  CUSTOM_MEMPOOL_EXPIRY = 10  # hours
  27  
  28  
  29  class MempoolExpiryTest(BitcoinTestFramework):
  30      def set_test_params(self):
  31          self.num_nodes = 1
  32  
  33      def test_transaction_expiry(self, timeout):
  34          """Tests that a transaction expires after the expiry timeout and its
  35          children are removed as well."""
  36          node = self.nodes[0]
  37  
  38          # Send a parent transaction that will expire.
  39          parent = self.wallet.send_self_transfer(from_node=node)
  40          parent_txid = parent["txid"]
  41          parent_utxo = self.wallet.get_utxo(txid=parent_txid)
  42          independent_utxo = self.wallet.get_utxo()
  43  
  44          # Add prioritisation to this transaction to check that it persists after the expiry
  45          node.prioritisetransaction(parent_txid, 0, COIN)
  46          assert_equal(node.getprioritisedtransactions()[parent_txid], { "fee_delta" : COIN, "in_mempool" : True, "modified_fee": COIN + COIN * parent["fee"] })
  47  
  48          # Ensure the transactions we send to trigger the mempool check spend utxos that are independent of
  49          # the transactions being tested for expiration.
  50          trigger_utxo1 = self.wallet.get_utxo()
  51          trigger_utxo2 = self.wallet.get_utxo()
  52  
  53          # Set the mocktime to the arrival time of the parent transaction.
  54          entry_time = node.getmempoolentry(parent_txid)['time']
  55          node.setmocktime(entry_time)
  56  
  57          # Let half of the timeout elapse and broadcast the child transaction spending the parent transaction.
  58          half_expiry_time = entry_time + int(60 * 60 * timeout/2)
  59          node.setmocktime(half_expiry_time)
  60          child_txid = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=parent_utxo)['txid']
  61          assert_equal(parent_txid, node.getmempoolentry(child_txid)['depends'][0])
  62          self.log.info('Broadcast child transaction after {} hours.'.format(
  63              timedelta(seconds=(half_expiry_time-entry_time))))
  64  
  65          # Broadcast another (independent) transaction.
  66          independent_txid = self.wallet.send_self_transfer(from_node=node, utxo_to_spend=independent_utxo)['txid']
  67  
  68          # Let most of the timeout elapse and check that the parent tx is still
  69          # in the mempool.
  70          nearly_expiry_time = entry_time + 60 * 60 * timeout - 5
  71          node.setmocktime(nearly_expiry_time)
  72          # Broadcast a transaction as the expiry of transactions in the mempool is only checked
  73          # when a new transaction is added to the mempool.
  74          self.wallet.send_self_transfer(from_node=node, utxo_to_spend=trigger_utxo1)
  75          self.log.info('Test parent tx not expired after {} hours.'.format(
  76              timedelta(seconds=(nearly_expiry_time-entry_time))))
  77          assert_equal(entry_time, node.getmempoolentry(parent_txid)['time'])
  78  
  79          # Transaction should be evicted from the mempool after the expiry time
  80          # has passed.
  81          expiry_time = entry_time + 60 * 60 * timeout + 5
  82          node.setmocktime(expiry_time)
  83          # Again, broadcast a transaction so the expiry of transactions in the mempool is checked.
  84          self.wallet.send_self_transfer(from_node=node, utxo_to_spend=trigger_utxo2)
  85          self.log.info('Test parent tx expiry after {} hours.'.format(
  86              timedelta(seconds=(expiry_time-entry_time))))
  87          assert_raises_rpc_error(-5, 'Transaction not in mempool',
  88                                  node.getmempoolentry, parent_txid)
  89  
  90          # Prioritisation does not disappear when transaction expires
  91          assert_equal(node.getprioritisedtransactions()[parent_txid], { "fee_delta" : COIN, "in_mempool" : False})
  92  
  93          # The child transaction should be removed from the mempool as well.
  94          self.log.info('Test child tx is evicted as well.')
  95          assert_raises_rpc_error(-5, 'Transaction not in mempool',
  96                                  node.getmempoolentry, child_txid)
  97  
  98          # Check that the independent tx is still in the mempool.
  99          self.log.info('Test the independent tx not expired after {} hours.'.format(
 100              timedelta(seconds=(expiry_time-half_expiry_time))))
 101          assert_equal(half_expiry_time, node.getmempoolentry(independent_txid)['time'])
 102  
 103      def run_test(self):
 104          self.wallet = MiniWallet(self.nodes[0])
 105  
 106          self.log.info('Test default mempool expiry timeout of %d hours.' %
 107                        DEFAULT_MEMPOOL_EXPIRY_HOURS)
 108          self.test_transaction_expiry(DEFAULT_MEMPOOL_EXPIRY_HOURS)
 109  
 110          self.log.info('Test custom mempool expiry timeout of %d hours.' %
 111                        CUSTOM_MEMPOOL_EXPIRY)
 112          self.restart_node(0, ['-mempoolexpiry=%d' % CUSTOM_MEMPOOL_EXPIRY])
 113          self.test_transaction_expiry(CUSTOM_MEMPOOL_EXPIRY)
 114  
 115  
 116  if __name__ == '__main__':
 117      MempoolExpiryTest(__file__).main()
 118