UNPKG

@lodestar/beacon-node

Version:

A Typescript implementation of the beacon chain

208 lines • 8.64 kB
import fs from "node:fs"; import path from "node:path"; import { Tree } from "@chainsafe/persistent-merkle-tree"; import { Repository } from "@lodestar/db"; import { ForkSeq, SLOTS_PER_EPOCH } from "@lodestar/params"; import { getLatestWeakSubjectivityCheckpointEpoch, loadState } from "@lodestar/state-transition"; import { ssz } from "@lodestar/types"; import { toHex, toRootHex } from "@lodestar/utils"; import { profileNodeJS, writeHeapSnapshot } from "../../../util/profile.js"; import { getStateResponseWithRegen } from "../beacon/state/utils.js"; export function getLodestarApi({ chain, config, db, network, sync, }) { let writingHeapdump = false; let writingProfile = false; const defaultProfileMs = SLOTS_PER_EPOCH * config.SECONDS_PER_SLOT * 1000; return { async writeHeapdump({ thread = "main", dirpath = "." }) { if (writingHeapdump) { throw Error("Already writing heapdump"); } try { writingHeapdump = true; let filepath; switch (thread) { case "network": filepath = await network.writeNetworkHeapSnapshot("network_thread", dirpath); break; case "discv5": filepath = await network.writeDiscv5HeapSnapshot("discv5_thread", dirpath); break; default: // main thread filepath = await writeHeapSnapshot("main_thread", dirpath); break; } return { data: { filepath } }; } finally { writingHeapdump = false; } }, async writeProfile({ thread = "network", duration = defaultProfileMs, dirpath = "." }) { if (writingProfile) { throw Error("Already writing network profile"); } writingProfile = true; try { let filepath; let profile; switch (thread) { case "network": filepath = await network.writeNetworkThreadProfile(duration, dirpath); break; case "discv5": filepath = await network.writeDiscv5Profile(duration, dirpath); break; default: // main thread profile = await profileNodeJS(duration); filepath = path.join(dirpath, `main_thread_${new Date().toISOString()}.cpuprofile`); fs.writeFileSync(filepath, profile); break; } return { data: { filepath } }; } finally { writingProfile = false; } }, async getLatestWeakSubjectivityCheckpointEpoch() { const state = chain.getHeadState(); return { data: getLatestWeakSubjectivityCheckpointEpoch(config, state) }; }, async getSyncChainsDebugState() { return { data: sync.getSyncChainsDebugState() }; }, async getGossipQueueItems({ gossipType }) { return { data: await network.dumpGossipQueue(gossipType), }; }, async getRegenQueueItems() { return { data: chain.regen.jobQueue.getItems().map((item) => ({ key: item.args[0].key, args: regenRequestToJson(config, item.args[0]), addedTimeMs: item.addedTimeMs, })), }; }, async getBlockProcessorQueueItems() { return { // biome-ignore lint/complexity/useLiteralKeys: The `blockProcessor` is a protected attribute data: chain["blockProcessor"].jobQueue.getItems().map((item) => { const [blockInputs, opts] = item.args; return { blockSlots: blockInputs.map((blockInput) => blockInput.block.message.slot), jobOpts: opts, addedTimeMs: item.addedTimeMs, }; }), }; }, async getStateCacheItems() { return { data: chain.regen.dumpCacheSummary() }; }, async getGossipPeerScoreStats() { return { data: Object.entries(await network.dumpGossipPeerScoreStats()).map(([peerId, stats]) => ({ peerId, ...stats })), }; }, async getLodestarPeerScoreStats() { return { data: await network.dumpPeerScoreStats() }; }, async runGC() { if (!global.gc) throw Error("You must expose GC running the Node.js process with 'node --expose_gc'"); global.gc(); }, async dropStateCache() { chain.regen.dropCache(); }, async connectPeer({ peerId, multiaddrs }) { await network.connectToPeer(peerId, multiaddrs); }, async disconnectPeer({ peerId }) { await network.disconnectPeer(peerId); }, async getPeers({ state, direction }) { const peers = (await network.dumpPeers()).filter((nodePeer) => (!state || state.length === 0 || state.includes(nodePeer.state)) && (!direction || direction.length === 0 || (nodePeer.direction && direction.includes(nodePeer.direction)))); return { data: peers, meta: { count: peers.length }, }; }, async getBlacklistedBlocks() { return { data: Array.from(chain.blacklistedBlocks.entries()).map(([root, slot]) => ({ root, slot })), }; }, async discv5GetKadValues() { return { data: await network.dumpDiscv5KadValues(), }; }, async dumpDbBucketKeys({ bucket }) { for (const repo of Object.values(db)) { // biome-ignore lint/complexity/useLiteralKeys: `bucket` is protected and `bucketId` is private if (repo instanceof Repository && (String(repo["bucket"]) === bucket || repo["bucketId"] === bucket)) { return { data: stringifyKeys(await repo.keys()) }; } } throw Error(`Unknown Bucket '${bucket}'`); }, async dumpDbStateIndex() { return { data: await db.stateArchive.dumpRootIndexEntries() }; }, async getHistoricalSummaries({ stateId }) { const { state, executionOptimistic, finalized } = await getStateResponseWithRegen(chain, stateId); const stateView = (state instanceof Uint8Array ? loadState(config, chain.getHeadState(), state).state : state.clone()); const fork = config.getForkName(stateView.slot); if (ForkSeq[fork] < ForkSeq.capella) { throw new Error("Historical summaries are not supported before Capella"); } const { gindex } = ssz[fork].BeaconState.getPathInfo(["historicalSummaries"]); const proof = new Tree(stateView.node).getSingleProof(gindex); return { data: { slot: stateView.slot, historicalSummaries: stateView.historicalSummaries.toValue(), proof: proof, }, meta: { executionOptimistic, finalized, version: fork }, }; }, }; } function regenRequestToJson(config, regenRequest) { switch (regenRequest.key) { case "getBlockSlotState": return { root: regenRequest.args[0], slot: regenRequest.args[1], }; case "getCheckpointState": return ssz.phase0.Checkpoint.toJson(regenRequest.args[0]); case "getPreState": { const slot = regenRequest.args[0].slot; return { root: toRootHex(config.getForkTypes(slot).BeaconBlock.hashTreeRoot(regenRequest.args[0])), slot, }; } case "getState": return { root: regenRequest.args[0], }; } } function stringifyKeys(keys) { return keys.map((key) => { if (key instanceof Uint8Array) { return toHex(key); } return `${key}`; }); } //# sourceMappingURL=index.js.map