diff --git a/test/functional/p2p_tx_download.py b/test/functional/p2p_tx_download.py index c69d6ff405aeb2..3dd75aeefeb7d1 100755 --- a/test/functional/p2p_tx_download.py +++ b/test/functional/p2p_tx_download.py @@ -44,12 +44,12 @@ def on_getdata(self, message): # Constants from net_processing GETDATA_TX_INTERVAL = 60 # seconds -INBOUND_PEER_TX_DELAY = 2 # seconds +NONPREF_PEER_TX_DELAY = 2 # seconds +INBOUND_PEER_TX_DELAY = NONPREF_PEER_TX_DELAY # inbound is non-preferred TXID_RELAY_DELAY = 2 # seconds OVERLOADED_PEER_DELAY = 2 # seconds MAX_GETDATA_IN_FLIGHT = 100 MAX_PEER_TX_ANNOUNCEMENTS = 5000 -NONPREF_PEER_TX_DELAY = 2 # Python test constants NUM_INBOUND = 10 @@ -193,18 +193,31 @@ def test_notfound_fallback(self): peer_notfound.send_and_ping(msg_notfound(vec=[CInv(MSG_WTX, WTXID)])) # Send notfound, so that fallback peer is selected peer_fallback.wait_until(lambda: peer_fallback.tx_getdata_count >= 1, timeout=1) - def test_preferred_inv(self, preferred=False): - if preferred: - self.log.info('Check invs from preferred peers are downloaded immediately') + def test_preferred_inv(self, preferred_type): + if preferred_type == "whitelist": + self.log.info('Check invs from preferred (whitelisted) peers are downloaded immediately') self.restart_node(0, extra_args=['-whitelist=noban@127.0.0.1']) - else: + elif preferred_type == "outbound": + self.log.info('Check invs from preferred (outbound) peers are downloaded immediately') + self.restart_node(0) + elif preferred_type is None: self.log.info('Check invs from non-preferred peers are downloaded after {} s'.format(NONPREF_PEER_TX_DELAY)) + self.restart_node(0) + else: + raise Exception("invalid preferred_type") + mock_time = int(time.time() + 1) self.nodes[0].setmocktime(mock_time) - peer = self.nodes[0].add_p2p_connection(TestP2PConn()) + + if preferred_type == "outbound": + peer = self.nodes[0].add_outbound_p2p_connection( + TestP2PConn(), wait_for_verack=True, p2p_idx=1, connection_type="outbound-full-relay") + else: + peer = self.nodes[0].add_p2p_connection(TestP2PConn()) + peer.send_message(msg_inv([CInv(t=MSG_WTX, h=0xff00ff00)])) peer.sync_with_ping() - if preferred: + if preferred_type: peer.wait_until(lambda: peer.tx_getdata_count >= 1, timeout=1) else: with p2p_lock: @@ -212,6 +225,58 @@ def test_preferred_inv(self, preferred=False): self.nodes[0].setmocktime(mock_time + NONPREF_PEER_TX_DELAY) peer.wait_until(lambda: peer.tx_getdata_count >= 1, timeout=1) + def test_preferred_tiebreaker_inv(self): + self.log.info("Test that preferred peers are always selected over non-preferred when ready") + + self.restart_node(0) + self.nodes[0].setmocktime(int(time.time())) + + # Peer that is immediately asked, but never responds. + # This will set us up to have two ready requests, one + # of which is preferred and one which is not + unresponsive_peer = self.nodes[0].add_outbound_p2p_connection( + TestP2PConn(), wait_for_verack=True, p2p_idx=0, connection_type="outbound-full-relay") + unresponsive_peer.send_message(msg_inv([CInv(t=MSG_WTX, h=0xff00ff00)])) + unresponsive_peer.sync_with_ping() + unresponsive_peer.wait_until(lambda: unresponsive_peer.tx_getdata_count >= 1, timeout=1) + + # A bunch of incoming (non-preferred) connections that advertise the same tx + non_pref_peers = [] + NUM_INBOUND = 10 + for _ in range(NUM_INBOUND): + non_pref_peers.append(self.nodes[0].add_p2p_connection(TestP2PConn())) + non_pref_peers[-1].send_message(msg_inv([CInv(t=MSG_WTX, h=0xff00ff00)])) + non_pref_peers[-1].sync_with_ping() + + # Check that no request made due to in-flight + self.nodes[0].bumpmocktime(NONPREF_PEER_TX_DELAY) + with p2p_lock: + for peer in non_pref_peers: + assert_equal(peer.tx_getdata_count, 0) + + # Now add another outbound (preferred) which is immediately ready for consideration + # upon advertisement + pref_peer = self.nodes[0].add_outbound_p2p_connection( + TestP2PConn(), wait_for_verack=True, p2p_idx=1, connection_type="outbound-full-relay") + pref_peer.send_message(msg_inv([CInv(t=MSG_WTX, h=0xff00ff00)])) + + assert_equal(len(self.nodes[0].getpeerinfo()), NUM_INBOUND + 2) + + # Still have to wait for in-flight to timeout + with p2p_lock: + assert_equal(pref_peer.tx_getdata_count, 0) + + # Timeout in-flight + self.nodes[0].bumpmocktime(GETDATA_TX_INTERVAL - NONPREF_PEER_TX_DELAY) + + # Preferred peers are *always* selected next if ready + pref_peer.wait_until(lambda: pref_peer.tx_getdata_count >= 1, timeout=10) + + # And none for non-preferred + for non_pref_peer in non_pref_peers: + with p2p_lock: + assert_equal(non_pref_peer.tx_getdata_count, 0) + def test_txid_inv_delay(self, glob_wtxid=False): 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)) self.restart_node(0, extra_args=['-whitelist=noban@127.0.0.1']) @@ -277,8 +342,10 @@ def run_test(self): self.test_expiry_fallback() self.test_disconnect_fallback() self.test_notfound_fallback() - self.test_preferred_inv() - self.test_preferred_inv(True) + self.test_preferred_tiebreaker_inv() + self.test_preferred_inv(None) + self.test_preferred_inv("outbound") + self.test_preferred_inv("whitelist") self.test_txid_inv_delay() self.test_txid_inv_delay(True) self.test_large_inv_batch() @@ -304,6 +371,5 @@ def run_test(self): self.log.info("Nodes are setup with {} incoming connections each".format(NUM_INBOUND)) test() - if __name__ == '__main__': TxDownloadTest(__file__).main()