@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
232 lines • 9.45 kB
JavaScript
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