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