Source code for crossbar

#####################################################################################
#
#  Copyright (c) typedef int GmbH
#  SPDX-License-Identifier: EUPL-1.2
#
#####################################################################################
"""Crossbar.io is a decentralized data plane for XBR/WAMP based application
service and data routing, built on Crossbar.io OSS."""

import os
import sys

# monkey patch eth_abi for master branch (which we need for python 3.11)
# https://github.com/ethereum/eth-abi/blob/master/docs/release_notes.rst#breaking-changes
# https://github.com/ethereum/eth-abi/pull/161
# ImportError: cannot import name 'encode_single' from 'eth_abi' (/home/oberstet/cpy311_2/lib/python3.11/site-packages/eth_abi/__init__.py)
import eth_abi
import psutil

if not hasattr(eth_abi, "encode_abi") and hasattr(eth_abi, "encode"):
    eth_abi.encode_abi = eth_abi.encode
if not hasattr(eth_abi, "encode_single") and hasattr(eth_abi, "encode"):
    eth_abi.encode_single = eth_abi.encode

import eth_typing

if not hasattr(eth_typing, "ChainID") and hasattr(eth_typing, "ChainId"):
    eth_typing.ChainID = eth_typing.ChainId

# monkey patch web3 for master branch / upcoming v6 (which we need for python 3.11)
# AttributeError: type object 'Web3' has no attribute 'toChecksumAddress'. Did you mean: 'to_checksum_address'?
import web3

if not hasattr(web3.Web3, "toChecksumAddress") and hasattr(web3.Web3, "to_checksum_address"):
    web3.Web3.toChecksumAddress = lambda x: web3.Web3.to_checksum_address(x)
if not hasattr(web3.Web3, "isConnected") and hasattr(web3.Web3, "is_connected"):
    web3.Web3.isConnected = web3.Web3.is_connected

import warnings

# prevent error in EXE building (pyinstaller):
# "pkg_resources.DistributionNotFound: The 'humanize' distribution was not found and is required by the application"
import humanize  # noqa

# monkey patch, see:
# https://github.com/ethereum/web3.py/issues/1201
# https://github.com/ethereum/eth-abi/pull/88
from eth_abi import abi

from crossbar._util import hl
from crossbar._version import __build__, __version__

if not hasattr(abi, "collapse_type"):

    def collapse_type(base, sub, arrlist):
        return base + sub + "".join(map(repr, arrlist))

    abi.collapse_type = collapse_type

if not hasattr(abi, "process_type"):
    from eth_abi.grammar import (
        TupleType,
        normalize,
        parse,
    )

    def process_type(type_str):
        normalized_type_str = normalize(type_str)
        abi_type = parse(normalized_type_str)

        type_str_repr = repr(type_str)
        if type_str != normalized_type_str:
            type_str_repr = "{} (normalized to {})".format(
                type_str_repr,
                repr(normalized_type_str),
            )

        if isinstance(abi_type, TupleType):
            raise ValueError(
                "Cannot process type {}: tuple types not supported".format(
                    type_str_repr,
                )
            )

        abi_type.validate()

        sub = abi_type.sub
        if isinstance(sub, tuple):
            sub = "x".join(map(str, sub))
        elif isinstance(sub, int):
            sub = str(sub)
        else:
            sub = ""

        arrlist = abi_type.arrlist
        if isinstance(arrlist, tuple):
            arrlist = list(map(list, arrlist))
        else:
            arrlist = []

        return abi_type.base, sub, arrlist

    abi.process_type = process_type

# https://stackoverflow.com/a/40846742/884770
# https://github.com/numpy/numpy/pull/432/commits/170ed4e33d6196d724dc18ddcd42311c291b4587?diff=split
# https://docs.python.org/3/library/warnings.html
# /opt/pypy3/lib-python/3/importlib/_bootstrap.py:223: builtins.UserWarning: builtins.type size changed, may indicate binary incompatibility. Expected 872, got 416
warnings.filterwarnings("ignore", message="builtins.type size changed, may indicate binary incompatibility")

# https://peps.python.org/pep-0632/
# site-packages/_distutils_hack/__init__.py:30: builtins.UserWarning: Setuptools is replacing distutils.
warnings.filterwarnings("ignore", message="Setuptools is replacing distutils.")

__all__ = ("__version__", "__build__", "run", "personalities")

_DEFINED_REACTORS = ["select", "poll", "epoll", "kqueue", "iocp"]

_DEFINED_PERSONALITIES = ["standalone", "edge", "master"]

_TOP_COMMANDS = ["standalone", "edge", "network", "master", "shell", "quickstart", "legal", "version"]

_HELP_USAGE = """
Usage: {executable} <command>

    <command>:

    standalone       Run a Crossbar.io node (default when no command is given)
    edge             Run a crossbar Edge node
    master           Run a crossbar Master node
    network          Run a crossbar XBR Network node
    shell            Run a management shell command
    quickstart       Create a WAMP/XBR application skeleton
    version          Print crossbar software versions
    legal            Print crossbar license terms

Command help: {executable} <command> --help
"""


[docs] def run(): """ CLI entry point into crossbar. * when called with no arguments, the arguments are read from the command line * when called with an explicit arguments list, use the same arguments in the list as on the command line, leaving out the initial `crossbar` command. twisted.internet.error.ReactorNotRestartable **Examples** To start Crossbar.io programmatically from a given node directory: .. code-block:: python import crossbar crossbar.run(['start', '--cbdir', '/tmp/mynode1/.crossbar']) To start Crossbar.io with log output at level "debug" .. code-block:: console $ crossbar start --loglevel=debug To chose a specific event reactor and print version information .. code-block:: console $ CROSSBAR_REACTOR="kqueue" crossbar version To start from a specified node directory (instead of the default `$CWD/.crossbar`): .. code-block:: console $ CROSSBAR_DIR="/tmp/test/.crossbar" crossbar start which is the same as .. code-block:: console $ crossbar start --cbdir=/tmp/test/.crossbar **Influential command line options and environment variables include:** ================== ======================== ============================================== Command line arg: Environment variable: Description: ================== ======================== ============================================== n.a. **CROSSBAR_REACTOR** Event reactor: * **auto** * **select** * **poll** * **epoll** * **kqueue** * **iocp** n.a. **CROSSBAR_PERSONALITY** Node personality: * **standalone** * **edge** * **master** ``--cbdir`` **CROSSBAR_DIR** Node directory (local directory) ``--config`` **CROSSBAR_CONFIG** Node configuration (local filename) ``--color`` n.a. Enable colored terminal output ``--loglevel`` n.a. Select log level ``--logformat`` n.a. Select log format ``--logdir`` n.a. Log to this local directory ``--logtofile`` n.a. Enable logging to file ================== ======================== ============================================== .. seealso:: `TwistedMatrix: Choosing a Reactor and GUI Toolkit Integration <https://twistedmatrix.com/documents/current/core/howto/choosing-reactor.html>`_ .. note:: The Twisted reactor to use can only be explicitly chosen via an environment variable, not a command line option. """ # # check command line args # argv = sys.argv[:] if len(argv) < 2: print(_HELP_USAGE.format(executable=hl(os.path.basename(argv[0]), bold=True, color="yellow"))) sys.exit(0) # XXX maybe we should Click here, too, since we're already depending on it? executable, command = argv[0:2] args = argv[2:] sys.argv = [executable] + argv[2:] # if no known top-level command was given, fallback to "edge" mode if command not in _TOP_COMMANDS: args = [command] + args command = "standalone" # redirect a plain "crossbar legal" to "crossbar master legal" if command == "legal": command = "master" args = ["legal"] # redirect a plain "crossbar version" to "crossbar master version" if command == "version": command = "master" args = ["version"] # shell command (using asyncio) if command == "shell": from crossbar.shell import main sys.exit(main.run()) elif command == "quickstart": from crossbar.quickstart import main sys.exit(main.run()) elif command == "quickstart-venv": from crossbar.quickstart.quickstartvenv import main sys.exit(main.run()) # edge/master commands (using Twisted) elif command in ["standalone", "edge", "master", "network"]: # FIXME :: having FX drop out due to lack of entropy makes things a lot # harder from a deployment / monitoring perspective. A retry with reporting # would make life a lot easier. # on Linux, check that we start with sufficient system entropy if sys.platform.startswith("linux"): try: with open("/proc/sys/kernel/random/entropy_avail", "r") as ent: entropy_avail = int(ent.read()) if entropy_avail < 64: print( "FATAL: cannot start due to insufficient entropy ({} bytes) available - try installing rng-tools".format( entropy_avail ) ) sys.exit(1) except PermissionError: # this happens when packaged as a snap: the code prevented from reading a location # # that is not allowed to a confined snap package entropy_avail = -1 mem_avail = psutil.virtual_memory().available // 2**20 if mem_avail < 100: print("FATAL: cannot start due to insufficient available memory ({} MB free)".format(mem_avail)) sys.exit(1) from autobahn.twisted import install_reactor # IMPORTANT: keep the reactor install as early as possible to avoid importing # any Twisted module that comes with the side effect of installing a default # reactor (which might not be what we want!). reactor = install_reactor( explicit_reactor=os.environ.get("CROSSBAR_REACTOR", None), verbose=False, require_optimal_reactor=False ) # get chosen personality class if command == "standalone": from crossbar import personality as standalone personality = standalone.Personality elif command == "edge": from crossbar import edge personality = edge.Personality elif command == "network": from crossbar import network personality = network.Personality elif command == "master": from crossbar import master personality = master.Personality else: # should not arrive here raise Exception("internal error") # do NOT move this import above *** (triggers reactor imports) from crossbar.node.main import main # and now actually enter here .. this never returns! sys.exit(main(executable, args, reactor, personality)) else: assert False, "should not arrive here"
[docs] def personalities(): """ Return a map from personality names to actual available (=installed) Personality classes. """ # # do NOT move the imports here to the module level! (triggers reactor imports) # from crossbar import personality as standalone personality_classes = {"standalone": standalone.Personality} try: from crossbar import edge # noqa except ImportError: pass else: personality_classes["edge"] = edge.Personality try: from crossbar import master # noqa except ImportError: pass else: personality_classes["master"] = master.Personality return personality_classes
if __name__ == "__main__": run()