Skip to content

Commit

Permalink
[functional test] orphan handling with multiple announcers
Browse files Browse the repository at this point in the history
  • Loading branch information
glozow committed Nov 28, 2024
1 parent c04d1a8 commit 780e28c
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 7 deletions.
8 changes: 1 addition & 7 deletions test/functional/p2p_1p1c_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,20 +143,14 @@ def run_test(self):
for (i, peer) in enumerate(self.peers):
for tx in transactions_to_presend[i]:
peer.send_and_ping(msg_tx(tx))
# This disconnect removes any sent orphans from the orphanage (EraseForPeer) and times
# out the in-flight requests. It is currently required for the test to pass right now,
# because the node will not reconsider an orphan tx and will not (re)try requesting
# orphan parents from multiple peers if the first one didn't respond.
# TODO: remove this in the future if the node tries orphan resolution with multiple peers.
peer.peer_disconnect()

self.log.info("Submit full packages to node0")
for package_hex in packages_to_submit:
submitpackage_result = self.nodes[0].submitpackage(package_hex)
assert_equal(submitpackage_result["package_msg"], "success")

self.log.info("Wait for mempools to sync")
self.sync_mempools(timeout=20)
self.sync_mempools(timeout=62)


if __name__ == '__main__':
Expand Down
71 changes: 71 additions & 0 deletions test/functional/p2p_orphan_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from test_framework.p2p import (
GETDATA_TX_INTERVAL,
NONPREF_PEER_TX_DELAY,
ORPHAN_ANCESTOR_GETDATA_INTERVAL,
OVERLOADED_PEER_TX_DELAY,
p2p_lock,
P2PInterface,
Expand Down Expand Up @@ -624,6 +625,74 @@ def test_max_orphan_amount(self):
peer_1.send_and_ping(msg_tx(parent_orphan))
assert_equal(len(node.getorphantxs()),0)

@cleanup
def test_orphan_handling_prefer_outbound(self):
self.log.info("Test that the node prefers requesting from outbound peers")
node = self.nodes[0]
orphan_wtxid, orphan_tx, parent_tx = self.create_parent_and_child()
orphan_inv = CInv(t=MSG_WTX, h=int(orphan_wtxid, 16))

peer_inbound = node.add_p2p_connection(PeerTxRelayer())
peer_outbound = node.add_outbound_p2p_connection(PeerTxRelayer(), p2p_idx=1)

# Inbound peer relays the transaction.
peer_inbound.send_and_ping(msg_inv([orphan_inv]))
self.nodes[0].bumpmocktime(TXREQUEST_TIME_SKIP)
peer_inbound.wait_for_getdata([int(orphan_wtxid, 16)])

# Both peers send invs for the orphan, so the node can expect both to know its ancestors.
peer_outbound.send_and_ping(msg_inv([orphan_inv]))

# The outbound peer should be preferred for getting orphan parents
peer_inbound.send_and_ping(msg_tx(orphan_tx))
self.nodes[0].bumpmocktime(TXID_RELAY_DELAY)
peer_outbound.wait_for_parent_requests([int(parent_tx.rehash(), 16)])

# There should be no request to the inbound peer
peer_inbound.assert_never_requested(int(parent_tx.rehash(), 16))

self.log.info("Test that, if the preferred peer doesn't respond, the node sends another request")
self.nodes[0].bumpmocktime(ORPHAN_ANCESTOR_GETDATA_INTERVAL)
peer_inbound.sync_with_ping()
peer_inbound.wait_for_parent_requests([int(parent_tx.rehash(), 16)])

@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")
node = self.nodes[0]
orphan_wtxid, orphan_tx, parent_tx = self.create_parent_and_child()
orphan_inv = CInv(t=MSG_WTX, h=int(orphan_wtxid, 16))

# Announces before tx is sent, disconnects while node is requesting parents
peer_early_disconnected = node.add_outbound_p2p_connection(PeerTxRelayer(), p2p_idx=3)
# Announces before tx is sent, doesn't respond to parent request
peer_early_unresponsive = node.add_p2p_connection(PeerTxRelayer())

# Announces after tx is sent
peer_late_announcer = node.add_p2p_connection(PeerTxRelayer())

# Both peers send invs for the orphan, so the node an expect both to know its ancestors.
peer_early_disconnected.send_and_ping(msg_inv([orphan_inv]))
self.nodes[0].bumpmocktime(TXREQUEST_TIME_SKIP)
peer_early_disconnected.wait_for_getdata([int(orphan_wtxid, 16)])
peer_early_unresponsive.send_and_ping(msg_inv([orphan_inv]))
peer_early_disconnected.send_and_ping(msg_tx(orphan_tx))

# Peer disconnects before responding to request
self.nodes[0].bumpmocktime(TXID_RELAY_DELAY)
peer_early_disconnected.wait_for_parent_requests([int(parent_tx.rehash(), 16)])
peer_early_disconnected.peer_disconnect()

# The node should retry with the other peer that announced the orphan earlier.
# This node's request was additionally delayed because it's an inbound peer.
self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY)
peer_early_unresponsive.wait_for_parent_requests([int(parent_tx.rehash(), 16)])

self.log.info("Test that the node uses peers who announce the tx after realizing it's an orphan")
peer_late_announcer.send_and_ping(msg_inv([orphan_inv]))

self.nodes[0].bumpmocktime(ORPHAN_ANCESTOR_GETDATA_INTERVAL)
peer_late_announcer.wait_for_parent_requests([int(parent_tx.rehash(), 16)])

def run_test(self):
self.nodes[0].setmocktime(int(time.time()))
Expand All @@ -641,6 +710,8 @@ def run_test(self):
self.test_same_txid_orphan_of_orphan()
self.test_orphan_txid_inv()
self.test_max_orphan_amount()
self.test_orphan_handling_prefer_outbound()
self.test_announcers_before_and_after()


if __name__ == '__main__':
Expand Down
2 changes: 2 additions & 0 deletions test/functional/test_framework/p2p.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@
OVERLOADED_PEER_TX_DELAY = 2
# How long to wait before downloading a transaction from an additional peer
GETDATA_TX_INTERVAL = 60
# How long to wait before requesting orphan ancpkginfo/parents from an additional peer
ORPHAN_ANCESTOR_GETDATA_INTERVAL = 60

MESSAGEMAP = {
b"addr": msg_addr,
Expand Down

0 comments on commit 780e28c

Please sign in to comment.