feature_config_args.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2017-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  """Test various command line arguments and configuration file parameters."""
   6  
   7  import os
   8  from pathlib import Path
   9  import platform
  10  import re
  11  import tempfile
  12  import time
  13  
  14  from test_framework.netutil import UNREACHABLE_PROXY_ARG
  15  from test_framework.test_framework import BitcoinTestFramework
  16  from test_framework.test_node import ErrorMatch
  17  from test_framework import util
  18  
  19  
  20  class ConfArgsTest(BitcoinTestFramework):
  21      def set_test_params(self):
  22          self.setup_clean_chain = True
  23          self.num_nodes = 1
  24          # Prune to prevent disk space warning on CI systems with limited space,
  25          # when using networks other than regtest.
  26          self.extra_args = [["-prune=550"]]
  27          self.supports_cli = False
  28          self.wallet_names = []
  29          self.disable_autoconnect = False
  30          self.uses_wallet = None
  31  
  32      # Overridden to avoid attempt to sync not yet started nodes.
  33      def setup_network(self):
  34          self.setup_nodes()
  35  
  36      # Overridden to not start nodes automatically - doing so is the
  37      # responsibility of each test function.
  38      def setup_nodes(self):
  39          self.add_nodes(self.num_nodes, self.extra_args)
  40          # Ensure a log file exists as TestNode.assert_debug_log() expects it.
  41          self.nodes[0].debug_log_path.parent.mkdir()
  42          self.nodes[0].debug_log_path.touch()
  43  
  44      def test_dir_config(self):
  45          self.log.info('Error should be emitted if config file is a directory')
  46          conf_path = self.nodes[0].datadir_path / 'bitcoin.conf'
  47          os.rename(conf_path, conf_path.with_suffix('.confbkp'))
  48          conf_path.mkdir()
  49          self.stop_node(0)
  50          self.nodes[0].assert_start_raises_init_error(
  51              extra_args=['-regtest'],
  52              expected_msg=f'Error: Error reading configuration file: Config file "{conf_path}" is a directory.',
  53          )
  54          conf_path.rmdir()
  55          os.rename(conf_path.with_suffix('.confbkp'), conf_path)
  56  
  57          self.log.debug('Verifying includeconf directive pointing to directory is caught')
  58          with open(conf_path, 'a') as conf:
  59              conf.write(f'includeconf={self.nodes[0].datadir_path}\n')
  60          self.nodes[0].assert_start_raises_init_error(
  61              extra_args=['-regtest'],
  62              expected_msg=f'Error: Error reading configuration file: Included config file "{self.nodes[0].datadir_path}" is a directory.',
  63          )
  64  
  65          self.nodes[0].replace_in_config([(f'includeconf={self.nodes[0].datadir_path}', '')])
  66  
  67      def test_negated_config(self):
  68          self.log.info('Disabling configuration via -noconf')
  69  
  70          conf_path = self.nodes[0].datadir_path / 'bitcoin.conf'
  71          with open(conf_path) as conf:
  72              settings = [f'-{line.rstrip()}' for line in conf if len(line) > 1 and line[0] != '[']
  73          os.rename(conf_path, conf_path.with_suffix('.confbkp'))
  74  
  75          self.log.debug('Verifying garbage in config can be detected')
  76          with open(conf_path, 'a') as conf:
  77              conf.write('garbage\n')
  78          self.nodes[0].assert_start_raises_init_error(
  79              extra_args=['-regtest'],
  80              expected_msg='Error: Error reading configuration file: parse error on line 1: garbage',
  81          )
  82  
  83          self.log.debug('Verifying that disabling of the config file means garbage inside of it does ' \
  84              'not prevent the node from starting, and message about existing config file is logged')
  85          ignored_file_message = [f'Data directory "{self.nodes[0].datadir_path}" contains a "bitcoin.conf" file which is explicitly ignored using -noconf.']
  86          with self.nodes[0].assert_debug_log(expected_msgs=ignored_file_message):
  87              self.start_node(0, extra_args=settings + ['-noconf'])
  88          self.stop_node(0)
  89  
  90          self.log.debug('Verifying no message appears when removing config file')
  91          os.remove(conf_path)
  92          with self.nodes[0].assert_debug_log(expected_msgs=[], unexpected_msgs=ignored_file_message):
  93              self.start_node(0, extra_args=settings + ['-noconf'])
  94          self.stop_node(0)
  95  
  96          os.rename(conf_path.with_suffix('.confbkp'), conf_path)
  97  
  98      def test_config_file_parser(self):
  99          self.log.info('Test config file parser')
 100  
 101          # Check that startup fails if conf= is set in bitcoin.conf or in an included conf file
 102          bad_conf_file_path = self.nodes[0].datadir_path / "bitcoin_bad.conf"
 103          util.write_config(bad_conf_file_path, n=0, chain='', extra_config='conf=some.conf\n')
 104          conf_in_config_file_err = 'Error: Error reading configuration file: conf cannot be set in the configuration file; use includeconf= if you want to include additional config files'
 105          self.nodes[0].assert_start_raises_init_error(
 106              extra_args=[f'-conf={bad_conf_file_path}'],
 107              expected_msg=conf_in_config_file_err,
 108          )
 109          inc_conf_file_path = self.nodes[0].datadir_path / 'include.conf'
 110          with open(self.nodes[0].datadir_path / 'bitcoin.conf', 'a') as conf:
 111              conf.write(f'includeconf={inc_conf_file_path}\n')
 112          with open(inc_conf_file_path, 'w') as conf:
 113              conf.write('conf=some.conf\n')
 114          self.nodes[0].assert_start_raises_init_error(
 115              expected_msg=conf_in_config_file_err,
 116          )
 117  
 118          self.nodes[0].assert_start_raises_init_error(
 119              expected_msg='Error: Error parsing command line arguments: Invalid parameter -dash_cli=1',
 120              extra_args=['-dash_cli=1'],
 121          )
 122          with open(inc_conf_file_path, 'w') as conf:
 123              conf.write('dash_conf=1\n')
 124  
 125          with self.nodes[0].assert_debug_log(expected_msgs=['Ignoring unknown configuration value dash_conf']):
 126              self.start_node(0)
 127          self.stop_node(0)
 128  
 129          with open(inc_conf_file_path, 'w') as conf:
 130              conf.write('reindex=1\n')
 131  
 132          with self.nodes[0].assert_debug_log(expected_msgs=["[warning] reindex=1 is set in the configuration file, which will significantly slow down startup. Consider removing or commenting out this option for better performance, unless there is currently a condition which makes rebuilding the indexes necessary"]):
 133              self.start_node(0)
 134          self.stop_node(0)
 135  
 136          with open(inc_conf_file_path, 'w') as conf:
 137              conf.write('-dash=1\n')
 138          self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 1: -dash=1, options in configuration file must be specified without leading -')
 139  
 140          if self.is_wallet_compiled():
 141              with open(inc_conf_file_path, 'w') as conf:
 142                  conf.write("wallet=foo\n")
 143              self.nodes[0].assert_start_raises_init_error(expected_msg=f'Error: Config setting for -wallet only applied on {self.chain} network when in [{self.chain}] section.')
 144  
 145          main_conf_file_path = self.nodes[0].datadir_path / "bitcoin_main.conf"
 146          util.write_config(main_conf_file_path, n=0, chain='', extra_config=f'includeconf={inc_conf_file_path}\n')
 147          with open(inc_conf_file_path, 'w') as conf:
 148              conf.write('acceptnonstdtxn=1\n')
 149          self.nodes[0].assert_start_raises_init_error(extra_args=[f"-conf={main_conf_file_path}", "-allowignoredconf"], expected_msg='Error: acceptnonstdtxn is not currently supported for main chain')
 150  
 151          with open(inc_conf_file_path, 'w') as conf:
 152              conf.write('nono\n')
 153          self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 1: nono, if you intended to specify a negated option, use nono=1 instead')
 154  
 155          with open(inc_conf_file_path, 'w') as conf:
 156              conf.write('server=1\nrpcuser=someuser\nrpcpassword=some#pass')
 157          self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided')
 158  
 159          with open(inc_conf_file_path, 'w') as conf:
 160              conf.write('server=1\nrpcuser=someuser\nmain.rpcpassword=some#pass')
 161          self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 3, using # in rpcpassword can be ambiguous and should be avoided')
 162  
 163          with open(inc_conf_file_path, 'w') as conf:
 164              conf.write('server=1\nrpcuser=someuser\n[main]\nrpcpassword=some#pass')
 165          self.nodes[0].assert_start_raises_init_error(expected_msg='Error: Error reading configuration file: parse error on line 4, using # in rpcpassword can be ambiguous and should be avoided')
 166  
 167          inc_conf_file2_path = self.nodes[0].datadir_path / 'include2.conf'
 168          with open(self.nodes[0].datadir_path / 'bitcoin.conf', 'a') as conf:
 169              conf.write(f'includeconf={inc_conf_file2_path}\n')
 170  
 171          with open(inc_conf_file_path, 'w') as conf:
 172              conf.write('testnot.datadir=1\n')
 173          with open(inc_conf_file2_path, 'w') as conf:
 174              conf.write('[testnet]\n')
 175          self.restart_node(0)
 176          self.nodes[0].stop_node(expected_stderr=f'Warning: {inc_conf_file_path}:1 Section [testnot] is not recognized.{os.linesep}{inc_conf_file2_path}:1 Section [testnet] is not recognized.')
 177  
 178          with open(inc_conf_file_path, 'w') as conf:
 179              conf.write('')  # clear
 180          with open(inc_conf_file2_path, 'w') as conf:
 181              conf.write('')  # clear
 182  
 183      def test_config_file_log(self):
 184          # Disable this test for windows currently because trying to override
 185          # the default datadir through the environment does not seem to work.
 186          if platform.system() == "Windows":
 187              return
 188  
 189          self.log.info('Test that correct configuration path is changed when configuration file changes the datadir')
 190  
 191          # Create a temporary directory that will be treated as the default data
 192          # directory by bitcoind.
 193          env, default_datadir = util.get_temp_default_datadir(Path(self.options.tmpdir, "test_config_file_log"))
 194          default_datadir.mkdir(parents=True)
 195  
 196          # Write a bitcoin.conf file in the default data directory containing a
 197          # datadir= line pointing at the node datadir.
 198          node = self.nodes[0]
 199          conf_text = node.bitcoinconf.read_text()
 200          conf_path = default_datadir / "bitcoin.conf"
 201          conf_path.write_text(f"datadir={node.datadir_path}\n{conf_text}")
 202  
 203          # Drop the node -datadir= argument during this test, because if it is
 204          # specified it would take precedence over the datadir setting in the
 205          # config file.
 206          node_args = node.args
 207          node.args = [arg for arg in node.args if not arg.startswith("-datadir=")]
 208  
 209          # Check that correct configuration file path is actually logged
 210          # (conf_path, not node.bitcoinconf)
 211          with self.nodes[0].assert_debug_log(expected_msgs=[f"Config file: {conf_path}"]):
 212              self.start_node(0, ["-allowignoredconf"], env=env)
 213              self.stop_node(0)
 214  
 215          # Restore node arguments after the test
 216          node.args = node_args
 217  
 218      def test_invalid_command_line_options(self):
 219          self.nodes[0].assert_start_raises_init_error(
 220              expected_msg='Error: Error parsing command line arguments: Can not set -proxy with no value. Please specify value with -proxy=value.',
 221              extra_args=['-proxy'],
 222          )
 223          # Provide a value different from 1 to the -wallet negated option
 224          if self.is_wallet_compiled():
 225              for value in [0, 'not_a_boolean']:
 226                  self.nodes[0].assert_start_raises_init_error(
 227                      expected_msg="Error: Invalid value detected for '-wallet' or '-nowallet'. '-wallet' requires a string value, while '-nowallet' accepts only '1' to disable all wallets",
 228                      extra_args=[f'-nowallet={value}'],
 229                  )
 230  
 231      def test_log_buffer(self):
 232          with self.nodes[0].assert_debug_log(expected_msgs=["[warning] Parsed potentially confusing double-negative -listen=0\n"]):
 233              self.start_node(0, extra_args=['-nolisten=0'])
 234          self.stop_node(0)
 235  
 236      def test_args_log(self):
 237          self.log.info('Test config args logging')
 238          with self.nodes[0].assert_debug_log(
 239                  expected_msgs=[
 240                      'Command-line arg: addnode="some.node"',
 241                      'Command-line arg: rpcauth=****',
 242                      'Command-line arg: rpcpassword=****',
 243                      'Command-line arg: rpcuser=****',
 244                      'Command-line arg: torpassword=****',
 245                      f'Config file arg: {self.chain}="1"',
 246                      f'Config file arg: [{self.chain}] server="1"',
 247                  ],
 248                  unexpected_msgs=[
 249                      'alice:f7efda5c189b999524f151318c0c86$d5b51b3beffbc0',
 250                      'secret-rpcuser',
 251                      'secret-torpassword',
 252                      'Command-line arg: rpcbind=****',
 253                      'Command-line arg: rpcallowip=****',
 254                  ]):
 255              self.start_node(0, extra_args=[
 256                  '-addnode=some.node',
 257                  '-rpcauth=alice:f7efda5c189b999524f151318c0c86$d5b51b3beffbc0',
 258                  '-rpcbind=127.1.1.1',
 259                  '-rpcbind=127.0.0.1',
 260                  "-rpcallowip=127.0.0.1",
 261                  '-rpcpassword=',
 262                  '-rpcuser=secret-rpcuser',
 263                  '-torpassword=secret-torpassword',
 264                  UNREACHABLE_PROXY_ARG,
 265              ])
 266          self.stop_node(0)
 267  
 268      def test_networkactive(self):
 269          self.log.info('Test -networkactive option')
 270          with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
 271              self.start_node(0)
 272  
 273          self.stop_node(0)
 274          with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
 275              self.start_node(0, extra_args=['-networkactive'])
 276  
 277          self.stop_node(0)
 278          with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: true\n']):
 279              self.start_node(0, extra_args=['-networkactive=1'])
 280  
 281          self.stop_node(0)
 282          with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
 283              self.start_node(0, extra_args=['-networkactive=0'])
 284  
 285          self.stop_node(0)
 286          with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
 287              self.start_node(0, extra_args=['-nonetworkactive'])
 288  
 289          self.stop_node(0)
 290          with self.nodes[0].assert_debug_log(expected_msgs=['SetNetworkActive: false\n']):
 291              self.start_node(0, extra_args=['-nonetworkactive=1'])
 292          self.stop_node(0)
 293  
 294      def test_seed_peers(self):
 295          self.log.info('Test seed peers')
 296          default_data_dir = self.nodes[0].datadir_path
 297          peer_dat = default_data_dir / 'peers.dat'
 298  
 299          # No peers.dat exists and -dnsseed=1
 300          # We expect the node will use DNS Seeds, but Regtest mode does not have
 301          # any valid DNS seeds. So after 60 seconds, the node should fallback to
 302          # fixed seeds
 303          assert not peer_dat.exists()
 304          start = int(time.time())
 305          with self.nodes[0].assert_debug_log(
 306                  expected_msgs=[
 307                      "Loaded 0 addresses from peers.dat",
 308                      "0 addresses found from DNS seeds",
 309                      "opencon thread start",  # Ensure ThreadOpenConnections::start time is properly set
 310                  ],
 311                  timeout=10,
 312          ):
 313              self.start_node(0, extra_args=['-dnsseed=1', '-fixedseeds=1', f'-mocktime={start}', UNREACHABLE_PROXY_ARG])
 314  
 315          # Only regtest has no fixed seeds. To avoid connections to random
 316          # nodes, regtest is the only network where it is safe to enable
 317          # -fixedseeds in tests
 318          util.assert_equal(self.nodes[0].getblockchaininfo()['chain'],'regtest')
 319  
 320          with self.nodes[0].assert_debug_log(expected_msgs=[
 321                  "Adding fixed seeds as 60 seconds have passed and addrman is empty",
 322          ], timeout=2):
 323              self.nodes[0].setmocktime(start + 65)
 324          self.stop_node(0)
 325  
 326          # No peers.dat exists and -dnsseed=0
 327          # We expect the node will fallback immediately to fixed seeds
 328          assert not peer_dat.exists()
 329          with self.nodes[0].assert_debug_log(expected_msgs=[
 330                  "Loaded 0 addresses from peers.dat",
 331                  "DNS seeding disabled",
 332                  "Adding fixed seeds as -dnsseed=0 (or IPv4/IPv6 connections are disabled via -onlynet) and neither -addnode nor -seednode are provided\n",
 333          ], timeout=2):
 334              self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=1'])
 335          self.stop_node(0)
 336          self.nodes[0].assert_start_raises_init_error(['-dnsseed=1', '-onlynet=i2p', '-i2psam=127.0.0.1:7656'], "Error: Incompatible options: -dnsseed=1 was explicitly specified, but -onlynet forbids connections to IPv4/IPv6")
 337  
 338          # No peers.dat exists and dns seeds are disabled.
 339          # We expect the node will not add fixed seeds when explicitly disabled.
 340          assert not peer_dat.exists()
 341          with self.nodes[0].assert_debug_log(expected_msgs=[
 342                  "Loaded 0 addresses from peers.dat",
 343                  "DNS seeding disabled",
 344                  "Fixed seeds are disabled",
 345          ], timeout=2):
 346              self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=0'])
 347          self.stop_node(0)
 348  
 349          # No peers.dat exists and -dnsseed=0, but a -addnode is provided
 350          # We expect the node will allow 60 seconds prior to using fixed seeds
 351          assert not peer_dat.exists()
 352          start = int(time.time())
 353          with self.nodes[0].assert_debug_log(
 354                  expected_msgs=[
 355                      "Loaded 0 addresses from peers.dat",
 356                      "DNS seeding disabled",
 357                      "opencon thread start",  # Ensure ThreadOpenConnections::start time is properly set
 358                  ],
 359                  timeout=10,
 360          ):
 361              self.start_node(0, extra_args=['-dnsseed=0', '-fixedseeds=1', '-addnode=fakenodeaddr', f'-mocktime={start}', UNREACHABLE_PROXY_ARG])
 362          with self.nodes[0].assert_debug_log(expected_msgs=[
 363                  "Adding fixed seeds as 60 seconds have passed and addrman is empty",
 364          ], timeout=2):
 365              self.nodes[0].setmocktime(start + 65)
 366          self.stop_node(0)
 367  
 368      def test_connect_with_seednode(self):
 369          self.log.info('Test -connect with -seednode')
 370          seednode_ignored = ['-seednode is ignored when -connect is used\n']
 371          dnsseed_ignored = ['-dnsseed is ignored when -connect is used and -proxy is specified\n']
 372          addcon_thread_started = ['addcon thread start\n']
 373          dnsseed_disabled = "parameter interaction: -connect or -maxconnections=0 set -> setting -dnsseed=0"
 374          listen_disabled = "parameter interaction: -connect or -maxconnections=0 set -> setting -listen=0"
 375  
 376          # When -connect is supplied, expanding addrman via getaddr calls to ADDR_FETCH(-seednode)
 377          # nodes is irrelevant and -seednode is ignored.
 378          with self.nodes[0].assert_debug_log(expected_msgs=seednode_ignored):
 379              self.start_node(0, extra_args=['-connect=fakeaddress1', '-seednode=fakeaddress2', UNREACHABLE_PROXY_ARG])
 380  
 381          # With -proxy, an ADDR_FETCH connection is made to a peer that the dns seed resolves to.
 382          # ADDR_FETCH connections are not used when -connect is used.
 383          with self.nodes[0].assert_debug_log(expected_msgs=dnsseed_ignored):
 384              self.restart_node(0, extra_args=['-connect=fakeaddress1', '-dnsseed=1', UNREACHABLE_PROXY_ARG])
 385  
 386          # If the user did not disable -dnsseed, but it was soft-disabled because they provided -connect,
 387          # they shouldn't see a warning about -dnsseed being ignored.
 388          with self.nodes[0].assert_debug_log(expected_msgs=addcon_thread_started,
 389                  unexpected_msgs=dnsseed_ignored, timeout=2):
 390              self.restart_node(0, extra_args=['-connect=fakeaddress1', UNREACHABLE_PROXY_ARG])
 391  
 392          # We have to supply expected_msgs as it's a required argument
 393          # The expected_msg must be something we are confident will be logged after the unexpected_msg
 394          # These cases test for -connect being supplied but only to disable it
 395          for connect_arg in ['-connect=0', '-noconnect']:
 396              with self.nodes[0].assert_debug_log(expected_msgs=addcon_thread_started,
 397                      unexpected_msgs=seednode_ignored, timeout=2):
 398                  self.restart_node(0, extra_args=[connect_arg, '-seednode=fakeaddress2'])
 399  
 400              # Make sure -noconnect soft-disables -listen and -dnsseed.
 401              # Need to temporarily remove these settings from the config file in
 402              # order for the two log messages to appear
 403              self.nodes[0].replace_in_config([("bind=", "#bind="), ("dnsseed=", "#dnsseed=")])
 404              with self.nodes[0].assert_debug_log(expected_msgs=[dnsseed_disabled, listen_disabled]):
 405                  self.restart_node(0, extra_args=[connect_arg])
 406              self.nodes[0].replace_in_config([("#bind=", "bind="), ("#dnsseed=", "dnsseed=")])
 407  
 408              # Make sure -proxy and -noconnect warn about -dnsseed setting being
 409              # ignored, just like -proxy and -connect do.
 410              with self.nodes[0].assert_debug_log(expected_msgs=dnsseed_ignored):
 411                  self.restart_node(0, extra_args=[connect_arg, '-dnsseed', '-proxy=localhost:1080'])
 412          self.stop_node(0)
 413  
 414      def test_privatebroadcast(self):
 415          self.log.info("Test that an invalid usage of -privatebroadcast throws an init error")
 416          self.stop_node(0)
 417          # -privatebroadcast init error: Tor/I2P not reachable at startup
 418          self.nodes[0].assert_start_raises_init_error(
 419              extra_args=["-privatebroadcast"],
 420              expected_msg=(
 421                  "Error: Private broadcast of own transactions requested (-privatebroadcast), "
 422                  "but none of Tor or I2P networks is reachable"))
 423          # -privatebroadcast init error: incompatible with -connect
 424          self.nodes[0].assert_start_raises_init_error(
 425              extra_args=["-privatebroadcast", "-connect=127.0.0.1:8333", "-onion=127.0.0.1:9050"],
 426              expected_msg=(
 427                  "Error: Private broadcast of own transactions requested (-privatebroadcast), but -connect is also configured. "
 428                  "They are incompatible because the private broadcast needs to open new connections to randomly "
 429                  "chosen Tor or I2P peers. Consider using -maxconnections=0 -addnode=... instead"))
 430          # Warning case: private broadcast allowed, but -proxyrandomize=0 triggers a privacy warning
 431          self.start_node(0, extra_args=["-privatebroadcast", "-onion=127.0.0.1:9050", "-proxyrandomize=0"])
 432          self.stop_node(0, expected_stderr=(
 433              "Warning: Private broadcast of own transactions requested (-privatebroadcast) and "
 434              "-proxyrandomize is disabled. Tor circuits for private broadcast connections may "
 435              "be correlated to other connections over Tor. For maximum privacy set -proxyrandomize=1."))
 436  
 437      def test_ignored_conf(self):
 438          self.log.info('Test error is triggered when the datadir in use contains a bitcoin.conf file that would be ignored '
 439                        'because a conflicting -conf file argument is passed.')
 440          node = self.nodes[0]
 441          with tempfile.NamedTemporaryFile(dir=self.options.tmpdir, mode="wt", delete=False) as temp_conf:
 442              temp_conf.write(f"datadir={node.datadir_path}\n")
 443          node.assert_start_raises_init_error([f"-conf={temp_conf.name}"], re.escape(
 444              f'Error: Data directory "{node.datadir_path}" contains a "bitcoin.conf" file which is ignored, because a '
 445              f'different configuration file "{temp_conf.name}" from command line argument "-conf={temp_conf.name}" '
 446              f'is being used instead.') + r"[\s\S]*", match=ErrorMatch.FULL_REGEX)
 447  
 448          # Test that passing a redundant -conf command line argument pointing to
 449          # the same bitcoin.conf that would be loaded anyway does not trigger an
 450          # error.
 451          self.start_node(0, [f'-conf={node.datadir_path}/bitcoin.conf'])
 452          self.stop_node(0)
 453  
 454      def test_ignored_default_conf(self):
 455          # Disable this test for windows currently because trying to override
 456          # the default datadir through the environment does not seem to work.
 457          if platform.system() == "Windows":
 458              return
 459  
 460          self.log.info('Test error is triggered when bitcoin.conf in the default data directory sets another datadir '
 461                        'and it contains a different bitcoin.conf file that would be ignored')
 462  
 463          # Create a temporary directory that will be treated as the default data
 464          # directory by bitcoind.
 465          env, default_datadir = util.get_temp_default_datadir(Path(self.options.tmpdir, "home"))
 466          default_datadir.mkdir(parents=True)
 467  
 468          # Write a bitcoin.conf file in the default data directory containing a
 469          # datadir= line pointing at the node datadir. This will trigger a
 470          # startup error because the node datadir contains a different
 471          # bitcoin.conf that would be ignored.
 472          node = self.nodes[0]
 473          (default_datadir / "bitcoin.conf").write_text(f"datadir={node.datadir_path}\n")
 474  
 475          # Drop the node -datadir= argument during this test, because if it is
 476          # specified it would take precedence over the datadir setting in the
 477          # config file.
 478          node_args = node.args
 479          node.args = [arg for arg in node.args if not arg.startswith("-datadir=")]
 480          node.assert_start_raises_init_error([], re.escape(
 481              f'Error: Data directory "{node.datadir_path}" contains a "bitcoin.conf" file which is ignored, because a '
 482              f'different configuration file "{default_datadir}/bitcoin.conf" from data directory "{default_datadir}" '
 483              f'is being used instead.') + r"[\s\S]*", env=env, match=ErrorMatch.FULL_REGEX)
 484          node.args = node_args
 485  
 486      def test_acceptstalefeeestimates_arg_support(self):
 487          self.log.info("Test -acceptstalefeeestimates option support")
 488          conf_file = self.nodes[0].datadir_path / "bitcoin.conf"
 489          for chain, chain_name in {("main", ""), ("test", "testnet3"), ("signet", "signet"), ("testnet4", "testnet4")}:
 490              util.write_config(conf_file, n=0, chain=chain_name, extra_config='acceptstalefeeestimates=1\n')
 491              self.nodes[0].assert_start_raises_init_error(expected_msg=f'Error: acceptstalefeeestimates is not supported on {chain} chain.')
 492          util.write_config(conf_file, n=0, chain="regtest")  # Reset to regtest
 493  
 494      def test_testnet3_deprecation_msg(self):
 495          self.log.info("Test testnet3 deprecation warning")
 496          t3_warning_log = "Warning: Support for testnet3 is deprecated and will be removed in an upcoming release. Consider switching to testnet4."
 497  
 498          self.log.debug("Testnet3 node will log the deprecation warning")
 499          self.nodes[0].chain = 'testnet3'
 500          self.nodes[0].replace_in_config([('regtest=', 'testnet='), ('[regtest]', '[test]')])
 501          with self.nodes[0].assert_debug_log([t3_warning_log]):
 502              self.start_node(0)
 503          self.stop_node(0)
 504  
 505          self.log.debug("Testnet4 node will not log the deprecation warning")
 506          self.nodes[0].chain = 'testnet4'
 507          self.nodes[0].replace_in_config([('testnet=', 'testnet4='), ('[test]', '[testnet4]')])
 508          with self.nodes[0].assert_debug_log([], unexpected_msgs=[t3_warning_log]):
 509              self.start_node(0)
 510          self.stop_node(0)
 511  
 512          self.log.debug("Reset to regtest")
 513          self.nodes[0].chain = 'regtest'
 514          self.nodes[0].replace_in_config([('testnet4=', 'regtest='), ('[testnet4]', '[regtest]')])
 515  
 516      def run_test(self):
 517          self.test_log_buffer()
 518          self.test_args_log()
 519          self.test_seed_peers()
 520          self.test_networkactive()
 521          self.test_connect_with_seednode()
 522          self.test_privatebroadcast()
 523  
 524          self.test_dir_config()
 525          self.test_negated_config()
 526          self.test_config_file_parser()
 527          self.test_config_file_log()
 528          self.test_invalid_command_line_options()
 529          self.test_ignored_conf()
 530          self.test_ignored_default_conf()
 531          self.test_acceptstalefeeestimates_arg_support()
 532          self.test_testnet3_deprecation_msg()
 533  
 534          # Remove the -datadir argument so it doesn't override the config file
 535          self.nodes[0].args = [arg for arg in self.nodes[0].args if not arg.startswith("-datadir")]
 536  
 537          default_data_dir = self.nodes[0].datadir_path
 538          new_data_dir = default_data_dir / 'newdatadir'
 539          new_data_dir_2 = default_data_dir / 'newdatadir2'
 540  
 541          # Check that using -datadir argument on non-existent directory fails
 542          self.nodes[0].datadir_path = new_data_dir
 543          self.nodes[0].assert_start_raises_init_error([f'-datadir={new_data_dir}'], f'Error: Specified data directory "{new_data_dir}" does not exist.')
 544  
 545          # Check that using non-existent datadir in conf file fails
 546          conf_file = default_data_dir / "bitcoin.conf"
 547  
 548          # datadir needs to be set before [chain] section
 549          with open(conf_file) as f:
 550              conf_file_contents = f.read()
 551          with open(conf_file, 'w') as f:
 552              f.write(f"datadir={new_data_dir}\n")
 553              f.write(conf_file_contents)
 554  
 555          self.nodes[0].assert_start_raises_init_error([f'-conf={conf_file}'], f'Error: Error reading configuration file: specified data directory "{new_data_dir}" does not exist.')
 556  
 557          # Check that an explicitly specified config file that cannot be opened fails
 558          none_existent_conf_file = default_data_dir / "none_existent_bitcoin.conf"
 559          self.nodes[0].assert_start_raises_init_error(['-conf=' + f'{none_existent_conf_file}'], 'Error: Error reading configuration file: specified config file "' + f'{none_existent_conf_file}' + '" could not be opened.')
 560  
 561          # Create the directory and ensure the config file now works
 562          new_data_dir.mkdir()
 563          self.start_node(0, [f'-conf={conf_file}'])
 564          self.stop_node(0)
 565          assert (new_data_dir / self.chain / 'blocks').exists()
 566  
 567          # Ensure command line argument overrides datadir in conf
 568          new_data_dir_2.mkdir()
 569          self.nodes[0].datadir_path = new_data_dir_2
 570          self.start_node(0, [f'-datadir={new_data_dir_2}', f'-conf={conf_file}'])
 571          assert (new_data_dir_2 / self.chain / 'blocks').exists()
 572  
 573  
 574  if __name__ == '__main__':
 575      ConfArgsTest(__file__).main()
 576