mempool_packages.py
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()