UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

179 lines 8.13 kB
import path from "node:path"; import { Thread, Worker, spawn } from "@chainsafe/threads"; import { privateKeyToProtobuf } from "@libp2p/crypto/keys"; import { chainConfigToJson } from "@lodestar/config"; import { AsyncIterableBridgeCaller, AsyncIterableBridgeHandler } from "../../util/asyncIterableToEvents.js"; import { peerIdFromString } from "../../util/peerId.js"; import { terminateWorkerThread, wireEventsOnMainThread } from "../../util/workerEvents.js"; import { networkEventDirection } from "../events.js"; import { NetworkWorkerThreadEventType, ReqRespBridgeEventBus, getReqRespBridgeReqEvents, getReqRespBridgeRespEvents, reqRespBridgeEventDirection, } from "./events.js"; // Worker constructor consider the path relative to the current working directory const workerDir = process.env.NODE_ENV === "test" ? "../../../lib/network/core/" : "./"; const NETWORK_WORKER_EXIT_TIMEOUT_MS = 1000; const NETWORK_WORKER_EXIT_RETRY_COUNT = 3; /** * NetworkCore implementation using a Worker thread */ export class WorkerNetworkCore { constructor(modules) { this.modules = modules; this.reqRespBridgeEventBus = new ReqRespBridgeEventBus(); // Get called from main thread to issue a ReqResp request, and emits event to worker this.reqRespBridgeReqCaller = new AsyncIterableBridgeCaller(getReqRespBridgeReqEvents(this.reqRespBridgeEventBus)); // Handles ReqResp response from worker and calls async generator in main thread this.reqRespBridgeRespHandler = new AsyncIterableBridgeHandler(getReqRespBridgeRespEvents(this.reqRespBridgeEventBus), (data) => modules.getReqRespHandler(data.method)(data.req, peerIdFromString(data.peerId))); wireEventsOnMainThread(NetworkWorkerThreadEventType.networkEvent, modules.events, modules.worker, modules.metrics, networkEventDirection); wireEventsOnMainThread(NetworkWorkerThreadEventType.reqRespBridgeEvents, this.reqRespBridgeEventBus, modules.worker, modules.metrics, reqRespBridgeEventDirection); Thread.errors(modules.networkThreadApi).subscribe((err) => { this.modules.logger.error("Network worker thread error", {}, err); }); const { metrics } = modules; if (metrics) { metrics.networkWorkerHandler.reqRespBridgeReqCallerPending.addCollect(() => { metrics.networkWorkerHandler.reqRespBridgeReqCallerPending.set(this.reqRespBridgeReqCaller.pendingCount); }); } } static async init(modules) { const { opts, config, privateKey } = modules; const { genesisTime, peerStoreDir, activeValidatorCount, localMultiaddrs, metricsEnabled, initialStatus } = opts; const workerData = { opts, chainConfigJson: chainConfigToJson(config), genesisValidatorsRoot: config.genesisValidatorsRoot, privateKeyProto: privateKeyToProtobuf(privateKey), localMultiaddrs, metricsEnabled, peerStoreDir, genesisTime, initialStatus, activeValidatorCount, loggerOpts: modules.logger.toOpts(), }; const worker = new Worker(path.join(workerDir, "networkCoreWorker.js"), { workerData, /** * maxYoungGenerationSizeMb defaults to 152mb through the cli option defaults. * That default value was determined via https://github.com/ChainSafe/lodestar/issues/2115 and * should be tuned further as needed. If we update network code and see substantial * difference in the quantity of garbage collected this should get updated. A value that is * too low will result in too much GC time and a value that is too high causes increased mark * and sweep for some reason (which is much much slower than scavenge). A marginally too high * number causes detrimental slowdown from increased variable lookup time. Empirical evidence * showed that there is a pretty big window of "correct" values but we can always tune as * necessary */ resourceLimits: { maxYoungGenerationSizeMb: opts.maxYoungGenerationSizeMb }, }); // biome-ignore lint/suspicious/noExplicitAny: <explanation> const networkThreadApi = (await spawn(worker, { // A Lodestar Node may do very expensive task at start blocking the event loop and causing // the initialization to timeout. The number below is big enough to almost disable the timeout timeout: 5 * 60 * 1000, // TODO: types are broken on spawn, which claims that `NetworkWorkerApi` does not satisfies its contrains })); return new WorkerNetworkCore({ ...modules, networkThreadApi, worker, }); } async close() { this.modules.logger.debug("closing network core running in network worker"); await this.getApi().close(); this.modules.logger.debug("terminating network worker"); await terminateWorkerThread({ worker: this.getApi(), retryCount: NETWORK_WORKER_EXIT_RETRY_COUNT, retryMs: NETWORK_WORKER_EXIT_TIMEOUT_MS, logger: this.modules.logger, }); this.modules.logger.debug("terminated network worker"); } async test() { return; } scrapeMetrics() { return this.getApi().scrapeMetrics(); } updateStatus(status) { return this.getApi().updateStatus(status); } reStatusPeers(peers) { return this.getApi().reStatusPeers(peers); } reportPeer(peer, action, actionName) { return this.getApi().reportPeer(peer, action, actionName); } // TODO: Should this just be events? Do they need to report errors back? prepareBeaconCommitteeSubnets(subscriptions) { return this.getApi().prepareBeaconCommitteeSubnets(subscriptions); } prepareSyncCommitteeSubnets(subscriptions) { return this.getApi().prepareSyncCommitteeSubnets(subscriptions); } subscribeGossipCoreTopics() { return this.getApi().subscribeGossipCoreTopics(); } unsubscribeGossipCoreTopics() { return this.getApi().unsubscribeGossipCoreTopics(); } // REST API queries getConnectedPeerCount() { return this.getApi().getConnectedPeerCount(); } getConnectedPeers() { return this.getApi().getConnectedPeers(); } getNetworkIdentity() { return this.getApi().getNetworkIdentity(); } // ReqResp and gossip outgoing sendReqRespRequest(data) { return this.reqRespBridgeReqCaller.getAsyncIterable(data); } publishGossip(topic, data, opts) { return this.getApi().publishGossip(topic, data, opts); } // Debug connectToPeer(peer, multiaddr) { return this.getApi().connectToPeer(peer, multiaddr); } disconnectPeer(peer) { return this.getApi().disconnectPeer(peer); } dumpPeers() { return this.getApi().dumpPeers(); } dumpPeer(peerIdStr) { return this.getApi().dumpPeer(peerIdStr); } dumpPeerScoreStats() { return this.getApi().dumpPeerScoreStats(); } dumpGossipPeerScoreStats() { return this.getApi().dumpGossipPeerScoreStats(); } dumpDiscv5KadValues() { return this.getApi().dumpDiscv5KadValues(); } dumpMeshPeers() { return this.getApi().dumpMeshPeers(); } writeNetworkThreadProfile(durationMs, dirpath) { return this.getApi().writeProfile(durationMs, dirpath); } writeDiscv5Profile(durationMs, dirpath) { return this.getApi().writeDiscv5Profile(durationMs, dirpath); } writeNetworkHeapSnapshot(prefix, dirpath) { return this.getApi().writeHeapSnapshot(prefix, dirpath); } writeDiscv5HeapSnapshot(prefix, dirpath) { return this.getApi().writeDiscv5HeapSnapshot(prefix, dirpath); } getApi() { return this.modules.networkThreadApi; } } //# sourceMappingURL=networkCoreWorkerHandler.js.map