Skip to content

Commit 846a138

Browse files
committed
func test: Expand tx download preference tests
1. Check that outbound nodes are treated the same as whitelisted connections for the purposes of getdata delays 2. Add test case that demonstrates download retries are preferentially given to outbound (preferred) connections even when multiple announcements are considered ready.
1 parent 22723c8 commit 846a138

File tree

1 file changed

+87
-11
lines changed

1 file changed

+87
-11
lines changed

test/functional/p2p_tx_download.py

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
Test transaction download behavior
77
"""
88
from decimal import Decimal
9+
from enum import Enum
910
import time
1011

1112
from test_framework.mempool_util import (
@@ -44,17 +45,26 @@ def on_getdata(self, message):
4445

4546
# Constants from net_processing
4647
GETDATA_TX_INTERVAL = 60 # seconds
47-
INBOUND_PEER_TX_DELAY = 2 # seconds
48+
NONPREF_PEER_TX_DELAY = 2 # seconds
49+
INBOUND_PEER_TX_DELAY = NONPREF_PEER_TX_DELAY # inbound is non-preferred
4850
TXID_RELAY_DELAY = 2 # seconds
4951
OVERLOADED_PEER_DELAY = 2 # seconds
5052
MAX_GETDATA_IN_FLIGHT = 100
5153
MAX_PEER_TX_ANNOUNCEMENTS = 5000
52-
NONPREF_PEER_TX_DELAY = 2
5354

5455
# Python test constants
5556
NUM_INBOUND = 10
5657
MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + INBOUND_PEER_TX_DELAY + TXID_RELAY_DELAY
5758

59+
class ConnectionType(Enum):
60+
""" Different connection types
61+
1. INBOUND: Incoming connection, not whitelisted
62+
2. OUTBOUND: Outgoing connection
63+
3. WHITELIST: Incoming connection, but whitelisted
64+
"""
65+
INBOUND = 0
66+
OUTBOUND = 1
67+
WHITELIST = 2
5868

5969
class TxDownloadTest(BitcoinTestFramework):
6070
def set_test_params(self):
@@ -193,25 +203,90 @@ def test_notfound_fallback(self):
193203
peer_notfound.send_and_ping(msg_notfound(vec=[CInv(MSG_WTX, WTXID)])) # Send notfound, so that fallback peer is selected
194204
peer_fallback.wait_until(lambda: peer_fallback.tx_getdata_count >= 1, timeout=1)
195205

196-
def test_preferred_inv(self, preferred=False):
197-
if preferred:
198-
self.log.info('Check invs from preferred peers are downloaded immediately')
206+
def test_preferred_inv(self, connection_type: ConnectionType):
207+
if connection_type == ConnectionType.WHITELIST:
208+
self.log.info('Check invs from preferred (whitelisted) peers are downloaded immediately')
199209
self.restart_node(0, extra_args=['[email protected]'])
200-
else:
210+
elif connection_type == ConnectionType.OUTBOUND:
211+
self.log.info('Check invs from preferred (outbound) peers are downloaded immediately')
212+
self.restart_node(0)
213+
elif connection_type == ConnectionType.INBOUND:
201214
self.log.info('Check invs from non-preferred peers are downloaded after {} s'.format(NONPREF_PEER_TX_DELAY))
215+
self.restart_node(0)
216+
else:
217+
raise Exception("invalid connection_type")
218+
202219
mock_time = int(time.time() + 1)
203220
self.nodes[0].setmocktime(mock_time)
204-
peer = self.nodes[0].add_p2p_connection(TestP2PConn())
221+
222+
if connection_type == ConnectionType.OUTBOUND:
223+
peer = self.nodes[0].add_outbound_p2p_connection(
224+
TestP2PConn(), wait_for_verack=True, p2p_idx=1, connection_type="outbound-full-relay")
225+
else:
226+
peer = self.nodes[0].add_p2p_connection(TestP2PConn())
227+
205228
peer.send_message(msg_inv([CInv(t=MSG_WTX, h=0xff00ff00)]))
206229
peer.sync_with_ping()
207-
if preferred:
230+
if connection_type != ConnectionType.INBOUND:
208231
peer.wait_until(lambda: peer.tx_getdata_count >= 1, timeout=1)
209232
else:
210233
with p2p_lock:
211234
assert_equal(peer.tx_getdata_count, 0)
212235
self.nodes[0].setmocktime(mock_time + NONPREF_PEER_TX_DELAY)
213236
peer.wait_until(lambda: peer.tx_getdata_count >= 1, timeout=1)
214237

238+
def test_preferred_tiebreaker_inv(self):
239+
self.log.info("Test that preferred peers are always selected over non-preferred when ready")
240+
241+
self.restart_node(0)
242+
self.nodes[0].setmocktime(int(time.time()))
243+
244+
# Peer that is immediately asked, but never responds.
245+
# This will set us up to have two ready requests, one
246+
# of which is preferred and one which is not
247+
unresponsive_peer = self.nodes[0].add_outbound_p2p_connection(
248+
TestP2PConn(), wait_for_verack=True, p2p_idx=0, connection_type="outbound-full-relay")
249+
unresponsive_peer.send_message(msg_inv([CInv(t=MSG_WTX, h=0xff00ff00)]))
250+
unresponsive_peer.sync_with_ping()
251+
unresponsive_peer.wait_until(lambda: unresponsive_peer.tx_getdata_count >= 1, timeout=1)
252+
253+
# A bunch of incoming (non-preferred) connections that advertise the same tx
254+
non_pref_peers = []
255+
NUM_INBOUND = 10
256+
for _ in range(NUM_INBOUND):
257+
non_pref_peers.append(self.nodes[0].add_p2p_connection(TestP2PConn()))
258+
non_pref_peers[-1].send_message(msg_inv([CInv(t=MSG_WTX, h=0xff00ff00)]))
259+
non_pref_peers[-1].sync_with_ping()
260+
261+
# Check that no request made due to in-flight
262+
self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY)
263+
with p2p_lock:
264+
for peer in non_pref_peers:
265+
assert_equal(peer.tx_getdata_count, 0)
266+
267+
# Now add another outbound (preferred) which is immediately ready for consideration
268+
# upon advertisement
269+
pref_peer = self.nodes[0].add_outbound_p2p_connection(
270+
TestP2PConn(), wait_for_verack=True, p2p_idx=1, connection_type="outbound-full-relay")
271+
pref_peer.send_message(msg_inv([CInv(t=MSG_WTX, h=0xff00ff00)]))
272+
273+
assert_equal(len(self.nodes[0].getpeerinfo()), NUM_INBOUND + 2)
274+
275+
# Still have to wait for in-flight to timeout
276+
with p2p_lock:
277+
assert_equal(pref_peer.tx_getdata_count, 0)
278+
279+
# Timeout in-flight
280+
self.nodes[0].bumpmocktime(GETDATA_TX_INTERVAL - NONPREF_PEER_TX_DELAY)
281+
282+
# Preferred peers are *always* selected next if ready
283+
pref_peer.wait_until(lambda: pref_peer.tx_getdata_count >= 1, timeout=10)
284+
285+
# And none for non-preferred
286+
for non_pref_peer in non_pref_peers:
287+
with p2p_lock:
288+
assert_equal(non_pref_peer.tx_getdata_count, 0)
289+
215290
def test_txid_inv_delay(self, glob_wtxid=False):
216291
self.log.info('Check that inv from a txid-relay peers are delayed by {} s, with a wtxid peer {}'.format(TXID_RELAY_DELAY, glob_wtxid))
217292
self.restart_node(0, extra_args=['[email protected]'])
@@ -277,8 +352,10 @@ def run_test(self):
277352
self.test_expiry_fallback()
278353
self.test_disconnect_fallback()
279354
self.test_notfound_fallback()
280-
self.test_preferred_inv()
281-
self.test_preferred_inv(True)
355+
self.test_preferred_tiebreaker_inv()
356+
self.test_preferred_inv(ConnectionType.INBOUND)
357+
self.test_preferred_inv(ConnectionType.OUTBOUND)
358+
self.test_preferred_inv(ConnectionType.WHITELIST)
282359
self.test_txid_inv_delay()
283360
self.test_txid_inv_delay(True)
284361
self.test_large_inv_batch()
@@ -304,6 +381,5 @@ def run_test(self):
304381
self.log.info("Nodes are setup with {} incoming connections each".format(NUM_INBOUND))
305382
test()
306383

307-
308384
if __name__ == '__main__':
309385
TxDownloadTest(__file__).main()

0 commit comments

Comments
 (0)