text: import logging
  import re
  from contextlib import contextmanager
  from tools.docker import docker_network
  import tools.resources as resources
  from tools.util import log_box, make_tempfile, make_tempdir
  from tools.wait import wait_for, node_started
  import shlex
  
  from multiprocessing import Queue, Process
  from queue import Empty
  
  default_image = "rchain-integration-testing:latest"
  
  rnode_binary = '/opt/docker/bin/rnode'
  rnode_directory = "/var/lib/rnode"
  rnode_deploy_dir = f"{rnode_directory}/deploy"
  rnode_bonds_file = f'{rnode_directory}/genesis/bonds.txt'
  rnode_certificate = f'{rnode_directory}/node.certificate.pem'
  rnode_key = f'{rnode_directory}/node.key.pem'
  
  
  class InterruptedException(Exception):
      pass
  
  
  class NonZeroExitCodeError(Exception):
      def __init__(self, command, exit_code, output):
          self.command = command
          self.exit_code = exit_code
          self.output = output
  
  
  class TimeoutError(Exception):
      def __init__(self, command, timeout):
          self.command = command
          self.timeout = timeout
  
  
  class Node:
      def __init__(self, container, deploy_dir, docker_client, timeout, network):
          self.container = container
          self.local_deploy_dir = deploy_dir
          self.remote_deploy_dir = rnode_deploy_dir
          self.name = container.name
          self.docker_client = docker_client
          self.timeout = timeout
          self.network = network
  
      def logs(self):
          return self.container.logs().decode('utf-8')
  
      def get_rnode_address(self):
          log_content = self.logs()
          m = re.search(f"Listening for traffic on (rnode://.+@{self.container.name}\\?protocol=\\d+&discovery=\\d+)\\.$", log_content, re.MULTILINE | re.DOTALL)
          address = m[1]
  
          logging.info(f"Bootstrap address: `{address}`")
          return address
  
      def get_metrics(self):
          cmd = f'curl -s http://localhost:40403/metrics'
  
          return self.exec_run(cmd=cmd)
  
      def cleanup(self):
          log_file = f"{self.container.name}.log"
  
          with open(log_file, "w") as f:
              f.write(self.logs())
  
          logging.info(f"Remove container {self.container.name}. Logs have been written to {log_file}")
  
          self.container.remove(force=True, v=True)
  
      def deploy_contract(self, contract):
          cmd = f'{rnode_binary} deploy --from "0x1" --phlo-limit 0 --phlo-price 0 --nonce 0 {rnode_deploy_dir}/{contract}'
          return self.exec_run(cmd)
  
      def propose_contract(self):
          return self.exec_run(f'{rnode_binary} propose')
  
      def show_blocks(self):
          return self.exec_run(f'{rnode_binary} show-blocks')
  
      def exec_run(self, cmd):
          queue = Queue(1)
  
          def execution():
              r = self.container.exec_run(cmd)
              queue.put((r.exit_code, r.output.decode('utf-8')))
  
          process = Process(target=execution)
  
          logging.info(f"{self.name}: Execute '{cmd}'. Timeout: {self.timeout}s")
  
          process.start()
  
          try:
              exit_code, output = queue.get(self.timeout)
              printed_output = output if len(output) < 150 else (output[0:150] + "...")
              single_line_output = printed_output.replace("\n", "\\n")
              logging.info(f"Returning: {exit_code}, '{single_line_output}'")
              return exit_code, output
          except Empty:
              process.terminate()
              process.join()
              raise TimeoutError(cmd, self.timeout)
  
      def shell_out(self, *cmd):
          exit_code, output = self.exec_run(cmd)
          if exit_code != 0:
              raise NonZeroExitCodeError(command=cmd, exit_code=exit_code, output=output)
          return output
  
      def call_rnode(self, *node_args):
          return self.shell_out(rnode_binary, *node_args)
  
      def eval(self, rho_file_path):
          return self.call_rnode('eval', rho_file_path)
  
      def deploy(self, rho_file_path):
          return self.call_rnode('deploy', '--from=0x1', '--phlo-limit=0', '--phlo-price=0', '--nonce=0', rho_file_path)
  
      def propose(self):
          return self.call_rnode('propose')
  
      def repl(self, rholang_code):
          quoted_rholang_code = shlex.quote(rholang_code)
          return self.shell_out('sh', '-c', f'echo {quoted_rholang_code} | {rnode_binary} repl')
  
      __timestamp_rx = "\\d\\d:\\d\\d:\\d\\d\\.\\d\\d\\d"
      __log_message_rx = re.compile(f"^{__timestamp_rx} (.*?)(?={__timestamp_rx})", re.MULTILINE | re.DOTALL)
  
      def log_lines(self):
          log_content = self.logs()
          return Node.__log_message_rx.split(log_content)
  
  
  def create_node_container(docker_client, image, name, network, bonds_file, command, rnode_timeout, extra_volumes, allowed_peers, memory, cpuset_cpus):
      deploy_dir = make_tempdir("rchain-integration-test")
  
      hosts_allow_file_content = \
          "ALL:ALL" if allowed_peers is None else "\n".join(f"ALL: {peer}" for peer in allowed_peers)
  
      hosts_allow_file = make_tempfile(f"hosts-allow-{name}", hosts_allow_file_content)
      hosts_deny_file = make_tempfile(f"hosts-deny-{name}", "ALL: ALL")
  
      cmd, args = command
      str_command = cmd + " " + " ".join(f"{k} {v}" for (k, v) in args.items())
  
      container = docker_client.containers.run(image,
                                               name=name,
                                               user='root',
                                               detach=True,
                                               cpuset_cpus=cpuset_cpus,
                                               mem_limit=memory,
                                               network=network,
                                               volumes=[
                                                             f"{hosts_allow_file}:/etc/hosts.allow",
                                                             f"{hosts_deny_file}:/etc/hosts.deny",
                                                             f"{bonds_file}:{rnode_bonds_file}",
                                                             f"{deploy_dir}:{rnode_deploy_dir}"
                                                         ] + extra_volumes,
                                               command=str_command,
                                               hostname=name)
      return Node(container, deploy_dir, docker_client, rnode_timeout, network)
  
  
  def create_bootstrap_node(docker_client,
                            network,
                            bonds_file,
                            key_pair,
                            rnode_timeout,
                            allowed_peers=None,
                            image=default_image,
                            memory="1024m",
                            cpuset_cpus="0"):
  
      key_file = resources.file_path("bootstrap_certificate/node.key.pem")
      cert_file = resources.file_path("bootstrap_certificate/node.certificate.pem")
  
      logging.info(f"Using key_file={key_file} and cert_file={cert_file}")
  
      name = f"bootstrap.{network}"
      command = ("run", {"--port": 40400,
                         "--standalone": "",
                         "--validator-private-key": key_pair.private_key,
                         "--validator-public-key": key_pair.public_key,
                         "--host": name
                         })
  
      volumes = [
          f"{cert_file}:{rnode_certificate}",
          f"{key_file}:{rnode_key}"
      ]
  
      logging.info(f"Starting bootstrap node {name}\ncommand:`{command}`")
  
      return create_node_container(docker_client, image, name, network, bonds_file, command, rnode_timeout, volumes, allowed_peers, memory, cpuset_cpus)
  
  
  def create_peer_nodes(docker_client,
                        bootstrap,
                        network,
                        bonds_file,
                        key_pairs,
                        rnode_timeout,
                        allowed_peers=None,
                        image=default_image,
                        memory="1024m",
                        cpuset_cpus="0"):
      """
      Create peer nodes
      """
      assert len(set(key_pairs)) == len(key_pairs), "There shouldn't be any duplicates in the key pairs"
  
      def peer_name(i): return f"peer{i}.{network}"
  
      if allowed_peers is None:
          allowed_peers = [bootstrap.name] + [peer_name(i) for i in range(0, len(key_pairs))]
  
      bootstrap_address = bootstrap.get_rnode_address()
  
      logging.info(f"Create {len(key_pairs)} peer nodes to connect to bootstrap {bootstrap_address}.")
  
      def create_peer(i, key_pair):
          name = peer_name(i)
  
          command = ("run ", {"--bootstrap": bootstrap_address,
                              "--validator-private-key": key_pair.private_key,
                              "--validator-public-key": key_pair.public_key,
                              "--host": name
                              })
  
          logging.info(f"Starting peer node {name} with command: `{command}`")
  
          return create_node_container(docker_client, image, name, network, bonds_file, command, rnode_timeout, [], allowed_peers, memory, cpuset_cpus)
  
      return [create_peer(i, key_pair) for i, key_pair in enumerate(key_pairs)]
  
  
  @contextmanager
  def create_bootstrap(docker, docker_network, timeout, validators_data):
      node = create_bootstrap_node(docker, docker_network, validators_data.bonds_file, validators_data.bootstrap_keys, timeout)
      try:
          yield node
      finally:
          node.cleanup()
  
  
  @contextmanager
  def start_bootstrap(docker_client, node_start_timeout, node_cmd_timeout, validators_data):
      with docker_network(docker_client) as network:
          with create_bootstrap(docker_client, network, node_cmd_timeout, validators_data) as node:
              wait_for(node_started(node), node_start_timeout, "Bootstrap node didn't start correctly")
              yield node
  