2023-07-10 20:44:58 +00:00
|
|
|
import BaseClient, { BaseClientOptions } from "#baseClient.js";
|
|
|
|
import {
|
|
|
|
ExecutionInfo,
|
|
|
|
IStore,
|
|
|
|
IVerifyingProvider,
|
|
|
|
IVerifyingProviderConstructor,
|
|
|
|
} from "#interfaces.js";
|
|
|
|
import { DEFAULT_BATCH_SIZE, POLLING_DELAY } from "#constants.js";
|
|
|
|
import { IClientProver } from "#client/prover.js";
|
|
|
|
import {
|
|
|
|
getCommitteeHash,
|
|
|
|
optimisticUpdateFromJSON,
|
|
|
|
optimisticUpdateVerify,
|
|
|
|
} from "#util.js";
|
|
|
|
import { equalBytes } from "@noble/curves/abstract/utils.js";
|
|
|
|
|
|
|
|
interface Config extends BaseClientOptions {
|
|
|
|
prover: IClientProver;
|
|
|
|
provider: IVerifyingProviderConstructor;
|
|
|
|
rpcHandler: Function;
|
|
|
|
}
|
|
|
|
|
|
|
|
export default class Client extends BaseClient {
|
|
|
|
protected declare options: Config;
|
|
|
|
|
|
|
|
constructor(options: Config) {
|
|
|
|
super(options);
|
|
|
|
}
|
|
|
|
|
2023-07-11 08:15:35 +00:00
|
|
|
private _provider?: IVerifyingProvider;
|
|
|
|
|
|
|
|
get provider(): IVerifyingProvider {
|
|
|
|
return this._provider as IVerifyingProvider;
|
2023-07-10 20:44:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async sync(): Promise<void> {
|
|
|
|
await super.sync();
|
|
|
|
|
2023-07-11 08:15:35 +00:00
|
|
|
if (!this._provider) {
|
2023-07-10 20:44:58 +00:00
|
|
|
const { blockHash, blockNumber } = await this.getNextValidExecutionInfo();
|
|
|
|
const factory = this.options.provider;
|
|
|
|
const provider = new factory(
|
|
|
|
this.options.rpcHandler,
|
|
|
|
blockNumber,
|
|
|
|
blockHash,
|
|
|
|
);
|
|
|
|
this.subscribe((ei) => {
|
|
|
|
console.log(
|
|
|
|
`Received a new blockheader: ${ei.blockNumber} ${ei.blockHash}`,
|
|
|
|
);
|
|
|
|
provider.update(ei.blockNumber, ei.blockHash);
|
|
|
|
});
|
|
|
|
|
2023-07-11 08:15:35 +00:00
|
|
|
this._provider = provider;
|
2023-07-10 20:44:58 +00:00
|
|
|
this.booted = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-11 08:15:35 +00:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-07-10 20:44:58 +00:00
|
|
|
protected syncFromGenesis(): Promise<Uint8Array[]> {
|
|
|
|
return Promise.resolve([]);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|