*Switch to optimistic approach that uses the relay as a light proxy
This commit is contained in:
parent
c7c44a0cd2
commit
4ac1621e3f
|
@ -9,6 +9,7 @@
|
|||
"build": "npm run compile && node ./dist-build/build.mjs dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chainsafe/as-sha256": "^0.3.1",
|
||||
"@chainsafe/bls": "^7.1.1",
|
||||
"@chainsafe/blst": "^0.2.8",
|
||||
"@chainsafe/ssz": "^0.10.2",
|
||||
|
@ -32,7 +33,6 @@
|
|||
"path-browserify": "^1.0.1",
|
||||
"rlp": "^3.0.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"ts-essentials": "^9.3.1",
|
||||
"web3-core": "^1.9.0",
|
||||
"web3-core-method": "^1.9.0",
|
||||
"web3-eth": "^1.9.0",
|
||||
|
|
|
@ -6,7 +6,11 @@ import {
|
|||
OptimisticUpdate,
|
||||
VerifyWithReason,
|
||||
} from "./types.js";
|
||||
import { getDefaultClientConfig } from "./utils.js";
|
||||
import {
|
||||
concatUint8Array,
|
||||
getDefaultClientConfig,
|
||||
isUint8ArrayEq,
|
||||
} from "./utils.js";
|
||||
import { IProver } from "./interfaces.js";
|
||||
import {
|
||||
BEACON_SYNC_SUPER_MAJORITY,
|
||||
|
@ -25,12 +29,12 @@ import { SyncCommitteeFast } from "@lodestar/light-client";
|
|||
import bls from "@chainsafe/bls/switchable";
|
||||
import { PublicKey } from "@chainsafe/bls/types.js";
|
||||
import { fromHexString, toHexString } from "@chainsafe/ssz";
|
||||
import { AsyncOrSync } from "ts-essentials";
|
||||
import * as altair from "@lodestar/types/altair";
|
||||
import * as phase0 from "@lodestar/types/phase0";
|
||||
import * as bellatrix from "@lodestar/types/bellatrix";
|
||||
import { init } from "@chainsafe/bls/switchable";
|
||||
import { VerifyingProvider } from "./rpc/provider.js";
|
||||
import { digest } from "@chainsafe/as-sha256";
|
||||
|
||||
export default class Client {
|
||||
latestCommittee?: Uint8Array[];
|
||||
|
@ -85,55 +89,13 @@ export default class Client {
|
|||
return this._provider;
|
||||
}
|
||||
|
||||
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.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,
|
||||
};
|
||||
}
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
// returns the prover info containing the current sync
|
||||
|
||||
public getCurrentPeriod(): number {
|
||||
return computeSyncPeriodAtSlot(
|
||||
getCurrentSlot(this.config.chainConfig, this.genesisTime)
|
||||
);
|
||||
}
|
||||
|
||||
public async subscribe(callback: (ei: ExecutionInfo) => AsyncOrSync<void>) {
|
||||
public async subscribe(callback: (ei: ExecutionInfo) => void) {
|
||||
setInterval(async () => {
|
||||
try {
|
||||
await this._sync();
|
||||
|
@ -148,10 +110,6 @@ export default class Client {
|
|||
}, POLLING_DELAY);
|
||||
}
|
||||
|
||||
optimisticUpdateFromJSON(update: any): OptimisticUpdate {
|
||||
return altair.ssz.LightClientOptimisticUpdate.fromJson(update);
|
||||
}
|
||||
|
||||
async optimisticUpdateVerify(
|
||||
committee: Uint8Array[],
|
||||
update: OptimisticUpdate
|
||||
|
@ -195,7 +153,7 @@ export default class Client {
|
|||
return this.getNextValidExecutionInfo(retry - 1);
|
||||
}
|
||||
|
||||
protected async _sync() {
|
||||
private async _sync() {
|
||||
const currentPeriod = this.getCurrentPeriod();
|
||||
if (currentPeriod > this.latestPeriod) {
|
||||
this.latestCommittee = await this.syncFromGenesis();
|
||||
|
@ -204,59 +162,47 @@ export default class Client {
|
|||
}
|
||||
|
||||
// committee and prover index of the first honest prover
|
||||
protected async syncFromGenesis(): Promise<Uint8Array[]> {
|
||||
// get the tree size by currentPeriod - genesisPeriod
|
||||
private async syncFromGenesis(): Promise<Uint8Array[]> {
|
||||
const currentPeriod = this.getCurrentPeriod();
|
||||
let startPeriod = this.genesisPeriod;
|
||||
let startCommittee = this.genesisCommittee;
|
||||
console.log(
|
||||
`Sync started from period(${startPeriod}) to period(${currentPeriod})`
|
||||
|
||||
let lastCommitteeHash: Uint8Array = this.getCommitteeHash(
|
||||
this.genesisCommittee
|
||||
);
|
||||
|
||||
const { syncCommittee, period } = await this.syncProver(
|
||||
startPeriod,
|
||||
currentPeriod,
|
||||
startCommittee
|
||||
);
|
||||
if (period === currentPeriod) {
|
||||
return syncCommittee;
|
||||
for (let period = startPeriod + 1; period <= currentPeriod; period++) {
|
||||
try {
|
||||
lastCommitteeHash = await this.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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new Error("no honest prover found");
|
||||
return this.getCommittee(currentPeriod, lastCommitteeHash);
|
||||
}
|
||||
|
||||
protected async syncUpdateVerifyGetCommittee(
|
||||
prevCommittee: Uint8Array[],
|
||||
async getCommittee(
|
||||
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 recieved ${updatePeriod}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const prevCommitteeFast = this.deserializeSyncCommittee(prevCommittee);
|
||||
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;
|
||||
}
|
||||
expectedCommitteeHash: Uint8Array | null
|
||||
): Promise<Uint8Array[]> {
|
||||
if (period === this.genesisPeriod) return this.genesisCommittee;
|
||||
if (!expectedCommitteeHash)
|
||||
throw new Error("expectedCommitteeHash required");
|
||||
const committee = await this.prover.getCommittee(period);
|
||||
const committeeHash = this.getCommitteeHash(committee);
|
||||
if (!isUint8ArrayEq(committeeHash, expectedCommitteeHash as Uint8Array))
|
||||
throw new Error("prover responded with an incorrect committee");
|
||||
return committee;
|
||||
}
|
||||
|
||||
protected async getLatestExecution(): Promise<ExecutionInfo | null> {
|
||||
private async getLatestExecution(): Promise<ExecutionInfo | null> {
|
||||
const updateJSON = await this.prover.callback(
|
||||
"/eth/v1/beacon/light_client/optimistic_update"
|
||||
"consensus_optimistic_update"
|
||||
);
|
||||
const update = this.optimisticUpdateFromJSON(updateJSON);
|
||||
const verify = await this.optimisticUpdateVerify(
|
||||
|
@ -276,11 +222,13 @@ export default class Client {
|
|||
);
|
||||
}
|
||||
|
||||
protected async getExecutionFromBlockRoot(
|
||||
private async getExecutionFromBlockRoot(
|
||||
slot: bigint,
|
||||
expectedBlockRoot: Bytes32
|
||||
): Promise<ExecutionInfo> {
|
||||
const res = await this.prover.callback(`/eth/v2/beacon/blocks/${slot}`);
|
||||
const res = await this.prover.callback("consensus_block", {
|
||||
block: slot,
|
||||
});
|
||||
const blockJSON = res.message.body;
|
||||
const block = bellatrix.ssz.BeaconBlockBody.fromJson(blockJSON);
|
||||
const blockRoot = toHexString(
|
||||
|
@ -311,4 +259,10 @@ export default class Client {
|
|||
private deserializePubkeys(pubkeys: Uint8Array[]): PublicKey[] {
|
||||
return pubkeys.map((pk) => bls.PublicKey.fromBytes(pk));
|
||||
}
|
||||
private getCommitteeHash(committee: Uint8Array[]): Uint8Array {
|
||||
return digest(concatUint8Array(committee));
|
||||
}
|
||||
private optimisticUpdateFromJSON(update: any): OptimisticUpdate {
|
||||
return altair.ssz.LightClientOptimisticUpdate.fromJson(update);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import { AsyncOrSync } from "ts-essentials";
|
||||
import { LightClientUpdate } from "./types.js";
|
||||
|
||||
export interface IProver {
|
||||
get callback(): Function;
|
||||
getSyncUpdate(
|
||||
|
||||
getCommittee(period: number | "latest"): Promise<Uint8Array[]>;
|
||||
|
||||
getCommitteeHash(
|
||||
period: number,
|
||||
currentPeriod: number,
|
||||
cacheCount: number
|
||||
): AsyncOrSync<LightClientUpdate>;
|
||||
count: number
|
||||
): Promise<Uint8Array>;
|
||||
|
||||
getSyncUpdate(period: number): Promise<LightClientUpdate>;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import * as altair from "@lodestar/types/altair";
|
||||
import { IProver } from "./interfaces.js";
|
||||
import { LightClientUpdate } from "./types.js";
|
||||
import { CommitteeSSZ, HashesSSZ, LightClientUpdateSSZ } from "./ssz.js";
|
||||
|
||||
export default class Prover implements IProver {
|
||||
cachedSyncUpdate: Map<number, LightClientUpdate> = new Map();
|
||||
cachedHashes: Map<number, Uint8Array> = new Map();
|
||||
|
||||
constructor(callback: Function) {
|
||||
this._callback = callback;
|
||||
|
@ -15,28 +16,38 @@ export default class Prover implements IProver {
|
|||
return this._callback;
|
||||
}
|
||||
|
||||
async _getSyncUpdates(
|
||||
startPeriod: number,
|
||||
maxCount: number
|
||||
): Promise<LightClientUpdate[]> {
|
||||
const res = await this._callback(
|
||||
`/eth/v1/beacon/light_client/updates?start_period=${startPeriod}&count=${maxCount}`
|
||||
);
|
||||
return res.map((u: any) => altair.ssz.LightClientUpdate.fromJson(u.data));
|
||||
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(
|
||||
async getSyncUpdate(period: number): Promise<LightClientUpdate> {
|
||||
const res = await this.callback("consensus_committee_period", { period });
|
||||
return LightClientUpdateSSZ.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<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]);
|
||||
): 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.cachedSyncUpdate.get(period)!;
|
||||
return this.cachedHashes.get(period)!;
|
||||
}
|
||||
}
|
||||
|
|
41
src/index.ts
41
src/index.ts
|
@ -1,6 +1,5 @@
|
|||
import { ActiveQuery, addHandler, handleMessage } from "libkmodule";
|
||||
import { createClient, RpcNetwork } from "@lumeweb/kernel-rpc-client";
|
||||
import { ConsensusRequest, ExecutionRequest } from "./types.js";
|
||||
import Client from "./client/client.js";
|
||||
import { Prover } from "./client/index.js";
|
||||
|
||||
|
@ -65,27 +64,22 @@ async function handleRpcMethod(aq: ActiveQuery) {
|
|||
);
|
||||
}
|
||||
|
||||
async function consensusHandler(endpoint: string) {
|
||||
let query;
|
||||
|
||||
async function consensusHandler(method: string, data: any) {
|
||||
while (true) {
|
||||
query = await rpc.simpleQuery({
|
||||
let query = await rpc.simpleQuery({
|
||||
query: {
|
||||
module: "eth",
|
||||
method: "consensus_request",
|
||||
data: {
|
||||
method: "GET",
|
||||
path: endpoint,
|
||||
} as ConsensusRequest,
|
||||
method,
|
||||
data,
|
||||
},
|
||||
options: {
|
||||
relayTimeout: 10,
|
||||
queryTimeout: 10,
|
||||
},
|
||||
});
|
||||
console.log("consensusHandler", endpoint);
|
||||
|
||||
const ret = await query.result;
|
||||
|
||||
if (ret.data) {
|
||||
return ret.data;
|
||||
}
|
||||
|
@ -93,23 +87,24 @@ async function consensusHandler(endpoint: string) {
|
|||
}
|
||||
|
||||
async function executionHandler(data: Map<string, string | any>) {
|
||||
let query = await rpc.simpleQuery({
|
||||
query: {
|
||||
module: "eth",
|
||||
method: "execution_request",
|
||||
data,
|
||||
},
|
||||
});
|
||||
while (true) {
|
||||
let query = await rpc.simpleQuery({
|
||||
query: {
|
||||
module: "eth",
|
||||
method: "execution_request",
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
console.log("executionHandler", data);
|
||||
let ret = await query.result;
|
||||
|
||||
let ret = await query.result;
|
||||
|
||||
return ret.data;
|
||||
if (ret.data) {
|
||||
return ret.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function setup() {
|
||||
console.time("setup");
|
||||
rpc = createClient();
|
||||
// @ts-ignore
|
||||
await (
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
export interface ConsensusRequest extends RequestInit {
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface ExecutionRequest {
|
||||
method: string;
|
||||
params: string;
|
||||
}
|
Loading…
Reference in New Issue