feat: implement epbs block production [wip]#8838
Conversation
Summary of ChangesHello @nflaig, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces the foundational changes required to support block production for the upcoming 'Gloas' fork. The core change involves a shift from directly embedding execution payloads in beacon blocks to a system where beacon blocks contain bids, and the full execution payload is published in a separate envelope. This architectural change is crucial for enabling a more flexible and potentially more decentralized block building process, allowing for external builders to participate more effectively. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces the initial implementation for enshrined proposer-builder separation (EPBS) through the new 'gloas' fork. It adds new API endpoints (produceBlockV4, getExecutionPayloadEnvelope, publishExecutionPayloadEnvelope) and modifies existing ones to support the new block production flow. The changes span across API definitions, beacon node implementation, and validator logic. The overall structure is sound, with a clear separation of concerns for the new gloas flow. However, as a work-in-progress, several critical components are marked with TODOs, such as signature verification for envelopes, state transition logic, and correct inclusion proof generation. I've provided specific feedback on a couple of areas: an incorrect type cast in the API route definitions and a logic improvement for retrieving cached block production data.
| signedBlockContents as SignedBlockContents<ForkPostDeneb> | ||
| ) | ||
| : sszTypesFor(fork).SignedBeaconBlock.toJson( | ||
| signedBlockContents.signedBlock as SignedBeaconBlock<ForkPreDeneb> |
There was a problem hiding this comment.
The type cast as SignedBeaconBlock<ForkPreDeneb> is incorrect for the gloas fork. This else branch is executed for gloas and later forks, but SignedBeaconBlock<ForkPostGloas> has a different structure than SignedBeaconBlock<ForkPreDeneb>. This incorrect cast may lead to improper serialization or runtime errors.
A more appropriate type should be used to encompass all forks handled in this branch (pre-Deneb and post-Gloas forks). A simple as SignedBeaconBlock might be sufficient if the toJson method can correctly handle the generic type.
A similar incorrect cast is present in writeReqSsz on line 460.
| if (gloasResult.executionPayload && Number(gloasResult.executionPayload.timestamp) > 0) { | ||
| // For self-builds at this slot, this should be our produced block | ||
| cachedResult = { | ||
| blockRootHex, | ||
| ...gloasResult, | ||
| }; | ||
| break; | ||
| } |
There was a problem hiding this comment.
The condition to find the cached block production result is not specific enough. It checks if the timestamp is greater than 0, which is always true for a valid payload. This could lead to returning an incorrect cached result if multiple blocks are produced in quick succession and the cache contains entries for different slots.
To ensure the correct cached result is retrieved, you should compare the payload's timestamp with the expected timestamp for the requested slot.
const expectedTimestamp = computeTimeAtSlot(config, slot, chain.genesisTime);
if (gloasResult.executionPayload?.timestamp === BigInt(expectedTimestamp)) {
// For self-builds at this slot, this should be our produced block
cachedResult = {
blockRootHex,
...gloasResult,
};
break;
}| type: SignableMessageType.BLOCK_V2, // TODO GLOAS: Add dedicated type for envelope signing | ||
| data: envelope as unknown as BeaconBlock, |
There was a problem hiding this comment.
As noted in the TODO, reusing SignableMessageType.BLOCK_V2 is not ideal. More importantly, casting envelope to BeaconBlock is incorrect and could cause issues with remote signers that expect a BeaconBlock structure for this message type. A dedicated SignableMessageType for ExecutionPayloadEnvelope should be added to ensure type safety and correct handling by remote signers.
Performance Report✔️ no performance regression detected Full benchmark results
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ddd4339d1e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Search the block production cache for a block produced at this slot | ||
| // The cache is keyed by block root, so we need to search through entries | ||
| let cachedResult: {blockRootHex: string; produceResult: ProduceFullGloas} | null = null; | ||
|
|
||
| for (const [blockRootHex, produceResult] of chain.blockProductionCache.entries()) { | ||
| if (produceResult.fork === fork && produceResult.type === BlockType.Full && "executionPayload" in produceResult) { | ||
| cachedResult = {blockRootHex, produceResult: produceResult as ProduceFullGloas}; | ||
| break; | ||
| } |
There was a problem hiding this comment.
Match cached production result to requested slot
getExecutionPayloadEnvelope claims to look up the cache entry “for a block produced at this slot,” but it only checks fork/type and then returns the first cached entry. If the cache contains multiple produced blocks (e.g., from prior slots or retries), this can return a payload/envelope that does not correspond to the requested slot, causing the validator to sign and publish an envelope that peers will reject. You need to filter by slot (or block root derived from the produced block for this slot) before building the envelope.
Useful? React with 👍 / 👎.
| // For GLOAS, the inclusion proof is for the envelope's blobKzgCommitments field | ||
| // The envelope structure has a different gindex than the block body | ||
| // TODO GLOAS: Compute proper inclusion proof for envelope's blobKzgCommitments | ||
| // For now, use an empty proof since the envelope already contains the commitments explicitly | ||
| const kzgCommitmentsInclusionProof = new Array(17).fill(new Uint8Array(32)) as fulu.KzgCommitmentsInclusionProof; |
There was a problem hiding this comment.
Do not publish data columns with a dummy inclusion proof
getDataColumnSidecarsForGloas constructs kzgCommitmentsInclusionProof as an all‑zero array with a TODO comment. The validation path uses verifyMerkleBranch against the block header’s bodyRoot; a dummy proof will fail on peers, so self‑built Gloas data column sidecars will be rejected and not propagated. This needs a real inclusion proof computed against the envelope’s blobKzgCommitments field before gossiping.
Useful? React with 👍 / 👎.
No description provided.