[functional test] orphan resolution works in the presence of DoSy peers

Co-authored-by: Greg Sanders <gsanders87@gmail.com>
This commit is contained in:
glozowandGreg Sanders committed 2025-07-14 16:13:47 -04:00
1 parent 835f5c77cd
commit 45c7a4b56d
3 files changed
+271 -2

No files matched your search

+72 -1
View File
@@ -5,10 +5,14 @@
import time
from test_framework.mempool_util import tx_in_orphanage
from test_framework.mempool_util import (
create_large_orphan,
tx_in_orphanage,
)
from test_framework.messages import (
CInv,
CTxInWitness,
DEFAULT_ANCESTOR_LIMIT,
MSG_TX,
MSG_WITNESS_TX,
MSG_WTX,
@@ -627,6 +631,72 @@ class OrphanHandlingTest(BitcoinTestFramework):
peer_inbound.sync_with_ping()
peer_inbound.wait_for_parent_requests([parent_tx.txid_int])
@cleanup
def test_maximal_package_protected(self):
self.log.info("Test that a node only announcing a maximally sized ancestor package is protected in orphanage")
self.nodes[0].setmocktime(int(time.time()))
node = self.nodes[0]
peer_normal = node.add_p2p_connection(P2PInterface())
peer_doser = node.add_p2p_connection(P2PInterface())
# Each of the num_peers peers creates a distinct set of orphans
large_orphans = [create_large_orphan() for _ in range(60)]
# Check to make sure these are orphans, within max standard size (to be accepted into the orphanage)
for large_orphan in large_orphans:
testres = node.testmempoolaccept([large_orphan.serialize().hex()])
assert not testres[0]["allowed"]
assert_equal(testres[0]["reject-reason"], "missing-inputs")
num_individual_dosers = 20
self.log.info(f"Connect {num_individual_dosers} peers and send a very large orphan from each one")
# This test assumes that unrequested transactions are processed (skipping inv and
# getdata steps because they require going through request delays)
# Connect 20 peers and have each of them send a large orphan.
for large_orphan in large_orphans[:num_individual_dosers]:
peer_doser_individual = node.add_p2p_connection(P2PInterface())
peer_doser_individual.send_and_ping(msg_tx(large_orphan))
node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY + 1)
peer_doser_individual.wait_for_getdata([large_orphan.vin[0].prevout.hash])
# Make sure that these transactions are going through the orphan handling codepaths.
# Subsequent rounds will not wait for getdata because the time mocking will cause the
# normal package request to time out.
self.wait_until(lambda: len(node.getorphantxs()) == num_individual_dosers)
# Now honest peer will send a maximally sized ancestor package of 24 orphans chaining
# off of a single missing transaction, with a total vsize 404,000Wu
ancestor_package = self.wallet.create_self_transfer_chain(chain_length=DEFAULT_ANCESTOR_LIMIT - 1)
sum_ancestor_package_vsize = sum([tx["tx"].get_vsize() for tx in ancestor_package])
final_tx = self.wallet.create_self_transfer(utxo_to_spend=ancestor_package[-1]["new_utxo"], target_vsize=101000 - sum_ancestor_package_vsize)
ancestor_package.append(final_tx)
# Peer sends all but first tx to fill up orphange with their orphans
for orphan in ancestor_package[1:]:
peer_normal.send_and_ping(msg_tx(orphan["tx"]))
orphan_set = node.getorphantxs()
for orphan in ancestor_package[1:]:
assert orphan["txid"] in orphan_set
# Wait for ultimate parent request (the root ancestor transaction)
parent_txid_int = ancestor_package[0]["tx"].txid_int
node.bumpmocktime(NONPREF_PEER_TX_DELAY + TXID_RELAY_DELAY)
self.wait_until(lambda: "getdata" in peer_normal.last_message and parent_txid_int in [inv.hash for inv in peer_normal.last_message.get("getdata").inv])
self.log.info("Send another round of very large orphans from a DoSy peer")
for large_orphan in large_orphans[num_individual_dosers:]:
peer_doser.send_and_ping(msg_tx(large_orphan))
self.log.info("Provide the top ancestor. The whole package should be re-evaluated after enough time.")
peer_normal.send_and_ping(msg_tx(ancestor_package[0]["tx"]))
# Wait until all transactions have been processed. When the last tx is accepted, it's
# guaranteed to have all ancestors.
self.wait_until(lambda: node.getmempoolentry(final_tx["txid"])["ancestorcount"] == DEFAULT_ANCESTOR_LIMIT)
@cleanup
def test_announcers_before_and_after(self):
self.log.info("Test that the node uses all peers who announced the tx prior to realizing it's an orphan")
@@ -779,6 +849,7 @@ class OrphanHandlingTest(BitcoinTestFramework):
self.test_orphan_handling_prefer_outbound()
self.test_announcers_before_and_after()
self.test_parents_change()
self.test_maximal_package_protected()
if __name__ == '__main__':