UNPKG

@lodestar/prover

Version:

A Typescript implementation of the Ethereum Consensus light client

159 lines 6.87 kB
import { getClient } from "@lodestar/api/beacon"; import { createChainForkConfig } from "@lodestar/config"; import { networksChainConfig } from "@lodestar/config/networks"; import { Lightclient, LightclientEvent, RunStatusCode } from "@lodestar/light-client"; import { LightClientRestTransport } from "@lodestar/light-client/transport"; import { isForkPostCapella } from "@lodestar/params"; import { LCTransport } from "../interfaces.js"; import { assertLightClient } from "../utils/assertion.js"; import { fetchBlock, getExecutionPayloads, getGenesisData, getSyncCheckpoint, getUnFinalizedRangeForPayloads, } from "../utils/consensus.js"; import { bufferToHex } from "../utils/conversion.js"; import { PayloadStore } from "./payload_store.js"; export class ProofProvider { constructor(opts) { this.opts = opts; this.store = new PayloadStore({ api: opts.api, logger: opts.logger }); this.logger = opts.logger; this.config = opts.config; this.api = opts.api; this.network = opts.config.PRESET_BASE; } async waitToBeReady() { return this.readyPromise; } static init(opts) { if (opts.transport === LCTransport.P2P) { throw new Error("P2P mode not supported yet"); } opts.logger.info("Creating ProofProvider instance with REST APIs", { network: opts.network, urls: opts.urls.join(","), }); const config = opts.network ? createChainForkConfig(networksChainConfig[opts.network]) : createChainForkConfig(opts.config); const api = getClient({ urls: opts.urls }, { config }); const transport = new LightClientRestTransport(api); const provider = new ProofProvider({ ...opts, config, api, transport, }); provider.readyPromise = provider.sync(opts.wsCheckpoint).catch((e) => { opts.logger.error("Error while syncing", e); return Promise.reject(e); }); return provider; } async sync(wsCheckpoint) { if (this.lightClient !== undefined) { throw Error("Light client already initialized and syncing."); } this.logger.info("Starting sync for proof provider"); const { api, config, transport } = this.opts; const checkpointRoot = await getSyncCheckpoint(api, wsCheckpoint); const genesisData = await getGenesisData(api); this.logger.info("Initializing lightclient", { checkpointRoot: bufferToHex(checkpointRoot) }); this.lightClient = await Lightclient.initializeFromCheckpointRoot({ checkpointRoot, config, transport, genesisData, }); assertLightClient(this.lightClient); this.logger.info("Initiating lightclient"); // Wait for the lightclient to start await this.lightClient?.start(); this.logger.info("Lightclient synced", this.getStatus()); this.registerEvents(); // Load the payloads from the CL this.logger.info("Building EL payload history"); const { start, end } = await getUnFinalizedRangeForPayloads(this.lightClient); const payloads = await getExecutionPayloads({ api: this.opts.api, startSlot: start, endSlot: end, logger: this.logger, }); for (const [slot, payload] of payloads.entries()) { this.store.set(payload, slot, false); } // Load the finalized payload from the CL const finalizedSlot = this.lightClient.getFinalized().beacon.slot; this.logger.debug("Getting finalized slot from lightclient", { finalizedSlot }); const block = await fetchBlock(this.opts.api, finalizedSlot); if (block) { this.store.set(block.message.body.executionPayload, finalizedSlot, true); } else { this.logger.error("Failed to fetch finalized block", finalizedSlot); } this.logger.info("Proof provider ready"); } getStatus() { if (!this.lightClient) { return { latest: 0, finalized: 0, status: RunStatusCode.uninitialized, }; } return { latest: this.lightClient.getHead().beacon.slot, finalized: this.lightClient.getFinalized().beacon.slot, status: this.lightClient.status, }; } async getExecutionPayload(blockNumber) { assertLightClient(this.lightClient); if (typeof blockNumber === "string" && blockNumber === "finalized") { const payload = this.store.finalized; if (!payload) throw new Error("No finalized payload"); return payload; } if (typeof blockNumber === "string" && blockNumber === "latest") { const payload = this.store.latest; if (!payload) throw new Error("No latest payload"); return payload; } if ((typeof blockNumber === "string" && blockNumber.startsWith("0x")) || typeof blockNumber === "number") { const payload = await this.store.get(blockNumber); if (!payload) throw new Error(`No payload for blockNumber ${blockNumber}`); return payload; } throw new Error(`Invalid blockNumber "${blockNumber}"`); } async processLCHeader(lcHeader, finalized = false) { const fork = this.opts.config.getForkName(lcHeader.beacon.slot); if (!isForkPostCapella(fork)) { return; } const sszType = this.opts.config.getPostBellatrixForkTypes(lcHeader.beacon.slot).ExecutionPayloadHeader; if (isForkPostCapella(fork) && (!("execution" in lcHeader) || sszType.equals(lcHeader.execution, sszType.defaultValue()))) { throw new Error("Execution payload is required for execution fork"); } await this.store.processLCHeader(lcHeader, finalized); } registerEvents() { assertLightClient(this.lightClient); this.opts.signal.addEventListener("abort", () => { this.lightClient?.stop(); }); this.lightClient.emitter.on(LightclientEvent.lightClientFinalityHeader, async (data) => { await this.processLCHeader(data, true).catch((e) => { this.logger.error("Error processing finality update", null, e); }); }); this.lightClient.emitter.on(LightclientEvent.lightClientOptimisticHeader, async (data) => { await this.processLCHeader(data).catch((e) => { this.logger.error("Error processing optimistic update", null, e); }); }); } } //# sourceMappingURL=proof_provider.js.map