UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

232 lines • 9.45 kB
import { setMaxListeners } from "node:events"; import { hasher } from "@chainsafe/persistent-merkle-tree"; import { sleep } from "@lodestar/utils"; import { BeaconRestApiServer, getApi } from "../api/index.js"; import { BeaconChain, initBeaconMetrics } from "../chain/index.js"; import { createValidatorMonitor } from "../chain/validatorMonitor.js"; import { initializeEth1ForBlockProduction } from "../eth1/index.js"; import { initializeExecutionBuilder, initializeExecutionEngine } from "../execution/index.js"; import { createMetrics, getHttpMetricsServer } from "../metrics/index.js"; import { MonitoringService } from "../monitoring/index.js"; import { Network, getReqRespHandlers } from "../network/index.js"; import { BackfillSync } from "../sync/backfill/index.js"; import { BeaconSync } from "../sync/index.js"; import { Clock } from "../util/clock.js"; import { runNodeNotifier } from "./notifier.js"; export * from "./options.js"; export var BeaconNodeStatus; (function (BeaconNodeStatus) { BeaconNodeStatus["started"] = "started"; BeaconNodeStatus["closing"] = "closing"; BeaconNodeStatus["closed"] = "closed"; })(BeaconNodeStatus || (BeaconNodeStatus = {})); var LoggerModule; (function (LoggerModule) { LoggerModule["api"] = "api"; LoggerModule["backfill"] = "backfill"; LoggerModule["chain"] = "chain"; LoggerModule["eth1"] = "eth1"; LoggerModule["execution"] = "execution"; LoggerModule["metrics"] = "metrics"; LoggerModule["monitoring"] = "monitoring"; LoggerModule["network"] = "network"; /** validator monitor */ LoggerModule["vmon"] = "vmon"; LoggerModule["rest"] = "rest"; LoggerModule["sync"] = "sync"; })(LoggerModule || (LoggerModule = {})); /** * Short delay before closing db to give async operations sufficient time to complete * and prevent "Database is not open" errors when shutting down beacon node. */ const DELAY_BEFORE_CLOSING_DB_MS = 500; /** * The main Beacon Node class. Contains various components for getting and processing data from the * Ethereum Consensus ecosystem as well as systems for getting beacon node metadata. */ export class BeaconNode { constructor({ opts, config, db, metrics, metricsServer, monitoring, validatorMonitor, network, chain, api, restApi, sync, backfillSync, controller, }) { this.opts = opts; this.config = config; this.metrics = metrics; this.metricsServer = metricsServer; this.monitoring = monitoring; this.validatorMonitor = validatorMonitor; this.db = db; this.chain = chain; this.api = api; this.restApi = restApi; this.network = network; this.sync = sync; this.backfillSync = backfillSync; this.controller = controller; this.status = BeaconNodeStatus.started; } /** * Initialize a beacon node. Initializes and `start`s the varied sub-component services of the * beacon node */ static async init({ opts, config, db, logger, processShutdownCallback, privateKey, dataDir, peerStoreDir, anchorState, wsCheckpoint, metricsRegistries = [], }) { if (hasher.name !== "hashtree") { logger.warn(`hashtree is not supported, using hasher ${hasher.name}`); } const controller = new AbortController(); // We set infinity to prevent MaxListenersExceededWarning which get logged when listeners > 10 // Since it is perfectly fine to have listeners > 10 setMaxListeners(Infinity, controller.signal); const signal = controller.signal; let metrics = null; if (opts.metrics.enabled || // monitoring relies on metrics data opts.monitoring.endpoint) { metrics = createMetrics(opts.metrics, anchorState.genesisTime, metricsRegistries); initBeaconMetrics(metrics, anchorState); // Since the db is instantiated before this, metrics must be injected manually afterwards db.setMetrics(metrics.db); signal.addEventListener("abort", metrics.close, { once: true }); } const validatorMonitor = opts.metrics.enabled || opts.validatorMonitor.validatorMonitorLogs ? createValidatorMonitor(metrics?.register ?? null, config, anchorState.genesisTime, logger.child({ module: LoggerModule.vmon }), opts.validatorMonitor) : null; const clock = new Clock({ config, genesisTime: anchorState.genesisTime, signal }); // Prune hot db repos // TODO: Should this call be awaited? await db.pruneHotDb(); const monitoring = opts.monitoring.endpoint ? new MonitoringService("beacon", { ...opts.monitoring, endpoint: opts.monitoring.endpoint }, { register: metrics.register, logger: logger.child({ module: LoggerModule.monitoring }) }) : null; const chain = new BeaconChain(opts.chain, { config, clock, dataDir, db, dbName: opts.db.name, logger: logger.child({ module: LoggerModule.chain }), processShutdownCallback, metrics, validatorMonitor, anchorState, eth1: initializeEth1ForBlockProduction(opts.eth1, { config, db, metrics, logger: logger.child({ module: LoggerModule.eth1 }), signal, }), executionEngine: initializeExecutionEngine(opts.executionEngine, { metrics, signal, logger: logger.child({ module: LoggerModule.execution }), }), executionBuilder: opts.executionBuilder.enabled ? initializeExecutionBuilder(opts.executionBuilder, config, metrics, logger) : undefined, }); // Load persisted data from disk to in-memory caches await chain.init(); // Network needs to be initialized before the sync // See https://github.com/ChainSafe/lodestar/issues/4543 const network = await Network.init({ opts: opts.network, config, logger: logger.child({ module: LoggerModule.network }), metrics, chain, db, privateKey, peerStoreDir, getReqRespHandler: getReqRespHandlers({ db, chain }), }); const sync = new BeaconSync(opts.sync, { config, db, chain, metrics, network, wsCheckpoint, logger: logger.child({ module: LoggerModule.sync }), }); const backfillSync = opts.sync.backfillBatchSize > 0 ? await BackfillSync.init(opts.sync, { config, db, chain, metrics, network, wsCheckpoint, anchorState, logger: logger.child({ module: LoggerModule.backfill }), signal, }) : null; const api = getApi(opts.api, { config, logger: logger.child({ module: LoggerModule.api }), db, sync, network, chain, metrics, }); // only start server if metrics are explicitly enabled const metricsServer = opts.metrics.enabled ? await getHttpMetricsServer(opts.metrics, { register: metrics.register, getOtherMetrics: async () => Promise.all([network.scrapeMetrics(), chain.archiveStore.scrapeMetrics()]), logger: logger.child({ module: LoggerModule.metrics }), }) : null; const restApi = new BeaconRestApiServer(opts.api.rest, { config, logger: logger.child({ module: LoggerModule.rest }), api, metrics: metrics ? metrics.apiRest : null, }); if (opts.api.rest.enabled) { await restApi.registerRoutes(opts.api.version); await restApi.listen(); } void runNodeNotifier({ network, chain, sync, config, logger, signal }); return new BeaconNode({ opts, config, db, metrics, metricsServer, monitoring, validatorMonitor, network, chain, api, restApi, sync, backfillSync, controller, }); } /** * Stop beacon node and its sub-components. */ async close() { if (this.status === BeaconNodeStatus.started) { this.status = BeaconNodeStatus.closing; this.sync.close(); this.backfillSync?.close(); if (this.restApi) await this.restApi.close(); await this.network.close(); if (this.metricsServer) await this.metricsServer.close(); if (this.monitoring) this.monitoring.close(); await this.chain.persistToDisk(); await this.chain.close(); if (this.controller) this.controller.abort(); await sleep(DELAY_BEFORE_CLOSING_DB_MS); await this.db.close(); this.status = BeaconNodeStatus.closed; } } } //# sourceMappingURL=nodejs.js.map