feature_asmap.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2020-2021 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 asmap config argument for ASN-based IP bucketing.
   6  
   7  Verify node behaviour and debug log when launching limenkad in these cases:
   8  
   9  1. `limenkad` with no -asmap arg, using /16 prefix for IP bucketing
  10  
  11  2. `limenkad -asmap=<absolute path>`, using the unit test skeleton asmap
  12  
  13  3. `limenkad -asmap=<relative path>`, using the unit test skeleton asmap
  14  
  15  4. `limenkad -asmap/-asmap=` with no file specified, using the default asmap
  16  
  17  5. `limenkad -asmap` restart with an addrman containing new and tried entries
  18  
  19  6. `limenkad -asmap` with no file specified and a missing default asmap file
  20  
  21  7. `limenkad -asmap` with an empty (unparsable) default asmap file
  22  
  23  The tests are order-independent.
  24  
  25  """
  26  import os
  27  import shutil
  28  
  29  from test_framework.test_framework import LimenkaTestFramework
  30  from test_framework.util import assert_equal
  31  
  32  DEFAULT_ASMAP_FILENAME = 'ip_asn.map' # defined in src/init.cpp
  33  ASMAP = 'src/test/data/asmap.raw' # path to unit test skeleton asmap
  34  VERSION = 'fec61fa21a9f46f3b17bdcd660d7f4cd90b966aad3aec593c99b35f0aca15853'
  35  
  36  def expected_messages(filename):
  37      return [f'Opened asmap file "{filename}" (59 bytes) from disk',
  38              f'Using asmap version {VERSION} for IP bucketing']
  39  
  40  class AsmapTest(LimenkaTestFramework):
  41      def set_test_params(self):
  42          self.num_nodes = 1
  43          # Do addrman checks on all operations and use deterministic addrman
  44          self.extra_args = [["-checkaddrman=1", "-test=addrman"]]
  45  
  46      def fill_addrman(self, node_id):
  47          """Add 2 tried addresses to the addrman, followed by 2 new addresses."""
  48          for addr, tried in [[0, True], [1, True], [2, False], [3, False]]:
  49              self.nodes[node_id].addpeeraddress(address=f"101.{addr}.0.0", tried=tried, port=8333)
  50  
  51      def test_without_asmap_arg(self):
  52          self.log.info('Test limenkad with no -asmap arg passed')
  53          self.stop_node(0)
  54          with self.node.assert_debug_log(['Using /16 prefix for IP bucketing']):
  55              self.start_node(0)
  56  
  57      def test_noasmap_arg(self):
  58          self.log.info('Test limenkad with -noasmap arg passed')
  59          self.stop_node(0)
  60          with self.node.assert_debug_log(['Using /16 prefix for IP bucketing']):
  61              self.start_node(0, ["-noasmap"])
  62  
  63      def test_asmap_with_absolute_path(self):
  64          self.log.info('Test limenkad -asmap=<absolute path>')
  65          self.stop_node(0)
  66          filename = os.path.join(self.datadir, 'my-map-file.map')
  67          shutil.copyfile(self.asmap_raw, filename)
  68          with self.node.assert_debug_log(expected_messages(filename)):
  69              self.start_node(0, [f'-asmap={filename}'])
  70          os.remove(filename)
  71  
  72      def test_asmap_with_relative_path(self):
  73          self.log.info('Test limenkad -asmap=<relative path>')
  74          self.stop_node(0)
  75          name = 'ASN_map'
  76          filename = os.path.join(self.datadir, name)
  77          shutil.copyfile(self.asmap_raw, filename)
  78          with self.node.assert_debug_log(expected_messages(filename)):
  79              self.start_node(0, [f'-asmap={name}'])
  80          os.remove(filename)
  81  
  82      def test_default_asmap(self):
  83          shutil.copyfile(self.asmap_raw, self.default_asmap)
  84          for arg in ['-asmap', '-asmap=']:
  85              self.log.info(f'Test limenkad {arg} (using default map file)')
  86              self.stop_node(0)
  87              with self.node.assert_debug_log(expected_messages(self.default_asmap)):
  88                  self.start_node(0, [arg])
  89          os.remove(self.default_asmap)
  90  
  91      def test_asmap_interaction_with_addrman_containing_entries(self):
  92          self.log.info("Test limenkad -asmap restart with addrman containing new and tried entries")
  93          self.stop_node(0)
  94          shutil.copyfile(self.asmap_raw, self.default_asmap)
  95          self.start_node(0, ["-asmap", "-checkaddrman=1", "-test=addrman"])
  96          self.fill_addrman(node_id=0)
  97          self.restart_node(0, ["-asmap", "-checkaddrman=1", "-test=addrman"])
  98          with self.node.assert_debug_log(
  99              expected_msgs=[
 100                  "CheckAddrman: new 2, tried 2, total 4 started",
 101                  "CheckAddrman: completed",
 102              ]
 103          ):
 104              self.node.getnodeaddresses()  # getnodeaddresses re-runs the addrman checks
 105          os.remove(self.default_asmap)
 106  
 107      def test_default_asmap_with_missing_file(self):
 108          self.log.info('Test limenkad -asmap with missing default map file')
 109          self.stop_node(0)
 110          msg = f"Error: Could not find asmap file \"{self.default_asmap}\""
 111          self.node.assert_start_raises_init_error(extra_args=['-asmap'], expected_msg=msg)
 112  
 113      def test_empty_asmap(self):
 114          self.log.info('Test limenkad -asmap with empty map file')
 115          self.stop_node(0)
 116          with open(self.default_asmap, "w", encoding="utf-8") as f:
 117              f.write("")
 118          msg = f"Error: Could not parse asmap file \"{self.default_asmap}\""
 119          self.node.assert_start_raises_init_error(extra_args=['-asmap'], expected_msg=msg)
 120          os.remove(self.default_asmap)
 121  
 122      def test_asmap_health_check(self):
 123          self.log.info('Test limenkad -asmap logs ASMap Health Check with basic stats')
 124          shutil.copyfile(self.asmap_raw, self.default_asmap)
 125          msg = "ASMap Health Check: 4 clearnet peers are mapped to 3 ASNs with 0 peers being unmapped"
 126          with self.node.assert_debug_log(expected_msgs=[msg]):
 127              self.start_node(0, extra_args=['-asmap'])
 128          raw_addrman = self.node.getrawaddrman()
 129          asns = []
 130          for _, entries in raw_addrman.items():
 131              for _, entry in entries.items():
 132                  asn = entry['mapped_as']
 133                  if asn not in asns:
 134                      asns.append(asn)
 135          assert_equal(len(asns), 3)
 136          os.remove(self.default_asmap)
 137  
 138      def run_test(self):
 139          self.node = self.nodes[0]
 140          self.datadir = self.node.chain_path
 141          self.default_asmap = os.path.join(self.datadir, DEFAULT_ASMAP_FILENAME)
 142          base_dir = self.config["environment"]["SRCDIR"]
 143          self.asmap_raw = os.path.join(base_dir, ASMAP)
 144  
 145          self.test_without_asmap_arg()
 146          self.test_noasmap_arg()
 147          self.test_asmap_with_absolute_path()
 148          self.test_asmap_with_relative_path()
 149          self.test_default_asmap()
 150          self.test_asmap_interaction_with_addrman_containing_entries()
 151          self.test_default_asmap_with_missing_file()
 152          self.test_empty_asmap()
 153          self.test_asmap_health_check()
 154  
 155  
 156  if __name__ == '__main__':
 157      AsmapTest(__file__).main()
 158