@lodestar/prover
Version:
A Typescript implementation of the Ethereum Consensus light client
159 lines • 6.87 kB
JavaScript
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