Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ export function verifyExecutionPayloadEnvelope(
}

// Verify consistency with expected withdrawals
const payloadWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(payload.withdrawals);
const expectedWithdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(state.payloadExpectedWithdrawals);
const payloadWithdrawalsRoot = ssz.gloas.Withdrawals.hashTreeRoot(payload.withdrawals);
const expectedWithdrawalsRoot = ssz.gloas.Withdrawals.hashTreeRoot(state.payloadExpectedWithdrawals);
if (!byteArrayEquals(payloadWithdrawalsRoot, expectedWithdrawalsRoot)) {
throw new Error(
`Withdrawals mismatch between payload and expected payload=${toRootHex(payloadWithdrawalsRoot)} expected=${toRootHex(expectedWithdrawalsRoot)}`
Expand Down
3 changes: 3 additions & 0 deletions packages/beacon-node/src/chain/errors/blockError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ export enum BlockErrorCode {
TOO_MANY_KZG_COMMITMENTS = "BLOCK_ERROR_TOO_MANY_KZG_COMMITMENTS",
/** Bid parent block root does not match block parent root */
BID_PARENT_ROOT_MISMATCH = "BLOCK_ERROR_BID_PARENT_ROOT_MISMATCH",
/** A block body operation or parent execution request list exceeds its per-block limit */
TOO_MANY_BLOCK_OPERATIONS = "BLOCK_ERROR_TOO_MANY_BLOCK_OPERATIONS",
/** The parent block's execution payload has been verified as invalid */
PARENT_EXECUTION_INVALID = "BLOCK_ERROR_PARENT_EXECUTION_INVALID",
/** The block's parent execution payload (defined by bid.parent_block_hash) has not been seen */
Expand Down Expand Up @@ -118,6 +120,7 @@ export type BlockErrorType =
| {code: BlockErrorCode.DATA_UNAVAILABLE}
| {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number}
| {code: BlockErrorCode.BID_PARENT_ROOT_MISMATCH; bidParentRoot: RootHex; blockParentRoot: RootHex}
| {code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS; name: string; count: number; limit: number}
| {code: BlockErrorCode.PARENT_EXECUTION_INVALID; parentRoot: RootHex}
| {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentRoot: RootHex; parentBlockHash: RootHex};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export enum ExecutionPayloadEnvelopeErrorCode {
BUILDER_INDEX_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_BUILDER_INDEX_MISMATCH",
BLOCK_HASH_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_BLOCK_HASH_MISMATCH",
EXECUTION_REQUESTS_ROOT_MISMATCH = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_EXECUTION_REQUESTS_ROOT_MISMATCH",
EXECUTION_REQUESTS_COUNT_EXCEEDED = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_EXECUTION_REQUESTS_COUNT_EXCEEDED",
WITHDRAWALS_COUNT_EXCEEDED = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_WITHDRAWALS_COUNT_EXCEEDED",
INVALID_SIGNATURE = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_INVALID_SIGNATURE",
PAYLOAD_ENVELOPE_INPUT_MISSING = "EXECUTION_PAYLOAD_ENVELOPE_ERROR_PAYLOAD_ENVELOPE_INPUT_MISSING",
}
Expand Down Expand Up @@ -42,6 +44,13 @@ export type ExecutionPayloadEnvelopeErrorType =
envelopeRequestsRoot: RootHex;
bidRequestsRoot: RootHex;
}
| {
code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_COUNT_EXCEEDED;
name: string;
count: number;
limit: number;
}
| {code: ExecutionPayloadEnvelopeErrorCode.WITHDRAWALS_COUNT_EXCEEDED; count: number; limit: number}
| {code: ExecutionPayloadEnvelopeErrorCode.INVALID_SIGNATURE}
| {code: ExecutionPayloadEnvelopeErrorCode.PAYLOAD_ENVELOPE_INPUT_MISSING; blockRoot: RootHex};

Expand Down
19 changes: 12 additions & 7 deletions packages/beacon-node/src/chain/lightClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import {
MIN_SYNC_COMMITTEE_PARTICIPANTS,
SLOTS_PER_EPOCH,
SYNC_COMMITTEE_SIZE,
forkPostAltair,
highestFork,
isForkPostElectra,
isForkPostGloas,
} from "@lodestar/params";
import {
type IBeaconStateViewAltair,
Expand Down Expand Up @@ -44,7 +43,6 @@ import {
electra,
phase0,
ssz,
sszTypesFor,
} from "@lodestar/types";
import {Logger, MapDef, byteArrayEquals, pruneSetToMax, toRootHex} from "@lodestar/utils";
import {ZERO_HASH} from "../../constants/index.js";
Expand Down Expand Up @@ -230,8 +228,8 @@ export class LightClientServer {
this.signal = signal;

this.zero = {
// Assign the hightest fork's default value because it can always be typecasted down to correct fork
finalizedHeader: sszTypesFor(highestFork(forkPostAltair)).LightClientHeader.defaultValue(),
// Assign the highest pre-Gloas light-client header because post-Gloas light-client updates are skipped for now.
finalizedHeader: ssz.electra.LightClientHeader.defaultValue(),
Comment thread
ensi321 marked this conversation as resolved.
// 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(),
Expand Down Expand Up @@ -658,10 +656,17 @@ export class LightClientServer {

const attestedFork = this.config.getForkName(attestedHeader.beacon.slot);
const numWitness = syncCommitteeWitness.witness.length;
if (isForkPostElectra(attestedFork) && numWitness !== NUM_WITNESS_ELECTRA) {
if (
isForkPostGloas(attestedFork) &&
(syncCommitteeWitness.currentSyncCommitteeBranch === undefined ||
syncCommitteeWitness.nextSyncCommitteeBranch === undefined)
) {
throw Error("Expected post-Gloas sync committee branches");
}
if (!isForkPostGloas(attestedFork) && isForkPostElectra(attestedFork) && numWitness !== NUM_WITNESS_ELECTRA) {
throw Error(`Expected ${NUM_WITNESS_ELECTRA} witnesses in post-Electra numWitness=${numWitness}`);
}
if (!isForkPostElectra(attestedFork) && numWitness !== NUM_WITNESS) {
if (!isForkPostGloas(attestedFork) && !isForkPostElectra(attestedFork) && numWitness !== NUM_WITNESS) {
throw Error(`Expected ${NUM_WITNESS} witnesses in pre-Electra numWitness=${numWitness}`);
}
Comment thread
ensi321 marked this conversation as resolved.

Expand Down
37 changes: 36 additions & 1 deletion packages/beacon-node/src/chain/lightClient/proofs.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import {Tree} from "@chainsafe/persistent-merkle-tree";
import {
BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX,
CURRENT_SYNC_COMMITTEE_GINDEX_GLOAS,
FINALIZED_ROOT_GINDEX,
FINALIZED_ROOT_GINDEX_ELECTRA,
FINALIZED_ROOT_GINDEX_GLOAS,
ForkName,
ForkPostBellatrix,
NEXT_SYNC_COMMITTEE_GINDEX_GLOAS,
isForkPostElectra,
isForkPostGloas,
} from "@lodestar/params";
import {BeaconStateAllForks, CachedBeaconStateAllForks} from "@lodestar/state-transition";
import {BeaconBlockBody, SSZTypesFor, ssz} from "@lodestar/types";
Expand All @@ -17,6 +21,24 @@ export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllFo
let currentSyncCommitteeRoot: Uint8Array;
let nextSyncCommitteeRoot: Uint8Array;

if (isForkPostGloas(fork)) {
const tree = new Tree(state.node);
const currentSyncCommitteeGindex = BigInt(CURRENT_SYNC_COMMITTEE_GINDEX_GLOAS);
const nextSyncCommitteeGindex = BigInt(NEXT_SYNC_COMMITTEE_GINDEX_GLOAS);

currentSyncCommitteeRoot = tree.getRoot(currentSyncCommitteeGindex);
nextSyncCommitteeRoot = tree.getRoot(nextSyncCommitteeGindex);
witness = [];

return {
witness,
currentSyncCommitteeRoot,
nextSyncCommitteeRoot,
currentSyncCommitteeBranch: tree.getSingleProof(currentSyncCommitteeGindex),
nextSyncCommitteeBranch: tree.getSingleProof(nextSyncCommitteeGindex),
};
}

if (isForkPostElectra(fork)) {
const n2 = n1.left;
const n5 = n2.right;
Expand Down Expand Up @@ -60,17 +82,30 @@ export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllFo
}

export function getNextSyncCommitteeBranch(syncCommitteesWitness: SyncCommitteeWitness): Uint8Array[] {
if (syncCommitteesWitness.nextSyncCommitteeBranch) {
return syncCommitteesWitness.nextSyncCommitteeBranch;
}

// Witness branch is sorted by descending gindex
return [syncCommitteesWitness.currentSyncCommitteeRoot, ...syncCommitteesWitness.witness];
}

export function getCurrentSyncCommitteeBranch(syncCommitteesWitness: SyncCommitteeWitness): Uint8Array[] {
if (syncCommitteesWitness.currentSyncCommitteeBranch) {
return syncCommitteesWitness.currentSyncCommitteeBranch;
}

// Witness branch is sorted by descending gindex
return [syncCommitteesWitness.nextSyncCommitteeRoot, ...syncCommitteesWitness.witness];
}

export function getFinalizedRootProof(state: CachedBeaconStateAllForks): Uint8Array[] {
const finalizedRootGindex = state.epochCtx.isPostElectra() ? FINALIZED_ROOT_GINDEX_ELECTRA : FINALIZED_ROOT_GINDEX;
const fork = state.config.getForkName(state.slot);
const finalizedRootGindex = isForkPostGloas(fork)
? FINALIZED_ROOT_GINDEX_GLOAS
: state.epochCtx.isPostElectra()
? FINALIZED_ROOT_GINDEX_ELECTRA
: FINALIZED_ROOT_GINDEX;
return new Tree(state.node).getSingleProof(BigInt(finalizedRootGindex));
}

Expand Down
4 changes: 3 additions & 1 deletion packages/beacon-node/src/chain/lightClient/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@
* ```
*/
export type SyncCommitteeWitness = {
/** Vector[Bytes32, 4] or Vector[Bytes32, 5] depending on the fork */
/** Shared witness for pre-Gloas forks where current and next sync committees are siblings. */
witness: Uint8Array[];
currentSyncCommitteeRoot: Uint8Array;
nextSyncCommitteeRoot: Uint8Array;
currentSyncCommitteeBranch?: Uint8Array[];
nextSyncCommitteeBranch?: Uint8Array[];
};
70 changes: 69 additions & 1 deletion packages/beacon-node/src/chain/validation/block.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import {ChainForkConfig} from "@lodestar/config";
import {ExecutionStatus} from "@lodestar/fork-choice";
import {ForkName, isForkPostBellatrix, isForkPostDeneb, isForkPostGloas} from "@lodestar/params";
import {
ForkName,
MAX_ATTESTATIONS_ELECTRA,
MAX_ATTESTER_SLASHINGS_ELECTRA,
MAX_BLS_TO_EXECUTION_CHANGES,
MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
MAX_DEPOSIT_REQUESTS_PER_PAYLOAD,
MAX_PAYLOAD_ATTESTATIONS,
MAX_PROPOSER_SLASHINGS,
MAX_VOLUNTARY_EXITS,
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
isForkPostBellatrix,
isForkPostDeneb,
isForkPostGloas,
} from "@lodestar/params";
import {
computeEpochAtSlot,
computeStartSlotAtEpoch,
Expand Down Expand Up @@ -164,6 +180,58 @@ export async function validateGossipBlock(
});
}

// [REJECT] The counts of `block.body.parent_execution_requests` are within
// their respective limits -- i.e. validate that
// `len(block.body.parent_execution_requests.deposits) <= MAX_DEPOSIT_REQUESTS_PER_PAYLOAD`,
// `len(block.body.parent_execution_requests.withdrawals) <= MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD`,
// `len(block.body.parent_execution_requests.consolidations) <= MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD`,
// `len(block.body.parent_execution_requests.builder_deposits) <= MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD`,
// and
// `len(block.body.parent_execution_requests.builder_exits) <= MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD`.
// [REJECT] The counts of the block body operations are within their respective
// limits -- i.e. validate that
// `len(block.body.proposer_slashings) <= MAX_PROPOSER_SLASHINGS`,
// `len(block.body.attester_slashings) <= MAX_ATTESTER_SLASHINGS_ELECTRA`,
// `len(block.body.attestations) <= MAX_ATTESTATIONS_ELECTRA`,
// `len(block.body.deposits) == 0`,
// `len(block.body.voluntary_exits) <= MAX_VOLUNTARY_EXITS`,
// `len(block.body.bls_to_execution_changes) <= MAX_BLS_TO_EXECUTION_CHANGES`,
// and `len(block.body.payload_attestations) <= MAX_PAYLOAD_ATTESTATIONS`.
const body = (block as gloas.BeaconBlock).body;
const requests = body.parentExecutionRequests;
const countLimits: [string, number, number][] = [
["parentExecutionRequests.deposits", requests.deposits.length, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD],
["parentExecutionRequests.withdrawals", requests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
[
"parentExecutionRequests.consolidations",
requests.consolidations.length,
MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
],
[
"parentExecutionRequests.builderDeposits",
requests.builderDeposits.length,
MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
],
["parentExecutionRequests.builderExits", requests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
["proposerSlashings", body.proposerSlashings.length, MAX_PROPOSER_SLASHINGS],
["attesterSlashings", body.attesterSlashings.length, MAX_ATTESTER_SLASHINGS_ELECTRA],
["attestations", body.attestations.length, MAX_ATTESTATIONS_ELECTRA],
["deposits", body.deposits.length, 0],
["voluntaryExits", body.voluntaryExits.length, MAX_VOLUNTARY_EXITS],
["blsToExecutionChanges", body.blsToExecutionChanges.length, MAX_BLS_TO_EXECUTION_CHANGES],
["payloadAttestations", body.payloadAttestations.length, MAX_PAYLOAD_ATTESTATIONS],
];
for (const [name, count, limit] of countLimits) {
if (count > limit) {
throw new BlockGossipError(GossipAction.REJECT, {
code: BlockErrorCode.TOO_MANY_BLOCK_OPERATIONS,
name,
count,
limit,
});
}
}

// TODO GLOAS: [REJECT] The block's execution payload parent (defined by bid.parent_block_hash) passes all validation
// This requires execution engine integration to verify the parent block hash
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import {PayloadStatus} from "@lodestar/fork-choice";
import {
MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
MAX_DEPOSIT_REQUESTS_PER_PAYLOAD,
MAX_WITHDRAWALS_PER_PAYLOAD,
MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
} from "@lodestar/params";
import {
computeStartSlotAtEpoch,
getExecutionPayloadEnvelopeSignatureSet,
Expand Down Expand Up @@ -115,6 +123,37 @@ async function validateExecutionPayloadEnvelope(
});
}

// [REJECT] The counts of `execution_requests` are within their respective limits.
// New in Gloas:EIP7688 — progressive lists are unbounded at the type level, so bounds
// are enforced here in gossip validation.
const {executionRequests} = envelope;
const requestCountLimits: [string, number, number][] = [
["deposits", executionRequests.deposits.length, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD],
["withdrawals", executionRequests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD],
["consolidations", executionRequests.consolidations.length, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD],
["builderDeposits", executionRequests.builderDeposits.length, MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD],
["builderExits", executionRequests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD],
];
for (const [name, count, limit] of requestCountLimits) {
if (count > limit) {
throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
code: ExecutionPayloadEnvelopeErrorCode.EXECUTION_REQUESTS_COUNT_EXCEEDED,
name,
count,
limit,
});
}
}

// [REJECT] The number of withdrawals is within the limit.
if (payload.withdrawals.length > MAX_WITHDRAWALS_PER_PAYLOAD) {
throw new ExecutionPayloadEnvelopeError(GossipAction.REJECT, {
code: ExecutionPayloadEnvelopeErrorCode.WITHDRAWALS_COUNT_EXCEEDED,
count: payload.withdrawals.length,
limit: MAX_WITHDRAWALS_PER_PAYLOAD,
});
}

// Get the block state to verify the builder's signature.
const blockState = await chain.regen
.getState(block.stateRoot, RegenCaller.validateGossipPayloadEnvelope)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {BeaconConfig} from "@lodestar/config";
import {DOMAIN_AGGREGATE_AND_PROOF, ForkSeq} from "@lodestar/params";
import {DOMAIN_AGGREGATE_AND_PROOF} from "@lodestar/params";
import {ISignatureSet, SignatureSetType, computeSigningRoot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
import {Epoch, SignedAggregateAndProof, ValidatorIndex, ssz} from "@lodestar/types";
import {Epoch, SignedAggregateAndProof, ValidatorIndex} from "@lodestar/types";

export function getAggregateAndProofSigningRoot(
config: BeaconConfig,
Expand All @@ -14,7 +14,7 @@ export function getAggregateAndProofSigningRoot(
const slot = computeStartSlotAtEpoch(epoch);
const fork = config.getForkName(slot);
const aggregatorDomain = config.getDomainAtFork(fork, DOMAIN_AGGREGATE_AND_PROOF);
const sszType = ForkSeq[fork] >= ForkSeq.electra ? ssz.electra.AggregateAndProof : ssz.phase0.AggregateAndProof;
const sszType = config.getForkTypes(slot).AggregateAndProof;
return computeSigningRoot(sszType, aggregateAndProof.message, aggregatorDomain);
}

Expand Down
Loading
Loading