feature_bind_extra.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2014-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 starting bitcoind with -bind and/or -bind=...=onion, confirm that
   7  it binds to the expected ports, and verify that duplicate or conflicting
   8  -bind/-whitebind configurations are rejected with a descriptive error.
   9  """
  10  
  11  from itertools import combinations_with_replacement
  12  from test_framework.netutil import (
  13      addr_to_hex,
  14      get_bind_addrs,
  15  )
  16  from test_framework.test_framework import (
  17      BitcoinTestFramework,
  18  )
  19  from test_framework.test_node import ErrorMatch
  20  from test_framework.util import (
  21      assert_equal,
  22      p2p_port,
  23      rpc_port,
  24  )
  25  
  26  class BindExtraTest(BitcoinTestFramework):
  27      def set_test_params(self):
  28          self.setup_clean_chain = True
  29          # Avoid any -bind= on the command line. Force the framework to avoid
  30          # adding -bind=127.0.0.1.
  31          self.bind_to_localhost_only = False
  32          self.num_nodes = 3
  33  
  34      def skip_test_if_missing_module(self):
  35          self.skip_if_platform_not_posix()
  36  
  37      def setup_network(self):
  38          loopback_ipv4 = addr_to_hex("127.0.0.1")
  39  
  40          # Start custom ports by reusing unused p2p ports
  41          def extra_port():
  42              port = p2p_port(extra_port.index)
  43              extra_port.index += 1
  44              return port
  45          extra_port.index = self.num_nodes
  46  
  47          # Array of tuples [command line arguments, expected bind addresses].
  48          self.expected = []
  49  
  50          # Node0, no normal -bind=... with -bind=...=onion, thus only the tor target.
  51          port = extra_port()
  52          self.expected.append(
  53              [
  54                  [f"-bind=127.0.0.1:{port}=onion"],
  55                  [(loopback_ipv4, port)],
  56              ],
  57          )
  58  
  59          # Node1, both -bind=... and -bind=...=onion.
  60          port = [extra_port(), extra_port()]
  61          self.expected.append(
  62              [
  63                  [f"-bind=127.0.0.1:{port[0]}", f"-bind=127.0.0.1:{port[1]}=onion"],
  64                  [(loopback_ipv4, port[0]), (loopback_ipv4, port[1])],
  65              ],
  66          )
  67  
  68          # Node2, no -bind=...=onion, thus no extra port for Tor target.
  69          port = extra_port()
  70          self.expected.append(
  71              [
  72                  [f"-bind=127.0.0.1:{port}"],
  73                  [(loopback_ipv4, port)],
  74              ],
  75          )
  76  
  77          self.extra_args = list(map(lambda e: e[0], self.expected))
  78          self.setup_nodes()
  79  
  80      def run_test(self):
  81          for i, (args, expected_services) in enumerate(self.expected):
  82              self.log.info(f"Checking listening ports of node {i} with {args}")
  83              pid = self.nodes[i].process.pid
  84              binds = set(get_bind_addrs(pid))
  85              # Remove IPv6 addresses because on some CI environments "::1" is not configured
  86              # on the system (so our test_ipv6_local() would return False), but it is
  87              # possible to bind on "::". This makes it unpredictable whether to expect
  88              # that bitcoind has bound on "::1" (for RPC) and "::" (for P2P).
  89              ipv6_addr_len_bytes = 32
  90              binds = set(filter(lambda e: len(e[0]) != ipv6_addr_len_bytes, binds))
  91              # Remove RPC ports. They are not relevant for this test.
  92              binds = set(filter(lambda e: e[1] != rpc_port(i), binds))
  93              assert_equal(binds, set(expected_services))
  94  
  95          self.stop_node(0)
  96  
  97          addr = "127.0.0.1:11012"
  98          for opt1, opt2 in combinations_with_replacement([f"-bind={addr}", f"-bind={addr}=onion", f"-whitebind=noban@{addr}"], 2):
  99              self.nodes[0].assert_start_raises_init_error(
 100                          [opt1, opt2],
 101                          "Error: Duplicate binding configuration",
 102                          match=ErrorMatch.PARTIAL_REGEX)
 103  
 104  if __name__ == '__main__':
 105      BindExtraTest(__file__).main()
 106