1 #!/usr/bin/env python3
2 # Copyright (c) 2024-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 """
6 Test opportunistic 1p1c package submission logic.
7 """
8 9 from decimal import Decimal
10 import random
11 import time
12 13 from test_framework.blocktools import MAX_STANDARD_TX_WEIGHT
14 from test_framework.mempool_util import (
15 create_large_orphan,
16 DEFAULT_MIN_RELAY_TX_FEE,
17 fill_mempool,
18 )
19 from test_framework.messages import (
20 CInv,
21 COIN,
22 COutPoint,
23 CTransaction,
24 CTxIn,
25 CTxOut,
26 CTxInWitness,
27 MAX_BIP125_RBF_SEQUENCE,
28 MSG_WTX,
29 msg_inv,
30 msg_tx,
31 tx_from_hex,
32 )
33 from test_framework.p2p import (
34 NONPREF_PEER_TX_DELAY,
35 P2PInterface,
36 TXID_RELAY_DELAY,
37 )
38 from test_framework.script import (
39 CScript,
40 OP_NOP,
41 OP_RETURN,
42 )
43 from test_framework.test_framework import BitcoinTestFramework
44 from test_framework.util import (
45 assert_equal,
46 assert_greater_than,
47 assert_greater_than_or_equal,
48 )
49 from test_framework.wallet import (
50 MiniWallet,
51 MiniWalletMode,
52 )
53 54 # 1sat/vB feerate denominated in BTC/KvB
55 FEERATE_1SAT_VB = Decimal("0.00001000")
56 # Number of seconds to wait to ensure no getdata is received
57 GETDATA_WAIT = 60
58 59 def cleanup(func):
60 def wrapper(self, *args, **kwargs):
61 func(self, *args, **kwargs)
62 63 self.nodes[0].disconnect_p2ps()
64 # Do not clear the node's mempool, as each test requires mempool min feerate > min
65 # relay feerate. However, do check that this is the case.
66 assert_greater_than(self.nodes[0].getmempoolinfo()["mempoolminfee"], self.nodes[0].getnetworkinfo()["relayfee"])
67 # Ensure we do not try to spend the same UTXOs in subsequent tests, as they will look like RBF attempts.
68 self.wallet.rescan_utxos(include_mempool=True)
69 70 # Resets if mocktime was used
71 self.nodes[0].setmocktime(0)
72 return wrapper
73 74 class PackageRelayTest(BitcoinTestFramework):
75 def set_test_params(self):
76 self.setup_clean_chain = True
77 self.num_nodes = 1
78 self.extra_args = [[
79 "-maxmempool=5",
80 ]]
81 82 def create_tx_below_mempoolminfee(self, wallet, utxo_to_spend=None):
83 """Create a 1-input 0.1sat/vB transaction using a confirmed UTXO. Decrement and use
84 self.sequence so that subsequent calls to this function result in unique transactions."""
85 86 self.sequence -= 1
87 assert_greater_than(self.nodes[0].getmempoolinfo()["mempoolminfee"], Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN)
88 89 return wallet.create_self_transfer(fee_rate=Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN, sequence=self.sequence, utxo_to_spend=utxo_to_spend, confirmed_only=True)
90 91 @cleanup
92 def test_basic_child_then_parent(self):
93 node = self.nodes[0]
94 self.log.info("Check that opportunistic 1p1c logic works when child is received before parent")
95 node.setmocktime(int(time.time()))
96 97 low_fee_parent = self.create_tx_below_mempoolminfee(self.wallet)
98 high_fee_child = self.wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=20*FEERATE_1SAT_VB)
99 100 peer_sender = node.add_p2p_connection(P2PInterface())
101 102 # 1. Child is received first (perhaps the low feerate parent didn't meet feefilter or the requests were sent to different nodes). It is missing an input.
103 high_child_wtxid_int = high_fee_child["tx"].wtxid_int
104 peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=high_child_wtxid_int)]))
105 node.bumpmocktime(NONPREF_PEER_TX_DELAY)
106 peer_sender.wait_for_getdata([high_child_wtxid_int])
107 peer_sender.send_and_ping(msg_tx(high_fee_child["tx"]))
108 109 # 2. Node requests the missing parent by txid.
110 parent_txid_int = int(low_fee_parent["txid"], 16)
111 node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)
112 peer_sender.wait_for_getdata([parent_txid_int])
113 114 # 3. Sender relays the parent. Parent+Child are evaluated as a package and accepted.
115 peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
116 117 # 4. Both transactions should now be in mempool.
118 node_mempool = node.getrawmempool()
119 assert low_fee_parent["txid"] in node_mempool
120 assert high_fee_child["txid"] in node_mempool
121 122 node.disconnect_p2ps()
123 124 @cleanup
125 def test_basic_parent_then_child(self, wallet):
126 node = self.nodes[0]
127 node.setmocktime(int(time.time()))
128 low_fee_parent = self.create_tx_below_mempoolminfee(wallet)
129 high_fee_child = wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=20*FEERATE_1SAT_VB)
130 131 peer_sender = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="outbound-full-relay")
132 peer_ignored = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=2, connection_type="outbound-full-relay")
133 134 # 1. Parent is relayed first. It is too low feerate.
135 parent_wtxid_int = low_fee_parent["tx"].wtxid_int
136 peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=parent_wtxid_int)]))
137 peer_sender.wait_for_getdata([parent_wtxid_int])
138 peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
139 assert low_fee_parent["txid"] not in node.getrawmempool()
140 141 # Send again from peer_ignored, check that it is ignored
142 peer_ignored.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=parent_wtxid_int)]))
143 assert "getdata" not in peer_ignored.last_message
144 145 # 2. Child is relayed next. It is missing an input.
146 high_child_wtxid_int = high_fee_child["tx"].wtxid_int
147 peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=high_child_wtxid_int)]))
148 peer_sender.wait_for_getdata([high_child_wtxid_int])
149 peer_sender.send_and_ping(msg_tx(high_fee_child["tx"]))
150 151 # 3. Node requests the missing parent by txid.
152 # It should do so even if it has previously rejected that parent for being too low feerate.
153 parent_txid_int = int(low_fee_parent["txid"], 16)
154 node.bumpmocktime(TXID_RELAY_DELAY)
155 peer_sender.wait_for_getdata([parent_txid_int])
156 157 # 4. Sender re-relays the parent. Parent+Child are evaluated as a package and accepted.
158 peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
159 160 # 5. Both transactions should now be in mempool.
161 node_mempool = node.getrawmempool()
162 assert low_fee_parent["txid"] in node_mempool
163 assert high_fee_child["txid"] in node_mempool
164 165 @cleanup
166 def test_low_and_high_child(self, wallet):
167 node = self.nodes[0]
168 node.setmocktime(int(time.time()))
169 low_fee_parent = self.create_tx_below_mempoolminfee(wallet)
170 # This feerate is above mempoolminfee, but not enough to also bump the low feerate parent.
171 feerate_just_above = node.getmempoolinfo()["mempoolminfee"]
172 med_fee_child = wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=feerate_just_above)
173 high_fee_child = wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=999*FEERATE_1SAT_VB)
174 175 peer_sender = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="outbound-full-relay")
176 peer_ignored = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=2, connection_type="outbound-full-relay")
177 178 self.log.info("Check that tx caches low fee parent + low fee child package rejections")
179 180 # 1. Send parent, rejected for being low feerate.
181 parent_wtxid_int = low_fee_parent["tx"].wtxid_int
182 peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=parent_wtxid_int)]))
183 peer_sender.wait_for_getdata([parent_wtxid_int])
184 peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
185 assert low_fee_parent["txid"] not in node.getrawmempool()
186 187 # Send again from peer_ignored, check that it is ignored
188 peer_ignored.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=parent_wtxid_int)]))
189 assert "getdata" not in peer_ignored.last_message
190 191 # 2. Send an (orphan) child that has a higher feerate, but not enough to bump the parent.
192 med_child_wtxid_int = med_fee_child["tx"].wtxid_int
193 peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=med_child_wtxid_int)]))
194 peer_sender.wait_for_getdata([med_child_wtxid_int])
195 peer_sender.send_and_ping(msg_tx(med_fee_child["tx"]))
196 197 # 3. Node requests the orphan's missing parent.
198 parent_txid_int = int(low_fee_parent["txid"], 16)
199 node.bumpmocktime(TXID_RELAY_DELAY)
200 peer_sender.wait_for_getdata([parent_txid_int])
201 202 # 4. The low parent + low child are submitted as a package. They are not accepted due to low package feerate.
203 peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
204 205 assert low_fee_parent["txid"] not in node.getrawmempool()
206 assert med_fee_child["txid"] not in node.getrawmempool()
207 208 # If peer_ignored announces the low feerate child, it should be ignored
209 peer_ignored.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=med_child_wtxid_int)]))
210 assert "getdata" not in peer_ignored.last_message
211 # If either peer sends the parent again, package evaluation should not be attempted
212 peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
213 peer_ignored.send_and_ping(msg_tx(low_fee_parent["tx"]))
214 215 assert low_fee_parent["txid"] not in node.getrawmempool()
216 assert med_fee_child["txid"] not in node.getrawmempool()
217 218 # 5. Send the high feerate (orphan) child
219 high_child_wtxid_int = high_fee_child["tx"].wtxid_int
220 peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=high_child_wtxid_int)]))
221 peer_sender.wait_for_getdata([high_child_wtxid_int])
222 peer_sender.send_and_ping(msg_tx(high_fee_child["tx"]))
223 224 # 6. Node requests the orphan's parent, even though it has already been rejected, both by
225 # itself and with a child. This is necessary, otherwise high_fee_child can be censored.
226 parent_txid_int = int(low_fee_parent["txid"], 16)
227 node.bumpmocktime(TXID_RELAY_DELAY)
228 peer_sender.wait_for_getdata([parent_txid_int])
229 230 # 7. The low feerate parent + high feerate child are submitted as a package.
231 peer_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
232 233 # 8. Both transactions should now be in mempool
234 node_mempool = node.getrawmempool()
235 assert low_fee_parent["txid"] in node_mempool
236 assert high_fee_child["txid"] in node_mempool
237 assert med_fee_child["txid"] not in node_mempool
238 239 @cleanup
240 def test_orphan_consensus_failure(self):
241 self.log.info("Check opportunistic 1p1c logic requires parent and child to be from the same peer")
242 node = self.nodes[0]
243 node.setmocktime(int(time.time()))
244 low_fee_parent = self.create_tx_below_mempoolminfee(self.wallet)
245 coin = low_fee_parent["new_utxo"]
246 address = node.get_deterministic_priv_key().address
247 # Create raw transaction spending the parent, but with no signature (a consensus error).
248 hex_orphan_no_sig = node.createrawtransaction([{"txid": coin["txid"], "vout": coin["vout"]}], {address : coin["value"] - Decimal("0.0001")})
249 tx_orphan_bad_wit = tx_from_hex(hex_orphan_no_sig)
250 tx_orphan_bad_wit.wit.vtxinwit.append(CTxInWitness())
251 tx_orphan_bad_wit.wit.vtxinwit[0].scriptWitness.stack = [b'garbage']
252 253 bad_orphan_sender = node.add_p2p_connection(P2PInterface())
254 parent_sender = node.add_p2p_connection(P2PInterface())
255 256 # 1. Child is received first. It is missing an input.
257 child_wtxid_int = tx_orphan_bad_wit.wtxid_int
258 bad_orphan_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=child_wtxid_int)]))
259 node.bumpmocktime(NONPREF_PEER_TX_DELAY)
260 bad_orphan_sender.wait_for_getdata([child_wtxid_int])
261 bad_orphan_sender.send_and_ping(msg_tx(tx_orphan_bad_wit))
262 263 # 2. Node requests the missing parent by txid.
264 parent_txid_int = int(low_fee_parent["txid"], 16)
265 node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)
266 bad_orphan_sender.wait_for_getdata([parent_txid_int])
267 268 # 3. A different peer relays the parent. Package is not evaluated because the transactions
269 # were not sent from the same peer.
270 parent_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
271 272 # 4. Transactions should not be in mempool.
273 node_mempool = node.getrawmempool()
274 assert low_fee_parent["txid"] not in node_mempool
275 assert tx_orphan_bad_wit.txid_hex not in node_mempool
276 277 # 5. Have the other peer send the tx too, so that tx_orphan_bad_wit package is attempted.
278 bad_orphan_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
279 280 # The bad orphan sender should not be disconnected.
281 bad_orphan_sender.sync_with_ping()
282 283 # The peer that didn't provide the orphan should not be disconnected.
284 parent_sender.sync_with_ping()
285 286 @cleanup
287 def test_parent_consensus_failure(self):
288 self.log.info("Check opportunistic 1p1c logic with consensus-invalid parent causes disconnect of the correct peer")
289 node = self.nodes[0]
290 node.setmocktime(int(time.time()))
291 292 low_fee_parent = self.create_tx_below_mempoolminfee(self.wallet)
293 high_fee_child = self.wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=999*FEERATE_1SAT_VB)
294 295 # Create invalid version of parent with a bad signature.
296 tx_parent_bad_wit = tx_from_hex(low_fee_parent["hex"])
297 tx_parent_bad_wit.wit.vtxinwit.append(CTxInWitness())
298 tx_parent_bad_wit.wit.vtxinwit[0].scriptWitness.stack = [b'garbage']
299 300 package_sender = node.add_p2p_connection(P2PInterface())
301 fake_parent_sender = node.add_p2p_connection(P2PInterface())
302 303 # 1. Child is received first. It is missing an input.
304 child_wtxid_int = high_fee_child["tx"].wtxid_int
305 package_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=child_wtxid_int)]))
306 node.bumpmocktime(NONPREF_PEER_TX_DELAY)
307 package_sender.wait_for_getdata([child_wtxid_int])
308 package_sender.send_and_ping(msg_tx(high_fee_child["tx"]))
309 310 # 2. Node requests the missing parent by txid.
311 parent_txid_int = tx_parent_bad_wit.txid_int
312 node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)
313 package_sender.wait_for_getdata([parent_txid_int])
314 315 # 3. A different node relays the parent. The parent is first evaluated by itself and
316 # rejected for being too low feerate. It is not evaluated as a package because the child was
317 # sent from a different peer, so we don't find out that the child is consensus-invalid.
318 fake_parent_sender.send_and_ping(msg_tx(tx_parent_bad_wit))
319 320 # 4. Transactions should not be in mempool.
321 node_mempool = node.getrawmempool()
322 assert tx_parent_bad_wit.txid_hex not in node_mempool
323 assert high_fee_child["txid"] not in node_mempool
324 325 self.log.info("Check that fake parent does not cause orphan to be deleted and real package can still be submitted")
326 # 5. Child-sending should not have been punished and the orphan should remain in orphanage.
327 # It can send the "real" parent transaction, and the package is accepted.
328 parent_wtxid_int = low_fee_parent["tx"].wtxid_int
329 package_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=parent_wtxid_int)]))
330 node.bumpmocktime(NONPREF_PEER_TX_DELAY)
331 package_sender.wait_for_getdata([parent_wtxid_int])
332 package_sender.send_and_ping(msg_tx(low_fee_parent["tx"]))
333 334 node_mempool = node.getrawmempool()
335 assert low_fee_parent["txid"] in node_mempool
336 assert high_fee_child["txid"] in node_mempool
337 338 @cleanup
339 def test_multiple_parents(self):
340 self.log.info("Check that node does not request more than 1 previously-rejected low feerate parent")
341 342 node = self.nodes[0]
343 node.setmocktime(int(time.time()))
344 345 # 2-parent-1-child package where both parents are below mempool min feerate
346 parent_low_1 = self.create_tx_below_mempoolminfee(self.wallet_nonsegwit)
347 parent_low_2 = self.create_tx_below_mempoolminfee(self.wallet_nonsegwit)
348 child_bumping = self.wallet_nonsegwit.create_self_transfer_multi(
349 utxos_to_spend=[parent_low_1["new_utxo"], parent_low_2["new_utxo"]],
350 fee_per_output=999*parent_low_1["tx"].get_vsize(),
351 )
352 353 peer_sender = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="outbound-full-relay")
354 355 # 1. Send both parents. Each should be rejected for being too low feerate.
356 # Send unsolicited so that we can later check that no "getdata" was ever received.
357 peer_sender.send_and_ping(msg_tx(parent_low_1["tx"]))
358 peer_sender.send_and_ping(msg_tx(parent_low_2["tx"]))
359 360 # parent_low_1 and parent_low_2 are rejected for being low feerate.
361 assert parent_low_1["txid"] not in node.getrawmempool()
362 assert parent_low_2["txid"] not in node.getrawmempool()
363 364 # 2. Send child.
365 peer_sender.send_and_ping(msg_tx(child_bumping["tx"]))
366 367 # 3. Node should not request any parents, as it should recognize that it will not accept
368 # multi-parent-1-child packages.
369 node.bumpmocktime(GETDATA_WAIT)
370 peer_sender.sync_with_ping()
371 assert "getdata" not in peer_sender.last_message
372 373 @cleanup
374 def test_other_parent_in_mempool(self):
375 self.log.info("Check opportunistic 1p1c works when part of a 2p1c (child already has another parent in mempool)")
376 node = self.nodes[0]
377 node.setmocktime(int(time.time()))
378 379 # Grandparent will enter mempool by itself
380 grandparent_high = self.wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*10, confirmed_only=True)
381 382 # This parent needs CPFP
383 parent_low = self.create_tx_below_mempoolminfee(self.wallet, utxo_to_spend=grandparent_high["new_utxo"])
384 # This parent does not need CPFP and can be submitted alone ahead of time
385 parent_high = self.wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*10, confirmed_only=True)
386 child = self.wallet.create_self_transfer_multi(
387 utxos_to_spend=[parent_high["new_utxo"], parent_low["new_utxo"]],
388 fee_per_output=999*parent_low["tx"].get_vsize(),
389 )
390 391 peer_sender = node.add_outbound_p2p_connection(P2PInterface(), p2p_idx=1, connection_type="outbound-full-relay")
392 393 # 1. Send grandparent which is accepted
394 peer_sender.send_and_ping(msg_tx(grandparent_high["tx"]))
395 assert grandparent_high["txid"] in node.getrawmempool()
396 397 # 2. Send first parent which is accepted.
398 peer_sender.send_and_ping(msg_tx(parent_high["tx"]))
399 assert parent_high["txid"] in node.getrawmempool()
400 401 # 3. Send child which is handled as an orphan.
402 peer_sender.send_and_ping(msg_tx(child["tx"]))
403 404 # 4. Node requests parent_low.
405 parent_low_txid_int = int(parent_low["txid"], 16)
406 node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)
407 peer_sender.wait_for_getdata([parent_low_txid_int])
408 peer_sender.send_and_ping(msg_tx(parent_low["tx"]))
409 410 node_mempool = node.getrawmempool()
411 assert grandparent_high["txid"] in node_mempool
412 assert parent_high["txid"] in node_mempool
413 assert parent_low["txid"] in node_mempool
414 assert child["txid"] in node_mempool
415 416 def create_small_orphan(self):
417 """Create small orphan transaction"""
418 tx = CTransaction()
419 # Nonexistent UTXO
420 tx.vin = [CTxIn(COutPoint(random.randrange(1 << 256), random.randrange(1, 100)))]
421 tx.wit.vtxinwit = [CTxInWitness()]
422 tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_NOP] * 5)]
423 tx.vout = [CTxOut(100, CScript([OP_RETURN, b'a' * 3]))]
424 return tx
425 426 @cleanup
427 def test_orphanage_dos_large(self):
428 self.log.info("Test that the node can still resolve orphans when peers use lots of orphanage space")
429 node = self.nodes[0]
430 node.setmocktime(int(time.time()))
431 432 peer_normal = node.add_p2p_connection(P2PInterface())
433 peer_doser = node.add_p2p_connection(P2PInterface())
434 num_individual_dosers = 10
435 436 self.log.info("Create very large orphans to be sent by DoSy peers (may take a while)")
437 large_orphans = [create_large_orphan() for _ in range(50)]
438 # Check to make sure these are orphans, within max standard size (to be accepted into the orphanage)
439 for large_orphan in large_orphans:
440 assert_greater_than_or_equal(100000, large_orphan.get_vsize())
441 assert_greater_than(MAX_STANDARD_TX_WEIGHT, large_orphan.get_weight())
442 assert_greater_than_or_equal(3 * large_orphan.get_vsize(), 2 * 100000)
443 testres = node.testmempoolaccept([large_orphan.serialize().hex()])
444 assert not testres[0]["allowed"]
445 assert_equal(testres[0]["reject-reason"], "missing-inputs")
446 447 self.log.info(f"Connect {num_individual_dosers} peers and send a very large orphan from each one")
448 # This test assumes that unrequested transactions are processed (skipping inv and
449 # getdata steps because they require going through request delays)
450 # Connect 10 peers and have each of them send a large orphan.
451 for large_orphan in large_orphans[:num_individual_dosers]:
452 peer_doser_individual = node.add_p2p_connection(P2PInterface())
453 peer_doser_individual.send_and_ping(msg_tx(large_orphan))
454 node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)
455 peer_doser_individual.wait_for_getdata([large_orphan.vin[0].prevout.hash])
456 457 # Make sure that these transactions are going through the orphan handling codepaths.
458 # Subsequent rounds will not wait for getdata because the time mocking will cause the
459 # normal package request to time out.
460 self.wait_until(lambda: len(node.getorphantxs()) == num_individual_dosers)
461 462 self.log.info("Send an orphan from a non-DoSy peer. Its orphan should not be evicted.")
463 low_fee_parent = self.create_tx_below_mempoolminfee(self.wallet)
464 high_fee_child = self.wallet.create_self_transfer(
465 utxo_to_spend=low_fee_parent["new_utxo"],
466 fee_rate=200*FEERATE_1SAT_VB,
467 target_vsize=100000
468 )
469 470 # Announce
471 orphan_tx = high_fee_child["tx"]
472 orphan_inv = CInv(t=MSG_WTX, h=orphan_tx.wtxid_int)
473 474 # Wait for getdata
475 peer_normal.send_and_ping(msg_inv([orphan_inv]))
476 node.bumpmocktime(NONPREF_PEER_TX_DELAY)
477 peer_normal.wait_for_getdata([orphan_tx.wtxid_int])
478 peer_normal.send_and_ping(msg_tx(orphan_tx))
479 480 # Wait for parent request
481 parent_txid_int = int(low_fee_parent["txid"], 16)
482 node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)
483 peer_normal.wait_for_getdata([parent_txid_int])
484 485 self.log.info("Send another round of very large orphans from a DoSy peer")
486 for large_orphan in large_orphans[num_individual_dosers:]:
487 peer_doser.send_and_ping(msg_tx(large_orphan))
488 489 # Something was evicted; the orphanage does not contain all large orphans + the 1p1c child
490 self.wait_until(lambda: len(node.getorphantxs()) < len(large_orphans) + 1)
491 492 self.log.info("Provide the orphan's parent. This 1p1c package should be successfully accepted.")
493 peer_normal.send_and_ping(msg_tx(low_fee_parent["tx"]))
494 assert_equal(node.getmempoolentry(orphan_tx.txid_hex)["ancestorcount"], 2)
495 496 @cleanup
497 def test_orphanage_dos_many(self):
498 self.log.info("Test that the node can still resolve orphans when peers are sending tons of orphans")
499 node = self.nodes[0]
500 node.setmocktime(int(time.time()))
501 502 peer_normal = node.add_p2p_connection(P2PInterface())
503 504 # The first set of peers all send the same batch_size orphans. Then a single peer sends
505 # batch_single_doser distinct orphans.
506 batch_size = 51
507 num_peers_shared = 60
508 batch_single_doser = 100
509 assert_greater_than(num_peers_shared * batch_size + batch_single_doser, 3000)
510 # 60 peers * 51 orphans = 3060 announcements
511 shared_orphans = [self.create_small_orphan() for _ in range(batch_size)]
512 self.log.info(f"Send the same {batch_size} orphans from {num_peers_shared} DoSy peers (may take a while)")
513 peer_doser_shared = [node.add_p2p_connection(P2PInterface()) for _ in range(num_peers_shared)]
514 for i in range(num_peers_shared):
515 for orphan in shared_orphans:
516 peer_doser_shared[i].send_without_ping(msg_tx(orphan))
517 518 # We sync peers to make sure we have processed as many orphans as possible. Ensure at least
519 # one of the orphans was processed.
520 for peer_doser in peer_doser_shared:
521 peer_doser.sync_with_ping()
522 self.wait_until(lambda: any([tx.txid_hex in node.getorphantxs() for tx in shared_orphans]))
523 524 self.log.info("Send an orphan from a non-DoSy peer. Its orphan should not be evicted.")
525 low_fee_parent = self.create_tx_below_mempoolminfee(self.wallet)
526 high_fee_child = self.wallet.create_self_transfer(
527 utxo_to_spend=low_fee_parent["new_utxo"],
528 fee_rate=200*FEERATE_1SAT_VB,
529 )
530 531 # Announce
532 orphan_tx = high_fee_child["tx"]
533 orphan_inv = CInv(t=MSG_WTX, h=orphan_tx.wtxid_int)
534 535 # Wait for getdata
536 peer_normal.send_and_ping(msg_inv([orphan_inv]))
537 node.bumpmocktime(NONPREF_PEER_TX_DELAY)
538 peer_normal.wait_for_getdata([orphan_tx.wtxid_int])
539 peer_normal.send_and_ping(msg_tx(orphan_tx))
540 541 # Orphan has been entered and evicted something else
542 self.wait_until(lambda: high_fee_child["txid"] in node.getorphantxs())
543 544 # Wait for parent request
545 parent_txid_int = low_fee_parent["tx"].txid_int
546 node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)
547 peer_normal.wait_for_getdata([parent_txid_int])
548 549 self.log.info(f"Send {batch_single_doser} new orphans from one DoSy peer")
550 peer_doser_batch = node.add_p2p_connection(P2PInterface())
551 this_batch_orphans = [self.create_small_orphan() for _ in range(batch_single_doser)]
552 for tx in this_batch_orphans:
553 # Don't wait for responses, because it dramatically increases the runtime of this test.
554 peer_doser_batch.send_without_ping(msg_tx(tx))
555 556 peer_doser_batch.sync_with_ping()
557 self.wait_until(lambda: any([tx.txid_hex in node.getorphantxs() for tx in this_batch_orphans]))
558 559 self.log.info("Check that orphan from normal peer still exists in orphanage")
560 assert high_fee_child["txid"] in node.getorphantxs()
561 562 self.log.info("Provide the orphan's parent. This 1p1c package should be successfully accepted.")
563 peer_normal.send_and_ping(msg_tx(low_fee_parent["tx"]))
564 assert orphan_tx.txid_hex in node.getrawmempool()
565 assert_equal(node.getmempoolentry(orphan_tx.txid_hex)["ancestorcount"], 2)
566 567 @cleanup
568 def test_1p1c_on_1p1c(self):
569 self.log.info("Test that opportunistic 1p1c works when part of a 4-generation chain (1p1c chained from a 1p1c)")
570 node = self.nodes[0]
571 572 # Prep 2 generations of 1p1c packages to be relayed
573 low_fee_great_grandparent = self.create_tx_below_mempoolminfee(self.wallet)
574 high_fee_grandparent = self.wallet.create_self_transfer(utxo_to_spend=low_fee_great_grandparent["new_utxo"], fee_rate=20*FEERATE_1SAT_VB)
575 576 low_fee_parent = self.create_tx_below_mempoolminfee(self.wallet, utxo_to_spend=high_fee_grandparent["new_utxo"])
577 high_fee_child = self.wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=20*FEERATE_1SAT_VB)
578 579 peer_sender = node.add_p2p_connection(P2PInterface())
580 581 # The 1p1c that spends the confirmed utxo must be received first. Afterwards, the "younger" 1p1c can be received.
582 for package in [[low_fee_great_grandparent, high_fee_grandparent], [low_fee_parent, high_fee_child]]:
583 # Aliases
584 parent_relative, child_relative = package
585 586 # 1. Child is received first (perhaps the low feerate parent didn't meet feefilter or the requests were sent to different nodes). It is missing an input.
587 high_child_wtxid_int = child_relative["tx"].wtxid_int
588 peer_sender.send_and_ping(msg_inv([CInv(t=MSG_WTX, h=high_child_wtxid_int)]))
589 peer_sender.wait_for_getdata([high_child_wtxid_int])
590 peer_sender.send_and_ping(msg_tx(child_relative["tx"]))
591 592 # 2. Node requests the missing parent by txid.
593 parent_txid_int = parent_relative["tx"].txid_int
594 peer_sender.wait_for_getdata([parent_txid_int])
595 596 # 3. Sender relays the parent. Parent+Child are evaluated as a package and accepted.
597 peer_sender.send_and_ping(msg_tx(parent_relative["tx"]))
598 599 # 4. All transactions should now be in mempool.
600 node_mempool = node.getrawmempool()
601 assert low_fee_great_grandparent["txid"] in node_mempool
602 assert high_fee_grandparent["txid"] in node_mempool
603 assert low_fee_parent["txid"] in node_mempool
604 assert high_fee_child["txid"] in node_mempool
605 assert_equal(node.getmempoolentry(low_fee_great_grandparent["txid"])["descendantcount"], 4)
606 607 def run_test(self):
608 node = self.nodes[0]
609 # To avoid creating transactions with the same txid (can happen if we set the same feerate
610 # and reuse the same input as a previous transaction that wasn't successfully submitted),
611 # we give each subtest a different nSequence for its transactions.
612 self.sequence = MAX_BIP125_RBF_SEQUENCE
613 614 self.wallet = MiniWallet(node)
615 self.wallet_nonsegwit = MiniWallet(node, mode=MiniWalletMode.RAW_P2PK)
616 self.generate(self.wallet_nonsegwit, 10)
617 self.generate(self.wallet, 20)
618 619 fill_mempool(self, node)
620 621 self.log.info("Check opportunistic 1p1c logic when parent (txid != wtxid) is received before child")
622 self.test_basic_parent_then_child(self.wallet)
623 624 self.log.info("Check opportunistic 1p1c logic when parent (txid == wtxid) is received before child")
625 self.test_basic_parent_then_child(self.wallet_nonsegwit)
626 627 self.log.info("Check opportunistic 1p1c logic when child is received before parent")
628 self.test_basic_child_then_parent()
629 630 self.log.info("Check opportunistic 1p1c logic when 2 candidate children exist (parent txid != wtxid)")
631 self.test_low_and_high_child(self.wallet)
632 633 self.log.info("Check opportunistic 1p1c logic when 2 candidate children exist (parent txid == wtxid)")
634 self.test_low_and_high_child(self.wallet_nonsegwit)
635 636 self.test_orphan_consensus_failure()
637 self.test_parent_consensus_failure()
638 self.test_multiple_parents()
639 self.test_other_parent_in_mempool()
640 self.test_1p1c_on_1p1c()
641 642 self.test_orphanage_dos_large()
643 self.test_orphanage_dos_many()
644 645 646 if __name__ == '__main__':
647 PackageRelayTest(__file__).main()
648