Skip to content
3 changes: 2 additions & 1 deletion packages/beacon-node/src/chain/blocks/importBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,8 @@ export async function importBlock(
this.forkChoice.notifyPtcMessages(
toRootHex(payloadAttestation.data.beaconBlockRoot),
ptcIndices,
payloadAttestation.data.payloadPresent
payloadAttestation.data.payloadPresent,
payloadAttestation.data.blobDataAvailable
);
}
} catch (e) {
Expand Down
5 changes: 4 additions & 1 deletion packages/beacon-node/src/chain/errors/blockError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,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",
/** Parent execution payload has not been seen */
PARENT_PAYLOAD_UNKNOWN = "BLOCK_ERROR_PARENT_PAYLOAD_UNKNOWN",
}

type ExecutionErrorStatus = Exclude<
Expand Down Expand Up @@ -114,7 +116,8 @@ export type BlockErrorType =
| {code: BlockErrorCode.EXECUTION_ENGINE_ERROR; execStatus: ExecutionErrorStatus; errorMessage: string}
| {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.BID_PARENT_ROOT_MISMATCH; bidParentRoot: RootHex; blockParentRoot: RootHex}
| {code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN; parentBlockHash: RootHex};

export class BlockGossipError extends GossipActionError<BlockErrorType> {}

Expand Down
12 changes: 12 additions & 0 deletions packages/beacon-node/src/chain/validation/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ export async function validateGossipBlock(
});
}

// [IGNORE] The block's parent execution payload (defined by bid.parent_block_hash) has been seen
// (via gossip or non-gossip sources) (a client MAY queue blocks for processing once the parent
// payload is retrieved).
// Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/p2p-interface.md
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to add the spec reference, please align with existing gossip checks

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the spec URL. Kept the inline [IGNORE] comment to match the pattern of other gossip checks.

const parentBlockHashHex = toRootHex(bid.parentBlockHash);
if (!chain.forkChoice.hasExecutionPayload(parentBlockHashHex)) {
throw new BlockGossipError(GossipAction.IGNORE, {
code: BlockErrorCode.PARENT_PAYLOAD_UNKNOWN,
parentBlockHash: parentBlockHashHex,
});
}

// 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
3 changes: 2 additions & 1 deletion packages/beacon-node/src/network/processor/gossipHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
chain.forkChoice.notifyPtcMessages(
toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
[validationResult.validatorCommitteeIndex],
payloadAttestationMessage.data.payloadPresent
payloadAttestationMessage.data.payloadPresent,
payloadAttestationMessage.data.blobDataAvailable
);
},
[GossipType.execution_payload_bid]: async ({
Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/test/spec/presets/fork_choice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ const forkChoiceTest =
// integrated
shouldSkip: (_testcase, name, _index) =>
name.includes("invalid_incorrect_proof") ||
// TODO GLOAS: Proposer boost specs have been changed retroactively in v1.7.0-alpha.1,
// TODO GLOAS: Proposer boost specs have been changed retroactively in v1.7.0-alpha.3,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this updated? this specifically mentions .1

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted back to .1 — the comment specifically describes the retroactive change in alpha.1, not alpha.3.

// and these tests are failing until we update our implementation.
name.includes("voting_source_beyond_two_epoch") ||
name.includes("justified_update_always_if_better") ||
Expand Down
17 changes: 13 additions & 4 deletions packages/beacon-node/test/spec/presets/operations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {AttesterSlashing, altair, bellatrix, capella, electra, gloas, phase0, ss
import {createCachedBeaconStateTest} from "../../utils/cachedBeaconState.js";
import {ethereumConsensusSpecsTests} from "../specTestVersioning.js";
import {expectEqualBeaconState, inputTypeSszTreeViewDU} from "../utils/expectEqualBeaconState.js";
import {specTestIterator} from "../utils/specTestIterator.js";
import {defaultSkipOpts, specTestIterator} from "../utils/specTestIterator.js";
import {BaseSpecTest, RunnerType, TestRunnerFn, shouldVerify} from "../utils/types.js";

// Define above to re-use in sync_aggregate and sync_aggregate_random
Expand Down Expand Up @@ -187,6 +187,15 @@ const operations: TestRunnerFn<OperationsTestCase, BeaconStateAllForks> = (fork,
};
};

specTestIterator(path.join(ethereumConsensusSpecsTests.outputDir, "tests", ACTIVE_PRESET), {
operations: {type: RunnerType.default, fn: operations},
});
specTestIterator(
path.join(ethereumConsensusSpecsTests.outputDir, "tests", ACTIVE_PRESET),
{
operations: {type: RunnerType.default, fn: operations},
},
{
...defaultSkipOpts,
// builder_voluntary_exit__success: spec test vector bug — missing voluntary_exit input yield
// See: https://github.com/ethereum/consensus-specs/pull/4908
skippedTests: [...(defaultSkipOpts.skippedTests ?? []), /builder_voluntary_exit__success/],
}
);
9 changes: 8 additions & 1 deletion packages/config/test/e2e/ensure-config-is-synced.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,16 @@ const ignoredRemoteConfigFields: (keyof ChainConfig)[] = [
// Networking params that may be in presets instead of chainConfig
"ATTESTATION_SUBNET_COUNT" as keyof ChainConfig,
"ATTESTATION_SUBNET_EXTRA_BITS" as keyof ChainConfig,
"ATTESTATION_SUBNET_PREFIX_BITS" as keyof ChainConfig,
// Future spec params not yet in Lodestar
"EPOCHS_PER_SHUFFLING_PHASE" as keyof ChainConfig,
"PROPOSER_SELECTION_GAP" as keyof ChainConfig,
// EIP-7928 and EIP-8025 forks - not yet implemented in Lodestar
"EIP7928_FORK_EPOCH" as keyof ChainConfig,
"EIP7928_FORK_VERSION" as keyof ChainConfig,
"EIP8025_FORK_EPOCH" as keyof ChainConfig,
"EIP8025_FORK_VERSION" as keyof ChainConfig,
// Heze fork config
"HEZE_FORK_VERSION" as keyof ChainConfig,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should be removed

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted — file is back to unstable. These were added during development to make the e2e test pass locally but shouldn't be part of this PR.

// Network-specific fork epochs and versions - these vary per network deployment
// and are not meant to be synced from the spec defaults
"ALTAIR_FORK_EPOCH",
Expand All @@ -37,6 +43,7 @@ const ignoredRemoteConfigFields: (keyof ChainConfig)[] = [
"ELECTRA_FORK_EPOCH",
"FULU_FORK_EPOCH",
"GLOAS_FORK_EPOCH",
"HEZE_FORK_EPOCH",
// Terminal values are network-specific
"TERMINAL_TOTAL_DIFFICULTY",
"TERMINAL_BLOCK_HASH",
Expand Down
7 changes: 6 additions & 1 deletion packages/fork-choice/src/forkChoice/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export enum InvalidAttestationCode {
* The attestation data index is invalid for a Gloas block (must be 0 or 1).
*/
INVALID_DATA_INDEX = "INVALID_DATA_INDEX",
/**
* Attestation index=1 (full node) but the payload is not known/available.
*/
UNKNOWN_PAYLOAD_STATUS = "UNKNOWN_PAYLOAD_STATUS",
}

export type InvalidAttestation =
Expand All @@ -69,7 +73,8 @@ export type InvalidAttestation =
| {code: InvalidAttestationCode.INVALID_TARGET; attestation: RootHex; local: RootHex}
| {code: InvalidAttestationCode.ATTESTS_TO_FUTURE_BLOCK; block: Slot; attestation: Slot}
| {code: InvalidAttestationCode.FUTURE_SLOT; attestationSlot: Slot; latestPermissibleSlot: Slot}
| {code: InvalidAttestationCode.INVALID_DATA_INDEX; index: number};
| {code: InvalidAttestationCode.INVALID_DATA_INDEX; index: number}
| {code: InvalidAttestationCode.UNKNOWN_PAYLOAD_STATUS; beaconBlockRoot: RootHex};

export enum ForkChoiceErrorCode {
INVALID_ATTESTATION = "FORKCHOICE_ERROR_INVALID_ATTESTATION",
Expand Down
35 changes: 32 additions & 3 deletions packages/fork-choice/src/forkChoice/forkChoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,7 @@ export class ForkChoice implements IForkChoice {
let payloadStatus: PayloadStatus;

// We need to retrieve block to check if it's Gloas and to compare slot
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/fork-choice.md#new-is_supporting_vote
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/fork-choice.md#new-is_supporting_vote
const block = this.getBlockHexDefaultStatus(blockRootHex);

if (block && isGloasBlock(block)) {
Expand Down Expand Up @@ -964,8 +964,13 @@ export class ForkChoice implements IForkChoice {
* Updates the PTC votes for multiple validators attesting to a block
* Spec: gloas/fork-choice.md#new-on_payload_attestation_message
*/
notifyPtcMessages(blockRoot: RootHex, ptcIndices: number[], payloadPresent: boolean): void {
this.protoArray.notifyPtcMessages(blockRoot, ptcIndices, payloadPresent);
notifyPtcMessages(
blockRoot: RootHex,
ptcIndices: number[],
payloadPresent: boolean,
blobDataAvailable: boolean
): void {
this.protoArray.notifyPtcMessages(blockRoot, ptcIndices, payloadPresent, blobDataAvailable);
}

/**
Expand Down Expand Up @@ -1056,6 +1061,14 @@ export class ForkChoice implements IForkChoice {
return this.protoArray.hasBlock(blockRoot);
}

/**
* Check if an execution payload with the given block hash has been seen.
* Spec: gloas/p2p-interface.md — parent execution payload must have been seen
*/
hasExecutionPayload(executionPayloadBlockHash: RootHex): boolean {
return this.protoArray.hasExecutionPayload(executionPayloadBlockHash);
}

/**
* Returns a MUTABLE `ProtoBlock` if the block is known **and** a descendant of the finalized root.
*/
Expand Down Expand Up @@ -1673,6 +1686,22 @@ export class ForkChoice implements IForkChoice {
});
}

// [New in Gloas:EIP7732]
// If attesting for a full node (index=1), the payload must be known (locally available)
// Spec: gloas/fork-choice.md#modified-validate_on_attestation
if (isGloasBlock(block) && attestationData.index === 1) {
const fullVariant = this.protoArray.getNodeIndexByRootAndStatus(beaconBlockRootHex, PayloadStatus.FULL);
if (fullVariant === undefined) {
throw new ForkChoiceError({
code: ForkChoiceErrorCode.INVALID_ATTESTATION,
err: {
code: InvalidAttestationCode.UNKNOWN_PAYLOAD_STATUS,
beaconBlockRoot: beaconBlockRootHex,
},
});
}
}

this.validatedAttestationDatas.add(attDataRoot);
}

Expand Down
21 changes: 16 additions & 5 deletions packages/fork-choice/src/forkChoice/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export interface IForkChoice {
* ## Specification
*
* Modified for Gloas to return ProtoNode instead of just root:
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/fork-choice.md#modified-get_ancestor
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/fork-choice.md#modified-get_ancestor
*
* Pre-Gloas: Returns (root, PAYLOAD_STATUS_FULL)
* Gloas: Returns (root, payloadStatus) based on actual node state
Expand Down Expand Up @@ -183,20 +183,26 @@ export interface IForkChoice {
*
* ## Specification
*
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/gloas/fork-choice.md#new-notify_ptc_messages
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/fork-choice.md#new-on_payload_attestation_message
*
* @param blockRoot - The beacon block root being attested
* @param ptcIndices - Array of PTC committee indices that voted
* @param payloadPresent - Whether validators attest the payload is present
* @param payloadPresent - Whether validators attest the payload is present (timeliness)
* @param blobDataAvailable - Whether validators attest blob data is available
*/
notifyPtcMessages(blockRoot: RootHex, ptcIndices: number[], payloadPresent: boolean): void;
notifyPtcMessages(
blockRoot: RootHex,
ptcIndices: number[],
payloadPresent: boolean,
blobDataAvailable: boolean
): void;
/**
* Notify fork choice that an execution payload has arrived (Gloas fork)
* Creates the FULL variant of a Gloas block when the payload becomes available
*
* ## Specification
*
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.1/specs/gloas/fork-choice.md#new-on_execution_payload
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/fork-choice.md#new-on_execution_payload
*
* @param blockRoot - The beacon block root for which the payload arrived
* @param executionPayloadBlockHash - The block hash of the execution payload
Expand Down Expand Up @@ -228,6 +234,11 @@ export interface IForkChoice {
*/
hasBlockUnsafe(blockRoot: Root): boolean;
hasBlockHexUnsafe(blockRoot: RootHex): boolean;
/**
* Check if an execution payload with the given block hash has been seen.
* For Gloas: checks if any block has a FULL variant with matching execution payload block hash.
*/
hasExecutionPayload(executionPayloadBlockHash: RootHex): boolean;
getSlotsPresent(windowStart: number): number;
/**
* Returns a `ProtoBlock` if the block is known **and** a descendant of the finalized root.
Expand Down
6 changes: 3 additions & 3 deletions packages/fork-choice/src/protoArray/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ export enum ExecutionStatus {
* Spec: gloas/fork-choice.md#constants
*/
export enum PayloadStatus {
PENDING = 0,
EMPTY = 1,
FULL = 2,
EMPTY = 0,
FULL = 1,
PENDING = 2,
}

/**
Expand Down
Loading
Loading