feat: add gossip bid selection to block production#9289
feat: add gossip bid selection to block production#9289
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements GLOAS block production by introducing the ability to select and build blocks using gossip bids in parallel with local execution engine blocks. It refactors the block production pipeline to support external bids, updates the bid pool to store signed messages, and integrates these changes into the validator API. Feedback focuses on a critical issue where using an external bid fails to populate the block production cache with required execution data, which will break subsequent API calls for the execution payload envelope. Additionally, it is recommended to improve error reporting when both local and gossip block production attempts fail.
Performance Report✔️ no performance regression detected Full benchmark results
|
| parentBlock: ProtoBlock; | ||
| feeRecipient?: string; | ||
| /** When provided, build block with this external bid instead of a self-build bid */ | ||
| externalBid?: gloas.SignedExecutionPayloadBid; |
There was a problem hiding this comment.
this name is confusing, if we self-build it's not really a "bid" just a placeholder, I would rather call this builderBid
| ? chain.executionPayloadBidPool.getBestBid(slot, parentBlockHash, parentBlockRootHex) | ||
| : null; | ||
|
|
||
| logger.verbose("Producing GLOAS block", { |
There was a problem hiding this comment.
please remove gloas from the logs, we already have fork in log context
| const baseAttrs = {slot, parentBlock, randaoReveal, graffiti: graffitiBytes, feeRecipient, commonBlockBodyPromise}; | ||
|
|
||
| // Always build local block. If gossip bid available, also build with it in parallel and prefer it. | ||
| if (gossipBid) { |
There was a problem hiding this comment.
why we need this if check? can't it just resolve null if there is no bid?
| @@ -910,68 +1008,7 @@ export function getValidatorApi( | |||
| throw new ApiError(400, `produceBlockV4 not supported for pre-gloas fork=${fork}`); | |||
| } | |||
|
|
|||
There was a problem hiding this comment.
can we jus inline the logic for now, the diff is hard to review otherwise
| // `(bid.slot, bid.parent_block_hash, bid.parent_block_root)`. | ||
| const bestBid = chain.executionPayloadBidPool.getBestBid(bid.slot, parentBlockHashHex, parentBlockRootHex); | ||
| if (bestBid !== null && bestBid.value >= bid.value) { | ||
| const bestSignedBid = chain.executionPayloadBidPool.getBestBid(bid.slot, parentBlockHashHex, parentBlockRootHex); |
There was a problem hiding this comment.
why do we rename this? I feel like prev. name is fine
| }); | ||
| } else { | ||
| // External bid: the builder is responsible for broadcasting the execution payload envelope | ||
| this.logger.info("Published block with external builder bid, envelope expected from builder", { |
There was a problem hiding this comment.
I don't think the "external" terminology is correct, it's either a builder bid or self-build, we can later differentiate gossip vs. api builder bids, but the term "external" seems confusing
| slot, | ||
| parentSlot, | ||
| parentBlockRoot: parentBlockRootHex, | ||
| fork, |
There was a problem hiding this comment.
need to also log parent hash everywhere
| if (gossipResult.status === "fulfilled") { | ||
| metrics?.blockProductionSuccess.inc({source: ProducedBlockSource.builder}); | ||
| logger.info("Selected gossip bid block", { | ||
| slot, |
There was a problem hiding this comment.
log more bid data to debug later
|
|
||
| if (engineResult.status === "fulfilled") { | ||
| metrics?.blockProductionSuccess.inc({source: ProducedBlockSource.engine}); | ||
| logger.warn("Gossip bid block production failed, using local block", {slot}); |
There was a problem hiding this comment.
may want a logCtx to be shared everywhere
|
|
||
| this.logger.verbose("Fetching execution payload from engine", {slot: blockSlot, payloadId}); | ||
| const payloadRes = await this.executionEngine.getPayload(fork, payloadId); | ||
| this.logger.verbose("Preparing execution payload from engine", { |
There was a problem hiding this comment.
may delay this to line 252 below to add more data to log?
| Object.assign(logMeta, {executionPayloadPrepType: prepType}); | ||
|
|
||
| if (prepType !== PayloadPreparationType.Cached) { | ||
| await sleep(PAYLOAD_GENERATION_TIME_MS); |
There was a problem hiding this comment.
not sure why we have to sleep() to wait for EL, may use Promise.race() or something like it if needed
There was a problem hiding this comment.
I think we don't modify this file much in this PR.
This code hasn't changed here. Just the diff shown is messed up. Will work on reducing diff for easier review
No description provided.