p2p_eviction.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2019-present The Bitcoin Core developers 3 # Distributed under the MIT software license, see the accompanying 4 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 6 """ Test node eviction logic 7 8 When the number of peers has reached the limit of maximum connections, 9 the next connecting inbound peer will trigger the eviction mechanism. 10 We cannot currently test the parts of the eviction logic that are based on 11 address/netgroup since in the current framework, all peers are connecting from 12 the same local address. See Issue #14210 for more info. 13 Therefore, this test is limited to the remaining protection criteria. 14 """ 15 import time 16 17 from test_framework.blocktools import ( 18 create_block, 19 ) 20 from test_framework.messages import ( 21 msg_pong, 22 msg_tx, 23 ) 24 from test_framework.p2p import ( 25 P2PDataStore, 26 P2PInterface, 27 ) 28 from test_framework.test_framework import BitcoinTestFramework 29 from test_framework.util import assert_equal 30 from test_framework.wallet import MiniWallet 31 32 33 class SlowP2PDataStore(P2PDataStore): 34 def on_ping(self, message): 35 time.sleep(0.1) 36 self.send_without_ping(msg_pong(message.nonce)) 37 38 39 class SlowP2PInterface(P2PInterface): 40 def on_ping(self, message): 41 time.sleep(0.1) 42 self.send_without_ping(msg_pong(message.nonce)) 43 44 45 class P2PEvict(BitcoinTestFramework): 46 def set_test_params(self): 47 self.num_nodes = 1 48 # The choice of maxconnections=32 results in a maximum of 21 inbound connections 49 # (32 - 10 outbound - 1 feeler). 20 inbound peers are protected from eviction: 50 # 4 by netgroup, 4 that sent us blocks, 4 that sent us transactions and 8 via lowest ping time 51 self.extra_args = [['-maxconnections=32']] 52 53 def run_test(self): 54 protected_peers = set() # peers that we expect to be protected from eviction 55 current_peer = -1 56 node = self.nodes[0] 57 self.wallet = MiniWallet(node) 58 59 self.log.info("Create 4 peers and protect them from eviction by sending us a block") 60 for _ in range(4): 61 block_peer = node.add_p2p_connection(SlowP2PDataStore()) 62 current_peer += 1 63 block_peer.sync_with_ping() 64 best_block = node.getbestblockhash() 65 tip = int(best_block, 16) 66 best_block_time = node.getblock(best_block)['time'] 67 block = create_block(tip, height=node.getblockcount() + 1, ntime=best_block_time + 1) 68 block.solve() 69 block_peer.send_blocks_and_test([block], node, success=True) 70 protected_peers.add(current_peer) 71 72 self.log.info("Create 5 slow-pinging peers, making them eviction candidates") 73 for _ in range(5): 74 node.add_p2p_connection(SlowP2PInterface()) 75 current_peer += 1 76 77 self.log.info("Create 4 peers and protect them from eviction by sending us a tx") 78 for i in range(4): 79 txpeer = node.add_p2p_connection(SlowP2PInterface()) 80 current_peer += 1 81 txpeer.sync_with_ping() 82 83 tx = self.wallet.create_self_transfer()['tx'] 84 txpeer.send_without_ping(msg_tx(tx)) 85 protected_peers.add(current_peer) 86 87 self.log.info("Create 8 peers and protect them from eviction by having faster pings") 88 for _ in range(8): 89 fastpeer = node.add_p2p_connection(P2PInterface()) 90 current_peer += 1 91 self.wait_until(lambda: "ping" in fastpeer.last_message, timeout=10) 92 93 # Make sure by asking the node what the actual min pings are 94 peerinfo = node.getpeerinfo() 95 pings = {} 96 for i in range(len(peerinfo)): 97 pings[i] = peerinfo[i]['minping'] if 'minping' in peerinfo[i] else 1000000 98 sorted_pings = sorted(pings.items(), key=lambda x: x[1]) 99 100 # Usually the 8 fast peers are protected. In rare case of unreliable pings, 101 # one of the slower peers might have a faster min ping though. 102 for i in range(8): 103 protected_peers.add(sorted_pings[i][0]) 104 105 self.log.info("Create peer that triggers the eviction mechanism") 106 node.add_p2p_connection(SlowP2PInterface()) 107 108 # One of the non-protected peers must be evicted. We can't be sure which one because 109 # 4 peers are protected via netgroup, which is identical for all peers, 110 # and the eviction mechanism doesn't preserve the order of identical elements. 111 evicted_peers = [] 112 for i in range(len(node.p2ps)): 113 if not node.p2ps[i].is_connected: 114 evicted_peers.append(i) 115 116 self.log.info("Test that one peer was evicted") 117 self.log.debug("{} evicted peer: {}".format(len(evicted_peers), set(evicted_peers))) 118 assert_equal(len(evicted_peers), 1) 119 120 self.log.info("Test that no peer expected to be protected was evicted") 121 self.log.debug("{} protected peers: {}".format(len(protected_peers), protected_peers)) 122 assert evicted_peers[0] not in protected_peers 123 124 125 if __name__ == '__main__': 126 P2PEvict(__file__).main()