/ test / functional / p2p_eviction.py
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()