Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 30 additions & 0 deletions packages/fork-choice/src/protoArray/protoArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1671,6 +1671,36 @@ export class ProtoArray {
return this.getDefaultNodeIndex(blockRoot) !== undefined;
}

/**
* Check if an execution payload with the given block hash has been seen.
* Looks through all nodes with FULL variant status for a matching executionPayloadBlockHash.
* Used for Gloas gossip validation: parent execution payload must have been seen.
*
* TODO GLOAS: Add secondary index (Set<RootHex> by executionPayloadBlockHash) for O(1) lookups.
Copy link

Choose a reason for hiding this comment

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

can you check ChainSafe@e275112 it has some optimization for this

Copy link
Owner Author

Choose a reason for hiding this comment

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

Applied in 0ac2422 — same approach as e275112:

  • private executionPayloadBlockHashes = new Set<RootHex>()
  • Maintained in onBlock() (pre-Gloas) and onExecutionPayload() (Gloas)
  • Pruned in maybePrune() alongside indices/ptcVotes
  • hasExecutionPayload() → single Set.has() call, O(1)

Thanks for the pointer!

* Current O(n) linear scan is acceptable during development but should be optimized before mainnet.
*/
hasExecutionPayload(executionPayloadBlockHash: RootHex): boolean {
for (const [, variantOrArr] of this.indices) {
if (!Array.isArray(variantOrArr)) {
// Pre-Gloas: check directly
const node = this.nodes[variantOrArr];
if (node?.executionPayloadBlockHash === executionPayloadBlockHash) {
return true;
}
} else {
// Gloas: check FULL variant if it exists
const fullIndex = variantOrArr[PayloadStatus.FULL];
if (fullIndex !== undefined) {
const node = this.nodes[fullIndex];
if (node?.executionPayloadBlockHash === executionPayloadBlockHash) {
return true;
}
}
}
}
return false;
}

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