def get_beacon_shell_context(database_dir: Path, trinity_config: TrinityConfig) -> Dict[str, Any]: app_config = trinity_config.get_app_config(BeaconAppConfig) ipc_path = trinity_config.database_ipc_path db: DatabaseAPI trinity_already_running = ipc_path.exists() if trinity_already_running: db = DBClient.connect(ipc_path) else: db = LevelDB(database_dir) chain_config = app_config.get_chain_config() chain = chain_config.beacon_chain_class(db, chain_config.genesis_config) chaindb = BeaconChainDB(db, chain_config.genesis_config) head = chaindb.get_canonical_head(BeaconBlock) return { 'db': db, 'chaindb': chaindb, 'trinity_config': trinity_config, 'chain_config': chain_config, 'chain': chain, 'block_number': head.slot, 'hex_hash': head.hash_tree_root.hex(), 'state_root_hex': encode_hex(head.state_root), 'trinity_already_running': trinity_already_running }
def database_server_ipc_path(): core_db = AtomicDB() core_db[b'key-a'] = b'value-a' chaindb = ChainDB(core_db) # TODO: use a custom chain class only for testing. chaindb.persist_header(ROPSTEN_GENESIS_HEADER) with tempfile.TemporaryDirectory() as temp_dir: trinity_config = TrinityConfig( network_id=ROPSTEN_NETWORK_ID, data_dir=temp_dir, ) manager = get_chaindb_manager(trinity_config, core_db) chaindb_server_process = multiprocessing.Process( target=serve_chaindb, args=(manager, ), ) chaindb_server_process.start() wait_for_ipc(trinity_config.database_ipc_path) try: yield trinity_config.database_ipc_path finally: kill_process_gracefully(chaindb_server_process, logging.getLogger())
def create_db_server_manager(trinity_config: TrinityConfig, base_db: BaseAtomicDB) -> BaseManager: app_config = trinity_config.get_app_config(BeaconAppConfig) chain_config = app_config.get_chain_config() chaindb = BeaconChainDB(base_db, chain_config.genesis_config) if not is_beacon_database_initialized(chaindb, BeaconBlock): initialize_beacon_database(chain_config, chaindb, base_db, BeaconBlock) # This enables connection when clients launch from another process on the shell multiprocessing.current_process().authkey = AUTH_KEY class DBManager(BaseManager): pass DBManager.register('get_db', callable=lambda: TracebackRecorder(base_db), proxytype=AsyncDBProxy) DBManager.register( 'get_chaindb', callable=lambda: TracebackRecorder(chaindb), proxytype=AsyncBeaconChainDBProxy, ) manager = DBManager(address=str( trinity_config.database_ipc_path)) # type: ignore return manager
def create_db_server_manager(trinity_config: TrinityConfig, base_db: BaseAtomicDB) -> BaseManager: app_config = trinity_config.get_app_config(BeaconAppConfig) chain_config = app_config.get_chain_config() chaindb = BeaconChainDB(base_db, chain_config.eth2_config) if not is_beacon_database_initialized(chaindb, BeaconBlock): initialize_beacon_database(chain_config, chaindb, base_db, BeaconBlock) class DBManager(BaseManager): pass DBManager.register('get_db', callable=lambda: TracebackRecorder(base_db), proxytype=AsyncDBProxy) DBManager.register( 'get_chaindb', callable=lambda: TracebackRecorder(chaindb), proxytype=AsyncBeaconChainDBProxy, ) manager = DBManager(address=str( trinity_config.database_ipc_path)) # type: ignore return manager
def chain_for_config( trinity_config: TrinityConfig, event_bus: EndpointAPI, ) -> Iterator[Union[AsyncChainAPI, AsyncBeaconChainDB]]: if trinity_config.has_app_config(BeaconAppConfig): beacon_app_config = trinity_config.get_app_config(BeaconAppConfig) with chain_for_beacon_config(trinity_config, beacon_app_config) as beacon_chain: yield beacon_chain elif trinity_config.has_app_config(Eth1AppConfig): eth1_app_config = trinity_config.get_app_config(Eth1AppConfig) with chain_for_eth1_config(trinity_config, eth1_app_config, event_bus) as eth1_chain: yield eth1_chain else: raise Exception("Unsupported Node Type")
def trinity_boot( args: Namespace, trinity_config: TrinityConfig, extra_kwargs: Dict[str, Any], listener: logging.handlers.QueueListener, logger: logging.Logger) -> Tuple[multiprocessing.Process, ...]: # start the listener thread to handle logs produced by other processes in # the local logger. listener.start() ensure_beacon_dirs(trinity_config.get_app_config(BeaconAppConfig)) # First initialize the database process. database_server_process = ctx.Process( name="DB", target=run_database_process, args=( trinity_config, LevelDB, ), kwargs=extra_kwargs, ) # start the processes database_server_process.start() logger.info("Started DB server process (pid=%d)", database_server_process.pid) try: wait_for_ipc(trinity_config.database_ipc_path) except TimeoutError as e: logger.error("Timeout waiting for database to start. Exiting...") kill_process_gracefully(database_server_process, logger) ArgumentParser().error(message="Timed out waiting for database start") return None return (database_server_process, )
async def launch_node_coro(args: Namespace, trinity_config: TrinityConfig) -> None: endpoint = TrinityEventBusEndpoint() NodeClass = trinity_config.get_app_config(Eth1AppConfig).node_class node = NodeClass(endpoint, trinity_config) networking_connection_config = ConnectionConfig.from_name( NETWORKING_EVENTBUS_ENDPOINT, trinity_config.ipc_dir) await endpoint.start_serving(networking_connection_config) endpoint.auto_connect_new_announced_endpoints() await endpoint.connect_to_endpoints( ConnectionConfig.from_name(MAIN_EVENTBUS_ENDPOINT, trinity_config.ipc_dir), # Plugins that run within the networking process broadcast and receive on the # the same endpoint networking_connection_config, ) await endpoint.announce_endpoint() # This is a second PluginManager instance governing plugins in a shared process. plugin_manager = PluginManager(SharedProcessScope(endpoint), get_all_plugins()) plugin_manager.prepare(args, trinity_config) asyncio.ensure_future( handle_networking_exit(node, plugin_manager, endpoint)) asyncio.ensure_future(node.run())
def create_db_server_manager(trinity_config: TrinityConfig, base_db: BaseAtomicDB) -> BaseManager: chain_config = trinity_config.get_chain_config() chaindb = ChainDB(base_db) if not is_database_initialized(chaindb): initialize_database(chain_config, chaindb, base_db) headerdb = HeaderDB(base_db) class DBManager(BaseManager): pass DBManager.register('get_db', callable=lambda: TracebackRecorder(base_db), proxytype=AsyncDBProxy) DBManager.register( 'get_chaindb', callable=lambda: TracebackRecorder(chaindb), proxytype=AsyncChainDBProxy, ) DBManager.register( 'get_headerdb', callable=lambda: TracebackRecorder(headerdb), proxytype=AsyncHeaderDBProxy, ) manager = DBManager(address=str( trinity_config.database_ipc_path)) # type: ignore return manager
async def test_logger_configuration(command, expected_stderr_logs, unexpected_stderr_logs, expected_file_logs, unexpected_file_logs, unused_tcp_port): command = amend_command_for_unused_port(command, unused_tcp_port) def contains_substring(iterable, substring): return any(substring in x for x in iterable) # Saw occasional (<25%, >5%) failures in CI at 30s because of slow machines or bad luck async with AsyncProcessRunner.run(command, timeout_sec=45) as runner: stderr_logs = [] # Collect logs up to the point when the sync begins so that we have enough logs # for assertions marker_seen_at = 0 async for line in runner.stderr: if marker_seen_at != 0 and time.time() - marker_seen_at > 3: break if "DiscoveryService" in line: marker_seen_at = time.time() stderr_logs.append(line) for log in expected_stderr_logs: if not contains_substring(stderr_logs, log): raise AssertionError(f"Log should contain `{log}` but does not") for log in unexpected_stderr_logs: if contains_substring(stderr_logs, log): raise AssertionError(f"Log should not contain `{log}` but does") log_dir = TrinityConfig(app_identifier="eth1", network_id=1).log_dir log_file_path = max(log_dir.glob('*'), key=os.path.getctime) with open(log_file_path) as log_file: file_content = log_file.read() for log in expected_file_logs: if log not in file_content: raise AssertionError(f"Logfile should contain `{log}` but does not") for log in unexpected_file_logs: if log in file_content: raise AssertionError(f"Logfile should not contain `{log}` but does")
def get_protocol(trinity_config: TrinityConfig) -> Type[Protocol]: # For now DiscoveryByTopicProtocol supports a single topic, so we use the latest # version of our supported protocols. Maybe this could be more generic? eth1_config = trinity_config.get_app_config(Eth1AppConfig) if eth1_config.database_mode is Eth1DbMode.LIGHT: return LESProtocolV2 else: return ETHProtocol
def run_database_process(trinity_config: TrinityConfig, db_class: Type[LevelDB]) -> None: with trinity_config.process_id_file('database'): app_config = trinity_config.get_app_config(BeaconAppConfig) chain_config = app_config.get_chain_config() base_db = db_class(db_path=app_config.database_dir) chaindb = BeaconChainDB(base_db, chain_config.genesis_config) if not is_beacon_database_initialized(chaindb, BeaconBlock): initialize_beacon_database(chain_config, chaindb, base_db, BeaconBlock) manager = DBManager(base_db) with manager.run(trinity_config.database_ipc_path): try: manager.wait_stopped() except KeyboardInterrupt: pass
def run_monitoring(cls, args: argparse.Namespace, trinity_config: TrinityConfig): normalized_name = friendly_filename_or_url("monitoring_ui") with trinity_config.process_id_file(normalized_name): loop = asyncio.get_event_loop() asyncio.ensure_future(monitoring(normalized_name, trinity_config)) loop.run_forever() loop.close()
def load_trinity_config_from_parser_args( parser: argparse.ArgumentParser, args: argparse.Namespace, app_identifier: str, sub_configs: SubConfigs) -> TrinityConfig: try: return TrinityConfig.from_parser_args(args, app_identifier, sub_configs) except AmbigiousFileSystem: parser.error(TRINITY_AMBIGIOUS_FILESYSTEM_INFO)
def test_trinity_config_computed_properties(xdg_trinity_root): data_dir = get_local_data_dir('muffin', xdg_trinity_root) trinity_config = TrinityConfig(network_id=1, data_dir=data_dir) assert trinity_config.network_id == 1 assert trinity_config.data_dir == data_dir assert trinity_config.database_dir == data_dir / DATABASE_DIR_NAME / "full" assert trinity_config.nodekey_path == get_nodekey_path(data_dir)
def run_shell(cls, args: Namespace, trinity_config: TrinityConfig) -> None: config: BaseAppConfig if trinity_config.has_app_config(Eth1AppConfig): config = trinity_config.get_app_config(Eth1AppConfig) context = get_eth1_shell_context(config.database_dir, trinity_config) db_shell(is_ipython_available(), context) elif trinity_config.has_app_config(BeaconAppConfig): config = trinity_config.get_app_config(BeaconAppConfig) context = get_beacon_shell_context(config.database_dir, trinity_config) db_shell(is_ipython_available(), context) else: cls.get_logger().error( "DB Shell only supports the Ethereum 1 and Beacon nodes at this time" )
def run_database_process(trinity_config: TrinityConfig, db_class: Type[AtomicDatabaseAPI]) -> None: with trinity_config.process_id_file('database'): app_config = trinity_config.get_app_config(Eth1AppConfig) base_db = db_class(db_path=app_config.database_dir) chaindb = ChainDB(base_db) if not is_database_initialized(chaindb): chain_config = app_config.get_chain_config() initialize_database(chain_config, chaindb, base_db) manager = DBManager(base_db) with manager.run(trinity_config.database_ipc_path): try: manager.wait_stopped() except KeyboardInterrupt: pass
def test_trinity_config_explicit_properties(): trinity_config = TrinityConfig( network_id=1, data_dir='./data-dir', nodekey_path='./nodekey' ) assert trinity_config.data_dir == Path('./data-dir').resolve() assert trinity_config.nodekey_path == Path('./nodekey').resolve()
def setup_eth1_modules(self, trinity_config: TrinityConfig) -> Tuple[BaseRPCModule, ...]: db_manager = create_db_consumer_manager(trinity_config.database_ipc_path) eth1_app_config = trinity_config.get_app_config(Eth1AppConfig) chain_config = trinity_config.get_chain_config() chain: BaseAsyncChain if eth1_app_config.database_mode is Eth1DbMode.LIGHT: header_db = db_manager.get_headerdb() # type: ignore event_bus_light_peer_chain = EventBusLightPeerChain(self.context.event_bus) chain = chain_config.light_chain_class(header_db, peer_chain=event_bus_light_peer_chain) elif eth1_app_config.database_mode is Eth1DbMode.FULL: db = db_manager.get_db() # type: ignore chain = chain_config.full_chain_class(db) else: raise Exception(f"Unsupported Database Mode: {eth1_app_config.database_mode}") return initialize_eth1_modules(chain, self.event_bus)
def launch_node(args: Namespace, trinity_config: TrinityConfig) -> None: with trinity_config.process_id_file('networking'): # The `networking` process creates a process pool executor to offload cpu intensive # tasks. We should revisit that when we move the sync in its own process ensure_global_asyncio_executor() asyncio.ensure_future(launch_node_coro(args, trinity_config)) loop = asyncio.get_event_loop() loop.run_forever() loop.close()
def test_trinity_config_computed_properties_custom_xdg(tmpdir, xdg_trinity_root): alt_xdg_root = tmpdir.mkdir('trinity-custom') assert not is_under_path(alt_xdg_root, xdg_trinity_root) data_dir = get_data_dir_for_network_id(1, alt_xdg_root) trinity_config = TrinityConfig(trinity_root_dir=alt_xdg_root, network_id=1) assert trinity_config.network_id == 1 assert trinity_config.data_dir == data_dir assert trinity_config.nodekey_path == get_nodekey_path(data_dir)
def __init__(self, event_bus: Endpoint, trinity_config: TrinityConfig) -> None: super().__init__(event_bus, trinity_config) self._bootstrap_nodes = trinity_config.bootstrap_nodes self._preferred_nodes = trinity_config.preferred_nodes self._node_key = trinity_config.nodekey self._node_port = trinity_config.port self._max_peers = trinity_config.max_peers app_config = trinity_config.get_app_config(Eth1AppConfig) self._nodedb_path = app_config.nodedb_path
def run_database_process(trinity_config: TrinityConfig, db_class: Type[BaseDB]) -> None: with trinity_config.process_id_file('database'): app_config = trinity_config.get_app_config(Eth1AppConfig) base_db = db_class(db_path=app_config.database_dir) manager = get_chaindb_manager(trinity_config, base_db) server = manager.get_server() # type: ignore def _sigint_handler(*args: Any) -> None: server.stop_event.set() signal.signal(signal.SIGINT, _sigint_handler) try: server.serve_forever() except SystemExit: server.stop_event.set() raise
def launch_node(args: Namespace, trinity_config: TrinityConfig, endpoint: Endpoint) -> None: with trinity_config.process_id_file('networking'): NodeClass = trinity_config.get_app_config(Eth1AppConfig).node_class node = NodeClass(endpoint, trinity_config) loop = node.get_event_loop() endpoint.connect_no_wait(loop) # This is a second PluginManager instance governing plugins in a shared process. plugin_manager = setup_plugins(SharedProcessScope(endpoint), get_all_plugins()) plugin_manager.prepare(args, trinity_config) asyncio.ensure_future(handle_networking_exit(node, plugin_manager, endpoint), loop=loop) asyncio.ensure_future(node.run(), loop=loop) loop.run_forever() loop.close()
def test_chain_config_computed_properties_custom_xdg(tmpdir, xdg_trinity_root): alt_xdg_root = tmpdir.mkdir('trinity-custom') assert not is_under_path(alt_xdg_root, xdg_trinity_root) data_dir = get_data_dir_for_network_id(1, alt_xdg_root) chain_config = TrinityConfig(trinity_root_dir=alt_xdg_root, network_id=1) assert chain_config.network_id == 1 assert chain_config.data_dir == data_dir assert chain_config.database_dir == data_dir / DATABASE_DIR_NAME / "full" assert chain_config.nodekey_path == get_nodekey_path(data_dir)
def get_protocol(trinity_config: TrinityConfig) -> Type[ProtocolAPI]: # For now DiscoveryByTopicProtocol supports a single topic, so we use the latest # version of our supported protocols. Maybe this could be more generic? # TODO: This needs to support the beacon protocol when we have a way to # check the config, if trinity is being run as a beacon node. eth1_config = trinity_config.get_app_config(Eth1AppConfig) if eth1_config.database_mode is Eth1DbMode.LIGHT: return LESProtocolV2 else: return ETHProtocol
def _setup_standalone_component( component_type: Union[Type['TrioIsolatedComponent'], Type['AsyncioIsolatedComponent']], app_identifier: str, ) -> Tuple[Union['TrioIsolatedComponent', 'AsyncioIsolatedComponent'], Tuple[ str, ...]]: if app_identifier == APP_IDENTIFIER_ETH1: app_cfg: Type[BaseAppConfig] = Eth1AppConfig elif app_identifier == APP_IDENTIFIER_BEACON: app_cfg = BeaconAppConfig else: raise ValueError("Unknown app identifier: %s", app_identifier) # Require a root dir to be specified as we don't want to mess with the default one. for action in parser._actions: if action.dest == 'trinity_root_dir': action.required = True break component_type.configure_parser(parser, subparser) parser.add_argument( '--connect-to-endpoints', help= "A list of event bus IPC files for components we should connect to", nargs='+', default=tuple(), ) args = parser.parse_args() # FIXME: Figure out a way to avoid having to set this. args.sync_mode = SYNC_FULL args.enable_metrics = False logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s', datefmt='%H:%M:%S') if args.log_levels is not None: for name, level in args.log_levels.items(): if name is None: name = '' get_logger(name).setLevel(level) trinity_config = TrinityConfig.from_parser_args(args, app_identifier, (app_cfg, )) trinity_config.trinity_root_dir.mkdir(exist_ok=True) if not is_data_dir_initialized(trinity_config): initialize_data_dir(trinity_config) boot_info = BootInfo( args=args, trinity_config=trinity_config, min_log_level=None, logger_levels=None, profile=False, ) return component_type(boot_info), args.connect_to_endpoints
def trinity_boot(args: Namespace, trinity_config: TrinityConfig, extra_kwargs: Dict[str, Any], plugin_manager: PluginManager, listener: logging.handlers.QueueListener, main_endpoint: TrinityMainEventBusEndpoint, logger: logging.Logger) -> None: # start the listener thread to handle logs produced by other processes in # the local logger. listener.start() ensure_beacon_dirs(trinity_config.get_app_config(BeaconAppConfig)) # First initialize the database process. database_server_process = ctx.Process( name="DB", target=run_database_process, args=( trinity_config, LevelDB, ), kwargs=extra_kwargs, ) # start the processes database_server_process.start() logger.info("Started DB server process (pid=%d)", database_server_process.pid) try: wait_for_ipc(trinity_config.database_ipc_path) except TimeoutError as e: logger.error("Timeout waiting for database to start. Exiting...") kill_process_gracefully(database_server_process, logger) ArgumentParser().error(message="Timed out waiting for database start") def kill_trinity_with_reason(reason: str) -> None: kill_trinity_gracefully(trinity_config, logger, (), plugin_manager, main_endpoint, reason=reason) main_endpoint.subscribe(ShutdownRequest, lambda ev: kill_trinity_with_reason(ev.reason)) plugin_manager.prepare(args, trinity_config, extra_kwargs) try: loop = asyncio.get_event_loop() loop.add_signal_handler(signal.SIGTERM, lambda: kill_trinity_with_reason("SIGTERM")) loop.run_forever() loop.close() except KeyboardInterrupt: kill_trinity_with_reason("CTRL+C / Keyboard Interrupt")
def test_sync_mode_effect_on_db_and_node_type(sync_mode, expected_full_db, expected_node_class): trinity_config = TrinityConfig(network_id=1) eth1_app_config = Eth1AppConfig(trinity_config, sync_mode) assert eth1_app_config.sync_mode == sync_mode assert eth1_app_config.node_class == expected_node_class if expected_full_db: assert eth1_app_config.database_mode is Eth1DbMode.FULL else: assert eth1_app_config.database_mode is Eth1DbMode.LIGHT
def test_trinity_config_app_identifier(xdg_trinity_root, app_identifier, expected_suffix): data_dir = get_local_data_dir('muffin', xdg_trinity_root) trinity_config = TrinityConfig(network_id=1, data_dir=data_dir, app_identifier=app_identifier) assert trinity_config.network_id == 1 assert trinity_config.data_dir == data_dir assert trinity_config.logfile_path == data_dir / (LOG_DIR + expected_suffix) / LOG_FILE assert trinity_config.jsonrpc_ipc_path == data_dir / (IPC_DIR + expected_suffix) / JSONRPC_SOCKET_FILENAME # noqa: E501 assert trinity_config.database_ipc_path == data_dir / (IPC_DIR + expected_suffix) / DATABASE_SOCKET_FILENAME # noqa: E501 assert trinity_config.pid_dir == data_dir / (PID_DIR + expected_suffix) assert trinity_config.nodekey_path == get_nodekey_path(data_dir)
def get_chain(trinity_config: TrinityConfig) -> ChainAPI: app_config = trinity_config.get_app_config(Eth1AppConfig) ensure_eth1_dirs(app_config) base_db = LevelDB(db_path=app_config.database_dir) chain_config = app_config.get_chain_config() chain = chain_config.full_chain_class(AtomicDB(base_db)) initialize_database(chain_config, chain.chaindb, base_db) return chain