6
6
Test transaction download behavior
7
7
"""
8
8
from decimal import Decimal
9
+ from enum import Enum
9
10
import time
10
11
11
12
from test_framework .mempool_util import (
@@ -44,17 +45,26 @@ def on_getdata(self, message):
44
45
45
46
# Constants from net_processing
46
47
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
48
50
TXID_RELAY_DELAY = 2 # seconds
49
51
OVERLOADED_PEER_DELAY = 2 # seconds
50
52
MAX_GETDATA_IN_FLIGHT = 100
51
53
MAX_PEER_TX_ANNOUNCEMENTS = 5000
52
- NONPREF_PEER_TX_DELAY = 2
53
54
54
55
# Python test constants
55
56
NUM_INBOUND = 10
56
57
MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + INBOUND_PEER_TX_DELAY + TXID_RELAY_DELAY
57
58
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
58
68
59
69
class TxDownloadTest (BitcoinTestFramework ):
60
70
def set_test_params (self ):
@@ -193,25 +203,90 @@ def test_notfound_fallback(self):
193
203
peer_notfound .send_and_ping (msg_notfound (vec = [CInv (MSG_WTX , WTXID )])) # Send notfound, so that fallback peer is selected
194
204
peer_fallback .wait_until (lambda : peer_fallback .tx_getdata_count >= 1 , timeout = 1 )
195
205
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' )
199
209
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 :
201
214
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
+
202
219
mock_time = int (time .time () + 1 )
203
220
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
+
205
228
peer .send_message (msg_inv ([CInv (t = MSG_WTX , h = 0xff00ff00 )]))
206
229
peer .sync_with_ping ()
207
- if preferred :
230
+ if connection_type != ConnectionType . INBOUND :
208
231
peer .wait_until (lambda : peer .tx_getdata_count >= 1 , timeout = 1 )
209
232
else :
210
233
with p2p_lock :
211
234
assert_equal (peer .tx_getdata_count , 0 )
212
235
self .nodes [0 ].setmocktime (mock_time + NONPREF_PEER_TX_DELAY )
213
236
peer .wait_until (lambda : peer .tx_getdata_count >= 1 , timeout = 1 )
214
237
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
+
215
290
def test_txid_inv_delay (self , glob_wtxid = False ):
216
291
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 ))
217
292
self .
restart_node (
0 ,
extra_args = [
'[email protected] ' ])
@@ -277,8 +352,10 @@ def run_test(self):
277
352
self .test_expiry_fallback ()
278
353
self .test_disconnect_fallback ()
279
354
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 )
282
359
self .test_txid_inv_delay ()
283
360
self .test_txid_inv_delay (True )
284
361
self .test_large_inv_batch ()
@@ -304,6 +381,5 @@ def run_test(self):
304
381
self .log .info ("Nodes are setup with {} incoming connections each" .format (NUM_INBOUND ))
305
382
test ()
306
383
307
-
308
384
if __name__ == '__main__' :
309
385
TxDownloadTest (__file__ ).main ()
0 commit comments