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