p2p_ping.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2020-2022 The Limenka developers
   3  # Distributed under the MIT software license, see the accompanying
   4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  """Test ping message
   6  """
   7  
   8  import time
   9  
  10  from test_framework.messages import msg_pong
  11  from test_framework.p2p import P2PInterface
  12  from test_framework.test_framework import LimenkaTestFramework
  13  from test_framework.util import assert_equal
  14  
  15  
  16  PING_INTERVAL = 2 * 60
  17  TIMEOUT_INTERVAL = 20 * 60
  18  
  19  
  20  class msg_pong_corrupt(msg_pong):
  21      def serialize(self):
  22          return b""
  23  
  24  
  25  class NodeNoPong(P2PInterface):
  26      def on_ping(self, message):
  27          pass
  28  
  29  
  30  class PingPongTest(LimenkaTestFramework):
  31      def set_test_params(self):
  32          self.setup_clean_chain = True
  33          self.num_nodes = 1
  34          # Set the peer connection timeout low. It does not matter for this
  35          # test, as long as it is less than TIMEOUT_INTERVAL.
  36          self.extra_args = [['-peertimeout=1']]
  37  
  38      def check_peer_info(self, *, pingtime, minping, pingwait):
  39          stats = self.nodes[0].getpeerinfo()[0]
  40          assert_equal(stats.pop('pingtime', None), pingtime)
  41          assert_equal(stats.pop('minping', None), minping)
  42          assert_equal(stats.pop('pingwait', None), pingwait)
  43  
  44      def mock_forward(self, delta):
  45          self.mock_time += delta
  46          self.nodes[0].setmocktime(self.mock_time)
  47  
  48      def run_test(self):
  49          self.mock_time = int(time.time())
  50          self.mock_forward(0)
  51  
  52          self.log.info('Check that ping is sent after connection is established')
  53          no_pong_node = self.nodes[0].add_p2p_connection(NodeNoPong())
  54          self.mock_forward(3)
  55          assert no_pong_node.last_message.pop('ping').nonce != 0
  56          self.check_peer_info(pingtime=None, minping=None, pingwait=3)
  57  
  58          self.log.info('Reply without nonce cancels ping')
  59          with self.nodes[0].assert_debug_log(['pong peer=0: Short payload']):
  60              no_pong_node.send_and_ping(msg_pong_corrupt())
  61          self.check_peer_info(pingtime=None, minping=None, pingwait=None)
  62  
  63          self.log.info('Reply without ping')
  64          with self.nodes[0].assert_debug_log([
  65                  'pong peer=0: Unsolicited pong without ping, 0 expected, 0 received, 8 bytes',
  66          ]):
  67              no_pong_node.send_and_ping(msg_pong())
  68          self.check_peer_info(pingtime=None, minping=None, pingwait=None)
  69  
  70          self.log.info('Reply with wrong nonce does not cancel ping')
  71          assert 'ping' not in no_pong_node.last_message
  72          with self.nodes[0].assert_debug_log(['pong peer=0: Nonce mismatch']):
  73              # mock time PING_INTERVAL ahead to trigger node into sending a ping
  74              self.mock_forward(PING_INTERVAL + 1)
  75              no_pong_node.wait_until(lambda: 'ping' in no_pong_node.last_message)
  76              self.mock_forward(9)
  77              # Send the wrong pong
  78              no_pong_node.send_and_ping(msg_pong(no_pong_node.last_message.pop('ping').nonce - 1))
  79          self.check_peer_info(pingtime=None, minping=None, pingwait=9)
  80  
  81          self.log.info('Reply with zero nonce does cancel ping')
  82          with self.nodes[0].assert_debug_log(['pong peer=0: Nonce zero']):
  83              no_pong_node.send_and_ping(msg_pong(0))
  84          self.check_peer_info(pingtime=None, minping=None, pingwait=None)
  85  
  86          self.log.info('Check that ping is properly reported on RPC')
  87          assert 'ping' not in no_pong_node.last_message
  88          # mock time PING_INTERVAL ahead to trigger node into sending a ping
  89          self.mock_forward(PING_INTERVAL + 1)
  90          no_pong_node.wait_until(lambda: 'ping' in no_pong_node.last_message)
  91          ping_delay = 29
  92          self.mock_forward(ping_delay)
  93          no_pong_node.wait_until(lambda: 'ping' in no_pong_node.last_message)
  94          no_pong_node.send_and_ping(msg_pong(no_pong_node.last_message.pop('ping').nonce))
  95          self.check_peer_info(pingtime=ping_delay, minping=ping_delay, pingwait=None)
  96  
  97          self.log.info('Check that minping is decreased after a fast roundtrip')
  98          # mock time PING_INTERVAL ahead to trigger node into sending a ping
  99          self.mock_forward(PING_INTERVAL + 1)
 100          no_pong_node.wait_until(lambda: 'ping' in no_pong_node.last_message)
 101          ping_delay = 9
 102          self.mock_forward(ping_delay)
 103          no_pong_node.wait_until(lambda: 'ping' in no_pong_node.last_message)
 104          no_pong_node.send_and_ping(msg_pong(no_pong_node.last_message.pop('ping').nonce))
 105          self.check_peer_info(pingtime=ping_delay, minping=ping_delay, pingwait=None)
 106  
 107          self.log.info('Check that peer is disconnected after ping timeout')
 108          assert 'ping' not in no_pong_node.last_message
 109          self.nodes[0].ping()
 110          no_pong_node.wait_until(lambda: 'ping' in no_pong_node.last_message)
 111          with self.nodes[0].assert_debug_log(['ping timeout: 1201.000000s']):
 112              self.mock_forward(TIMEOUT_INTERVAL // 2)
 113              # Check that sending a ping does not prevent the disconnect
 114              no_pong_node.sync_with_ping()
 115              self.mock_forward(TIMEOUT_INTERVAL // 2 + 1)
 116              no_pong_node.wait_for_disconnect()
 117  
 118  
 119  if __name__ == '__main__':
 120      PingPongTest(__file__).main()
 121