From 64cfe4dd2fb270542cdfd196443776c86b36047f Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Tue, 22 Oct 2024 00:17:04 -0700 Subject: [PATCH 1/5] initial commit --- .../src/chain/lightClient/index.ts | 5 +- .../src/chain/lightClient/proofs.ts | 54 ++++++++++++++----- .../test/unit/chain/lightclient/proof.test.ts | 8 +-- packages/light-client/src/spec/index.ts | 6 +-- packages/light-client/src/spec/utils.ts | 10 +++- packages/light-client/src/validation.ts | 13 ++++- packages/types/src/utils/typeguards.ts | 11 ++++ 7 files changed, 80 insertions(+), 27 deletions(-) diff --git a/packages/beacon-node/src/chain/lightClient/index.ts b/packages/beacon-node/src/chain/lightClient/index.ts index 0a41ea059e73..bb7de1f0478a 100644 --- a/packages/beacon-node/src/chain/lightClient/index.ts +++ b/packages/beacon-node/src/chain/lightClient/index.ts @@ -388,12 +388,13 @@ export class LightClientServer { parentBlockSlot: Slot ): Promise { const blockSlot = block.slot; - const header = blockToLightClientHeader(this.config.getForkName(blockSlot), block); + const fork = this.config.getForkName(blockSlot); + const header = blockToLightClientHeader(fork, block); const blockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(header.beacon); const blockRootHex = toRootHex(blockRoot); - const syncCommitteeWitness = getSyncCommitteesWitness(postState); + const syncCommitteeWitness = getSyncCommitteesWitness(fork, postState); // Only store current sync committee once per run if (!this.storedCurrentSyncCommittee) { diff --git a/packages/beacon-node/src/chain/lightClient/proofs.ts b/packages/beacon-node/src/chain/lightClient/proofs.ts index 8d273e30ae5c..5c01ae3059f2 100644 --- a/packages/beacon-node/src/chain/lightClient/proofs.ts +++ b/packages/beacon-node/src/chain/lightClient/proofs.ts @@ -5,28 +5,54 @@ import { BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX, ForkExecution, FINALIZED_ROOT_GINDEX_ELECTRA, + ForkName, + isForkPostElectra, } from "@lodestar/params"; import {BeaconBlockBody, SSZTypesFor, ssz} from "@lodestar/types"; import {SyncCommitteeWitness} from "./types.js"; -export function getSyncCommitteesWitness(state: BeaconStateAllForks): SyncCommitteeWitness { +export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllForks): SyncCommitteeWitness { state.commit(); const n1 = state.node; - const n3 = n1.right; // [1]0110 - const n6 = n3.left; // 1[0]110 - const n13 = n6.right; // 10[1]10 - const n27 = n13.right; // 101[1]0 - const currentSyncCommitteeRoot = n27.left.root; // n54 1011[0] - const nextSyncCommitteeRoot = n27.right.root; // n55 1011[1] + let witness: Uint8Array[]; + let currentSyncCommitteeRoot: Uint8Array; + let nextSyncCommitteeRoot: Uint8Array; - // Witness branch is sorted by descending gindex - const witness = [ - n13.left.root, // 26 - n6.left.root, // 12 - n3.right.root, // 7 - n1.left.root, // 2 - ]; + if (isForkPostElectra(fork)) { + const n2 = n1.left; + const n5 = n2.right; + const n10 = n5.left; + const n21 = n10.right; + const n43 = n21.right; + + currentSyncCommitteeRoot = n43.left.root; // n86 + nextSyncCommitteeRoot = n43.right.root; // n87 + + // Witness branch is sorted by descending gindex + witness = [ + n21.left.root, // 42 + n10.left.root, // 20 + n5.right.root, // 11 + n2.right.root, // 4 + n1.right.root, // 3 + ]; + } else { + const n3 = n1.right; // [1]0110 + const n6 = n3.left; // 1[0]110 + const n13 = n6.right; // 10[1]10 + const n27 = n13.right; // 101[1]0 + currentSyncCommitteeRoot = n27.left.root; // n54 1011[0] + nextSyncCommitteeRoot = n27.right.root; // n55 1011[1] + + // Witness branch is sorted by descending gindex + witness = [ + n13.left.root, // 26 + n6.left.root, // 12 + n3.right.root, // 7 + n1.left.root, // 2 + ]; + } return { witness, diff --git a/packages/beacon-node/test/unit/chain/lightclient/proof.test.ts b/packages/beacon-node/test/unit/chain/lightclient/proof.test.ts index b30e0f9a9ddb..38f4ab6aea39 100644 --- a/packages/beacon-node/test/unit/chain/lightclient/proof.test.ts +++ b/packages/beacon-node/test/unit/chain/lightclient/proof.test.ts @@ -1,6 +1,6 @@ import {describe, it, expect, beforeAll} from "vitest"; import {BeaconStateAltair} from "@lodestar/state-transition"; -import {SYNC_COMMITTEE_SIZE} from "@lodestar/params"; +import {ForkName, SYNC_COMMITTEE_SIZE} from "@lodestar/params"; import {altair, ssz} from "@lodestar/types"; import {verifyMerkleBranch, hash} from "@lodestar/utils"; import {getNextSyncCommitteeBranch, getSyncCommitteesWitness} from "../../../../src/chain/lightClient/proofs.js"; @@ -25,7 +25,7 @@ describe("chain / lightclient / proof", () => { }); it("SyncCommittees proof", () => { - const syncCommitteesWitness = getSyncCommitteesWitness(state); + const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.altair, state); const syncCommitteesLeaf = hash( syncCommitteesWitness.currentSyncCommitteeRoot, syncCommitteesWitness.nextSyncCommitteeRoot @@ -42,7 +42,7 @@ describe("chain / lightclient / proof", () => { }); it("currentSyncCommittee proof", () => { - const syncCommitteesWitness = getSyncCommitteesWitness(state); + const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.altair, state); const currentSyncCommitteeBranch = [syncCommitteesWitness.nextSyncCommitteeRoot, ...syncCommitteesWitness.witness]; expect( @@ -56,7 +56,7 @@ describe("chain / lightclient / proof", () => { }); it("nextSyncCommittee proof", () => { - const syncCommitteesWitness = getSyncCommitteesWitness(state); + const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.altair, state); const nextSyncCommitteeBranch = getNextSyncCommitteeBranch(syncCommitteesWitness); expect( diff --git a/packages/light-client/src/spec/index.ts b/packages/light-client/src/spec/index.ts index 0934e15b1c17..4fd45f0f2e7d 100644 --- a/packages/light-client/src/spec/index.ts +++ b/packages/light-client/src/spec/index.ts @@ -10,7 +10,7 @@ import { import {computeSyncPeriodAtSlot} from "../utils/index.js"; import {getSyncCommitteeAtPeriod, processLightClientUpdate, ProcessUpdateOpts} from "./processLightClientUpdate.js"; import {ILightClientStore, LightClientStore, LightClientStoreEvents} from "./store.js"; -import {ZERO_FINALITY_BRANCH, ZERO_HEADER, ZERO_SYNC_COMMITTEE, getZeroSyncCommitteeBranch} from "./utils.js"; +import {ZERO_HEADER, ZERO_SYNC_COMMITTEE, getZeroFinalityBranch, getZeroSyncCommitteeBranch} from "./utils.js"; export {isBetterUpdate, toLightClientUpdateSummary} from "./isBetterUpdate.js"; export type {LightClientUpdateSummary} from "./isBetterUpdate.js"; @@ -39,7 +39,7 @@ export class LightclientSpec { nextSyncCommittee: ZERO_SYNC_COMMITTEE, nextSyncCommitteeBranch: getZeroSyncCommitteeBranch(this.config.getForkName(finalityUpdate.signatureSlot)), finalizedHeader: finalityUpdate.finalizedHeader, - finalityBranch: finalityUpdate.finalityBranch, + finalityBranch: getZeroFinalityBranch(this.config.getForkName(finalityUpdate.signatureSlot)), syncAggregate: finalityUpdate.syncAggregate, signatureSlot: finalityUpdate.signatureSlot, }); @@ -51,7 +51,7 @@ export class LightclientSpec { nextSyncCommittee: ZERO_SYNC_COMMITTEE, nextSyncCommitteeBranch: getZeroSyncCommitteeBranch(this.config.getForkName(optimisticUpdate.signatureSlot)), finalizedHeader: {beacon: ZERO_HEADER}, - finalityBranch: ZERO_FINALITY_BRANCH, + finalityBranch: getZeroFinalityBranch(this.config.getForkName(optimisticUpdate.signatureSlot)), syncAggregate: optimisticUpdate.syncAggregate, signatureSlot: optimisticUpdate.signatureSlot, }); diff --git a/packages/light-client/src/spec/utils.ts b/packages/light-client/src/spec/utils.ts index 36bc7098fcc5..602e1264e159 100644 --- a/packages/light-client/src/spec/utils.ts +++ b/packages/light-client/src/spec/utils.ts @@ -33,7 +33,6 @@ export const ZERO_HASH = new Uint8Array(32); export const ZERO_PUBKEY = new Uint8Array(48); export const ZERO_SYNC_COMMITTEE = ssz.altair.SyncCommittee.defaultValue(); export const ZERO_HEADER = ssz.phase0.BeaconBlockHeader.defaultValue(); -export const ZERO_FINALITY_BRANCH = Array.from({length: FINALIZED_ROOT_DEPTH}, () => ZERO_HASH); /** From https://notes.ethereum.org/@vbuterin/extended_light_client_protocol#Optimistic-head-determining-function */ const SAFETY_THRESHOLD_FACTOR = 2; @@ -53,6 +52,12 @@ export function getZeroSyncCommitteeBranch(fork: ForkName): Uint8Array[] { return Array.from({length: nextSyncCommitteeDepth}, () => ZERO_HASH); } +export function getZeroFinalityBranch(fork: ForkName): Uint8Array[] { + const finalizedRootDepth = isForkPostElectra(fork) ? FINALIZED_ROOT_DEPTH_ELECTRA : FINALIZED_ROOT_DEPTH; + + return Array.from({length: finalizedRootDepth}, () => ZERO_HASH); +} + export function isSyncCommitteeUpdate(update: LightClientUpdate): boolean { return ( // Fast return for when constructing full LightClientUpdate from partial updates @@ -65,7 +70,8 @@ export function isSyncCommitteeUpdate(update: LightClientUpdate): boolean { export function isFinalityUpdate(update: LightClientUpdate): boolean { return ( // Fast return for when constructing full LightClientUpdate from partial updates - update.finalityBranch !== ZERO_FINALITY_BRANCH && + update.finalityBranch !== + getZeroFinalityBranch(isElectraLightClientUpdate(update) ? ForkName.electra : ForkName.altair) && update.finalityBranch.some((branch) => !byteArrayEquals(branch, ZERO_HASH)) ); } diff --git a/packages/light-client/src/validation.ts b/packages/light-client/src/validation.ts index c756d612f3e7..bb9e13eec897 100644 --- a/packages/light-client/src/validation.ts +++ b/packages/light-client/src/validation.ts @@ -2,6 +2,7 @@ import bls from "@chainsafe/bls"; import type {PublicKey, Signature} from "@chainsafe/bls/types"; import { altair, + isELectraLightClientFinalityUpdate, isElectraLightClientUpdate, LightClientFinalityUpdate, LightClientUpdate, @@ -19,6 +20,7 @@ import { NEXT_SYNC_COMMITTEE_DEPTH_ELECTRA, FINALIZED_ROOT_DEPTH_ELECTRA, NEXT_SYNC_COMMITTEE_INDEX_ELECTRA, + FINALIZED_ROOT_INDEX_ELECTRA, } from "@lodestar/params"; import {BeaconConfig} from "@lodestar/config"; import {isValidMerkleBranch} from "./utils/verifyMerkleBranch.js"; @@ -80,12 +82,19 @@ export function assertValidLightClientUpdate( * Where `hashTreeRoot(state) == update.finalityHeader.stateRoot` */ export function assertValidFinalityProof(update: LightClientFinalityUpdate): void { + const finalizedRootDepth = isELectraLightClientFinalityUpdate(update) + ? FINALIZED_ROOT_DEPTH_ELECTRA + : FINALIZED_ROOT_DEPTH; + const finalizedRootIndex = isELectraLightClientFinalityUpdate(update) + ? FINALIZED_ROOT_INDEX_ELECTRA + : FINALIZED_ROOT_INDEX; + if ( !isValidMerkleBranch( ssz.phase0.BeaconBlockHeader.hashTreeRoot(update.finalizedHeader.beacon), update.finalityBranch, - FINALIZED_ROOT_DEPTH, - FINALIZED_ROOT_INDEX, + finalizedRootDepth, + finalizedRootIndex, update.attestedHeader.beacon.stateRoot ) ) { diff --git a/packages/types/src/utils/typeguards.ts b/packages/types/src/utils/typeguards.ts index a892c3a0c9c0..6afde52da472 100644 --- a/packages/types/src/utils/typeguards.ts +++ b/packages/types/src/utils/typeguards.ts @@ -15,6 +15,7 @@ import { BeaconBlock, Attestation, LightClientUpdate, + LightClientFinalityUpdate, } from "../types.js"; export function isExecutionPayload( @@ -80,3 +81,13 @@ export function isElectraLightClientUpdate(update: LightClientUpdate): update is updatePostElectra.finalityBranch.length === FINALIZED_ROOT_DEPTH_ELECTRA ); } + +export function isELectraLightClientFinalityUpdate( + update: LightClientFinalityUpdate +): update is LightClientFinalityUpdate { + const updatePostElectra = update as LightClientUpdate; + return ( + updatePostElectra.finalityBranch !== undefined && + updatePostElectra.finalityBranch.length === FINALIZED_ROOT_DEPTH_ELECTRA + ); +} From 118e065de031b63ec07231b63e555943f1292fad Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 23 Oct 2024 00:42:34 -0700 Subject: [PATCH 2/5] Rewrite SyncCommitteeWitnessRepository --- .../src/chain/lightClient/index.ts | 11 +++- .../src/chain/lightClient/types.ts | 2 +- .../lightclientSyncCommitteeWitness.ts | 55 ++++++++++++++++++- 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/packages/beacon-node/src/chain/lightClient/index.ts b/packages/beacon-node/src/chain/lightClient/index.ts index bb7de1f0478a..d3a620008c11 100644 --- a/packages/beacon-node/src/chain/lightClient/index.ts +++ b/packages/beacon-node/src/chain/lightClient/index.ts @@ -622,6 +622,16 @@ export class LightClientServer { if (!syncCommitteeWitness) { throw Error(`syncCommitteeWitness not available at ${toRootHex(attestedData.blockRoot)}`); } + + const attestedFork = this.config.getForkName(attestedHeader.beacon.slot); + const numWitness = syncCommitteeWitness.witness.length; + if (isForkPostElectra(attestedFork) && numWitness !== NUM_WITNESS_ELECTRA) { + throw Error(`Expected ${NUM_WITNESS_ELECTRA} witnesses in post-Electra numWiteness=${numWitness}`); + } + if (!isForkPostElectra(attestedFork) && numWitness !== NUM_WITNESS) { + throw Error(`Expected ${NUM_WITNESS} witnesses in pre-Electra numWiteness=${numWitness}`); + } + const nextSyncCommittee = await this.db.syncCommittee.get(syncCommitteeWitness.nextSyncCommitteeRoot); if (!nextSyncCommittee) { throw Error("nextSyncCommittee not available"); @@ -642,7 +652,6 @@ export class LightClientServer { finalityBranch = attestedData.finalityBranch; finalizedHeader = finalizedHeaderAttested; // Fork of LightClientUpdate is based off on attested header's fork - const attestedFork = this.config.getForkName(attestedHeader.beacon.slot); if (this.config.getForkName(finalizedHeader.beacon.slot) !== attestedFork) { finalizedHeader = upgradeLightClientHeader(this.config, attestedFork, finalizedHeader); } diff --git a/packages/beacon-node/src/chain/lightClient/types.ts b/packages/beacon-node/src/chain/lightClient/types.ts index b253c05d45fb..00a819f30c15 100644 --- a/packages/beacon-node/src/chain/lightClient/types.ts +++ b/packages/beacon-node/src/chain/lightClient/types.ts @@ -26,7 +26,7 @@ * ``` */ export type SyncCommitteeWitness = { - /** Vector[Bytes32, 4] */ + /** Vector[Bytes32, 4] or Vector[Bytes32, 5] depends on the fork */ witness: Uint8Array[]; currentSyncCommitteeRoot: Uint8Array; nextSyncCommitteeRoot: Uint8Array; diff --git a/packages/beacon-node/src/db/repositories/lightclientSyncCommitteeWitness.ts b/packages/beacon-node/src/db/repositories/lightclientSyncCommitteeWitness.ts index 45f91f159997..e323c3e55f61 100644 --- a/packages/beacon-node/src/db/repositories/lightclientSyncCommitteeWitness.ts +++ b/packages/beacon-node/src/db/repositories/lightclientSyncCommitteeWitness.ts @@ -5,6 +5,15 @@ import {ssz} from "@lodestar/types"; import {SyncCommitteeWitness} from "../../chain/lightClient/types.js"; import {Bucket, getBucketNameByValue} from "../buckets.js"; +// We add a 1-byte prefix where 0 means pre-electra and 1 means post-electra +enum PrefixByte { + PRE_ELECTRA = 0, + POST_ELECTRA = 1, +} + +export const NUM_WITNESS = 4; +export const NUM_WITNESS_ELECTRA = 5; + /** * Historical sync committees witness by block root * @@ -13,12 +22,56 @@ import {Bucket, getBucketNameByValue} from "../buckets.js"; export class SyncCommitteeWitnessRepository extends Repository { constructor(config: ChainForkConfig, db: DatabaseController) { const bucket = Bucket.lightClient_syncCommitteeWitness; + // Pick some type but won't be used. Witness can be 4 or 5 so need to handle dynamically const type = new ContainerType({ - witness: new VectorCompositeType(ssz.Root, 4), + witness: new VectorCompositeType(ssz.Root, NUM_WITNESS), currentSyncCommitteeRoot: ssz.Root, nextSyncCommitteeRoot: ssz.Root, }); super(config, db, bucket, type, getBucketNameByValue(bucket)); } + + // Overrides for multi-fork + encodeValue(value: SyncCommitteeWitness): Uint8Array { + const numWitness = value.witness.length; + + if (numWitness !== NUM_WITNESS && numWitness !== NUM_WITNESS_ELECTRA) { + throw Error(`Number of witness can only be 4 pre-electra or 5 post-electra numWitness=${numWitness}`); + } + + const type = new ContainerType({ + witness: new VectorCompositeType(ssz.Root, numWitness), + currentSyncCommitteeRoot: ssz.Root, + nextSyncCommitteeRoot: ssz.Root, + }); + + const valueBytes = type.serialize(value); + + // We need to differentiate between post-electra and pre-electra witness + // such that we can deserialize correctly + const isPostElectra = numWitness === NUM_WITNESS_ELECTRA; + const prefixByte = new Uint8Array(1); + prefixByte[0] = isPostElectra ? PrefixByte.POST_ELECTRA : PrefixByte.PRE_ELECTRA; + + const prefixedData = new Uint8Array(1 + valueBytes.length); + prefixedData.set(prefixByte, 0); + prefixedData.set(valueBytes, 1); + + return prefixedData; + } + + decodeValue(data: Uint8Array): SyncCommitteeWitness { + // First byte is written + const prefix = data.subarray(0, 1); + const isPostElectra = prefix[0] === PrefixByte.POST_ELECTRA; + + const type = new ContainerType({ + witness: new VectorCompositeType(ssz.Root, isPostElectra ? NUM_WITNESS_ELECTRA : NUM_WITNESS), + currentSyncCommitteeRoot: ssz.Root, + nextSyncCommitteeRoot: ssz.Root, + }); + + return type.deserialize(data.subarray(1)); + } } From 2d88eb8c1b02d0433938e6f4296e48dd8e5656d1 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:56:41 -0700 Subject: [PATCH 3/5] Fix finality branch --- packages/beacon-node/src/chain/lightClient/index.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/beacon-node/src/chain/lightClient/index.ts b/packages/beacon-node/src/chain/lightClient/index.ts index d3a620008c11..9eb1ea3161ef 100644 --- a/packages/beacon-node/src/chain/lightClient/index.ts +++ b/packages/beacon-node/src/chain/lightClient/index.ts @@ -3,6 +3,7 @@ import { altair, BeaconBlock, BeaconBlockBody, + electra, LightClientBootstrap, LightClientFinalityUpdate, LightClientHeader, @@ -42,6 +43,7 @@ import { ForkLightClient, highestFork, forkLightClient, + isForkPostElectra, } from "@lodestar/params"; import {IBeaconDb} from "../../db/index.js"; @@ -57,6 +59,7 @@ import { getCurrentSyncCommitteeBranch, getBlockBodyExecutionHeaderProof, } from "./proofs.js"; +import {NUM_WITNESS, NUM_WITNESS_ELECTRA} from "../../db/repositories/lightclientSyncCommitteeWitness.js"; export type LightClientServerOpts = { disableLightClientServerOnImportBlockHead?: boolean; @@ -208,7 +211,9 @@ export class LightClientServer { private checkpointHeaders = new Map(); private latestHeadUpdate: LightClientOptimisticUpdate | null = null; - private readonly zero: Pick; + private readonly zero: + | Pick + | Pick; private finalized: LightClientFinalityUpdate | null = null; constructor( @@ -225,7 +230,9 @@ export class LightClientServer { this.zero = { // Assign the hightest fork's default value because it can always be typecasted down to correct fork finalizedHeader: sszTypesFor(highestFork(forkLightClient)).LightClientHeader.defaultValue(), - finalityBranch: ssz.altair.LightClientUpdate.fields.finalityBranch.defaultValue(), + // Electra finalityBranch has fixed length of 5 whereas altair has 4. The fifth element will be ignored + // when serializing as altair LightClientUpdate + finalityBranch: ssz.electra.LightClientUpdate.fields.finalityBranch.defaultValue(), }; if (metrics) { From e2ea96dbbe2fb48eb01ed5e7ab038b5bd754e10f Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:19:47 -0700 Subject: [PATCH 4/5] Update unit test --- .../src/chain/lightClient/proofs.ts | 2 +- .../test/unit/chain/lightclient/proof.test.ts | 105 +++++++++++++++--- 2 files changed, 90 insertions(+), 17 deletions(-) diff --git a/packages/beacon-node/src/chain/lightClient/proofs.ts b/packages/beacon-node/src/chain/lightClient/proofs.ts index 5c01ae3059f2..cc4cb247b9dd 100644 --- a/packages/beacon-node/src/chain/lightClient/proofs.ts +++ b/packages/beacon-node/src/chain/lightClient/proofs.ts @@ -34,7 +34,7 @@ export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllFo n21.left.root, // 42 n10.left.root, // 20 n5.right.root, // 11 - n2.right.root, // 4 + n2.left.root, // 4 n1.right.root, // 3 ]; } else { diff --git a/packages/beacon-node/test/unit/chain/lightclient/proof.test.ts b/packages/beacon-node/test/unit/chain/lightclient/proof.test.ts index 38f4ab6aea39..ea8438ea84b9 100644 --- a/packages/beacon-node/test/unit/chain/lightclient/proof.test.ts +++ b/packages/beacon-node/test/unit/chain/lightclient/proof.test.ts @@ -1,73 +1,146 @@ import {describe, it, expect, beforeAll} from "vitest"; -import {BeaconStateAltair} from "@lodestar/state-transition"; +import {BeaconStateAltair, BeaconStateElectra} from "@lodestar/state-transition"; import {ForkName, SYNC_COMMITTEE_SIZE} from "@lodestar/params"; import {altair, ssz} from "@lodestar/types"; import {verifyMerkleBranch, hash} from "@lodestar/utils"; import {getNextSyncCommitteeBranch, getSyncCommitteesWitness} from "../../../../src/chain/lightClient/proofs.js"; +import {NUM_WITNESS, NUM_WITNESS_ELECTRA} from "../../../../src/db/repositories/lightclientSyncCommitteeWitness.js"; const currentSyncCommitteeGindex = 54; const nextSyncCommitteeGindex = 55; const syncCommitteesGindex = 27; +const currentSyncCommitteeGindexElectra = 86; +const nextSyncCommitteeGindexElectra = 87; +const syncCommitteesGindexElectra = 43; describe("chain / lightclient / proof", () => { - let state: BeaconStateAltair; - let stateRoot: Uint8Array; + let stateAltair: BeaconStateAltair; + let stateElectra: BeaconStateElectra; + let stateRootAltair: Uint8Array; + let stateRootElectra: Uint8Array; const currentSyncCommittee = fillSyncCommittee(Buffer.alloc(48, 0xbb)); const nextSyncCommittee = fillSyncCommittee(Buffer.alloc(48, 0xcc)); beforeAll(() => { - state = ssz.altair.BeaconState.defaultViewDU(); - state.currentSyncCommittee = ssz.altair.SyncCommittee.toViewDU(currentSyncCommittee); - state.nextSyncCommittee = ssz.altair.SyncCommittee.toViewDU(nextSyncCommittee); + stateAltair = ssz.altair.BeaconState.defaultViewDU(); + stateAltair.currentSyncCommittee = ssz.altair.SyncCommittee.toViewDU(currentSyncCommittee); + stateAltair.nextSyncCommittee = ssz.altair.SyncCommittee.toViewDU(nextSyncCommittee); // Note: .hashTreeRoot() automatically commits() - stateRoot = state.hashTreeRoot(); + stateRootAltair = stateAltair.hashTreeRoot(); + + stateElectra = ssz.electra.BeaconState.defaultViewDU(); + stateElectra.currentSyncCommittee = ssz.altair.SyncCommittee.toViewDU(currentSyncCommittee); + stateElectra.nextSyncCommittee = ssz.altair.SyncCommittee.toViewDU(nextSyncCommittee); + stateRootElectra = stateElectra.hashTreeRoot(); }); - it("SyncCommittees proof", () => { - const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.altair, state); + it("SyncCommittees proof altair", () => { + const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.altair, stateAltair); const syncCommitteesLeaf = hash( syncCommitteesWitness.currentSyncCommitteeRoot, syncCommitteesWitness.nextSyncCommitteeRoot ); + expect(syncCommitteesWitness.witness.length).toBe(NUM_WITNESS); expect( verifyMerkleBranch( syncCommitteesLeaf, syncCommitteesWitness.witness, ...fromGindex(syncCommitteesGindex), - stateRoot + stateRootAltair ) ).toBe(true); }); - it("currentSyncCommittee proof", () => { - const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.altair, state); + it("currentSyncCommittee proof altair", () => { + const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.altair, stateAltair); const currentSyncCommitteeBranch = [syncCommitteesWitness.nextSyncCommitteeRoot, ...syncCommitteesWitness.witness]; + expect(syncCommitteesWitness.witness.length).toBe(NUM_WITNESS); expect( verifyMerkleBranch( ssz.altair.SyncCommittee.hashTreeRoot(currentSyncCommittee), currentSyncCommitteeBranch, ...fromGindex(currentSyncCommitteeGindex), - stateRoot + stateRootAltair ) ).toBe(true); }); - it("nextSyncCommittee proof", () => { - const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.altair, state); + it("nextSyncCommittee proof altair", () => { + const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.altair, stateAltair); const nextSyncCommitteeBranch = getNextSyncCommitteeBranch(syncCommitteesWitness); + expect(syncCommitteesWitness.witness.length).toBe(NUM_WITNESS); expect( verifyMerkleBranch( ssz.altair.SyncCommittee.hashTreeRoot(nextSyncCommittee), nextSyncCommitteeBranch, ...fromGindex(nextSyncCommitteeGindex), - stateRoot + stateRootAltair + ) + ).toBe(true); + }); + + it("SyncCommittees proof electra", () => { + const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.electra, stateElectra); + const syncCommitteesLeaf = hash( + syncCommitteesWitness.currentSyncCommitteeRoot, + syncCommitteesWitness.nextSyncCommitteeRoot + ); + + expect(syncCommitteesWitness.witness.length).toBe(NUM_WITNESS_ELECTRA); + expect( + verifyMerkleBranch( + syncCommitteesLeaf, + syncCommitteesWitness.witness, + ...fromGindex(syncCommitteesGindexElectra), + stateRootElectra + ) + ).toBe(true); + }); + + it("currentSyncCommittee proof electra", () => { + const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.electra, stateElectra); + const currentSyncCommitteeBranch = [syncCommitteesWitness.nextSyncCommitteeRoot, ...syncCommitteesWitness.witness]; + + expect(syncCommitteesWitness.witness.length).toBe(NUM_WITNESS_ELECTRA); + expect( + verifyMerkleBranch( + ssz.altair.SyncCommittee.hashTreeRoot(currentSyncCommittee), + currentSyncCommitteeBranch, + ...fromGindex(currentSyncCommitteeGindexElectra), + stateRootElectra + ) + ).toBe(true); + }); + + it("nextSyncCommittee proof electra", () => { + const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.electra, stateElectra); + const nextSyncCommitteeBranch = getNextSyncCommitteeBranch(syncCommitteesWitness); + + expect( + verifyMerkleBranch( + ssz.altair.SyncCommittee.hashTreeRoot(nextSyncCommittee), + nextSyncCommitteeBranch, + ...fromGindex(nextSyncCommitteeGindexElectra), + stateRootElectra ) ).toBe(true); }); + + it("getSyncCommitteesWitness returns correct number of witness altair", () => { + const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.altair, stateAltair); + + expect(syncCommitteesWitness.witness.length).toBe(NUM_WITNESS); + }); + + it("getSyncCommitteesWitness returns correct number of witness electra", () => { + const syncCommitteesWitness = getSyncCommitteesWitness(ForkName.electra, stateElectra); + + expect(syncCommitteesWitness.witness.length).toBe(NUM_WITNESS_ELECTRA); + }); }); function fillSyncCommittee(pubkey: Uint8Array): altair.SyncCommittee { From 547239b5401dac7d0ca20e5de9fb353a18701604 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:45:22 -0700 Subject: [PATCH 5/5] fix e2e --- packages/light-client/src/spec/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/light-client/src/spec/index.ts b/packages/light-client/src/spec/index.ts index 4fd45f0f2e7d..11ce1f8b3f29 100644 --- a/packages/light-client/src/spec/index.ts +++ b/packages/light-client/src/spec/index.ts @@ -39,7 +39,7 @@ export class LightclientSpec { nextSyncCommittee: ZERO_SYNC_COMMITTEE, nextSyncCommitteeBranch: getZeroSyncCommitteeBranch(this.config.getForkName(finalityUpdate.signatureSlot)), finalizedHeader: finalityUpdate.finalizedHeader, - finalityBranch: getZeroFinalityBranch(this.config.getForkName(finalityUpdate.signatureSlot)), + finalityBranch: finalityUpdate.finalityBranch, syncAggregate: finalityUpdate.syncAggregate, signatureSlot: finalityUpdate.signatureSlot, });