def log_run(self) -> None: """ Log timestamp and raiden version to help with debugging """ version = get_system_spec()["raiden"] cursor = self.conn.cursor() cursor.execute("INSERT INTO runs(raiden_version) VALUES (?)", [version]) self.maybe_commit()
def test_api_get_raiden_version(api_server_test_instance: APIServer): request = grequests.get( api_url_for(api_server_test_instance, "versionresource")) response = request.send().response assert_proper_response(response) raiden_version = get_system_spec()["raiden"] assert get_json_response(response) == {"version": raiden_version}
def run(self) -> None: configure_logging( self._options["log_config"], log_json=self._options["log_json"], log_file=self._options["log_file"], disable_debug_logfile=self._options["disable_debug_logfile"], debug_log_file_path=self._options["debug_logfile_path"], ) log.info("Starting Raiden", **get_system_spec()) if self._options["config_file"]: log.debug("Using config file", config_file=self._options["config_file"])
def smoketest( ctx: Context, debug: bool, eth_client: EthClient, report_path: Optional[str] ) -> None: """ Test, that the raiden installation is sane. """ from raiden.tests.utils.smoketest import ( setup_raiden, run_smoketest, setup_matrix_for_smoketest, setup_testchain_for_smoketest, ) from raiden.tests.utils.transport import make_requests_insecure, ParsedURL step_count = 8 step = 0 stdout = sys.stdout raiden_stdout = StringIO() assert ctx.parent, MYPY_ANNOTATION environment_type = ctx.parent.params["environment_type"] transport = ctx.parent.params["transport"] disable_debug_logfile = ctx.parent.params["disable_debug_logfile"] matrix_server = ctx.parent.params["matrix_server"] if transport != "matrix": raise RuntimeError(f"Invalid transport type '{transport}'") if report_path is None: report_file = mktemp(suffix=".log") else: report_file = report_path make_requests_insecure() urllib3.disable_warnings(InsecureRequestWarning) click.secho(f"Report file: {report_file}", fg="yellow") configure_logging( logger_level_config={"": "DEBUG"}, log_file=report_file, disable_debug_logfile=disable_debug_logfile, ) def append_report(subject: str, data: Optional[AnyStr] = None) -> None: with open(report_file, "a", encoding="UTF-8") as handler: handler.write(f'{f" {subject.upper()} ":=^80}{os.linesep}') if data is not None: write_data: str if isinstance(data, bytes): write_data = data.decode() else: write_data = data handler.writelines([write_data + os.linesep]) append_report("Raiden version", json.dumps(get_system_spec())) append_report("Raiden log") def print_step(description: str, error: bool = False) -> None: nonlocal step step += 1 click.echo( "{} {}".format( click.style(f"[{step}/{step_count}]", fg="blue"), click.style(description, fg="green" if not error else "red"), ), file=stdout, ) contracts_version = RAIDEN_CONTRACT_VERSION try: free_port_generator = get_free_port() ethereum_nodes = None datadir = mkdtemp() testchain_manager: ContextManager[Dict[str, Any]] = setup_testchain_for_smoketest( eth_client=eth_client, print_step=print_step, free_port_generator=free_port_generator, base_datadir=datadir, base_logdir=datadir, ) matrix_manager: ContextManager[ List[Tuple[ParsedURL, HTTPExecutor]] ] = setup_matrix_for_smoketest( print_step=print_step, free_port_generator=free_port_generator, broadcast_rooms_aliases=[ make_room_alias(NETWORKNAME_TO_ID["smoketest"], DISCOVERY_DEFAULT_ROOM), make_room_alias(NETWORKNAME_TO_ID["smoketest"], PATH_FINDING_BROADCASTING_ROOM), ], ) # Do not redirect the stdout on a debug session, otherwise the REPL # will also be redirected if debug: stdout_manager = contextlib.nullcontext() else: stdout_manager = contextlib.redirect_stdout(raiden_stdout) with stdout_manager, testchain_manager as testchain, matrix_manager as server_urls: result = setup_raiden( transport=transport, matrix_server=matrix_server, print_step=print_step, contracts_version=contracts_version, eth_client=testchain["eth_client"], eth_rpc_endpoint=testchain["eth_rpc_endpoint"], web3=testchain["web3"], base_datadir=testchain["base_datadir"], keystore=testchain["keystore"], ) args = result["args"] contract_addresses = result["contract_addresses"] ethereum_nodes = testchain["node_executors"] token = result["token"] port = next(free_port_generator) args["api_address"] = f"localhost:{port}" args["environment_type"] = environment_type # Matrix server # TODO: do we need more than one here? first_server = server_urls[0] args["matrix_server"] = first_server[0] args["one_to_n_contract_address"] = "0x" + "1" * 40 args["routing_mode"] = RoutingMode.LOCAL args["flat_fee"] = () args["proportional_fee"] = () args["proportional_imbalance_fee"] = () for option_ in run.params: if option_.name in args.keys(): args[option_.name] = option_.process_value(ctx, args[option_.name]) else: args[option_.name] = option_.default try: run_smoketest( print_step=print_step, args=args, contract_addresses=contract_addresses, token=token, ) finally: if ethereum_nodes: for node_executor in ethereum_nodes: node = node_executor.process node.send_signal(signal.SIGINT) try: node.wait(10) except TimeoutExpired: print_step("Ethereum node shutdown unclean, check log!", error=True) node.kill() if isinstance(node_executor.stdio, tuple): logfile = node_executor.stdio[1] logfile.flush() logfile.seek(0) append_report("Ethereum Node log output", logfile.read()) append_report("Raiden Node stdout", raiden_stdout.getvalue()) except: # noqa pylint: disable=bare-except if debug: import pdb pdb.post_mortem() # pylint: disable=no-member error = traceback.format_exc() append_report("Smoketest execution error", error) print_step("Smoketest execution error", error=True) success = False else: print_step(f"Smoketest successful") success = True if not success: sys.exit(1)
def version(short: bool) -> None: """Print version information and exit. """ if short: print(get_system_spec()["raiden"]) else: print(json.dumps(get_system_spec(), indent=2))
def run_services(options: Dict[str, Any]) -> None: if options["config_file"]: log.debug("Using config file", config_file=options["config_file"]) app = run_app(**options) gevent_tasks: List[gevent.Greenlet] = list() if options["console"]: from raiden.ui.console import Console console = Console(app) console.start() gevent_tasks.append(console) gevent_tasks.append(spawn_named("check_version", check_version, get_system_spec()["raiden"])) gevent_tasks.append(spawn_named("check_gas_reserve", check_gas_reserve, app.raiden)) gevent_tasks.append( spawn_named( "check_network_id", check_network_id, app.raiden.rpc_client.chain_id, app.raiden.rpc_client.web3, ) ) spawn_user_deposit_task = app.user_deposit and ( options["pathfinding_service_address"] or options["enable_monitoring"] ) if spawn_user_deposit_task: gevent_tasks.append( spawn_named("check_rdn_deposits", check_rdn_deposits, app.raiden, app.user_deposit) ) stop_event: AsyncResult[Optional[signal.Signals]] # pylint: disable=no-member stop_event = AsyncResult() def sig_set(sig: int, _frame: Any = None) -> None: stop_event.set(signal.Signals(sig)) # pylint: disable=no-member gevent.signal.signal(signal.SIGQUIT, sig_set) # pylint: disable=no-member gevent.signal.signal(signal.SIGTERM, sig_set) # pylint: disable=no-member gevent.signal.signal(signal.SIGINT, sig_set) # pylint: disable=no-member # The SIGPIPE handler should not be installed. It is handled by the python # runtime, and an exception will be raised at the call site that triggered # the error. # # The default SIGPIPE handler set by the libc will terminate the process # [4]. However, the CPython interpreter changes the handler to IGN [3]. # This allows for error reporting by the system calls that write to files. # Because of this, calling `send` to a closed socket will return an `EPIPE` # error [2], the error is then converted to an exception [5,6]. # # 1 - https://github.com/python/cpython/blob/3.8/Modules/socketmodule.c#L4088 # 2 - http://man7.org/linux/man-pages/man2/send.2.html # 3 - https://github.com/python/cpython/blob/3.8/Python/pylifecycle.c#L2306-L2307 # 4 - https://www.gnu.org/software/libc/manual/html_node/Operation-Error-Signals.html # 5 - https://github.com/python/cpython/blob/3.8/Modules/socketmodule.c#L836-L838 # 6 - https://github.com/python/cpython/blob/3.8/Modules/socketmodule.c#L627-L628 # 7 - https://docs.python.org/3/library/signal.html#note-on-sigpipe # # gevent.signal.signal(signal.SIGPIPE, sig_set) # pylint: disable=no-member # quit if any task exits, successfully or not app.raiden.greenlet.link(stop_event) for task in gevent_tasks: task.link(stop_event) try: signal_received = stop_event.get() if signal_received: print("\r", end="") # Reset cursor to overwrite a possibly printed "^C" log.info("Signal received. Shutting down.", signal=signal_received) finally: for task in gevent_tasks: task.kill() app.raiden.stop() gevent.joinall( set(gevent_tasks + [app.raiden]), app.config.shutdown_timeout, raise_error=True ) app.stop()
def smoketest(ctx: Context, debug: bool, eth_client: EthClient, report_path: Optional[str]) -> None: # pragma: no cover """ Test, that the raiden installation is sane. """ from raiden.tests.utils.smoketest import run_smoketest, setup_smoketest, step_printer raiden_stdout = StringIO() assert ctx.parent, MYPY_ANNOTATION environment_type = ctx.parent.params["environment_type"] disable_debug_logfile = ctx.parent.params["disable_debug_logfile"] if report_path is None: report_file = mktemp(suffix=".log") else: report_file = report_path click.secho(f"Report file: {report_file}", fg="yellow") configure_logging( logger_level_config={"": "DEBUG"}, log_file=report_file, disable_debug_logfile=disable_debug_logfile, ) def append_report(subject: str, data: Optional[AnyStr] = None) -> None: with open(report_file, "a", encoding="UTF-8") as handler: handler.write(f'{f" {subject.upper()} ":=^80}{os.linesep}') if data is not None: write_data: str if isinstance(data, bytes): write_data = data.decode() else: write_data = data handler.writelines([write_data + os.linesep]) append_report("Raiden version", json.dumps(get_system_spec())) append_report("Raiden log") free_port_generator = get_free_port() try: with step_printer(step_count=7, stdout=sys.stdout) as print_step: with setup_smoketest( eth_client=eth_client, print_step=print_step, free_port_generator=free_port_generator, debug=debug, stdout=raiden_stdout, append_report=append_report, ) as setup: args = setup.args port = next(free_port_generator) args["api_address"] = f"localhost:{port}" args["environment_type"] = environment_type # Matrix server args["one_to_n_contract_address"] = "0x" + "1" * 40 args["routing_mode"] = RoutingMode.PRIVATE args["flat_fee"] = () args["proportional_fee"] = () args["proportional_imbalance_fee"] = () for option_ in run.params: if option_.name in args.keys(): args[option_.name] = option_.process_value( ctx, args[option_.name]) else: args[option_.name] = option_.default run_smoketest(print_step=print_step, setup=setup) append_report("Raiden Node stdout", raiden_stdout.getvalue()) except: # noqa pylint: disable=bare-except if debug: import pdb pdb.post_mortem() # pylint: disable=no-member error = traceback.format_exc() append_report("Smoketest execution error", error) print_step("Smoketest execution error", error=True) success = False else: print_step("Smoketest successful") success = True if not success: sys.exit(1)
def run(ctx: Context, **kwargs: Any) -> None: # pylint: disable=too-many-locals,too-many-branches,too-many-statements if kwargs["config_file"]: apply_config_file(run, kwargs, ctx) configure_logging( kwargs["log_config"], log_json=kwargs["log_json"], log_file=kwargs["log_file"], disable_debug_logfile=kwargs["disable_debug_logfile"], debug_log_file_path=kwargs["debug_logfile_path"], ) flamegraph = kwargs.pop("flamegraph", None) switch_tracing = kwargs.pop("switch_tracing", None) profiler = None switch_monitor = None enable_gevent_monitoring_signal() if flamegraph: # pragma: no cover windows_not_supported("flame graph") from raiden.utils.profiling.sampler import FlameGraphCollector, TraceSampler os.makedirs(flamegraph, exist_ok=True) now = datetime.datetime.now().isoformat() address = to_checksum_address(kwargs["address"]) stack_path = os.path.join(flamegraph, f"{address}_{now}_stack.data") stack_stream = open(stack_path, "w") flame = FlameGraphCollector(stack_stream) profiler = TraceSampler(flame) if switch_tracing is True: # pragma: no cover windows_not_supported("switch tracing") from raiden.utils.profiling.greenlets import SwitchMonitoring switch_monitor = SwitchMonitoring() if kwargs["environment_type"] == Environment.DEVELOPMENT: IDLE.enable() memory_logger = None log_memory_usage_interval = kwargs.pop("log_memory_usage_interval", 0) if log_memory_usage_interval > 0: # pragma: no cover windows_not_supported("memory usage logging") from raiden.utils.profiling.memory import MemoryLogger memory_logger = MemoryLogger(log_memory_usage_interval) memory_logger.start() if ctx.invoked_subcommand is not None: # Pass parsed args on to subcommands. ctx.obj = kwargs return raiden_version = get_system_spec()["raiden"] click.secho(f"Welcome to Raiden, version {raiden_version}!", fg="green") click.secho( textwrap.dedent("""\ +------------------------------------------------------------------------+ | This is a Beta version of experimental open source software released | | as a test version under an MIT license and may contain errors and/or | | bugs. No guarantee or representation whatsoever is made regarding its | | suitability (or its use) for any purpose or regarding its compliance | | with any applicable laws and regulations. Use of the software is at | | your own risk and discretion and by using the software you warrant and | | represent that you have read this disclaimer, understand its contents, | | assume all risk related thereto and hereby release, waive, discharge | | and covenant not to hold liable Brainbot Labs Establishment or any of | | its officers, employees or affiliates from and for any direct or | | indirect damage resulting from the software or the use thereof. | | Such to the extent as permissible by applicable laws and regulations. | | | | Privacy warning: Please be aware, that by using the Raiden Client, | | among others your Ethereum address, channels, channel deposits, | | settlements and the Ethereum address of your channel counterparty will | | be stored on the Ethereum chain, i.e. on servers of Ethereum node | | operators and ergo are to a certain extent publicly available. The | | same might also be stored on systems of parties running Raiden nodes | | connected to the same token network. Data present in the Ethereum | | chain is very unlikely to be able to be changed, removed or deleted | | from the public arena. | | | | Also be aware, that data on individual Raiden token transfers will be | | made available via the Matrix protocol to the recipient, | | intermediating nodes of a specific transfer as well as to the Matrix | | server operators, see Raiden Transport Specification. | +------------------------------------------------------------------------+""" ), fg="yellow", ) if not kwargs["accept_disclaimer"]: click.confirm( "\nHave you read, understood and hereby accept the above " "disclaimer and privacy warning?", abort=True, ) # Name used in the exception handlers, make sure the kwargs contains the # key with the correct name by always running it. name_or_id = ID_TO_CHAINNAME.get(kwargs["chain_id"], kwargs["chain_id"]) # TODO: # - Ask for confirmation to quit if there are any locked transfers that did # not timeout. try: run_services(kwargs) except KeyboardInterrupt: # The user requested a shutdown. Assume that if the exception # propagated all the way to the top-level everything was shutdown # properly. # # Notes about edge cases: # - It could happen the exception was handled somewhere else in the # code, and did not reach the top-level, ideally that should result in # an exit with a non-zero code, but currently there is not way to # detect that. # - Just because the exception reached main, it doesn't mean that all # services were properly cleaned up. Ideally at this stage we should # run extra code to verify the state of the main services, and if any # of the is not properly shutdown exit with a non-zero code. pass except (ReplacementTransactionUnderpriced, EthereumNonceTooLow) as ex: click.secho( f"{ex}. Please make sure that this Raiden node is the " f"only user of the selected account", fg="red", ) sys.exit(ReturnCode.ETH_ACCOUNT_ERROR) except (ConnectionError, ConnectTimeout, RequestsConnectionError, ReadTimeoutError): print(COMMUNICATION_ERROR.format(kwargs["eth_rpc_endpoint"])) sys.exit(ReturnCode.GENERIC_COMMUNICATION_ERROR) except EthNodeInterfaceError as e: click.secho(str(e), fg="red") sys.exit(ReturnCode.ETH_INTERFACE_ERROR) except RaidenUnrecoverableError as ex: write_stack_trace(ex) sys.exit(ReturnCode.FATAL) except APIServerPortInUseError as ex: click.secho( f"ERROR: API Address {ex} is in use. Use --api-address <host:port> " f"to specify a different port.", fg="red", ) sys.exit(ReturnCode.PORT_ALREADY_IN_USE) except (KeystoreAuthenticationError, KeystoreFileNotFound) as e: click.secho(str(e), fg="red") sys.exit(ReturnCode.ETH_ACCOUNT_ERROR) except ConfigurationError as e: click.secho(str(e), fg="red") sys.exit(ReturnCode.RAIDEN_CONFIGURATION_ERROR) except ContractCodeMismatch as e: click.secho( f"{e}. This may happen if Raiden is configured to use an " f"unsupported version of the contracts.", fg="red", ) sys.exit(ReturnCode.SMART_CONTRACTS_CONFIGURATION_ERROR) except AddressWithoutCode as e: click.secho( f"{e}. This may happen if an external ERC20 smart contract " f"selfdestructed, or if the configured address is misconfigured, make " f"sure the used address is not a normal account but a smart contract, " f"and that it is deployed to {name_or_id}.", fg="red", ) sys.exit(ReturnCode.SMART_CONTRACTS_CONFIGURATION_ERROR) except filelock.Timeout: click.secho( f"FATAL: Another Raiden instance already running for account " f"{to_checksum_address(kwargs['address'])} on network id {name_or_id}", fg="red", ) sys.exit(ReturnCode.RAIDEN_CONFIGURATION_ERROR) except Exception as ex: write_stack_trace(ex) sys.exit(ReturnCode.FATAL) finally: # pragma: no cover # teardown order is important because of side-effects, both the # switch_monitor and profiler could use the tracing api, for the # teardown code to work correctly the teardown has to be done in the # reverse order of the initialization. if switch_monitor is not None: switch_monitor.stop() if memory_logger is not None: memory_logger.stop() if profiler is not None: profiler.stop()
def _start_services(self) -> None: if self._options["showconfig"]: print("Configuration Dump:") dump_cmd_options(self._options) dump_module("settings", settings) dump_module("constants", constants) app = run_app(**self._options) gevent_tasks: List[gevent.Greenlet] = list() runnable_tasks: List[Runnable] = list() runnable_tasks.append(app.raiden) domain_list = [] if self._options["rpccorsdomain"]: if "," in self._options["rpccorsdomain"]: for domain in self._options["rpccorsdomain"].split(","): domain_list.append(str(domain)) else: domain_list.append(str(self._options["rpccorsdomain"])) self.raiden_api = RaidenAPI(app.raiden) if self._options["rpc"]: rest_api = RestAPI(self.raiden_api) (api_host, api_port) = split_endpoint(self._options["api_address"]) if not api_port: api_port = Port(settings.DEFAULT_HTTP_SERVER_PORT) api_server = APIServer( rest_api, config={"host": api_host, "port": api_port}, cors_domain_list=domain_list, web_ui=self._options["web_ui"], eth_rpc_endpoint=self._options["eth_rpc_endpoint"], ) api_server.start() url = f"http://{api_host}:{api_port}/" print( f"The Raiden API RPC server is now running at {url}.\n\n See " f"the Raiden documentation for all available endpoints at\n " f"{DOC_URL}" ) runnable_tasks.append(api_server) if self._options["console"]: from raiden.ui.console import Console console = Console(app) console.start() gevent_tasks.append(console) gevent_tasks.append(gevent.spawn(check_version, get_system_spec()["raiden"])) gevent_tasks.append(gevent.spawn(check_gas_reserve, app.raiden)) gevent_tasks.append( gevent.spawn( check_network_id, app.raiden.rpc_client.chain_id, app.raiden.rpc_client.web3 ) ) spawn_user_deposit_task = app.user_deposit and ( self._options["pathfinding_service_address"] or self._options["enable_monitoring"] ) if spawn_user_deposit_task: gevent_tasks.append(gevent.spawn(check_rdn_deposits, app.raiden, app.user_deposit)) self._startup_hook() stop_event: AsyncResult[None] = AsyncResult() def sig_set(sig: Any = None, _frame: Any = None) -> None: stop_event.set(sig) gevent.signal(signal.SIGQUIT, sig_set) gevent.signal(signal.SIGTERM, sig_set) gevent.signal(signal.SIGINT, sig_set) # Make sure RaidenService is the last service in the list. runnable_tasks.reverse() # quit if any task exits, successfully or not for runnable in runnable_tasks: runnable.greenlet.link(stop_event) for task in gevent_tasks: task.link(stop_event) msg = ( "The RaidenService must be last service to stop, since the other " "services depend on it to run. Without this it is not possible to have a " "clean shutdown, e.g. the RestAPI must be stopped before " "RaidenService, otherwise it is possible for a request to be " "processed after the RaidenService was stopped and it will cause a " "crash." ) assert isinstance(runnable_tasks[-1], RaidenService), msg try: stop_event.get() print("Signal received. Shutting down ...") finally: self._shutdown_hook() for task in gevent_tasks: task.kill() for task in runnable_tasks: task.stop() gevent.joinall( set(gevent_tasks + runnable_tasks), app.config.shutdown_timeout, raise_error=True ) app.stop()
expiration_block=self.expiration_block, chain_id=self.chain_id, ) if self.signature is not None: data["signature"] = to_hex(self.signature) return data USER_AGENT_STR = ( ( "Raiden/{raiden}/DB:{raiden_db_version}/{python_implementation}/" "{python_version}/{system}/{architecture}/{distribution}" ) .format(**get_system_spec()) .replace(" ", "-") ) session = Session() session.headers["User-Agent"] = USER_AGENT_STR timeout_adapter = TimeoutHTTPAdapter(timeout=DEFAULT_HTTP_REQUEST_TIMEOUT) session.mount("http://", timeout_adapter) session.mount("https://", timeout_adapter) MAX_PATHS_QUERY_ATTEMPTS = 2 def get_pfs_info(url: str) -> PFSInfo: try: response = session.get(f"{url}/api/v1/info")