@lodestar/beacon-node
Version:
A Typescript implementation of the beacon chain
133 lines (111 loc) • 4.18 kB
text/typescript
import EventEmitter from "node:events";
import {privateKeyToProtobuf} from "@libp2p/crypto/keys";
import {PrivateKey} from "@libp2p/interface";
import {StrictEventEmitter} from "strict-event-emitter-types";
import {ENR, ENRData, SignableENR} from "@chainsafe/enr";
import {Thread, Worker, spawn} from "@chainsafe/threads";
import {BeaconConfig, chainConfigFromJson, chainConfigToJson} from "@lodestar/config";
import {LoggerNode} from "@lodestar/logger/node";
import {NetworkCoreMetrics} from "../core/metrics.js";
import {Discv5WorkerApi, Discv5WorkerData, LodestarDiscv5Opts} from "./types.js";
export type Discv5Opts = {
privateKey: PrivateKey;
discv5: LodestarDiscv5Opts;
logger: LoggerNode;
config: BeaconConfig;
genesisTime: number;
metrics?: NetworkCoreMetrics;
};
export type Discv5Events = {
discovered: (enr: ENR) => void;
};
/**
* Wrapper class abstracting the details of discv5 worker instantiation and message-passing
*/
export class Discv5Worker extends (EventEmitter as {new (): StrictEventEmitter<EventEmitter, Discv5Events>}) {
private readonly subscription: {unsubscribe: () => void};
private closed = false;
constructor(
private readonly opts: Discv5Opts,
private readonly workerApi: Discv5WorkerApi
) {
super();
this.subscription = workerApi.discovered().subscribe((enrObj) => this.onDiscovered(enrObj));
}
static async init(opts: Discv5Opts): Promise<Discv5Worker> {
const workerData: Discv5WorkerData = {
enr: opts.discv5.enr,
privateKeyProto: privateKeyToProtobuf(opts.privateKey),
bindAddrs: opts.discv5.bindAddrs,
config: opts.discv5.config ?? {},
bootEnrs: opts.discv5.bootEnrs,
metrics: Boolean(opts.metrics),
chainConfig: chainConfigFromJson(chainConfigToJson(opts.config)),
genesisValidatorsRoot: opts.config.genesisValidatorsRoot,
loggerOpts: opts.logger.toOpts(),
genesisTime: opts.genesisTime,
};
const worker = new Worker("./worker.js", {
suppressTranspileTS: Boolean(globalThis.Bun),
workerData,
} as ConstructorParameters<typeof Worker>[1]);
const workerApi = await spawn<Discv5WorkerApi>(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,
});
return new Discv5Worker(opts, workerApi);
}
async close(): Promise<void> {
if (this.closed) return;
this.closed = true;
this.subscription.unsubscribe();
await this.workerApi.close();
await Thread.terminate(this.workerApi as unknown as Thread);
}
onDiscovered(obj: ENRData): void {
const enr = this.decodeEnr(obj);
if (enr) {
this.emit("discovered", enr);
}
}
async enr(): Promise<SignableENR> {
const obj = await this.workerApi.enr();
return new SignableENR(obj.kvs, obj.seq, this.opts.privateKey.raw);
}
setEnrValue(key: string, value: Uint8Array): Promise<void> {
return this.workerApi.setEnrValue(key, value);
}
async kadValues(): Promise<ENR[]> {
return this.decodeEnrs(await this.workerApi.kadValues());
}
discoverKadValues(): Promise<void> {
return this.workerApi.discoverKadValues();
}
async findRandomNode(): Promise<ENR[]> {
return this.decodeEnrs(await this.workerApi.findRandomNode());
}
scrapeMetrics(): Promise<string> {
return this.workerApi.scrapeMetrics();
}
async writeProfile(durationMs: number, dirpath: string): Promise<string> {
return this.workerApi.writeProfile(durationMs, dirpath);
}
async writeHeapSnapshot(prefix: string, dirpath: string): Promise<string> {
return this.workerApi.writeHeapSnapshot(prefix, dirpath);
}
private decodeEnrs(objs: ENRData[]): ENR[] {
const enrs: ENR[] = [];
for (const obj of objs) {
const enr = this.decodeEnr(obj);
if (enr) {
enrs.push(enr);
}
}
return enrs;
}
private decodeEnr(obj: ENRData): ENR | null {
this.opts.metrics?.discv5.decodeEnrAttemptCount.inc(1);
return new ENR(obj.kvs, obj.seq, obj.signature);
}
}