Skip to content

Commit

Permalink
Light client initialization from checkpoint (#2718)
Browse files Browse the repository at this point in the history
* Replace initFromStateRoot with initFromCheckpoint

* Add comments to initial proof path

* Disable mock light client server test
  • Loading branch information
wemeetagain authored Jun 24, 2021
1 parent 0851586 commit dc16c5a
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 25 deletions.
58 changes: 33 additions & 25 deletions packages/light-client/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import mitt from "mitt";
import {EPOCHS_PER_SYNC_COMMITTEE_PERIOD, SLOTS_PER_EPOCH} from "@chainsafe/lodestar-params";
import {getClient, Api} from "@chainsafe/lodestar-api";
import {altair, Root, Slot, ssz, SyncPeriod} from "@chainsafe/lodestar-types";
import {altair, Root, ssz, SyncPeriod} from "@chainsafe/lodestar-types";
import {IBeaconConfig} from "@chainsafe/lodestar-config";
import {computeSyncPeriodAtSlot, ZERO_HASH} from "@chainsafe/lodestar-beacon-state-transition";
import {computeSyncPeriodAtSlot} from "@chainsafe/lodestar-beacon-state-transition";
import {TreeOffsetProof} from "@chainsafe/persistent-merkle-tree";
import {Path, toHexString} from "@chainsafe/ssz";
import {BeaconBlockHeader} from "@chainsafe/lodestar-types/phase0";
import {BeaconBlockHeader, Checkpoint} from "@chainsafe/lodestar-types/phase0";
import {Clock, IClock} from "../utils/clock";
import {deserializeSyncCommittee, isEmptyHeader, serializeSyncCommittee, sumBits} from "../utils/utils";
import {LightClientStoreFast} from "./types";
Expand All @@ -18,8 +18,6 @@ import {isBetterUpdate} from "./update";
// Re-export event types
export {LightclientEvent} from "./events";

type Optional<T, K extends keyof T> = Partial<T> & Omit<T, K>;

export type LightclientModules = {
config: IBeaconConfig;
clock: IClock;
Expand Down Expand Up @@ -48,43 +46,53 @@ export class Lightclient {
this.clock.runEverySlot(this.syncToLatest);
}

static async initializeFromTrustedStateRoot(
modules: Optional<LightclientModules, "clock" | "genesisValidatorsRoot">,
trustedRoot: {stateRoot: Root; slot: Slot}
static async initializeFromCheckpoint(
config: IBeaconConfig,
beaconApiUrl: string,
checkpoint: Checkpoint
): Promise<Lightclient> {
const {beaconApiUrl, config} = modules;
const {slot, stateRoot} = trustedRoot;
// TODO: Consider initializing only the lightclient namespace
const api = getClient(config, {baseUrl: beaconApiUrl});

const paths = getSyncCommitteesProofPaths();
// if the clock or genesisValidatorsRoot isn't supplied
// then add the required fields to the requested proof paths
if (!modules.clock) {
paths.push(["genesisTime"]);
}
if (!modules.genesisValidatorsRoot) {
paths.push(["genesisValidatorsRoot"]);
// fetch block header matching checkpoint root
const headerResp = await api.beacon.getBlockHeader(toHexString(checkpoint.root));

// verify the response matches the requested root
if (
!ssz.Root.equals(checkpoint.root, headerResp.data.root) ||
!ssz.Root.equals(checkpoint.root, ssz.phase0.BeaconBlockHeader.hashTreeRoot(headerResp.data.header.message))
) {
throw new Error("Retrieved header does not match trusted checkpoint");
}
const header = headerResp.data.header.message;
const stateRoot = header.stateRoot;

// fetch a proof with everything needed to bootstrap the client
const paths = [
// initial sync committee list
...getSyncCommitteesProofPaths(),
// required to initialize a slot clock
["genesisTime"],
// required to verify signatures
["genesisValidatorsRoot"],
];
const proof = await api.lightclient.getStateProof(toHexString(stateRoot), paths);

const state = ssz.altair.BeaconState.createTreeBackedFromProof(stateRoot as Uint8Array, proof.data);
const store: LightClientStoreFast = {
bestUpdates: new Map<SyncPeriod, altair.LightClientUpdate>(),
snapshot: {
header: {slot, proposerIndex: 0, parentRoot: ZERO_HASH, stateRoot, bodyRoot: ZERO_HASH},
header,
currentSyncCommittee: deserializeSyncCommittee(state.currentSyncCommittee),
nextSyncCommittee: deserializeSyncCommittee(state.nextSyncCommittee),
},
};

return new Lightclient(
{
...modules,
// if the clock or genesisValidatorsRoot isn't supplied
// then set them in the new modules object
clock: modules.clock || new Clock(config, state.genesisTime),
genesisValidatorsRoot: modules.genesisValidatorsRoot || (state.genesisValidatorsRoot.valueOf() as Root),
config,
beaconApiUrl,
clock: new Clock(config, state.genesisTime),
genesisValidatorsRoot: state.genesisValidatorsRoot.valueOf() as Root,
},
store
);
Expand Down
7 changes: 7 additions & 0 deletions packages/light-client/test/unit/sync.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* TODO re-enable this test when the mock server is updated OR test against a full backend in another package
import {expect} from "chai";
import {SecretKey} from "@chainsafe/bls";
import {config} from "@chainsafe/lodestar-config/default";
Expand All @@ -12,9 +13,12 @@ import {Lightclient} from "../../src/client";
import {ServerOpts} from "../lightclientApiServer";
import {IClock} from "../../src/utils/clock";
import {generateBalances, generateValidators, getInteropSyncCommittee} from "../utils";
*/

/* eslint-disable @typescript-eslint/naming-convention, no-console */

/*
describe("Lightclient flow with LightClientUpdater", () => {
let lightclientServer: LightclientMockServer;
let genesisStateRoot: Root;
Expand Down Expand Up @@ -152,6 +156,7 @@ describe("Lightclient flow with LightClientUpdater", () => {
});
});
class MockClock implements IClock {
constructor(readonly currentSlot: Slot) {}
start(): void {
Expand All @@ -161,3 +166,5 @@ class MockClock implements IClock {
//
}
}
*/

0 comments on commit dc16c5a

Please sign in to comment.