Compare commits

..

No commits in common. "v0.1.0-develop.26" and "v0.1.0-develop.25" have entirely different histories.

14 changed files with 424 additions and 282 deletions

View File

@ -1,5 +1,3 @@
# [0.1.0-develop.26](https://git.lumeweb.com/LumeWeb/libethsync/compare/v0.1.0-develop.25...v0.1.0-develop.26) (2023-07-12)
# [0.1.0-develop.25](https://git.lumeweb.com/LumeWeb/libethsync/compare/v0.1.0-develop.24...v0.1.0-develop.25) (2023-07-12) # [0.1.0-develop.25](https://git.lumeweb.com/LumeWeb/libethsync/compare/v0.1.0-develop.24...v0.1.0-develop.25) (2023-07-12)
# [0.1.0-develop.24](https://git.lumeweb.com/LumeWeb/libethsync/compare/v0.1.0-develop.23...v0.1.0-develop.24) (2023-07-11) # [0.1.0-develop.24](https://git.lumeweb.com/LumeWeb/libethsync/compare/v0.1.0-develop.23...v0.1.0-develop.24) (2023-07-11)

4
npm-shrinkwrap.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "@lumeweb/libethclient", "name": "@lumeweb/libethclient",
"version": "0.1.0-develop.26", "version": "0.1.0-develop.25",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@lumeweb/libethclient", "name": "@lumeweb/libethclient",
"version": "0.1.0-develop.26", "version": "0.1.0-develop.25",
"dependencies": { "dependencies": {
"@chainsafe/as-sha256": "^0.3.1", "@chainsafe/as-sha256": "^0.3.1",
"@chainsafe/bls": "7.1.1", "@chainsafe/bls": "7.1.1",

View File

@ -1,6 +1,6 @@
{ {
"name": "@lumeweb/libethsync", "name": "@lumeweb/libethsync",
"version": "0.1.0-develop.26", "version": "0.1.0-develop.25",
"type": "module", "type": "module",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -6,16 +6,14 @@ import {
} from "@lodestar/light-client/utils"; } from "@lodestar/light-client/utils";
import { init } from "@chainsafe/bls/switchable"; import { init } from "@chainsafe/bls/switchable";
import { Mutex } from "async-mutex"; import { Mutex } from "async-mutex";
import { fromHexString, toHexString } from "@chainsafe/ssz"; import { fromHexString } from "@chainsafe/ssz";
import { deserializePubkeys, getDefaultClientConfig } from "#util.js"; import { getDefaultClientConfig } from "#util.js";
import { capella, LightClientUpdate } from "#types.js";
import { deserializeSyncCommittee } from "@lodestar/light-client/utils/index.js"; import isNode from "detect-node";
import bls from "@chainsafe/bls/switchable.js";
import { assertValidLightClientUpdate } from "@lodestar/light-client/validation.js";
export interface BaseClientOptions { export interface BaseClientOptions {
prover: IProver; prover: IProver;
store: IStore; store?: IStore;
} }
export default abstract class BaseClient { export default abstract class BaseClient {
@ -26,10 +24,10 @@ export default abstract class BaseClient {
(pk) => fromHexString(pk), (pk) => fromHexString(pk),
); );
protected genesisPeriod = computeSyncPeriodAtSlot(this.config.genesis.slot); protected genesisPeriod = computeSyncPeriodAtSlot(this.config.genesis.slot);
protected booted = false;
protected options: BaseClientOptions;
private genesisTime = this.config.genesis.time; private genesisTime = this.config.genesis.time;
protected booted = false;
private syncMutex = new Mutex(); private syncMutex = new Mutex();
protected options: BaseClientOptions;
constructor(options: BaseClientOptions) { constructor(options: BaseClientOptions) {
this.options = options; this.options = options;
@ -45,10 +43,6 @@ export default abstract class BaseClient {
return this._latestPeriod === this.getCurrentPeriod(); return this._latestPeriod === this.getCurrentPeriod();
} }
public get store(): IStore {
return this.options.store as IStore;
}
public async sync(): Promise<void> { public async sync(): Promise<void> {
await init("herumi"); await init("herumi");
@ -94,6 +88,7 @@ export default abstract class BaseClient {
protected async subscribe(callback?: (ei: ExecutionInfo) => void) { protected async subscribe(callback?: (ei: ExecutionInfo) => void) {
setInterval(async () => { setInterval(async () => {
try { try {
await this._sync();
const ei = await this.getLatestExecution(); const ei = await this.getLatestExecution();
if (ei && ei.blockHash !== this.latestBlockHash) { if (ei && ei.blockHash !== this.latestBlockHash) {
this.latestBlockHash = ei.blockHash; this.latestBlockHash = ei.blockHash;
@ -105,128 +100,16 @@ export default abstract class BaseClient {
}, POLLING_DELAY); }, POLLING_DELAY);
} }
protected async getLatestExecution(): Promise<ExecutionInfo | null> { public get store(): IStore {
await this._sync(); return this.options.store as IStore;
const update = capella.ssz.LightClientUpdate.deserialize(
this.store.getUpdate(this.latestPeriod),
);
return {
blockHash: toHexString(update.attestedHeader.execution.blockHash),
blockNumber: update.attestedHeader.execution.blockNumber,
};
}
async syncProver(
startPeriod: number,
currentPeriod: number,
startCommittee: Uint8Array[],
): Promise<{ syncCommittee: Uint8Array[]; period: number }> {
for (let period = startPeriod; period < currentPeriod; period += 1) {
try {
const updates = await this.options.prover.getSyncUpdate(
period,
currentPeriod,
);
for (let i = 0; i < updates.length; i++) {
const curPeriod = period + i;
const update = updates[i];
const validOrCommittee = await this.syncUpdateVerifyGetCommittee(
startCommittee,
curPeriod,
update,
);
if (!(validOrCommittee as boolean)) {
console.log(`Found invalid update at period(${curPeriod})`);
return {
syncCommittee: startCommittee,
period: curPeriod,
};
} }
await this.options.store.addUpdate(period, update); // committee and prover index of the first honest prover
protected abstract syncFromGenesis(): Promise<Uint8Array[]>;
startCommittee = validOrCommittee as Uint8Array[]; protected abstract syncFromLastUpdate(
period = curPeriod; startPeriod?: number,
} ): Promise<Uint8Array[]>;
} catch (e) {
console.error(`failed to fetch sync update for period(${period})`);
return {
syncCommittee: startCommittee,
period,
};
}
}
return {
syncCommittee: startCommittee,
period: currentPeriod,
};
}
protected async syncUpdateVerifyGetCommittee( protected abstract getLatestExecution(): Promise<ExecutionInfo | null>;
prevCommittee: Uint8Array[],
period: number,
update: LightClientUpdate,
): Promise<false | Uint8Array[]> {
const updatePeriod = computeSyncPeriodAtSlot(
update.attestedHeader.beacon.slot,
);
if (period !== updatePeriod) {
console.error(
`Expected update with period ${period}, but received ${updatePeriod}`,
);
return false;
}
const prevCommitteeFast = deserializeSyncCommittee({
pubkeys: prevCommittee,
aggregatePubkey: bls.PublicKey.aggregate(
deserializePubkeys(prevCommittee),
).toBytes(),
});
try {
// check if the update has valid signatures
await assertValidLightClientUpdate(
this.config.chainConfig,
prevCommitteeFast,
update,
);
return update.nextSyncCommittee.pubkeys;
} catch (e) {
console.error(e);
return false;
}
}
protected async syncFromGenesis(): Promise<Uint8Array[]> {
return this.syncFromLastUpdate(this.genesisPeriod);
}
protected async syncFromLastUpdate(
startPeriod = this.latestPeriod,
): Promise<Uint8Array[]> {
const currentPeriod = this.getCurrentPeriod();
let startCommittee = this.genesisCommittee;
console.debug(
`Sync started from period(${startPeriod}) to period(${currentPeriod})`,
);
const { syncCommittee, period } = await this.syncProver(
startPeriod,
currentPeriod,
startCommittee,
);
if (period === currentPeriod) {
console.debug(
`Sync completed from period(${startPeriod}) to period(${currentPeriod})`,
);
return syncCommittee;
}
throw new Error("no honest prover found");
}
} }

View File

@ -1,9 +1,17 @@
import BaseClient, { BaseClientOptions } from "#baseClient.js"; import BaseClient, { BaseClientOptions } from "#baseClient.js";
import { IProver, IVerifyingProviderConstructor } from "#interfaces.js"; import { ExecutionInfo, IVerifyingProviderConstructor } from "#interfaces.js";
import { DEFAULT_BATCH_SIZE } from "#constants.js";
import { IClientProver } from "#client/prover.js";
import {
getCommitteeHash,
optimisticUpdateFromJSON,
optimisticUpdateVerify,
} from "#util.js";
import { equalBytes } from "@noble/curves/abstract/utils";
import { IClientVerifyingProvider } from "#client/verifyingProvider.js"; import { IClientVerifyingProvider } from "#client/verifyingProvider.js";
interface Config extends BaseClientOptions { interface Config extends BaseClientOptions {
prover: IProver; prover: IClientProver;
provider: IVerifyingProviderConstructor<IClientVerifyingProvider>; provider: IVerifyingProviderConstructor<IClientVerifyingProvider>;
rpcHandler: Function; rpcHandler: Function;
} }
@ -40,6 +48,74 @@ export default class Client extends BaseClient {
} }
} }
protected async getLatestExecution(): Promise<ExecutionInfo | null> {
const updateJSON = await this.options.prover.callback(
"consensus_optimistic_update",
);
const update = optimisticUpdateFromJSON(updateJSON);
const verify = await optimisticUpdateVerify(
this.latestCommittee as Uint8Array[],
update,
);
if (!verify.correct) {
console.error(`Invalid Optimistic Update: ${verify.reason}`);
return null;
}
console.log(
`Optimistic update verified for slot ${updateJSON.attested_header.beacon.slot}`,
);
return {
blockHash: updateJSON.attested_header.execution.block_hash,
blockNumber: updateJSON.attested_header.execution.block_number,
};
}
protected syncFromGenesis(): Promise<Uint8Array[]> {
return this.syncFromLastUpdate(0);
}
protected async syncFromLastUpdate(
startPeriod = this.latestPeriod,
): Promise<Uint8Array[]> {
const currentPeriod = this.getCurrentPeriod();
let lastCommitteeHash: Uint8Array = getCommitteeHash(this.genesisCommittee);
for (let period = startPeriod + 1; period <= currentPeriod; period++) {
try {
lastCommitteeHash = await this.options.prover.getCommitteeHash(
period,
currentPeriod,
DEFAULT_BATCH_SIZE,
);
} catch (e: any) {
throw new Error(
`failed to fetch committee hash for prover at period(${period}): ${e.meessage}`,
);
}
}
return this.getCommittee(currentPeriod, lastCommitteeHash);
}
private async getCommittee(
period: number,
expectedCommitteeHash: Uint8Array | null,
): Promise<Uint8Array[]> {
if (period === this.genesisPeriod) {
return this.genesisCommittee;
}
if (!expectedCommitteeHash) {
throw new Error("expectedCommitteeHash required");
}
const committee = await this.options.prover.getCommittee(period);
const committeeHash = getCommitteeHash(committee);
if (!equalBytes(committeeHash, expectedCommitteeHash as Uint8Array)) {
throw new Error("prover responded with an incorrect committee");
}
return committee;
}
public async rpcCall(method: string, params: any) { public async rpcCall(method: string, params: any) {
return this.provider?.rpcMethod(method, params); return this.provider?.rpcMethod(method, params);
} }

View File

@ -1,7 +1,6 @@
import Client from "./client.js"; import Client from "./client.js";
import Prover, { ProverRequestCallback } from "../prover.js"; import Prover, { IClientProver, ProverRequestCallback } from "./prover.js";
import VerifyingProvider from "./verifyingProvider.js"; import VerifyingProvider from "./verifyingProvider.js";
import Store from "#store.js";
function createDefaultClient( function createDefaultClient(
proverHandler: ProverRequestCallback, proverHandler: ProverRequestCallback,
@ -9,7 +8,6 @@ function createDefaultClient(
): Client { ): Client {
return new Client({ return new Client({
prover: new Prover(proverHandler), prover: new Prover(proverHandler),
store: new Store(60 * 60),
provider: VerifyingProvider, provider: VerifyingProvider,
rpcHandler, rpcHandler,
}); });
@ -17,5 +15,5 @@ function createDefaultClient(
export { RPCRequest, RPCRequestRaw, RPCResponse } from "./rpc.js"; export { RPCRequest, RPCRequestRaw, RPCResponse } from "./rpc.js";
export { Client, Prover, VerifyingProvider, createDefaultClient }; export { Client, Prover, VerifyingProvider, createDefaultClient };
export { ProverRequestCallback }; export { IClientProver, ProverRequestCallback };
export * from "#interfaces.js"; export * from "#interfaces.js";

73
src/client/prover.ts Normal file
View File

@ -0,0 +1,73 @@
import {
ConsensusCommitteeHashesRequest,
ConsensusCommitteePeriodRequest,
IProver,
} from "#interfaces.js";
import { CommitteeSSZ, HashesSSZ } from "#ssz.js";
import { LightClientUpdate } from "#types.js";
import * as capella from "@lodestar/types/capella";
export type ProverRequestCallback = (
action: string,
args?: ConsensusCommitteeHashesRequest | ConsensusCommitteePeriodRequest,
) => Promise<any>;
export interface IClientProver extends IProver {
get callback(): ProverRequestCallback;
getCommittee(period: number | "latest"): Promise<Uint8Array[]>;
getSyncUpdate(period: number): Promise<LightClientUpdate>;
getCommitteeHash(
period: number,
currentPeriod: number,
cacheCount: number,
): Promise<Uint8Array>;
}
export default class Prover implements IClientProver {
cachedHashes: Map<number, Uint8Array> = new Map();
constructor(callback: ProverRequestCallback) {
this._callback = callback;
}
private _callback: ProverRequestCallback;
get callback(): ProverRequestCallback {
return this._callback;
}
async getCommittee(period: number | "latest"): Promise<Uint8Array[]> {
const res = await this.callback("consensus_committee_period", { period });
return CommitteeSSZ.deserialize(Uint8Array.from(Object.values(res)));
}
async getSyncUpdate(period: number): Promise<LightClientUpdate> {
const res = await this.callback("consensus_committee_period", { period });
return capella.ssz.LightClientUpdate.deserialize(
Uint8Array.from(Object.values(res)),
);
}
async _getHashes(startPeriod: number, count: number): Promise<Uint8Array[]> {
const res = await this.callback("consensus_committee_hashes", {
start: startPeriod,
count,
});
return HashesSSZ.deserialize(Uint8Array.from(Object.values(res)));
}
async getCommitteeHash(
period: number,
currentPeriod: number,
cacheCount: number,
): Promise<Uint8Array> {
const _count = Math.min(currentPeriod - period + 1, cacheCount);
if (!this.cachedHashes.has(period)) {
const vals = await this._getHashes(period, _count);
for (let i = 0; i < _count; i++) {
this.cachedHashes.set(period + i, vals[i]);
}
}
return this.cachedHashes.get(period)!;
}
}

View File

@ -1,21 +1,24 @@
import { BeaconConfig } from "@lodestar/config"; import { BeaconConfig } from "@lodestar/config";
import { GenesisData, LightClientUpdate } from "#types.js"; import { GenesisData, LightClientUpdate } from "#types.js";
import { ProverRequestCallback } from "#client/index.js";
import BaseClient from "#baseClient.js"; import Provider from "#client/verifyingProvider.js";
export interface IProver { export interface IProver {
get callback(): ProverRequestCallback;
set client(value: BaseClient);
getSyncUpdate( getSyncUpdate(
startPeriod: number,
period: number, period: number,
): Promise<LightClientUpdate[]>; currentPeriod: number,
cacheCount: number,
): Promise<LightClientUpdate>;
} }
export interface IStore { export interface IStore {
addUpdate(period: number, update: LightClientUpdate): void; addUpdate(period: number, update: LightClientUpdate): Promise<void>;
getUpdate(period: number): Uint8Array; getUpdate(period: number): Uint8Array;
hasUpdate(period: number): boolean;
getCommittee(period: number): Uint8Array;
getCommitteeHashes(period: number, count: number): Uint8Array;
} }
export interface IVerifyingProvider { export interface IVerifyingProvider {
@ -38,7 +41,15 @@ export interface ExecutionInfo {
blockNumber: number; blockNumber: number;
} }
export interface ConsensusCommitteeUpdateRequest { export interface ConsensusCommitteeHashesRequest {
start: number; start: number;
count: number; count: number;
} }
export interface ConsensusCommitteePeriodRequest {
period: number | "latest";
}
export interface ConsensusBlockRequest {
block: number;
}

View File

@ -42,9 +42,144 @@ export default class Client extends BaseClient {
this.http.defaults.baseURL = this.beaconUrl; this.http.defaults.baseURL = this.beaconUrl;
} }
private _latestOptimisticUpdate: any;
get latestOptimisticUpdate(): any {
return this._latestOptimisticUpdate;
}
async sync(): Promise<void> { async sync(): Promise<void> {
await super.sync(); await super.sync();
this.subscribe(); this.subscribe();
} }
async syncProver(
startPeriod: number,
currentPeriod: number,
startCommittee: Uint8Array[],
): Promise<{ syncCommittee: Uint8Array[]; period: number }> {
for (let period = startPeriod; period < currentPeriod; period += 1) {
try {
const update = await this.options.prover.getSyncUpdate(
period,
currentPeriod,
DEFAULT_BATCH_SIZE,
);
const validOrCommittee = await this.syncUpdateVerifyGetCommittee(
startCommittee,
period,
update,
);
if (!(validOrCommittee as boolean)) {
console.log(`Found invalid update at period(${period})`);
return {
syncCommittee: startCommittee,
period,
};
}
await this.options.store?.addUpdate(period, update);
startCommittee = validOrCommittee as Uint8Array[];
} catch (e) {
console.error(`failed to fetch sync update for period(${period})`);
return {
syncCommittee: startCommittee,
period,
};
}
}
return {
syncCommittee: startCommittee,
period: currentPeriod,
};
}
protected async getLatestExecution(): Promise<ExecutionInfo | null> {
const updateJSON = await getConsensusOptimisticUpdate();
const update = optimisticUpdateFromJSON(updateJSON);
const verify = await optimisticUpdateVerify(
this.latestCommittee as Uint8Array[],
update,
);
if (!verify.correct) {
// @ts-ignore
console.error(`Invalid Optimistic Update: ${verify?.reason}`);
return null;
}
this._latestOptimisticUpdate = updateJSON;
return {
blockHash: toHexString(update.attestedHeader.execution.blockHash),
blockNumber: update.attestedHeader.execution.blockNumber,
};
}
protected async syncFromGenesis(): Promise<Uint8Array[]> {
return this.syncFromLastUpdate(this.genesisPeriod);
}
protected async syncFromLastUpdate(
startPeriod = this.latestPeriod,
): Promise<Uint8Array[]> {
const currentPeriod = this.getCurrentPeriod();
let startCommittee = this.genesisCommittee;
console.debug(
`Sync started from period(${startPeriod}) to period(${currentPeriod})`,
);
const { syncCommittee, period } = await this.syncProver(
startPeriod,
currentPeriod,
startCommittee,
);
if (period === currentPeriod) {
console.debug(
`Sync completed from period(${startPeriod}) to period(${currentPeriod})`,
);
return syncCommittee;
}
throw new Error("no honest prover found");
}
protected async syncUpdateVerifyGetCommittee(
prevCommittee: Uint8Array[],
period: number,
update: LightClientUpdate,
): Promise<false | Uint8Array[]> {
const updatePeriod = computeSyncPeriodAtSlot(
update.attestedHeader.beacon.slot,
);
if (period !== updatePeriod) {
console.error(
`Expected update with period ${period}, but received ${updatePeriod}`,
);
return false;
}
const prevCommitteeFast = deserializeSyncCommittee({
pubkeys: prevCommittee,
aggregatePubkey: bls.PublicKey.aggregate(
deserializePubkeys(prevCommittee),
).toBytes(),
});
try {
// check if the update has valid signatures
await assertValidLightClientUpdate(
this.config.chainConfig,
prevCommitteeFast,
update,
);
return update.nextSyncCommittee.pubkeys;
} catch (e) {
console.error(e);
return false;
}
}
} }

View File

@ -1,20 +1,11 @@
import Client from "./client.js"; import Client from "./client.js";
import Store from "../store.js"; import Prover from "./prover.js";
import Prover from "#prover.js"; import Store from "./store.js";
import * as capella from "@lodestar/types/capella";
import { consensusClient } from "#util.js";
function createDefaultClient(beaconUrl: string): Client { function createDefaultClient(beaconUrl: string): Client {
return new Client({ return new Client({
store: new Store(), store: new Store(),
prover: new Prover(async (args) => { prover: new Prover(),
const res = await consensusClient.get(
`/eth/v1/beacon/light_client/updates?start_period=${args.start}&count=${args.count}`,
);
return res.data.map((u: any) =>
capella.ssz.LightClientUpdate.fromJson(u.data),
);
}),
beaconUrl, beaconUrl,
}); });
} }

37
src/node/prover.ts Normal file
View File

@ -0,0 +1,37 @@
import { IProver } from "#interfaces.js";
import { LightClientUpdate } from "#types.js";
import { consensusClient } from "#util.js";
import { AxiosInstance } from "axios";
import * as capella from "@lodestar/types/capella";
export default class Prover implements IProver {
cachedSyncUpdate: Map<number, LightClientUpdate> = new Map();
private http: AxiosInstance = consensusClient;
async _getSyncUpdates(
startPeriod: number,
maxCount: number,
): Promise<LightClientUpdate[]> {
const res = await this.http(
`/eth/v1/beacon/light_client/updates?start_period=${startPeriod}&count=${maxCount}`,
);
return res.data.map((u: any) =>
capella.ssz.LightClientUpdate.fromJson(u.data),
);
}
async getSyncUpdate(
period: number,
currentPeriod: number,
cacheCount: number,
): Promise<LightClientUpdate> {
const _cacheCount = Math.min(currentPeriod - period + 1, cacheCount);
if (!this.cachedSyncUpdate.has(period)) {
const vals = await this._getSyncUpdates(period, _cacheCount);
for (let i = 0; i < _cacheCount; i++) {
this.cachedSyncUpdate.set(period + i, vals[i]);
}
}
return this.cachedSyncUpdate.get(period)!;
}
}

57
src/node/store.ts Normal file
View File

@ -0,0 +1,57 @@
import { digest } from "@chainsafe/as-sha256";
import { CommitteeSSZ, HashesSSZ } from "#ssz.js";
import { IStore } from "#interfaces.js";
import { concatBytes } from "@noble/hashes/utils";
import { LightClientUpdate } from "#types.js";
import * as capella from "@lodestar/types/capella";
export default class Store implements IStore {
store: {
[period: number]: {
update: Uint8Array;
nextCommittee: Uint8Array;
nextCommitteeHash: Uint8Array;
};
} = {};
async addUpdate(period: number, update: LightClientUpdate) {
try {
this.store[period] = {
update: capella.ssz.LightClientUpdate.serialize(update),
nextCommittee: CommitteeSSZ.serialize(update.nextSyncCommittee.pubkeys),
nextCommitteeHash: digest(
concatBytes(...update.nextSyncCommittee.pubkeys),
),
};
} catch (e) {
console.log(e);
}
}
getUpdate(period: number): Uint8Array {
if (period in this.store) return this.store[period].update;
throw new Error(`update unavailable for period ${period}`);
}
getCommittee(period: number): Uint8Array {
if (period < 1)
throw new Error("committee not unavailable for period less than 1");
const predPeriod = period - 1;
if (predPeriod in this.store) return this.store[predPeriod].nextCommittee;
throw new Error(`committee unavailable for period ${predPeriod}`);
}
getCommitteeHashes(period: number, count: number): Uint8Array {
if (period < 1)
throw new Error("committee not unavailable for period less than 1");
const predPeriod = period - 1;
const hashes = new Array(count).fill(0).map((_, i) => {
const p = predPeriod + i;
if (p in this.store) return this.store[p].nextCommitteeHash;
throw new Error(`committee unavailable for period ${p}`);
});
return HashesSSZ.serialize(hashes);
}
}

View File

@ -1,72 +0,0 @@
import { ConsensusCommitteeUpdateRequest, IProver } from "#interfaces.js";
import { LightClientUpdate } from "#types.js";
import * as capella from "@lodestar/types/capella";
import BaseClient from "#baseClient.js";
export type ProverRequestCallback = (
args: ConsensusCommitteeUpdateRequest,
) => Promise<any>;
export default class Prover implements IProver {
constructor(callback: ProverRequestCallback) {
this._callback = callback;
}
private _client?: BaseClient;
set client(value: BaseClient) {
this._client = value;
}
private _callback: ProverRequestCallback;
get callback(): ProverRequestCallback {
return this._callback;
}
async getSyncUpdate(
startPeriod: number,
count: number,
): Promise<LightClientUpdate[]> {
let end = startPeriod + count;
let hasStart = this.client.store.hasUpdate(startPeriod);
let hasEnd = this.client.store.hasUpdate(startPeriod + count);
let trueStart = startPeriod;
let trueCount = count;
if (hasStart && !hasEnd) {
for (let i = startPeriod; i <= end; i++) {
if (!this.client.store.hasUpdate(i)) {
trueStart = i;
trueCount = end - i;
}
}
}
const res = await this.callback({
start: trueStart,
count: trueCount,
});
const updates: LightClientUpdate[] = [];
if (trueStart != startPeriod) {
for (let i = 0; i < trueStart - startPeriod; i++) {
updates.push(
capella.ssz.LightClientUpdate.deserialize(
this.client.store.getUpdate(startPeriod + i),
),
);
}
}
for (let i = 0; i < trueCount; i++) {
updates.push(
capella.ssz.LightClientUpdate.deserialize(res[startPeriod + i]),
);
}
return updates;
}
}

View File

@ -1,45 +0,0 @@
import { digest } from "@chainsafe/as-sha256";
import { CommitteeSSZ, HashesSSZ } from "#ssz.js";
import { IStore } from "#interfaces.js";
import { concatBytes } from "@noble/hashes/utils";
import { LightClientUpdate } from "#types.js";
import * as capella from "@lodestar/types/capella";
import NodeCache from "node-cache";
export interface StoreItem {
update: Uint8Array;
nextCommittee: Uint8Array;
nextCommitteeHash: Uint8Array;
}
export default class Store implements IStore {
private store = new NodeCache();
constructor(expire: number = 0) {
this.store.options.stdTTL = 0;
}
addUpdate(period: number, update: LightClientUpdate) {
try {
this.store.set(period, {
update: capella.ssz.LightClientUpdate.serialize(update),
nextCommittee: CommitteeSSZ.serialize(update.nextSyncCommittee.pubkeys),
nextCommitteeHash: digest(
concatBytes(...update.nextSyncCommittee.pubkeys),
),
});
} catch (e) {
console.log(e);
}
}
getUpdate(period: number): Uint8Array {
if (this.store.has(period)) {
return this.store.get<StoreItem>(period)?.update as Uint8Array;
}
throw new Error(`update unavailable for period ${period}`);
}
hasUpdate(period: number): boolean {
return this.store.has(period);
}
}