Skip to content

Commit 1334ca6

Browse files
committed
Merge bitcoin#31437: func test: Expand tx download preference tests
846a138 func test: Expand tx download preference tests (Greg Sanders) Pull request description: 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. `NUM_INBOUND` is a magic number large enough that it should fail over 90% of the time if the underlying outbound->preferred->PriorityComputer logic was broken. Bumping this to 100 peers cost another 14 seconds locally for the sub-test, so I made it pretty small. ACKs for top commit: i-am-yuvi: tACK 846a138 good catch maflcko: ACK 846a138 🍕 marcofleon: lgtm ACK 846a138 Tree-SHA512: 337aa4dc33b5c0abeb4fe7e4cd5e389f7f53ae25dd991ba26615c16999872542391993020122fd255af4c7163f76c1d1feb2f2d6114f12a364c0360d4d52b8c3
2 parents 33932d3 + 846a138 commit 1334ca6

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]'])
@@ -307,8 +382,10 @@ def run_test(self):
307382
self.test_expiry_fallback()
308383
self.test_disconnect_fallback()
309384
self.test_notfound_fallback()
310-
self.test_preferred_inv()
311-
self.test_preferred_inv(True)
385+
self.test_preferred_tiebreaker_inv()
386+
self.test_preferred_inv(ConnectionType.INBOUND)
387+
self.test_preferred_inv(ConnectionType.OUTBOUND)
388+
self.test_preferred_inv(ConnectionType.WHITELIST)
312389
self.test_txid_inv_delay()
313390
self.test_txid_inv_delay(True)
314391
self.test_large_inv_batch()
@@ -335,6 +412,5 @@ def run_test(self):
335412
self.log.info("Nodes are setup with {} incoming connections each".format(NUM_INBOUND))
336413
test()
337414

338-
339415
if __name__ == '__main__':
340416
TxDownloadTest(__file__).main()

0 commit comments

Comments
 (0)