mempool_packages.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2014-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  """Test descendant package tracking code."""
   6  
   7  from decimal import Decimal
   8  
   9  from test_framework.messages import (
  10      DEFAULT_CLUSTER_LIMIT,
  11  )
  12  from test_framework.p2p import P2PTxInvStore
  13  from test_framework.test_framework import BitcoinTestFramework
  14  from test_framework.util import (
  15      assert_equal,
  16  )
  17  from test_framework.wallet import MiniWallet
  18  from test_framework.blocktools import create_empty_fork
  19  
  20  # custom limits for node1
  21  CUSTOM_CLUSTER_LIMIT = 10
  22  assert CUSTOM_CLUSTER_LIMIT < DEFAULT_CLUSTER_LIMIT
  23  
  24  class MempoolPackagesTest(BitcoinTestFramework):
  25      def set_test_params(self):
  26          self.num_nodes = 2
  27          # whitelist peers to speed up tx relay / mempool sync
  28          self.noban_tx_relay = True
  29          self.extra_args = [
  30              [
  31              ],
  32              [
  33                  "-limitclustercount={}".format(CUSTOM_CLUSTER_LIMIT),
  34              ],
  35          ]
  36  
  37      def trigger_reorg(self, fork_blocks, node):
  38          """Trigger reorg of the fork blocks."""
  39          for block in fork_blocks:
  40              node.submitblock(block.serialize().hex())
  41          assert_equal(node.getbestblockhash(), fork_blocks[-1].hash_hex)
  42  
  43      def run_test(self):
  44          self.wallet = MiniWallet(self.nodes[0])
  45          self.wallet.rescan_utxos()
  46  
  47          peer_inv_store = self.nodes[0].add_p2p_connection(P2PTxInvStore()) # keep track of invs
  48  
  49          # DEFAULT_CLUSTER_LIMIT transactions off a confirmed tx should be fine for default node
  50          chain = self.wallet.create_self_transfer_chain(chain_length=DEFAULT_CLUSTER_LIMIT)
  51          witness_chain = [t["wtxid"] for t in chain]
  52          ancestor_vsize = 0
  53          ancestor_fees = Decimal(0)
  54  
  55          for i, t in enumerate(chain):
  56              ancestor_vsize += t["tx"].get_vsize()
  57              ancestor_fees += t["fee"]
  58              self.wallet.sendrawtransaction(from_node=self.nodes[0], tx_hex=t["hex"])
  59  
  60          # Wait until mempool transactions have passed initial broadcast (sent inv and received getdata)
  61          # Otherwise, getrawmempool may be inconsistent with getmempoolentry if unbroadcast changes in between
  62          peer_inv_store.wait_for_broadcast(witness_chain)
  63  
  64          # Check mempool has DEFAULT_CLUSTER_LIMIT transactions in it, and descendant and ancestor
  65          # count and fees should look correct
  66          mempool = self.nodes[0].getrawmempool(True)
  67          assert_equal(len(mempool), DEFAULT_CLUSTER_LIMIT)
  68          descendant_count = 1
  69          descendant_fees = 0
  70          descendant_vsize = 0
  71  
  72          assert_equal(ancestor_vsize, sum([mempool[tx]['vsize'] for tx in mempool]))
  73          ancestor_count = DEFAULT_CLUSTER_LIMIT
  74          assert_equal(ancestor_fees, sum([mempool[tx]['fees']['base'] for tx in mempool]))
  75  
  76          descendants = []
  77          ancestors = [t["txid"] for t in chain]
  78          chain = [t["txid"] for t in chain]
  79          for x in reversed(chain):
  80              # Check that getmempoolentry is consistent with getrawmempool
  81              entry = self.nodes[0].getmempoolentry(x)
  82              assert_equal(entry, mempool[x])
  83  
  84              # Check that gettxspendingprevout is consistent with getrawmempool
  85              witnesstx = self.nodes[0].getrawtransaction(txid=x, verbose=True)
  86              for tx_in in witnesstx["vin"]:
  87                  spending_result = self.nodes[0].gettxspendingprevout([ {'txid' : tx_in["txid"], 'vout' : tx_in["vout"]} ])
  88                  assert_equal(spending_result, [ {'txid' : tx_in["txid"], 'vout' : tx_in["vout"], 'spendingtxid' : x} ])
  89  
  90              # Check that the descendant calculations are correct
  91              assert_equal(entry['descendantcount'], descendant_count)
  92              descendant_fees += entry['fees']['base']
  93              assert_equal(entry['fees']['modified'], entry['fees']['base'])
  94              assert_equal(entry['fees']['descendant'], descendant_fees)
  95              descendant_vsize += entry['vsize']
  96              assert_equal(entry['descendantsize'], descendant_vsize)
  97              descendant_count += 1
  98  
  99              # Check that ancestor calculations are correct
 100              assert_equal(entry['ancestorcount'], ancestor_count)
 101              assert_equal(entry['fees']['ancestor'], ancestor_fees)
 102              assert_equal(entry['ancestorsize'], ancestor_vsize)
 103              ancestor_vsize -= entry['vsize']
 104              ancestor_fees -= entry['fees']['base']
 105              ancestor_count -= 1
 106  
 107              # Check that parent/child list is correct
 108              assert_equal(entry['spentby'], descendants[-1:])
 109              assert_equal(entry['depends'], ancestors[-2:-1])
 110  
 111              # Check that getmempooldescendants is correct
 112              assert_equal(sorted(descendants), sorted(self.nodes[0].getmempooldescendants(x)))
 113  
 114              # Check getmempooldescendants verbose output is correct
 115              for descendant, dinfo in self.nodes[0].getmempooldescendants(x, True).items():
 116                  assert_equal(dinfo['depends'], [chain[chain.index(descendant)-1]])
 117                  if dinfo['descendantcount'] > 1:
 118                      assert_equal(dinfo['spentby'], [chain[chain.index(descendant)+1]])
 119                  else:
 120                      assert_equal(dinfo['spentby'], [])
 121              descendants.append(x)
 122  
 123              # Check that getmempoolancestors is correct
 124              ancestors.remove(x)
 125              assert_equal(sorted(ancestors), sorted(self.nodes[0].getmempoolancestors(x)))
 126  
 127              # Check that getmempoolancestors verbose output is correct
 128              for ancestor, ainfo in self.nodes[0].getmempoolancestors(x, True).items():
 129                  assert_equal(ainfo['spentby'], [chain[chain.index(ancestor)+1]])
 130                  if ainfo['ancestorcount'] > 1:
 131                      assert_equal(ainfo['depends'], [chain[chain.index(ancestor)-1]])
 132                  else:
 133                      assert_equal(ainfo['depends'], [])
 134  
 135  
 136          # Check that getmempoolancestors/getmempooldescendants correctly handle verbose=true
 137          v_ancestors = self.nodes[0].getmempoolancestors(chain[-1], True)
 138          assert_equal(len(v_ancestors), len(chain)-1)
 139          for x in v_ancestors.keys():
 140              assert_equal(mempool[x], v_ancestors[x])
 141          assert chain[-1] not in v_ancestors.keys()
 142  
 143          v_descendants = self.nodes[0].getmempooldescendants(chain[0], True)
 144          assert_equal(len(v_descendants), len(chain)-1)
 145          for x in v_descendants.keys():
 146              assert_equal(mempool[x], v_descendants[x])
 147          assert chain[0] not in v_descendants.keys()
 148  
 149          # Check that ancestor modified fees includes fee deltas from
 150          # prioritisetransaction
 151          self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=1000)
 152          ancestor_fees = 0
 153          for x in chain:
 154              entry = self.nodes[0].getmempoolentry(x)
 155              ancestor_fees += entry['fees']['base']
 156              assert_equal(entry['fees']['ancestor'], ancestor_fees + Decimal('0.00001'))
 157  
 158          # Undo the prioritisetransaction for later tests
 159          self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=-1000)
 160  
 161          # Check that descendant modified fees includes fee deltas from
 162          # prioritisetransaction
 163          self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=1000)
 164  
 165          descendant_fees = 0
 166          for x in reversed(chain):
 167              entry = self.nodes[0].getmempoolentry(x)
 168              descendant_fees += entry['fees']['base']
 169              assert_equal(entry['fees']['descendant'], descendant_fees + Decimal('0.00001'))
 170  
 171          # Check that prioritising a tx before it's added to the mempool works
 172          # First clear the mempool by mining a block.
 173          self.generate(self.nodes[0], 1)
 174          assert_equal(len(self.nodes[0].getrawmempool()), 0)
 175          # Prioritise a transaction that has been mined, then add it back to the
 176          # mempool by using invalidateblock.
 177          self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=2000)
 178          self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
 179          # Keep node1's tip synced with node0
 180          self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash())
 181  
 182          # Now check that the transaction is in the mempool, with the right modified fee
 183          descendant_fees = 0
 184          for x in reversed(chain):
 185              entry = self.nodes[0].getmempoolentry(x)
 186              descendant_fees += entry['fees']['base']
 187              if (x == chain[-1]):
 188                  assert_equal(entry['fees']['modified'], entry['fees']['base'] + Decimal("0.00002"))
 189              assert_equal(entry['fees']['descendant'], descendant_fees + Decimal("0.00002"))
 190  
 191          # Now test descendant chain limits
 192          tx_children = []
 193          # First create one parent tx with 10 children
 194          tx_with_children = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=10)
 195          parent_transaction = tx_with_children["txid"]
 196          transaction_package = tx_with_children["new_utxos"]
 197  
 198          # Sign and send up to MAX_DESCENDANT transactions chained off the parent tx
 199          chain = [] # save sent txs for the purpose of checking node1's mempool later (see below)
 200          for _ in range(DEFAULT_CLUSTER_LIMIT - 1):
 201              utxo = transaction_package.pop(0)
 202              new_tx = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=10, utxos_to_spend=[utxo])
 203              txid = new_tx["txid"]
 204              chain.append(txid)
 205              if utxo['txid'] is parent_transaction:
 206                  tx_children.append(txid)
 207              transaction_package.extend(new_tx["new_utxos"])
 208  
 209          mempool = self.nodes[0].getrawmempool(True)
 210          assert_equal(mempool[parent_transaction]['descendantcount'], DEFAULT_CLUSTER_LIMIT)
 211          assert_equal(sorted(mempool[parent_transaction]['spentby']), sorted(tx_children))
 212  
 213          for child in tx_children:
 214              assert_equal(mempool[child]['depends'], [parent_transaction])
 215  
 216          # Check that node1's mempool is as expected, containing:
 217          # - parent tx for descendant test
 218          # - txs chained off parent tx (-> custom descendant limit)
 219          self.wait_until(lambda: len(self.nodes[1].getrawmempool()) == 2*CUSTOM_CLUSTER_LIMIT, timeout=10)
 220          mempool0 = self.nodes[0].getrawmempool(False)
 221          mempool1 = self.nodes[1].getrawmempool(False)
 222          assert set(mempool1).issubset(set(mempool0))
 223          assert parent_transaction in mempool1
 224          for tx in chain:
 225              if tx in mempool1:
 226                  entry0 = self.nodes[0].getmempoolentry(tx)
 227                  entry1 = self.nodes[1].getmempoolentry(tx)
 228                  assert not entry0['unbroadcast']
 229                  assert not entry1['unbroadcast']
 230                  assert entry1["descendantcount"] <= CUSTOM_CLUSTER_LIMIT
 231                  assert_equal(entry1['fees']['base'], entry0['fees']['base'])
 232                  assert_equal(entry1['vsize'], entry0['vsize'])
 233                  assert_equal(entry1['depends'], entry0['depends'])
 234  
 235          # Test reorg handling
 236          # First, the basics:
 237          fork_blocks = create_empty_fork(self.nodes[0])
 238          mempool0 = self.nodes[0].getrawmempool(False)
 239          self.generate(self.nodes[0], 1)
 240          self.trigger_reorg(fork_blocks, self.nodes[0])
 241  
 242          # Check that the txs are returned to the mempool, and that transaction ordering is
 243          # unchanged, as it is deterministic.
 244          assert_equal(self.nodes[0].getrawmempool(), mempool0)
 245  
 246          # Clean-up the mempool
 247          self.generate(self.nodes[0], 1)
 248  
 249          # Now test the case where node1 has a transaction T in its mempool that
 250          # depends on transactions A and B which are in a mined block, and the
 251          # block containing A and B is disconnected, AND B is not accepted back
 252          # into node1's mempool because its ancestor count is too high.
 253  
 254          # Create 8 transactions, like so:
 255          # Tx0 -> Tx1 (vout0)
 256          #   \--> Tx2 (vout1) -> Tx3 -> Tx4 -> Tx5 -> Tx6 -> Tx7
 257          #
 258          # Mine them in the next block, then generate a new tx8 that spends
 259          # Tx1 and Tx7, and add to node1's mempool, then disconnect the
 260          # last block.
 261  
 262          # Prep for fork
 263          fork_blocks = create_empty_fork(self.nodes[0])
 264  
 265          # Create tx0 with 2 outputs
 266          tx0 = self.wallet.send_self_transfer_multi(from_node=self.nodes[0], num_outputs=2)
 267  
 268          # Create tx1
 269          tx1 = self.wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=tx0["new_utxos"][0])
 270  
 271          # Create tx2-7
 272          tx7 = self.wallet.send_self_transfer_chain(from_node=self.nodes[0], utxo_to_spend=tx0["new_utxos"][1], chain_length=6)[-1]
 273  
 274          # Mine these in a block
 275          self.generate(self.nodes[0], 1)
 276  
 277          # Now generate tx8, with a big fee
 278          self.wallet.send_self_transfer_multi(from_node=self.nodes[0], utxos_to_spend=[tx1["new_utxo"], tx7["new_utxo"]], fee_per_output=40000)
 279          self.sync_mempools()
 280  
 281          # Now try to disconnect the tip on each node...
 282          self.trigger_reorg(fork_blocks, self.nodes[0])
 283          self.sync_blocks()
 284  
 285  if __name__ == '__main__':
 286      MempoolPackagesTest(__file__).main()
 287