Skip to content

Commit

Permalink
Add LC header validations (#5246)
Browse files Browse the repository at this point in the history
  • Loading branch information
g11tech authored Mar 9, 2023
1 parent eb4b43d commit 4d89a0e
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 7 deletions.
2 changes: 1 addition & 1 deletion packages/light-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export class Lightclient {
// Fetch bootstrap state with proof at the trusted block root
const {data: bootstrap} = await transport.getBootstrap(toHexString(checkpointRoot));

validateLightClientBootstrap(checkpointRoot, bootstrap);
validateLightClientBootstrap(args.config, checkpointRoot, bootstrap);

return new Lightclient({...args, bootstrap});
}
Expand Down
4 changes: 3 additions & 1 deletion packages/light-client/src/spec/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@ export {upgradeLightClientHeader} from "./utils.js";

export class LightclientSpec {
readonly store: ILightClientStore;
readonly config: BeaconConfig;

constructor(
config: BeaconConfig,
private readonly opts: ProcessUpdateOpts & LightClientStoreEvents,
bootstrap: allForks.LightClientBootstrap
) {
this.store = new LightClientStore(config, bootstrap, opts);
this.config = config;
}

onUpdate(currentSlot: Slot, update: allForks.LightClientUpdate): void {
processLightClientUpdate(this.store, currentSlot, this.opts, update);
processLightClientUpdate(this.config, this.store, currentSlot, this.opts, update);
}

onFinalityUpdate(currentSlot: Slot, finalityUpdate: allForks.LightClientFinalityUpdate): void {
Expand Down
4 changes: 3 additions & 1 deletion packages/light-client/src/spec/processLightClientUpdate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {SYNC_COMMITTEE_SIZE} from "@lodestar/params";
import {Slot, SyncPeriod, allForks} from "@lodestar/types";
import {ChainForkConfig} from "@lodestar/config";
import {pruneSetToMax} from "@lodestar/utils";
import {computeSyncPeriodAtSlot, deserializeSyncCommittee, sumBits} from "../utils/index.js";
import {isBetterUpdate, LightClientUpdateSummary, toLightClientUpdateSummary} from "./isBetterUpdate.js";
Expand All @@ -13,6 +14,7 @@ export interface ProcessUpdateOpts {
}

export function processLightClientUpdate(
config: ChainForkConfig,
store: ILightClientStore,
currentSlot: Slot,
opts: ProcessUpdateOpts,
Expand All @@ -27,7 +29,7 @@ export function processLightClientUpdate(
// Note: store.getSyncCommitteeAtPeriod() may advance store
const syncCommittee = getSyncCommitteeAtPeriod(store, updateSignaturePeriod, opts);

validateLightClientUpdate(store, update, syncCommittee);
validateLightClientUpdate(config, store, update, syncCommittee);

// Track the maximum number of active participants in the committee signatures
const syncCommitteeTrueBits = sumBits(update.syncAggregate.syncCommitteeBits);
Expand Down
51 changes: 50 additions & 1 deletion packages/light-client/src/spec/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import {BitArray, byteArrayEquals} from "@chainsafe/ssz";
import {FINALIZED_ROOT_DEPTH, NEXT_SYNC_COMMITTEE_DEPTH, ForkSeq, ForkName} from "@lodestar/params";

import {
FINALIZED_ROOT_DEPTH,
NEXT_SYNC_COMMITTEE_DEPTH,
ForkSeq,
ForkName,
BLOCK_BODY_EXECUTION_PAYLOAD_DEPTH as EXECUTION_PAYLOAD_DEPTH,
BLOCK_BODY_EXECUTION_PAYLOAD_INDEX as EXECUTION_PAYLOAD_INDEX,
} from "@lodestar/params";
import {altair, phase0, ssz, allForks, capella, deneb} from "@lodestar/types";
import {ChainForkConfig} from "@lodestar/config";
import {computeEpochAtSlot} from "@lodestar/state-transition";

import {isValidMerkleBranch} from "../utils/verifyMerkleBranch.js";

export const GENESIS_SLOT = 0;
export const ZERO_HASH = new Uint8Array(32);
Expand Down Expand Up @@ -91,3 +102,41 @@ export function upgradeLightClientHeader(
}
return upgradedHeader;
}

export function isValidLightClientHeader(config: ChainForkConfig, header: allForks.LightClientHeader): boolean {
const epoch = computeEpochAtSlot(header.beacon.slot);

if (epoch < config.CAPELLA_FORK_EPOCH) {
return (
((header as capella.LightClientHeader).execution === undefined ||
ssz.capella.ExecutionPayloadHeader.equals(
(header as capella.LightClientHeader).execution,
ssz.capella.LightClientHeader.fields.execution.defaultValue()
)) &&
((header as capella.LightClientHeader).executionBranch === undefined ||
ssz.capella.LightClientHeader.fields.executionBranch.equals(
ssz.capella.LightClientHeader.fields.executionBranch.defaultValue(),
(header as capella.LightClientHeader).executionBranch
))
);
}

if (epoch < config.EIP4844_FORK_EPOCH) {
if (
(header as deneb.LightClientHeader).execution.excessDataGas &&
(header as deneb.LightClientHeader).execution.excessDataGas !== BigInt(0)
) {
return false;
}
}

return isValidMerkleBranch(
config
.getExecutionForkTypes(header.beacon.slot)
.ExecutionPayloadHeader.hashTreeRoot((header as capella.LightClientHeader).execution),
(header as capella.LightClientHeader).executionBranch,
EXECUTION_PAYLOAD_DEPTH,
EXECUTION_PAYLOAD_INDEX,
header.beacon.bodyRoot
);
}
13 changes: 12 additions & 1 deletion packages/light-client/src/spec/validateLightClientBootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import {byteArrayEquals} from "@chainsafe/ssz";
import {Root, ssz, allForks} from "@lodestar/types";
import {ChainForkConfig} from "@lodestar/config";
import {toHex} from "@lodestar/utils";
import {isValidMerkleBranch} from "../utils/verifyMerkleBranch.js";
import {isValidLightClientHeader} from "./utils.js";

const CURRENT_SYNC_COMMITTEE_INDEX = 22;
const CURRENT_SYNC_COMMITTEE_DEPTH = 5;

export function validateLightClientBootstrap(trustedBlockRoot: Root, bootstrap: allForks.LightClientBootstrap): void {
export function validateLightClientBootstrap(
config: ChainForkConfig,
trustedBlockRoot: Root,
bootstrap: allForks.LightClientBootstrap
): void {
const headerRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(bootstrap.header.beacon);

if (!isValidLightClientHeader(config, bootstrap.header)) {
throw Error("Bootstrap Header is not Valid Light Client Header");
}

if (!byteArrayEquals(headerRoot, trustedBlockRoot)) {
throw Error(`bootstrap header root ${toHex(headerRoot)} != trusted root ${toHex(trustedBlockRoot)}`);
}
Expand Down
21 changes: 19 additions & 2 deletions packages/light-client/src/spec/validateLightClientUpdate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Root, ssz, allForks} from "@lodestar/types";
import {ChainForkConfig} from "@lodestar/config";
import bls from "@chainsafe/bls/switchable";
import type {PublicKey, Signature} from "@chainsafe/bls/types";
import {
Expand All @@ -13,10 +14,18 @@ import {
import {getParticipantPubkeys, sumBits} from "../utils/utils.js";
import {isValidMerkleBranch} from "../utils/index.js";
import {SyncCommitteeFast} from "../types.js";
import {isFinalityUpdate, isSyncCommitteeUpdate, isZeroedHeader, isZeroedSyncCommittee, ZERO_HASH} from "./utils.js";
import {
isFinalityUpdate,
isSyncCommitteeUpdate,
isZeroedHeader,
isZeroedSyncCommittee,
ZERO_HASH,
isValidLightClientHeader,
} from "./utils.js";
import {ILightClientStore} from "./store.js";

export function validateLightClientUpdate(
config: ChainForkConfig,
store: ILightClientStore,
update: allForks.LightClientUpdate,
syncCommittee: SyncCommitteeFast
Expand All @@ -26,6 +35,10 @@ export function validateLightClientUpdate(
throw Error("Sync committee has not sufficient participants");
}

if (!isValidLightClientHeader(config, update.attestedHeader)) {
throw Error("Attested Header is not Valid Light Client Header");
}

// Sanity check that slots are in correct order
if (update.signatureSlot <= update.attestedHeader.beacon.slot) {
throw Error(
Expand All @@ -48,12 +61,16 @@ export function validateLightClientUpdate(
} else {
let finalizedRoot: Root;

if (update.finalizedHeader.beacon.slot == GENESIS_SLOT) {
if (update.finalizedHeader.beacon.slot === GENESIS_SLOT) {
if (!isZeroedHeader(update.finalizedHeader.beacon)) {
throw Error("finalizedHeader must be zero for not finality update");
}
finalizedRoot = ZERO_HASH;
} else {
if (!isValidLightClientHeader(config, update.finalizedHeader)) {
throw Error("Finalized Header is not valid Light Client Header");
}

finalizedRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(update.finalizedHeader.beacon);
}

Expand Down
97 changes: 97 additions & 0 deletions packages/light-client/test/unit/isValidLightClientHeader.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {expect} from "chai";
import {ssz, allForks} from "@lodestar/types";
import {createBeaconConfig, createChainForkConfig, defaultChainConfig} from "@lodestar/config";
import {fromHexString} from "@chainsafe/ssz";
import {isValidLightClientHeader} from "../../src/spec/utils.js";

describe("isValidLightClientHeader", function () {
/* eslint-disable @typescript-eslint/naming-convention */
const chainConfig = createChainForkConfig({
...defaultChainConfig,
ALTAIR_FORK_EPOCH: 0,
BELLATRIX_FORK_EPOCH: 0,
CAPELLA_FORK_EPOCH: 1,
EIP4844_FORK_EPOCH: Infinity,
});

const genesisValidatorsRoot = Buffer.alloc(32, 0xaa);
const config = createBeaconConfig(chainConfig, genesisValidatorsRoot);

const altairLCHeader = {
beacon: {
slot: 5,
proposerIndex: 29852,
parentRoot: fromHexString("0x2490c4e438b6c1476c4a666011955f6a239b82e7c31af452ee72e263e8a82cef"),
stateRoot: fromHexString("0x95f9a788a5ebed7275dc809978064ddf62a57864a0c48b10aa87e9ebec87b6c5"),
bodyRoot: fromHexString("0x48a2ebb21de0cf70f599d4c0bcdb2e4ca791d5e9b396cbebfe50ce3295396041"),
},
};

const altairUpgradedCapellaLCHeader = {
beacon: altairLCHeader.beacon,
execution: ssz.capella.LightClientHeader.fields.execution.defaultValue(),
executionBranch: ssz.capella.LightClientHeader.fields.executionBranch.defaultValue(),
};

const altairUpgradedDenebLCHeader = {
beacon: altairLCHeader.beacon,
execution: ssz.deneb.LightClientHeader.fields.execution.defaultValue(),
executionBranch: ssz.deneb.LightClientHeader.fields.executionBranch.defaultValue(),
};

const capellaLCHeader = {
beacon: {
slot: 100936,
proposerIndex: 29852,
parentRoot: fromHexString("0x2490c4e438b6c1476c4a666011955f6a239b82e7c31af452ee72e263e8a82cef"),
stateRoot: fromHexString("0x95f9a788a5ebed7275dc809978064ddf62a57864a0c48b10aa87e9ebec87b6c5"),
bodyRoot: fromHexString("0x48a2ebb21de0cf70f599d4c0bcdb2e4ca791d5e9b396cbebfe50ce3295396041"),
},
execution: {
parentHash: fromHexString("0x8dbfa7d03da88416dabda95cf83e3d2c7bbc820bfbe2a685a2a629eda54b2320"),
feeRecipient: fromHexString("0xf97e180c050e5ab072211ad2c213eb5aee4df134"),
stateRoot: fromHexString("0x51a4df7228204e2d2ba15c8664c16b7abd41480e8087727a96d3b84e04f661d8"),
receiptsRoot: fromHexString("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"),
logsBloom: fromHexString(
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
),
prevRandao: fromHexString("0x7408cf3778b7d879b3317160858c3ef2818d2cad53dbf3a638af598c351be46b"),
blockNumber: 96392,
gasLimit: 30000000,
gasUsed: 0,
timestamp: 1676474832,
extraData: fromHexString("0x"),
baseFeePerGas: BigInt(7),
blockHash: fromHexString("0x5570e6072f4e469f256fd2c2d0ee7ddce2da8cce8b0ac1b36495e5aa25987e7b"),
transactionsRoot: fromHexString("0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1"),
withdrawalsRoot: fromHexString("0x21bb571478b90df5866e87aa358f5a5e93682db3ba242baf2bdf127f2a9a54ce"),
},
executionBranch: [
fromHexString("0xbc9397945c24273581f86275332d37761dff3b9fdaaddee03749bcee644213a8"),
fromHexString("0x336488033fe5f3ef4ccc12af07b9370b92e553e35ecb4a337a1b1c0e4afe1e0e"),
fromHexString("0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71"),
fromHexString("0x8bbebf0f8b663cd4a56f3739019e52507ba6c6b99ad530ab36925c12d84b7b5e"),
],
};

const capellaUpgradedDenebHeader = {
beacon: capellaLCHeader.beacon,
execution: {...capellaLCHeader.execution, excessDataGas: 0},
executionBranch: capellaLCHeader.executionBranch,
};

const testCases: [string, allForks.LightClientHeader][] = [
["altair LC header", altairLCHeader],
["altair upgraded to capella", altairUpgradedCapellaLCHeader],
["altair upgraded to deneb", altairUpgradedDenebLCHeader],
["capella LC header", capellaLCHeader],
["capella upgraded to deneb LC header", capellaUpgradedDenebHeader],
];

testCases.forEach(([name, header]: [string, allForks.LightClientHeader]) => {
it(name, function () {
const isValid = isValidLightClientHeader(config, header);
expect(isValid).to.be.true;
});
});
});

0 comments on commit 4d89a0e

Please sign in to comment.