test_framework.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  """Base class for RPC testing."""
   6  
   7  import configparser
   8  from enum import Enum
   9  import argparse
  10  from datetime import datetime, timezone
  11  from importlib.util import find_spec
  12  import logging
  13  import os
  14  from pathlib import Path
  15  import platform
  16  import pdb
  17  import random
  18  import re
  19  import shutil
  20  import subprocess
  21  import sys
  22  import tempfile
  23  import time
  24  
  25  from .address import create_deterministic_address_bcrt1_p2tr_op_true
  26  from . import coverage
  27  from .messages import CAddress
  28  from .p2p import NetworkThread
  29  from .test_node import TestNode
  30  from .util import (
  31      Binaries,
  32      MAX_NODES,
  33      PortSeed,
  34      assert_equal,
  35      check_json_precision,
  36      export_env_build_path,
  37      find_vout_for_address,
  38      get_binary_paths,
  39      get_datadir_path,
  40      initialize_datadir,
  41      p2p_port,
  42      wait_until_helper_internal,
  43      wallet_importprivkey,
  44      JSONRPCException,
  45  )
  46  
  47  
  48  class TestStatus(Enum):
  49      PASSED = 1
  50      FAILED = 2
  51      SKIPPED = 3
  52  
  53  TEST_EXIT_PASSED = 0
  54  TEST_EXIT_FAILED = 1
  55  TEST_EXIT_SKIPPED = 77
  56  
  57  TMPDIR_PREFIX = "bitcoin_func_test_"
  58  
  59  
  60  class SkipTest(Exception):
  61      """This exception is raised to skip a test"""
  62  
  63      def __init__(self, message):
  64          self.message = message
  65  
  66  
  67  class BitcoinTestMetaClass(type):
  68      """Metaclass for BitcoinTestFramework.
  69  
  70      Ensures that any attempt to register a subclass of `BitcoinTestFramework`
  71      adheres to a standard whereby the subclass overrides `set_test_params` and
  72      `run_test` but DOES NOT override either `__init__` or `main`. If any of
  73      those standards are violated, a ``TypeError`` is raised."""
  74  
  75      def __new__(cls, clsname, bases, dct):
  76          if not clsname == 'BitcoinTestFramework':
  77              if not ('run_test' in dct and 'set_test_params' in dct):
  78                  raise TypeError("BitcoinTestFramework subclasses must override "
  79                                  "'run_test' and 'set_test_params'")
  80              if '__init__' in dct or 'main' in dct:
  81                  raise TypeError("BitcoinTestFramework subclasses may not override "
  82                                  "'__init__' or 'main'")
  83  
  84          return super().__new__(cls, clsname, bases, dct)
  85  
  86  
  87  class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
  88      """Base class for a bitcoin test script.
  89  
  90      Individual bitcoin test scripts should subclass this class and override the set_test_params() and run_test() methods.
  91  
  92      Individual tests can also override the following methods to customize the test setup:
  93  
  94      - add_options()
  95      - setup_chain()
  96      - setup_network()
  97      - setup_nodes()
  98  
  99      The __init__() and main() methods should not be overridden.
 100  
 101      This class also contains various public and private helper methods."""
 102  
 103      def __init__(self, test_file) -> None:
 104          """Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method"""
 105          self.chain: str = 'regtest'
 106          self.setup_clean_chain: bool = False
 107          self.noban_tx_relay: bool = False
 108          self.nodes: list[TestNode] = []
 109          self.extra_args = None
 110          self.extra_init = None
 111          self.network_thread = None
 112          self.rpc_timeout = 60  # Wait for up to 60 seconds for the RPC server to respond
 113          self.supports_cli = True
 114          self.bind_to_localhost_only = True
 115          self.parse_args(test_file)
 116          self.default_wallet_name = "default_wallet"
 117          self.wallet_data_filename = "wallet.dat"
 118          # Optional list of wallet names that can be set in set_test_params to
 119          # create and import keys to. If unset, default is len(nodes) *
 120          # [default_wallet_name]. If wallet names are None, wallet creation is
 121          # skipped. If list is truncated, wallet creation is skipped and keys
 122          # are not imported.
 123          self.wallet_names = None
 124          # By default the wallet is not required. Set to true by skip_if_no_wallet().
 125          # Can also be set to None to indicate that the wallet will be used if available.
 126          # When False or None, we ignore wallet_names in setup_nodes().
 127          self.uses_wallet = False
 128          # Disable ThreadOpenConnections by default, so that adding entries to
 129          # addrman will not result in automatic connections to them.
 130          self.disable_autoconnect = True
 131          self.set_test_params()
 132          assert self.wallet_names is None or len(self.wallet_names) <= self.num_nodes
 133          self.rpc_timeout = int(self.rpc_timeout * self.options.timeout_factor) # optionally, increase timeout by a factor
 134  
 135      def main(self):
 136          """Main function. This should not be overridden by the subclass test scripts."""
 137  
 138          assert hasattr(self, "num_nodes"), "Test must set self.num_nodes in set_test_params()"
 139  
 140          try:
 141              self.setup()
 142              if self.options.test_methods:
 143                  self.run_test_methods()
 144              else:
 145                  self.run_test()
 146  
 147          except SkipTest as e:
 148              self.log.warning("Test Skipped: %s" % e.message)
 149              self.success = TestStatus.SKIPPED
 150          except subprocess.CalledProcessError as e:
 151              self.log.exception(f"Called Process failed with stdout='{e.stdout}'; stderr='{e.stderr}';")
 152              self.success = TestStatus.FAILED
 153          except BaseException:
 154              # The `exception` log will add the exception info to the message.
 155              # https://docs.python.org/3/library/logging.html#logging.exception
 156              self.log.exception("Unexpected exception:")
 157              self.success = TestStatus.FAILED
 158          finally:
 159              exit_code = self.shutdown()
 160              sys.exit(exit_code)
 161  
 162      def run_test_methods(self):
 163          for method_name in self.options.test_methods:
 164              self.log.info(f"Attempting to execute method: {method_name}")
 165              method = getattr(self, method_name)
 166              method()
 167              self.log.info(f"Method '{method_name}' executed successfully.")
 168  
 169      def parse_args(self, test_file):
 170          previous_releases_path = os.getenv("PREVIOUS_RELEASES_DIR") or os.getcwd() + "/releases"
 171          parser = argparse.ArgumentParser(usage="%(prog)s [options]")
 172          parser.add_argument("--nocleanup", dest="nocleanup", default=False, action="store_true",
 173                              help="Leave bitcoinds and test.* datadir on exit or error")
 174          parser.add_argument("--cachedir", dest="cachedir", default=os.path.abspath(os.path.dirname(test_file) + "/../cache"),
 175                              help="Directory for caching pregenerated datadirs (default: %(default)s)")
 176          parser.add_argument("--tmpdir", dest="tmpdir", help="Root directory for datadirs (must not exist)")
 177          parser.add_argument("-l", "--loglevel", dest="loglevel", default="INFO",
 178                              help="log events at this level and higher to the console. Can be set to DEBUG, INFO, WARNING, ERROR or CRITICAL. Passing --loglevel DEBUG will output all logs to console. Note that logs at all levels are always written to the test_framework.log file in the temporary test directory.")
 179          parser.add_argument("--tracerpc", dest="trace_rpc", default=False, action="store_true",
 180                              help="Print out all RPC calls as they are made")
 181          parser.add_argument("--portseed", dest="port_seed", default=os.getpid(), type=int,
 182                              help="The seed to use for assigning port numbers (default: current process id)")
 183          parser.add_argument("--previous-releases", dest="prev_releases", action="store_true",
 184                              default=os.path.isdir(previous_releases_path) and bool(os.listdir(previous_releases_path)),
 185                              help="Force test of previous releases (default: %(default)s). Previous releases binaries can be downloaded via `test/get_previous_releases.py`.")
 186          parser.add_argument("--coveragedir", dest="coveragedir",
 187                              help="Write tested RPC commands into this directory")
 188          parser.add_argument("--configfile", dest="configfile",
 189                              default=os.path.abspath(os.path.dirname(test_file) + "/../config.ini"),
 190                              help="Location of the test framework config file (default: %(default)s)")
 191          parser.add_argument("--pdbonfailure", dest="pdbonfailure", default=False, action="store_true",
 192                              help="Attach a python debugger if test fails")
 193          parser.add_argument("--usecli", dest="usecli", default=False, action="store_true",
 194                              help="use bitcoin-cli instead of RPC for all commands")
 195          parser.add_argument("--valgrind", dest="valgrind", default=False, action="store_true",
 196                              help="Run binaries under the valgrind memory error detector: Expect at least a ~10x slowdown. Does not apply to previous release binaries.")
 197          parser.add_argument("--randomseed", type=int,
 198                              help="set a random seed for deterministically reproducing a previous test run")
 199          parser.add_argument("--timeout-factor", dest="timeout_factor", type=float, help="adjust test timeouts by a factor. Setting it to 0 disables all timeouts")
 200          parser.add_argument("--v2transport", dest="v2transport", default=False, action="store_true",
 201                              help="use BIP324 v2 connections between all nodes by default")
 202          parser.add_argument("--v1transport", dest="v1transport", default=False, action="store_true",
 203                              help="Explicitly use v1 transport (can be used to overwrite global --v2transport option)")
 204          parser.add_argument("--test_methods", dest="test_methods", nargs='*',
 205                              help="Run specified test methods sequentially instead of the full test. Use only for methods that do not depend on any context set up in run_test or other methods.")
 206  
 207          self.add_options(parser)
 208          # Running TestShell in a Jupyter notebook causes an additional -f argument
 209          # To keep TestShell from failing with an "unrecognized argument" error, we add a dummy "-f" argument
 210          # source: https://stackoverflow.com/questions/48796169/how-to-fix-ipykernel-launcher-py-error-unrecognized-arguments-in-jupyter/56349168#56349168
 211          parser.add_argument("-f", "--fff", help="a dummy argument to fool ipython", default="1")
 212          self.options = parser.parse_args()
 213          if self.options.timeout_factor == 0:
 214              self.options.timeout_factor = 999
 215          self.options.timeout_factor = self.options.timeout_factor or (4 if self.options.valgrind else 1)
 216          self.options.previous_releases_path = previous_releases_path
 217  
 218          self.config = configparser.ConfigParser()
 219          self.config.read_file(open(self.options.configfile))
 220          self.binary_paths = get_binary_paths(self.config)
 221          if self.options.v1transport:
 222              self.options.v2transport=False
 223  
 224          PortSeed.n = self.options.port_seed
 225  
 226      def get_binaries(self, bin_dir=None):
 227          return Binaries(self.binary_paths, bin_dir, use_valgrind=self.options.valgrind)
 228  
 229      def setup(self):
 230          """Call this method to start up the test framework object with options set."""
 231  
 232          check_json_precision()
 233          export_env_build_path(self.config)
 234  
 235          self.options.cachedir = os.path.abspath(self.options.cachedir)
 236  
 237          # Set up temp directory and start logging
 238          if self.options.tmpdir:
 239              self.options.tmpdir = os.path.abspath(self.options.tmpdir)
 240              os.makedirs(self.options.tmpdir, exist_ok=False)
 241          else:
 242              self.options.tmpdir = tempfile.mkdtemp(prefix=TMPDIR_PREFIX)
 243          self._start_logging()
 244  
 245          # Seed the PRNG. Note that test runs are reproducible if and only if
 246          # a single thread accesses the PRNG. For more information, see
 247          # https://docs.python.org/3/library/random.html#notes-on-reproducibility.
 248          # The network thread shouldn't access random. If we need to change the
 249          # network thread to access randomness, it should instantiate its own
 250          # random.Random object.
 251          seed = self.options.randomseed
 252  
 253          if seed is None:
 254              seed = random.randrange(sys.maxsize)
 255          else:
 256              self.log.info("User supplied random seed {}".format(seed))
 257  
 258          random.seed(seed)
 259          self.log.info("PRNG seed is: {}".format(seed))
 260  
 261          self.log.debug('Setting up network thread')
 262          self.network_thread = NetworkThread()
 263          self.network_thread.start()
 264          self.wait_until(lambda: self.network_thread.network_event_loop is not None and self.network_thread.network_event_loop.is_running())
 265  
 266          if self.options.usecli:
 267              if not self.supports_cli:
 268                  raise SkipTest("--usecli specified but test does not support using CLI")
 269              self.skip_if_no_cli()
 270          self.skip_test_if_missing_module()
 271          self.setup_chain()
 272          self.setup_network()
 273  
 274          self.success = TestStatus.PASSED
 275  
 276      def shutdown(self):
 277          """Call this method to shut down the test framework object."""
 278  
 279          if self.success == TestStatus.FAILED and self.options.pdbonfailure:
 280              print("Testcase failed. Attaching python debugger. Enter ? for help")
 281              pdb.set_trace()
 282  
 283          self.log.debug('Closing down network thread')
 284          self.network_thread.close(timeout=self.options.timeout_factor * 10)
 285          if self.success == TestStatus.FAILED:
 286              self.log.info("Not stopping nodes as test failed. The dangling processes will be cleaned up later.")
 287          else:
 288              self.log.info("Stopping nodes")
 289              if self.nodes:
 290                  self.stop_nodes()
 291  
 292          should_clean_up = (
 293              not self.options.nocleanup and
 294              self.success != TestStatus.FAILED
 295          )
 296          if should_clean_up:
 297              self.log.info("Cleaning up {} on exit".format(self.options.tmpdir))
 298              cleanup_tree_on_exit = True
 299          else:
 300              self.log.warning("Not cleaning up dir {}".format(self.options.tmpdir))
 301              cleanup_tree_on_exit = False
 302  
 303          if self.success == TestStatus.PASSED:
 304              self.log.info("Tests successful")
 305              exit_code = TEST_EXIT_PASSED
 306          elif self.success == TestStatus.SKIPPED:
 307              self.log.info("Test skipped")
 308              exit_code = TEST_EXIT_SKIPPED
 309          else:
 310              self.log.error("Test failed. Test logging available at %s/test_framework.log", self.options.tmpdir)
 311              self.log.error("")
 312              self.log.error("Hint: Call {} '{}' to consolidate all logs".format(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../combine_logs.py"), self.options.tmpdir))
 313              self.log.error("")
 314              self.log.error("If this failure happened unexpectedly or intermittently, please file a bug and provide a link or upload of the combined log.")
 315              self.log.error(self.config['environment']['CLIENT_BUGREPORT'])
 316              self.log.error("")
 317              exit_code = TEST_EXIT_FAILED
 318          # Logging.shutdown will not remove stream- and filehandlers, so we must
 319          # do it explicitly. Handlers are removed so the next test run can apply
 320          # different log handler settings.
 321          # See: https://docs.python.org/3/library/logging.html#logging.shutdown
 322          for h in list(self.log.handlers):
 323              h.flush()
 324              h.close()
 325              self.log.removeHandler(h)
 326          rpc_logger = logging.getLogger("BitcoinRPC")
 327          for h in list(rpc_logger.handlers):
 328              h.flush()
 329              rpc_logger.removeHandler(h)
 330          if cleanup_tree_on_exit:
 331              self.cleanup_folder(self.options.tmpdir)
 332  
 333          self.nodes.clear()
 334          return exit_code
 335  
 336      # Methods to override in subclass test scripts.
 337      def set_test_params(self):
 338          """Tests must override this method to change default values for number of nodes, topology, etc"""
 339          raise NotImplementedError
 340  
 341      def add_options(self, parser):
 342          """Override this method to add command-line options to the test"""
 343          pass
 344  
 345      def skip_test_if_missing_module(self):
 346          """Override this method to skip a test if a module is not compiled"""
 347          pass
 348  
 349      def setup_chain(self):
 350          """Override this method to customize blockchain setup"""
 351          self.log.info("Initializing test directory " + self.options.tmpdir)
 352          if self.setup_clean_chain:
 353              self._initialize_chain_clean()
 354          else:
 355              self._initialize_chain()
 356  
 357      def setup_network(self):
 358          """Override this method to customize test network topology"""
 359          self.setup_nodes()
 360  
 361          # Connect the nodes as a "chain".  This allows us
 362          # to split the network between nodes 1 and 2 to get
 363          # two halves that can work on competing chains.
 364          #
 365          # Topology looks like this:
 366          # node0 <-- node1 <-- node2 <-- node3
 367          #
 368          # If all nodes are in IBD (clean chain from genesis), node0 is assumed to be the source of blocks (miner). To
 369          # ensure block propagation, all nodes will establish outgoing connections toward node0.
 370          # See fPreferredDownload in net_processing.
 371          #
 372          # If further outbound connections are needed, they can be added at the beginning of the test with e.g.
 373          # self.connect_nodes(1, 2)
 374          for i in range(self.num_nodes - 1):
 375              self.connect_nodes(i + 1, i)
 376          self.sync_all()
 377  
 378      def setup_nodes(self):
 379          """Override this method to customize test node setup"""
 380          self.add_nodes(self.num_nodes, self.extra_args)
 381          self.start_nodes()
 382          if self.uses_wallet:
 383              self.import_deterministic_coinbase_privkeys()
 384          if not self.setup_clean_chain:
 385              for n in self.nodes:
 386                  assert_equal(n.getblockchaininfo()["blocks"], 199)
 387              # To ensure that all nodes are out of IBD, the most recent block
 388              # must have a timestamp not too old (see IsInitialBlockDownload()).
 389              self.log.debug('Generate a block with current time')
 390              block_hash = self.generate(self.nodes[0], 1, sync_fun=self.no_op)[0]
 391              block = self.nodes[0].getblock(blockhash=block_hash, verbosity=0)
 392              for n in self.nodes:
 393                  n.submitblock(block)
 394                  chain_info = n.getblockchaininfo()
 395                  assert_equal(chain_info["blocks"], 200)
 396                  assert_equal(chain_info["initialblockdownload"], False)
 397  
 398      def import_deterministic_coinbase_privkeys(self):
 399          for i in range(self.num_nodes):
 400              self.init_wallet(node=i)
 401  
 402      def init_wallet(self, *, node):
 403          wallet_name = self.default_wallet_name if self.wallet_names is None else self.wallet_names[node] if node < len(self.wallet_names) else False
 404          if wallet_name is not False:
 405              n = self.nodes[node]
 406              if wallet_name is not None:
 407                  n.createwallet(wallet_name=wallet_name, load_on_startup=True)
 408              wallet_importprivkey(n.get_wallet_rpc(wallet_name), n.get_deterministic_priv_key().key, 0, label="coinbase")
 409  
 410      def run_test(self):
 411          """Tests must override this method to define test logic"""
 412          raise NotImplementedError
 413  
 414      # Public helper methods. These can be accessed by the subclass test scripts.
 415  
 416      def add_nodes(self, num_nodes: int, extra_args=None, *, rpchost=None, versions=None):
 417          """Instantiate TestNode objects.
 418  
 419          Should only be called once after the nodes have been specified in
 420          set_test_params()."""
 421          def bin_dir_from_version(version):
 422              if not version:
 423                  return None
 424              if version > 219999:
 425                  # Starting at client version 220000 the first two digits represent
 426                  # the major version, e.g. v22.0 instead of v0.22.0.
 427                  version *= 100
 428              return os.path.join(
 429                  self.options.previous_releases_path,
 430                  re.sub(
 431                      r'\.0$' if version <= 219999 else r'(\.0){1,2}$',
 432                      '', # Remove trailing dot for point releases, after 22.0 also remove double trailing dot.
 433                      'v{}.{}.{}.{}'.format(
 434                          (version % 100000000) // 1000000,
 435                          (version % 1000000) // 10000,
 436                          (version % 10000) // 100,
 437                          (version % 100) // 1,
 438                      ),
 439                  ),
 440                  'bin',
 441              )
 442  
 443          if self.bind_to_localhost_only:
 444              extra_confs = [["bind=127.0.0.1"]] * num_nodes
 445          else:
 446              extra_confs = [[]] * num_nodes
 447          if extra_args is None:
 448              extra_args = [[]] * num_nodes
 449          # Whitelist peers to speed up tx relay / mempool sync. Don't use it if testing tx relay or timing.
 450          if self.noban_tx_relay:
 451              for i in range(len(extra_args)):
 452                  extra_args[i] = extra_args[i] + ["-whitelist=noban,in,out@127.0.0.1"]
 453          if versions is None:
 454              versions = [None] * num_nodes
 455          bin_dirs = []
 456          for v in versions:
 457              bin_dir = bin_dir_from_version(v)
 458              bin_dirs.append(bin_dir)
 459  
 460          extra_init = [{}] * num_nodes if self.extra_init is None else self.extra_init # type: ignore[var-annotated]
 461          assert_equal(len(extra_init), num_nodes)
 462          assert_equal(len(extra_confs), num_nodes)
 463          assert_equal(len(extra_args), num_nodes)
 464          assert_equal(len(versions), num_nodes)
 465          assert_equal(len(bin_dirs), num_nodes)
 466          for i in range(num_nodes):
 467              args = list(extra_args[i])
 468              init = dict(
 469                  chain=self.chain,
 470                  rpchost=rpchost,
 471                  timewait=self.rpc_timeout,
 472                  timeout_factor=self.options.timeout_factor,
 473                  binaries=self.get_binaries(bin_dirs[i]),
 474                  version=versions[i],
 475                  coverage_dir=self.options.coveragedir,
 476                  cwd=self.options.tmpdir,
 477                  extra_conf=extra_confs[i],
 478                  extra_args=args,
 479                  use_cli=self.options.usecli,
 480                  v2transport=self.options.v2transport,
 481                  uses_wallet=self.uses_wallet,
 482              )
 483              init.update(extra_init[i])
 484              test_node_i = TestNode(
 485                  i,
 486                  get_datadir_path(self.options.tmpdir, i),
 487                  **init)
 488              self.nodes.append(test_node_i)
 489              if not test_node_i.version_is_at_least(170000):
 490                  # adjust conf for pre 17
 491                  test_node_i.replace_in_config([('[regtest]', '')])
 492  
 493      def start_node(self, i, *args, **kwargs):
 494          """Start a bitcoind"""
 495  
 496          node = self.nodes[i]
 497  
 498          node.start(*args, **kwargs)
 499          node.wait_for_rpc_connection()
 500  
 501          if self.options.coveragedir is not None:
 502              coverage.write_all_rpc_commands(self.options.coveragedir, node._rpc)
 503  
 504      def start_nodes(self, extra_args=None, *args, **kwargs):
 505          """Start multiple bitcoinds"""
 506  
 507          if extra_args is None:
 508              extra_args = [None] * self.num_nodes
 509          assert_equal(len(extra_args), self.num_nodes)
 510          for i, node in enumerate(self.nodes):
 511              node.start(extra_args[i], *args, **kwargs)
 512          for node in self.nodes:
 513              node.wait_for_rpc_connection()
 514  
 515          if self.options.coveragedir is not None:
 516              for node in self.nodes:
 517                  coverage.write_all_rpc_commands(self.options.coveragedir, node._rpc)
 518  
 519      def stop_node(self, i, expected_stderr='', wait=0):
 520          """Stop a bitcoind test node"""
 521          self.nodes[i].stop_node(expected_stderr, wait=wait)
 522  
 523      def stop_nodes(self, wait=0):
 524          """Stop multiple bitcoind test nodes"""
 525          for node in self.nodes:
 526              # Issue RPC to stop nodes
 527              node.stop_node(wait=wait, wait_until_stopped=False)
 528  
 529          for node in self.nodes:
 530              # Wait for nodes to stop
 531              node.wait_until_stopped()
 532  
 533      def cleanup_partially_started_nodes(self):
 534          """Tear down nodes left running after a failed start_nodes().
 535  
 536          After start_nodes() raises (e.g. FailedToStartError), some nodes may be
 537          RPC-connected, some may have a live process without RPC, and some may
 538          already have exited. Stop the connected ones cleanly and force-kill the
 539          rest so the framework's teardown can proceed.
 540          """
 541          for node in self.nodes:
 542              if not node.running:
 543                  continue
 544              if node.rpc_connected:
 545                  node.stop_node(wait=node.rpc_timeout)
 546              else:
 547                  node.process.kill()
 548                  node.process.wait(timeout=node.rpc_timeout)
 549                  node.process = None
 550                  node.stdout.close()
 551                  node.stderr.close()
 552                  node.running = False
 553  
 554      def restart_node(self, i, extra_args=None, clear_addrman=False, *, expected_stderr=''):
 555          """Stop and start a test node"""
 556          self.stop_node(i, expected_stderr=expected_stderr)
 557          if clear_addrman:
 558              peers_dat = self.nodes[i].chain_path / "peers.dat"
 559              os.remove(peers_dat)
 560              with self.nodes[i].assert_debug_log(expected_msgs=[f'Creating peers.dat because the file was not found ("{peers_dat}")']):
 561                  self.start_node(i, extra_args)
 562          else:
 563              self.start_node(i, extra_args)
 564  
 565      def wait_for_node_exit(self, i, timeout):
 566          self.nodes[i].process.wait(timeout)
 567  
 568      def connect_nodes(self, a, b, *, peer_advertises_v2=None):
 569          from_connection = self.nodes[a]
 570          to_connection = self.nodes[b]
 571  
 572          # Use subversion as peer id. Test nodes have their node number appended to the user agent string
 573          from_connection_subver = from_connection.getnetworkinfo()['subversion']
 574          to_connection_subver = to_connection.getnetworkinfo()['subversion']
 575  
 576          def find_conn(node, peer_subversion, inbound):
 577              return next(filter(lambda peer: peer['subver'] == peer_subversion and peer['inbound'] == inbound, node.getpeerinfo()), None)
 578  
 579          self.wait_until(lambda: not find_conn(from_connection, to_connection_subver, inbound=False))
 580          self.wait_until(lambda: not find_conn(to_connection, from_connection_subver, inbound=True))
 581  
 582          ip_port = "127.0.0.1:" + str(p2p_port(b))
 583  
 584          if peer_advertises_v2 is None:
 585              peer_advertises_v2 = from_connection.use_v2transport
 586  
 587          if peer_advertises_v2 != from_connection.use_v2transport:
 588              from_connection.addnode(node=ip_port, command="onetry", v2transport=peer_advertises_v2)
 589          else:
 590              # skip the optional third argument if it matches the default, for
 591              # compatibility with older clients
 592              from_connection.addnode(ip_port, "onetry")
 593  
 594          self.wait_until(lambda: find_conn(from_connection, to_connection_subver, inbound=False) is not None)
 595          self.wait_until(lambda: find_conn(to_connection, from_connection_subver, inbound=True) is not None)
 596  
 597          def check_bytesrecv(peer, msg_type, min_bytes_recv):
 598              assert peer is not None, "Error: peer disconnected"
 599              return peer['bytesrecv_per_msg'].pop(msg_type, 0) >= min_bytes_recv
 600  
 601          # Poll until version handshake (fSuccessfullyConnected) is complete to
 602          # avoid race conditions, because some message types are blocked from
 603          # being sent or received before fSuccessfullyConnected.
 604          #
 605          # As the flag fSuccessfullyConnected is not exposed, check it by
 606          # waiting for a pong, which can only happen after the flag was set.
 607          self.wait_until(lambda: check_bytesrecv(find_conn(from_connection, to_connection_subver, inbound=False), 'pong', 29))
 608          self.wait_until(lambda: check_bytesrecv(find_conn(to_connection, from_connection_subver, inbound=True), 'pong', 29))
 609  
 610      def disconnect_nodes(self, a, b):
 611          def disconnect_nodes_helper(node_a, node_b):
 612              def get_peer_ids(from_connection, node_num):
 613                  result = []
 614                  for peer in from_connection.getpeerinfo():
 615                      if "testnode{}".format(node_num) in peer['subver']:
 616                          result.append(peer['id'])
 617                  return result
 618  
 619              peer_ids = get_peer_ids(node_a, node_b.index)
 620              if not peer_ids:
 621                  self.log.warning("disconnect_nodes: {} and {} were not connected".format(
 622                      node_a.index,
 623                      node_b.index,
 624                  ))
 625                  return
 626              for peer_id in peer_ids:
 627                  try:
 628                      node_a.disconnectnode(nodeid=peer_id)
 629                  except JSONRPCException as e:
 630                      # If this node is disconnected between calculating the peer id
 631                      # and issuing the disconnect, don't worry about it.
 632                      # This avoids a race condition if we're mass-disconnecting peers.
 633                      if e.error['code'] != -29:  # RPC_CLIENT_NODE_NOT_CONNECTED
 634                          raise
 635  
 636              # wait to disconnect
 637              self.wait_until(lambda: not get_peer_ids(node_a, node_b.index), timeout=5)
 638              self.wait_until(lambda: not get_peer_ids(node_b, node_a.index), timeout=5)
 639  
 640          disconnect_nodes_helper(self.nodes[a], self.nodes[b])
 641  
 642      def split_network(self):
 643          """
 644          Split the network of four nodes into nodes 0/1 and 2/3.
 645          """
 646          self.disconnect_nodes(1, 2)
 647          self.sync_all(self.nodes[:2])
 648          self.sync_all(self.nodes[2:])
 649  
 650      def join_network(self):
 651          """
 652          Join the (previously split) network halves together.
 653          """
 654          self.connect_nodes(1, 2)
 655          self.sync_all()
 656  
 657      def no_op(self):
 658          pass
 659  
 660      def generate(self, generator, *args, sync_fun=None, **kwargs):
 661          blocks = generator.generate(*args, called_by_framework=True, **kwargs)
 662          sync_fun() if sync_fun else self.sync_all()
 663          return blocks
 664  
 665      def generateblock(self, generator, *args, sync_fun=None, **kwargs):
 666          blocks = generator.generateblock(*args, called_by_framework=True, **kwargs)
 667          sync_fun() if sync_fun else self.sync_all()
 668          return blocks
 669  
 670      def generatetoaddress(self, generator, *args, sync_fun=None, **kwargs):
 671          blocks = generator.generatetoaddress(*args, called_by_framework=True, **kwargs)
 672          sync_fun() if sync_fun else self.sync_all()
 673          return blocks
 674  
 675      def generatetodescriptor(self, generator, *args, sync_fun=None, **kwargs):
 676          blocks = generator.generatetodescriptor(*args, called_by_framework=True, **kwargs)
 677          sync_fun() if sync_fun else self.sync_all()
 678          return blocks
 679  
 680      def create_outpoints(self, node, *, outputs):
 681          """Send funds to a given list of `{address: amount}` targets using the bitcoind
 682          wallet and return the corresponding outpoints as a list of dictionaries
 683          `[{"txid": txid, "vout": vout1}, {"txid": txid, "vout": vout2}, ...]`.
 684          The result can be used to specify inputs for RPCs like `createrawtransaction`,
 685          `createpsbt`, `lockunspent` etc."""
 686          for output in outputs:
 687              assert_equal(len(output.keys()), 1)
 688          send_res = node.send(outputs)
 689          assert send_res["complete"]
 690          utxos = []
 691          for output in outputs:
 692              address = list(output.keys())[0]
 693              vout = find_vout_for_address(node, send_res["txid"], address)
 694              utxos.append({"txid": send_res["txid"], "vout": vout})
 695          return utxos
 696  
 697      def sync_blocks(self, nodes=None, wait=1, timeout=60):
 698          """
 699          Wait until everybody has the same tip.
 700          sync_blocks needs to be called with an rpc_connections set that has least
 701          one node already synced to the latest, stable tip, otherwise there's a
 702          chance it might return before all nodes are stably synced.
 703          """
 704          rpc_connections = nodes or self.nodes
 705          timeout = int(timeout * self.options.timeout_factor)
 706          stop_time = time.time() + timeout
 707          while time.time() <= stop_time:
 708              best_hash = [x.getbestblockhash() for x in rpc_connections]
 709              if best_hash.count(best_hash[0]) == len(rpc_connections):
 710                  return
 711              # Check that each peer has at least one connection
 712              assert (all([len(x.getpeerinfo()) for x in rpc_connections]))
 713              time.sleep(wait)
 714          raise AssertionError("Block sync timed out after {}s:{}".format(
 715              timeout,
 716              "".join("\n  {!r}".format(b) for b in best_hash),
 717          ))
 718  
 719      def sync_mempools(self, nodes=None, wait=1, timeout=60, flush_scheduler=True):
 720          """
 721          Wait until everybody has the same transactions in their memory
 722          pools
 723          """
 724          rpc_connections = nodes or self.nodes
 725          timeout = int(timeout * self.options.timeout_factor)
 726          stop_time = time.time() + timeout
 727          while time.time() <= stop_time:
 728              pool = [set(r.getrawmempool()) for r in rpc_connections]
 729              if pool.count(pool[0]) == len(rpc_connections):
 730                  if flush_scheduler:
 731                      for r in rpc_connections:
 732                          r.syncwithvalidationinterfacequeue()
 733                  return
 734              # Check that each peer has at least one connection
 735              assert (all([len(x.getpeerinfo()) for x in rpc_connections]))
 736              time.sleep(wait)
 737          raise AssertionError("Mempool sync timed out after {}s:{}".format(
 738              timeout,
 739              "".join("\n  {!r}".format(m) for m in pool),
 740          ))
 741  
 742      def sync_all(self, nodes=None):
 743          self.sync_blocks(nodes)
 744          self.sync_mempools(nodes)
 745  
 746      def wait_until(self, test_function, timeout=60, check_interval=0.05):
 747          return wait_until_helper_internal(test_function, timeout=timeout, timeout_factor=self.options.timeout_factor, check_interval=check_interval)
 748  
 749      def fill_node_addrman(self, *, node_index, address_types_to_add):
 750          ADDRESSES = {
 751              CAddress.NET_IPV4: [
 752                  "20.0.0.1",
 753                  "30.0.0.1",
 754                  "40.0.0.1",
 755                  "50.0.0.1",
 756                  "60.0.0.1",
 757                  "70.0.0.1",
 758                  "80.0.0.1",
 759                  "90.0.0.1",
 760                  "100.0.0.1",
 761                  "110.0.0.1",
 762                  "120.0.0.1",
 763                  "130.0.0.1",
 764                  "140.0.0.1",
 765                  "150.0.0.1",
 766                  "160.0.0.1",
 767                  "170.0.0.1",
 768                  "180.0.0.1",
 769                  "190.0.0.1",
 770                  "200.0.0.1",
 771                  "210.0.0.1",
 772              ],
 773              CAddress.NET_IPV6: [
 774                  "[20::1]",
 775                  "[30::1]",
 776                  "[40::1]",
 777                  "[50::1]",
 778                  "[60::1]",
 779                  "[70::1]",
 780                  "[80::1]",
 781                  "[90::1]",
 782                  "[100::1]",
 783                  "[110::1]",
 784                  "[120::1]",
 785                  "[130::1]",
 786                  "[140::1]",
 787                  "[150::1]",
 788                  "[160::1]",
 789                  "[170::1]",
 790                  "[180::1]",
 791                  "[190::1]",
 792                  "[200::1]",
 793                  "[210::1]",
 794              ],
 795              CAddress.NET_TORV3: [
 796                  "testonlyad777777777777777777777777777777777777777775b6qd.onion",
 797                  "testonlyah77777777777777777777777777777777777777777z7ayd.onion",
 798                  "testonlyal77777777777777777777777777777777777777777vp6qd.onion",
 799                  "testonlyap77777777777777777777777777777777777777777r5qad.onion",
 800                  "testonlyat77777777777777777777777777777777777777777udsid.onion",
 801                  "testonlyax77777777777777777777777777777777777777777yciid.onion",
 802                  "testonlya777777777777777777777777777777777777777777rhgyd.onion",
 803                  "testonlybd77777777777777777777777777777777777777777rs4ad.onion",
 804                  "testonlybp77777777777777777777777777777777777777777zs2ad.onion",
 805                  "testonlybt777777777777777777777777777777777777777777x6id.onion",
 806                  "testonlybx777777777777777777777777777777777777777775styd.onion",
 807                  "testonlyb3777777777777777777777777777777777777777774ckid.onion",
 808                  "testonlycd77777777777777777777777777777777777777777733id.onion",
 809                  "testonlych77777777777777777777777777777777777777777t6kid.onion",
 810                  "testonlycl77777777777777777777777777777777777777777tt3ad.onion",
 811                  "testonlyct77777777777777777777777777777777777777777wvhyd.onion",
 812                  "testonlycx7777777777777777777777777777777777777777774bad.onion",
 813                  "testonlyc377777777777777777777777777777777777777777u6aid.onion",
 814                  "testonlydd777777777777777777777777777777777777777777u5ad.onion",
 815                  "testonlydh77777777777777777777777777777777777777777wgnyd.onion",
 816              ],
 817              CAddress.NET_I2P: [
 818                  "testonlyad77777777777777777777777777777777777777777q.b32.i2p",
 819                  "testonlyah77777777777777777777777777777777777777777q.b32.i2p",
 820                  "testonlyap77777777777777777777777777777777777777777q.b32.i2p",
 821                  "testonlyat77777777777777777777777777777777777777777q.b32.i2p",
 822                  "testonlyax77777777777777777777777777777777777777777q.b32.i2p",
 823                  "testonlya377777777777777777777777777777777777777777q.b32.i2p",
 824                  "testonlya777777777777777777777777777777777777777777q.b32.i2p",
 825                  "testonlybd77777777777777777777777777777777777777777q.b32.i2p",
 826                  "testonlybh77777777777777777777777777777777777777777q.b32.i2p",
 827                  "testonlybl77777777777777777777777777777777777777777q.b32.i2p",
 828                  "testonlybp77777777777777777777777777777777777777777q.b32.i2p",
 829                  "testonlybt77777777777777777777777777777777777777777q.b32.i2p",
 830                  "testonlybx77777777777777777777777777777777777777777q.b32.i2p",
 831                  "testonlyb777777777777777777777777777777777777777777q.b32.i2p",
 832                  "testonlych77777777777777777777777777777777777777777q.b32.i2p",
 833                  "testonlycp77777777777777777777777777777777777777777q.b32.i2p",
 834                  "testonlyct77777777777777777777777777777777777777777q.b32.i2p",
 835                  "testonlycx77777777777777777777777777777777777777777q.b32.i2p",
 836                  "testonlyc377777777777777777777777777777777777777777q.b32.i2p",
 837                  "testonlyc777777777777777777777777777777777777777777q.b32.i2p",
 838              ],
 839              CAddress.NET_CJDNS: [
 840                  "[fc00::1]",
 841                  "[fc00::2]",
 842                  "[fc00::3]",
 843                  "[fc00::5]",
 844                  "[fc00::6]",
 845                  "[fc00::7]",
 846                  "[fc00::8]",
 847                  "[fc00::9]",
 848                  "[fc00::10]",
 849                  "[fc00::11]",
 850                  "[fc00::12]",
 851                  "[fc00::13]",
 852                  "[fc00::15]",
 853                  "[fc00::16]",
 854                  "[fc00::17]",
 855                  "[fc00::18]",
 856                  "[fc00::19]",
 857                  "[fc00::20]",
 858                  "[fc00::22]",
 859                  "[fc00::23]",
 860              ],
 861          }
 862          for addr_type in address_types_to_add:
 863              for addr in ADDRESSES[addr_type]:
 864                  res = self.nodes[node_index].addpeeraddress(address=addr, port=0 if addr.endswith(".i2p") else 8333, tried=False)
 865                  if not res["success"]:
 866                      self.log.debug(f"Could not add {addr} to nodes[{node_index}]'s addrman (collision?)")
 867  
 868      # Private helper methods. These should not be accessed by the subclass test scripts.
 869  
 870      def _start_logging(self):
 871          # Add logger and logging handlers
 872          self.log = logging.getLogger('TestFramework')
 873          self.log.setLevel(logging.DEBUG)
 874          # Create file handler to log all messages
 875          fh = logging.FileHandler(self.options.tmpdir + '/test_framework.log')
 876          fh.setLevel(logging.DEBUG)
 877          # Create console handler to log messages to stderr. By default this logs only error messages, but can be configured with --loglevel.
 878          ch = logging.StreamHandler(sys.stdout)
 879          # User can provide log level as a number or string (eg DEBUG). loglevel was caught as a string, so try to convert it to an int
 880          ll = int(self.options.loglevel) if self.options.loglevel.isdigit() else self.options.loglevel.upper()
 881          ch.setLevel(ll)
 882  
 883          # Format logs the same as bitcoind's debug.log with microprecision (so log files can be concatenated and sorted)
 884          class MicrosecondFormatter(logging.Formatter):
 885              def formatTime(self, record, _=None):
 886                  dt = datetime.fromtimestamp(record.created, timezone.utc)
 887                  return dt.strftime('%Y-%m-%dT%H:%M:%S.%f')
 888  
 889          formatter = MicrosecondFormatter(
 890              fmt='%(asctime)sZ %(name)s (%(levelname)s): %(message)s',
 891          )
 892          fh.setFormatter(formatter)
 893          ch.setFormatter(formatter)
 894          # add the handlers to the logger
 895          self.log.addHandler(fh)
 896          self.log.addHandler(ch)
 897  
 898          if self.options.trace_rpc:
 899              rpc_logger = logging.getLogger("BitcoinRPC")
 900              rpc_logger.setLevel(logging.DEBUG)
 901              rpc_handler = logging.StreamHandler(sys.stdout)
 902              rpc_handler.setLevel(logging.DEBUG)
 903              rpc_logger.addHandler(rpc_handler)
 904  
 905      def _initialize_chain(self):
 906          """Initialize a pre-mined blockchain for use by the test.
 907  
 908          Create a cache of a 199-block-long chain
 909          Afterward, create num_nodes copies from the cache."""
 910  
 911          CACHE_NODE_ID = 0  # Use node 0 to create the cache for all other nodes
 912          cache_node_dir = get_datadir_path(self.options.cachedir, CACHE_NODE_ID)
 913          assert self.num_nodes <= MAX_NODES
 914  
 915          if not os.path.isdir(cache_node_dir):
 916              self.log.debug("Creating cache directory {}".format(cache_node_dir))
 917  
 918              initialize_datadir(self.options.cachedir, CACHE_NODE_ID, self.chain, self.disable_autoconnect)
 919              self.nodes.append(
 920                  TestNode(
 921                      CACHE_NODE_ID,
 922                      cache_node_dir,
 923                      chain=self.chain,
 924                      extra_conf=["bind=127.0.0.1"],
 925                      extra_args=[],
 926                      rpchost=None,
 927                      timewait=self.rpc_timeout,
 928                      timeout_factor=self.options.timeout_factor,
 929                      binaries=self.get_binaries(),
 930                      coverage_dir=None,
 931                      cwd=self.options.tmpdir,
 932                      uses_wallet=self.uses_wallet,
 933                  ))
 934              self.start_node(CACHE_NODE_ID)
 935              cache_node = self.nodes[CACHE_NODE_ID]
 936  
 937              # Wait for RPC connections to be ready
 938              cache_node.wait_for_rpc_connection()
 939  
 940              # Set a time in the past, so that blocks don't end up in the future
 941              cache_node.setmocktime(cache_node.getblockheader(cache_node.getbestblockhash())['time'])
 942  
 943              # Create a 199-block-long chain; each of the 3 first nodes
 944              # gets 25 mature blocks and 25 immature.
 945              # The 4th address gets 25 mature and only 24 immature blocks so that the very last
 946              # block in the cache does not age too much (have an old tip age).
 947              # This is needed so that we are out of IBD when the test starts,
 948              # see the tip age check in IsInitialBlockDownload().
 949              gen_addresses = [k.address for k in TestNode.PRIV_KEYS][:3] + [create_deterministic_address_bcrt1_p2tr_op_true()[0]]
 950              assert_equal(len(gen_addresses), 4)
 951              for i in range(8):
 952                  self.generatetoaddress(
 953                      cache_node,
 954                      nblocks=25 if i != 7 else 24,
 955                      address=gen_addresses[i % len(gen_addresses)],
 956                  )
 957  
 958              assert_equal(cache_node.getblockchaininfo()["blocks"], 199)
 959  
 960              # Shut it down, and clean up cache directories:
 961              self.stop_nodes()
 962              self.nodes = []
 963  
 964              def cache_path(*paths):
 965                  return os.path.join(cache_node_dir, self.chain, *paths)
 966  
 967              os.rmdir(cache_path('wallets'))  # Remove empty wallets dir
 968              for entry in os.listdir(cache_path()):
 969                  if entry not in ['chainstate', 'blocks', 'indexes']:  # Only indexes, chainstate and blocks folders
 970                      os.remove(cache_path(entry))
 971  
 972          for i in range(self.num_nodes):
 973              self.log.debug("Copy cache directory {} to node {}".format(cache_node_dir, i))
 974              to_dir = get_datadir_path(self.options.tmpdir, i)
 975              shutil.copytree(cache_node_dir, to_dir)
 976              initialize_datadir(self.options.tmpdir, i, self.chain, self.disable_autoconnect)  # Overwrite port/rpcport in bitcoin.conf
 977  
 978      def _initialize_chain_clean(self):
 979          """Initialize empty blockchain for use by the test.
 980  
 981          Create an empty blockchain and num_nodes wallets.
 982          Useful if a test case wants complete control over initialization."""
 983          for i in range(self.num_nodes):
 984              initialize_datadir(self.options.tmpdir, i, self.chain, self.disable_autoconnect)
 985  
 986      def skip_if_no_py3_zmq(self):
 987          """Attempt to import the zmq package and skip the test if the import fails."""
 988          try:
 989              import zmq  # noqa
 990          except ImportError:
 991              raise SkipTest("python3-zmq module not available.")
 992  
 993      def skip_if_no_py_sqlite3(self):
 994          """Attempt to import the sqlite3 package and skip the test if the import fails."""
 995          try:
 996              import sqlite3  # noqa
 997          except ImportError:
 998              raise SkipTest("sqlite3 module not available.")
 999  
1000      def skip_if_no_py_capnp(self):
1001          """Attempt to import the capnp package and skip the test if the import fails."""
1002          try:
1003              import capnp  # type: ignore[import] # noqa: F401
1004          except ImportError:
1005              raise SkipTest("capnp module not available.")
1006  
1007      def skip_if_no_python_bcc(self):
1008          """Attempt to import the bcc package and skip the tests if the import fails."""
1009          try:
1010              import bcc  # type: ignore[import] # noqa: F401
1011          except ImportError:
1012              raise SkipTest("bcc python module not available")
1013  
1014      def skip_if_no_bitcoind_tracepoints(self):
1015          """Skip the running test if bitcoind has not been compiled with USDT tracepoint support."""
1016          if not self.is_usdt_compiled():
1017              raise SkipTest("bitcoind has not been built with USDT tracepoints enabled.")
1018  
1019      def skip_if_no_bpf_permissions(self):
1020          """Skip the running test if we don't have permissions to do BPF syscalls and load BPF maps."""
1021          # check for 'root' permissions
1022          if os.geteuid() != 0:
1023              raise SkipTest("no permissions to use BPF (please review the tests carefully before running them with higher privileges)")
1024  
1025      def skip_if_platform_not_linux(self):
1026          """Skip the running test if we are not on a Linux platform"""
1027          if platform.system() != "Linux":
1028              raise SkipTest("not on a Linux system")
1029  
1030      def skip_if_platform_not_posix(self):
1031          """Skip the running test if we are not on a POSIX platform"""
1032          if os.name != 'posix':
1033              raise SkipTest("not on a POSIX system")
1034  
1035      def skip_if_no_bitcoind_zmq(self):
1036          """Skip the running test if bitcoind has not been compiled with zmq support."""
1037          if not self.is_zmq_compiled():
1038              raise SkipTest("bitcoind has not been built with zmq enabled.")
1039  
1040      def skip_if_no_wallet(self):
1041          """Skip the running test if wallet has not been compiled."""
1042          self.uses_wallet = True
1043          if not self.is_wallet_compiled():
1044              raise SkipTest("wallet has not been compiled.")
1045  
1046      def skip_if_no_wallet_tool(self):
1047          """Skip the running test if bitcoin-wallet has not been compiled."""
1048          if not self.is_wallet_tool_compiled():
1049              raise SkipTest("bitcoin-wallet has not been compiled")
1050  
1051      def skip_if_no_bitcoin_tx(self):
1052          """Skip the running test if bitcoin-tx has not been compiled."""
1053          if not self.is_bitcoin_tx_compiled():
1054              raise SkipTest("bitcoin-tx has not been compiled")
1055  
1056      def skip_if_no_bitcoin_util(self):
1057          """Skip the running test if bitcoin-util has not been compiled."""
1058          if not self.is_bitcoin_util_compiled():
1059              raise SkipTest("bitcoin-util has not been compiled")
1060  
1061      def skip_if_no_bitcoin_chainstate(self):
1062          """Skip the running test if bitcoin-chainstate has not been compiled."""
1063          if not self.is_bitcoin_chainstate_compiled():
1064              raise SkipTest("bitcoin-chainstate has not been compiled")
1065  
1066      def skip_if_no_bitcoin_bench(self):
1067          """Skip the running test if bench_bitcoin has not been compiled."""
1068          if not self.is_bench_compiled():
1069              raise SkipTest("bench_bitcoin has not been compiled")
1070  
1071      def skip_if_no_cli(self):
1072          """Skip the running test if bitcoin-cli has not been compiled."""
1073          if not self.is_cli_compiled():
1074              raise SkipTest("bitcoin-cli has not been compiled.")
1075  
1076      def skip_if_no_ipc(self):
1077          """Skip the running test if ipc is not compiled."""
1078          if not self.is_ipc_compiled():
1079              raise SkipTest("ipc has not been compiled.")
1080  
1081      def skip_if_no_previous_releases(self):
1082          """Skip the running test if previous releases are not available."""
1083          if not self.has_previous_releases():
1084              raise SkipTest("previous releases not available or disabled")
1085  
1086      def has_resource_module(self):
1087          """Checks whether the resource module is available."""
1088          return find_spec('resource') is not None
1089  
1090      @property
1091      def RLIM_INFINITY(self):
1092          if not self.has_resource_module():
1093              return None
1094          import resource
1095          return resource.RLIM_INFINITY
1096  
1097      def has_previous_releases(self):
1098          """Checks whether previous releases are present and enabled."""
1099          if not os.path.isdir(self.options.previous_releases_path):
1100              if self.options.prev_releases:
1101                  raise AssertionError(f"Force test of previous releases but releases missing: {self.options.previous_releases_path}\n"
1102                                       "Previous releases binaries can be downloaded via `test/get_previous_releases.py`.")
1103          return self.options.prev_releases
1104  
1105      def skip_if_no_external_signer(self):
1106          """Skip the running test if external signer support has not been compiled."""
1107          if not self.is_external_signer_compiled():
1108              raise SkipTest("external signer support has not been compiled.")
1109  
1110      def skip_if_running_under_valgrind(self):
1111          """Skip the running test if Valgrind is being used."""
1112          if self.options.valgrind:
1113              raise SkipTest("This test is not compatible with Valgrind.")
1114  
1115      def is_bench_compiled(self):
1116          """Checks whether bench_bitcoin was compiled."""
1117          return self.config["components"].getboolean("BUILD_BENCH")
1118  
1119      def is_cli_compiled(self):
1120          """Checks whether bitcoin-cli was compiled."""
1121          return self.config["components"].getboolean("ENABLE_CLI")
1122  
1123      def is_external_signer_compiled(self):
1124          """Checks whether external signer support was compiled."""
1125          return self.config["components"].getboolean("ENABLE_EXTERNAL_SIGNER")
1126  
1127      def is_wallet_compiled(self):
1128          """Checks whether the wallet module was compiled."""
1129          return self.config["components"].getboolean("ENABLE_WALLET")
1130  
1131      def is_wallet_tool_compiled(self):
1132          """Checks whether bitcoin-wallet was compiled."""
1133          return self.config["components"].getboolean("ENABLE_WALLET_TOOL")
1134  
1135      def is_bitcoin_tx_compiled(self):
1136          """Checks whether bitcoin-tx was compiled."""
1137          return self.config["components"].getboolean("BUILD_BITCOIN_TX")
1138  
1139      def is_bitcoin_util_compiled(self):
1140          """Checks whether bitcoin-util was compiled."""
1141          return self.config["components"].getboolean("ENABLE_BITCOIN_UTIL")
1142  
1143      def is_bitcoin_chainstate_compiled(self):
1144          """Checks whether bitcoin-chainstate was compiled."""
1145          return self.config["components"].getboolean("ENABLE_BITCOIN_CHAINSTATE")
1146  
1147      def is_zmq_compiled(self):
1148          """Checks whether the zmq module was compiled."""
1149          return self.config["components"].getboolean("ENABLE_ZMQ")
1150  
1151      def is_embedded_asmap_compiled(self):
1152          """Checks whether ASMap data was embedded during compilation."""
1153          return self.config["components"].getboolean("ENABLE_EMBEDDED_ASMAP")
1154  
1155      def is_usdt_compiled(self):
1156          """Checks whether the USDT tracepoints were compiled."""
1157          return self.config["components"].getboolean("ENABLE_USDT_TRACEPOINTS")
1158  
1159      def is_ipc_compiled(self):
1160          """Checks whether ipc was compiled."""
1161          return self.config["components"].getboolean("ENABLE_IPC")
1162  
1163      def has_blockfile(self, node, filenum: str):
1164          return (node.blocks_path/ f"blk{filenum}.dat").is_file()
1165  
1166      def inspect_sqlite_db(self, path, fn, *args, **kwargs):
1167          try:
1168              import sqlite3 # type: ignore[import]
1169              conn = sqlite3.connect(path)
1170              with conn:
1171                  result = fn(conn, *args, **kwargs)
1172              conn.close()
1173              return result
1174          except ImportError:
1175              self.log.warning("sqlite3 module not available, skipping tests that inspect the database")
1176  
1177      def cleanup_folder(self, _path):
1178          path = Path(_path)
1179          if not path.is_relative_to(self.options.tmpdir):
1180              raise AssertionError(f"Trying to delete #{path} outside of #{self.options.tmpdir}")
1181          shutil.rmtree(path)
1182