66Test transaction download behavior
77"""
88from decimal import Decimal
9+ from enum import Enum
910import time
1011
1112from test_framework .mempool_util import (
@@ -44,17 +45,26 @@ def on_getdata(self, message):
4445
4546# Constants from net_processing
4647GETDATA_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
4850TXID_RELAY_DELAY = 2 # seconds
4951OVERLOADED_PEER_DELAY = 2 # seconds
5052MAX_GETDATA_IN_FLIGHT = 100
5153MAX_PEER_TX_ANNOUNCEMENTS = 5000
52- NONPREF_PEER_TX_DELAY = 2
5354
5455# Python test constants
5556NUM_INBOUND = 10
5657MAX_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
5969class 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-
339415if __name__ == '__main__' :
340416 TxDownloadTest (__file__ ).main ()
0 commit comments