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