p2p_eviction.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2019-2021 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 create_coinbase, 20 ) 21 from test_framework.messages import ( 22 msg_pong, 23 msg_tx, 24 ) 25 from test_framework.p2p import ( 26 P2PDataStore, 27 P2PInterface, 28 ) 29 from test_framework.test_framework import BitcoinTestFramework 30 from test_framework.util import assert_equal 31 from test_framework.wallet import MiniWallet 32 33 34 class SlowP2PDataStore(P2PDataStore): 35 def on_ping(self, message): 36 time.sleep(0.1) 37 self.send_message(msg_pong(message.nonce)) 38 39 40 class SlowP2PInterface(P2PInterface): 41 def on_ping(self, message): 42 time.sleep(0.1) 43 self.send_message(msg_pong(message.nonce)) 44 45 46 class P2PEvict(BitcoinTestFramework): 47 def set_test_params(self): 48 self.num_nodes = 1 49 # The choice of maxconnections=32 results in a maximum of 21 inbound connections 50 # (32 - 10 outbound - 1 feeler). 20 inbound peers are protected from eviction: 51 # 4 by netgroup, 4 that sent us blocks, 4 that sent us transactions and 8 via lowest ping time 52 self.extra_args = [['-maxconnections=32']] 53 54 def run_test(self): 55 protected_peers = set() # peers that we expect to be protected from eviction 56 current_peer = -1 57 node = self.nodes[0] 58 self.wallet = MiniWallet(node) 59 60 self.log.info("Create 4 peers and protect them from eviction by sending us a block") 61 for _ in range(4): 62 block_peer = node.add_p2p_connection(SlowP2PDataStore()) 63 current_peer += 1 64 block_peer.sync_with_ping() 65 best_block = node.getbestblockhash() 66 tip = int(best_block, 16) 67 best_block_time = node.getblock(best_block)['time'] 68 block = create_block(tip, create_coinbase(node.getblockcount() + 1), best_block_time + 1) 69 block.solve() 70 block_peer.send_blocks_and_test([block], node, success=True) 71 protected_peers.add(current_peer) 72 73 self.log.info("Create 5 slow-pinging peers, making them eviction candidates") 74 for _ in range(5): 75 node.add_p2p_connection(SlowP2PInterface()) 76 current_peer += 1 77 78 self.log.info("Create 4 peers and protect them from eviction by sending us a tx") 79 for i in range(4): 80 txpeer = node.add_p2p_connection(SlowP2PInterface()) 81 current_peer += 1 82 txpeer.sync_with_ping() 83 84 tx = self.wallet.create_self_transfer()['tx'] 85 txpeer.send_message(msg_tx(tx)) 86 protected_peers.add(current_peer) 87 88 self.log.info("Create 8 peers and protect them from eviction by having faster pings") 89 for _ in range(8): 90 fastpeer = node.add_p2p_connection(P2PInterface()) 91 current_peer += 1 92 self.wait_until(lambda: "ping" in fastpeer.last_message, timeout=10) 93 94 # Make sure by asking the node what the actual min pings are 95 peerinfo = node.getpeerinfo() 96 pings = {} 97 for i in range(len(peerinfo)): 98 pings[i] = peerinfo[i]['minping'] if 'minping' in peerinfo[i] else 1000000 99 sorted_pings = sorted(pings.items(), key=lambda x: x[1]) 100 101 # Usually the 8 fast peers are protected. In rare case of unreliable pings, 102 # one of the slower peers might have a faster min ping though. 103 for i in range(8): 104 protected_peers.add(sorted_pings[i][0]) 105 106 self.log.info("Create peer that triggers the eviction mechanism") 107 node.add_p2p_connection(SlowP2PInterface()) 108 109 # One of the non-protected peers must be evicted. We can't be sure which one because 110 # 4 peers are protected via netgroup, which is identical for all peers, 111 # and the eviction mechanism doesn't preserve the order of identical elements. 112 evicted_peers = [] 113 for i in range(len(node.p2ps)): 114 if not node.p2ps[i].is_connected: 115 evicted_peers.append(i) 116 117 self.log.info("Test that one peer was evicted") 118 self.log.debug("{} evicted peer: {}".format(len(evicted_peers), set(evicted_peers))) 119 assert_equal(len(evicted_peers), 1) 120 121 self.log.info("Test that no peer expected to be protected was evicted") 122 self.log.debug("{} protected peers: {}".format(len(protected_peers), protected_peers)) 123 assert evicted_peers[0] not in protected_peers 124 125 126 if __name__ == '__main__': 127 P2PEvict().main()