Skip to content
Open
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
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
11 changes: 11 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,17 @@ 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).
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
8 changes: 8 additions & 0 deletions packages/fork-choice/src/forkChoice/forkChoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,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
5 changes: 5 additions & 0 deletions packages/fork-choice/src/forkChoice/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,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
32 changes: 32 additions & 0 deletions packages/fork-choice/src/protoArray/protoArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ export class ProtoArray {
*/
private ptcVotes = new Map<RootHex, BitArray>();

/**
* Secondary index: O(1) lookup by execution payload block hash.
* Maintained by onBlock() (pre-Gloas) and onExecutionPayload() (Gloas).
* Pruned in maybePrune().
*/
private executionPayloadBlockHashes = new Set<RootHex>();

constructor({
pruneThreshold,
justifiedEpoch,
Expand Down Expand Up @@ -515,6 +522,11 @@ export class ProtoArray {
// Pre-Gloas: store FULL index instead of array
this.indices.set(block.blockRoot, nodeIndex);

// Maintain secondary index for O(1) hasExecutionPayload lookups
if (block.executionPayloadBlockHash != null) {
this.executionPayloadBlockHashes.add(block.executionPayloadBlockHash);
}

// If this node is valid, lets propagate the valid status up the chain
// and throw error if we counter invalid, as this breaks consensus
if (node.parent !== undefined) {
Expand Down Expand Up @@ -602,6 +614,9 @@ export class ProtoArray {
// Add FULL variant to the indices array
variants[PayloadStatus.FULL] = fullIndex;

// Maintain secondary index for O(1) hasExecutionPayload lookups
this.executionPayloadBlockHashes.add(executionPayloadBlockHash);

// Update bestChild for PENDING node (may now prefer FULL over EMPTY)
this.maybeUpdateBestChildAndDescendant(pendingIndex, fullIndex, currentSlot, proposerBoostRoot);
}
Expand Down Expand Up @@ -1078,6 +1093,13 @@ export class ProtoArray {
prunedRoots.add(node.blockRoot);
}

// Clean up secondary execution payload block hash index
for (let i = 0; i < finalizedIndex; i++) {
const node = this.nodes[i];
if (node?.executionPayloadBlockHash != null) {
this.executionPayloadBlockHashes.delete(node.executionPayloadBlockHash);
}
}
// Remove indices for pruned blocks and PTC votes
for (const root of prunedRoots) {
this.indices.delete(root);
Expand Down Expand Up @@ -1671,6 +1693,16 @@ export class ProtoArray {
return this.getDefaultNodeIndex(blockRoot) !== undefined;
}

/**
* Check if an execution payload with the given block hash has been seen.
* Used for Gloas gossip validation: parent execution payload must have been seen.
*
* O(1) via secondary index maintained in onBlock() (pre-Gloas) and onExecutionPayload() (Gloas).
*/
hasExecutionPayload(executionPayloadBlockHash: RootHex): boolean {
return this.executionPayloadBlockHashes.has(executionPayloadBlockHash);
}

/**
* Return ProtoNode for blockRoot with explicit payload status
*
Expand Down
Loading