|
7 | 7 | """
|
8 | 8 |
|
9 | 9 | from decimal import Decimal
|
| 10 | +import random |
10 | 11 | import time
|
| 12 | + |
| 13 | +from test_framework.blocktools import MAX_STANDARD_TX_WEIGHT |
11 | 14 | from test_framework.mempool_util import (
|
| 15 | + create_large_orphan, |
12 | 16 | fill_mempool,
|
13 | 17 | )
|
14 | 18 | from test_framework.messages import (
|
15 | 19 | CInv,
|
| 20 | + COutPoint, |
| 21 | + CTransaction, |
| 22 | + CTxIn, |
| 23 | + CTxOut, |
16 | 24 | CTxInWitness,
|
17 | 25 | MAX_BIP125_RBF_SEQUENCE,
|
18 | 26 | MSG_WTX,
|
|
21 | 29 | tx_from_hex,
|
22 | 30 | )
|
23 | 31 | from test_framework.p2p import (
|
| 32 | + NONPREF_PEER_TX_DELAY, |
24 | 33 | P2PInterface,
|
| 34 | + TXID_RELAY_DELAY, |
| 35 | +) |
| 36 | +from test_framework.script import ( |
| 37 | + CScript, |
| 38 | + OP_NOP, |
| 39 | + OP_RETURN, |
25 | 40 | )
|
26 | 41 | from test_framework.test_framework import BitcoinTestFramework
|
27 | 42 | from test_framework.util import (
|
28 | 43 | assert_equal,
|
29 | 44 | assert_greater_than,
|
| 45 | + assert_greater_than_or_equal, |
30 | 46 | )
|
31 | 47 | from test_framework.wallet import (
|
32 | 48 | MiniWallet,
|
@@ -373,6 +389,164 @@ def test_other_parent_in_mempool(self):
|
373 | 389 | result_missing_parent = node.submitpackage(package_hex_missing_parent)
|
374 | 390 | assert_equal(result_missing_parent["package_msg"], "package-not-child-with-unconfirmed-parents")
|
375 | 391 |
|
| 392 | + def create_small_orphan(self): |
| 393 | + """Create small orphan transaction""" |
| 394 | + tx = CTransaction() |
| 395 | + # Nonexistent UTXO |
| 396 | + tx.vin = [CTxIn(COutPoint(random.randrange(1 << 256), random.randrange(1, 100)))] |
| 397 | + tx.wit.vtxinwit = [CTxInWitness()] |
| 398 | + tx.wit.vtxinwit[0].scriptWitness.stack = [CScript([OP_NOP] * 5)] |
| 399 | + tx.vout = [CTxOut(100, CScript([OP_RETURN, b'a' * 3]))] |
| 400 | + return tx |
| 401 | + |
| 402 | + @cleanup |
| 403 | + def test_orphanage_dos_large(self): |
| 404 | + self.log.info("Test that the node can still resolve orphans when peers use lots of orphanage space") |
| 405 | + node = self.nodes[0] |
| 406 | + node.setmocktime(int(time.time())) |
| 407 | + |
| 408 | + peer_normal = node.add_p2p_connection(P2PInterface()) |
| 409 | + peer_doser = node.add_p2p_connection(P2PInterface()) |
| 410 | + |
| 411 | + self.log.info("Create very large orphans to be sent by DoSy peers (may take a while)") |
| 412 | + large_orphans = [create_large_orphan() for _ in range(100)] |
| 413 | + # Check to make sure these are orphans, within max standard size (to be accepted into the orphanage) |
| 414 | + for large_orphan in large_orphans: |
| 415 | + assert_greater_than_or_equal(100000, large_orphan.get_vsize()) |
| 416 | + assert_greater_than(MAX_STANDARD_TX_WEIGHT, large_orphan.get_weight()) |
| 417 | + assert_greater_than_or_equal(3 * large_orphan.get_vsize(), 2 * 100000) |
| 418 | + testres = node.testmempoolaccept([large_orphan.serialize().hex()]) |
| 419 | + assert not testres[0]["allowed"] |
| 420 | + assert_equal(testres[0]["reject-reason"], "missing-inputs") |
| 421 | + |
| 422 | + num_individual_dosers = 30 |
| 423 | + self.log.info(f"Connect {num_individual_dosers} peers and send a very large orphan from each one") |
| 424 | + # This test assumes that unrequested transactions are processed (skipping inv and |
| 425 | + # getdata steps because they require going through request delays) |
| 426 | + # Connect 20 peers and have each of them send a large orphan. |
| 427 | + for large_orphan in large_orphans[:num_individual_dosers]: |
| 428 | + peer_doser_individual = node.add_p2p_connection(P2PInterface()) |
| 429 | + peer_doser_individual.send_and_ping(msg_tx(large_orphan)) |
| 430 | + node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY) |
| 431 | + peer_doser_individual.wait_for_getdata([large_orphan.vin[0].prevout.hash]) |
| 432 | + |
| 433 | + # Make sure that these transactions are going through the orphan handling codepaths. |
| 434 | + # Subsequent rounds will not wait for getdata because the time mocking will cause the |
| 435 | + # normal package request to time out. |
| 436 | + self.wait_until(lambda: len(node.getorphantxs()) == num_individual_dosers) |
| 437 | + |
| 438 | + self.log.info("Send an orphan from a non-DoSy peer. Its orphan should not be evicted.") |
| 439 | + low_fee_parent = self.create_tx_below_mempoolminfee(self.wallet) |
| 440 | + high_fee_child = self.wallet.create_self_transfer( |
| 441 | + utxo_to_spend=low_fee_parent["new_utxo"], |
| 442 | + fee_rate=200*FEERATE_1SAT_VB, |
| 443 | + target_vsize=100000 |
| 444 | + ) |
| 445 | + |
| 446 | + # Announce |
| 447 | + orphan_tx = high_fee_child["tx"] |
| 448 | + orphan_inv = CInv(t=MSG_WTX, h=orphan_tx.wtxid_int) |
| 449 | + |
| 450 | + # Wait for getdata |
| 451 | + peer_normal.send_and_ping(msg_inv([orphan_inv])) |
| 452 | + node.bumpmocktime(NONPREF_PEER_TX_DELAY) |
| 453 | + peer_normal.wait_for_getdata([orphan_tx.wtxid_int]) |
| 454 | + peer_normal.send_and_ping(msg_tx(orphan_tx)) |
| 455 | + |
| 456 | + # Wait for parent request |
| 457 | + parent_txid_int = int(low_fee_parent["txid"], 16) |
| 458 | + node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY) |
| 459 | + peer_normal.wait_for_getdata([parent_txid_int]) |
| 460 | + |
| 461 | + self.log.info("Send another round of very large orphans from a DoSy peer") |
| 462 | + for large_orphan in large_orphans[30:]: |
| 463 | + peer_doser.send_and_ping(msg_tx(large_orphan)) |
| 464 | + |
| 465 | + # Something was evicted; the orphanage does not contain all large orphans + the 1p1c child |
| 466 | + self.wait_until(lambda: len(node.getorphantxs()) < len(large_orphans) + 1) |
| 467 | + |
| 468 | + self.log.info("Provide the orphan's parent. This 1p1c package should be successfully accepted.") |
| 469 | + peer_normal.send_and_ping(msg_tx(low_fee_parent["tx"])) |
| 470 | + assert_equal(node.getmempoolentry(orphan_tx.txid_hex)["ancestorcount"], 2) |
| 471 | + |
| 472 | + @cleanup |
| 473 | + def test_orphanage_dos_many(self): |
| 474 | + self.log.info("Test that the node can still resolve orphans when peers are sending tons of orphans") |
| 475 | + node = self.nodes[0] |
| 476 | + node.setmocktime(int(time.time())) |
| 477 | + |
| 478 | + peer_normal = node.add_p2p_connection(P2PInterface()) |
| 479 | + |
| 480 | + # 2 sets of peers: the first set all send the same batch_size orphans. The second set each |
| 481 | + # sends batch_size distinct orphans. |
| 482 | + batch_size = 51 |
| 483 | + num_peers_shared = 60 |
| 484 | + num_peers_unique = 40 |
| 485 | + |
| 486 | + # 60 peers * 51 orphans = 3060 announcements |
| 487 | + shared_orphans = [self.create_small_orphan() for _ in range(batch_size)] |
| 488 | + self.log.info(f"Send the same {batch_size} orphans from {num_peers_shared} DoSy peers (may take a while)") |
| 489 | + peer_doser_shared = [node.add_p2p_connection(P2PInterface()) for _ in range(num_peers_shared)] |
| 490 | + for i in range(num_peers_shared): |
| 491 | + for orphan in shared_orphans: |
| 492 | + peer_doser_shared[i].send_without_ping(msg_tx(orphan)) |
| 493 | + |
| 494 | + # We sync peers to make sure we have processed as many orphans as possible. Ensure at least |
| 495 | + # one of the orphans was processed. |
| 496 | + for peer_doser in peer_doser_shared: |
| 497 | + peer_doser.sync_with_ping() |
| 498 | + self.wait_until(lambda: any([tx.txid_hex in node.getorphantxs() for tx in shared_orphans])) |
| 499 | + |
| 500 | + self.log.info("Send an orphan from a non-DoSy peer. Its orphan should not be evicted.") |
| 501 | + low_fee_parent = self.create_tx_below_mempoolminfee(self.wallet) |
| 502 | + high_fee_child = self.wallet.create_self_transfer( |
| 503 | + utxo_to_spend=low_fee_parent["new_utxo"], |
| 504 | + fee_rate=200*FEERATE_1SAT_VB, |
| 505 | + ) |
| 506 | + |
| 507 | + # Announce |
| 508 | + orphan_tx = high_fee_child["tx"] |
| 509 | + orphan_inv = CInv(t=MSG_WTX, h=orphan_tx.wtxid_int) |
| 510 | + |
| 511 | + # Wait for getdata |
| 512 | + peer_normal.send_and_ping(msg_inv([orphan_inv])) |
| 513 | + node.bumpmocktime(NONPREF_PEER_TX_DELAY) |
| 514 | + peer_normal.wait_for_getdata([orphan_tx.wtxid_int]) |
| 515 | + peer_normal.send_and_ping(msg_tx(orphan_tx)) |
| 516 | + |
| 517 | + # Orphan has been entered and evicted something else |
| 518 | + self.wait_until(lambda: high_fee_child["txid"] in node.getorphantxs()) |
| 519 | + |
| 520 | + # Wait for parent request |
| 521 | + parent_txid_int = low_fee_parent["tx"].txid_int |
| 522 | + node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY) |
| 523 | + peer_normal.wait_for_getdata([parent_txid_int]) |
| 524 | + |
| 525 | + # Each of the num_peers_unique peers creates a distinct set of orphans |
| 526 | + many_orphans = [self.create_small_orphan() for _ in range(batch_size * num_peers_unique)] |
| 527 | + |
| 528 | + self.log.info(f"Send sets of {batch_size} orphans from {num_peers_unique} DoSy peers (may take a while)") |
| 529 | + for peernum in range(num_peers_unique): |
| 530 | + peer_doser_batch = node.add_p2p_connection(P2PInterface()) |
| 531 | + this_batch_orphans = many_orphans[batch_size*peernum : batch_size*(peernum+1)] |
| 532 | + for tx in this_batch_orphans: |
| 533 | + # Don't wait for responses, because it dramatically increases the runtime of this test. |
| 534 | + peer_doser_batch.send_without_ping(msg_tx(tx)) |
| 535 | + |
| 536 | + # Ensure at least one of the peer's orphans shows up in getorphantxs. Since each peer is |
| 537 | + # reserved a portion of orphanage space, this must happen as long as the orphans are not |
| 538 | + # rejected for some other reason. |
| 539 | + peer_doser_batch.sync_with_ping() |
| 540 | + self.wait_until(lambda: any([tx.txid_hex in node.getorphantxs() for tx in this_batch_orphans])) |
| 541 | + |
| 542 | + self.log.info("Check that orphan from normal peer still exists in orphanage") |
| 543 | + assert high_fee_child["txid"] in node.getorphantxs() |
| 544 | + |
| 545 | + self.log.info("Provide the orphan's parent. This 1p1c package should be successfully accepted.") |
| 546 | + peer_normal.send_and_ping(msg_tx(low_fee_parent["tx"])) |
| 547 | + assert orphan_tx.txid_hex in node.getrawmempool() |
| 548 | + assert_equal(node.getmempoolentry(orphan_tx.txid_hex)["ancestorcount"], 2) |
| 549 | + |
376 | 550 | def run_test(self):
|
377 | 551 | node = self.nodes[0]
|
378 | 552 | # To avoid creating transactions with the same txid (can happen if we set the same feerate
|
@@ -407,6 +581,9 @@ def run_test(self):
|
407 | 581 | self.test_multiple_parents()
|
408 | 582 | self.test_other_parent_in_mempool()
|
409 | 583 |
|
| 584 | + self.test_orphanage_dos_large() |
| 585 | + self.test_orphanage_dos_many() |
| 586 | + |
410 | 587 |
|
411 | 588 | if __name__ == '__main__':
|
412 | 589 | PackageRelayTest(__file__).main()
|
0 commit comments