From 95739bde2cc380ab0089910f0a22eac51e63be21 Mon Sep 17 00:00:00 2001 From: dapplion <35266934+dapplion@users.noreply.github.com> Date: Tue, 6 Dec 2022 16:06:29 +0700 Subject: [PATCH 1/2] Update produce block flow for EIP-4844 --- .../src/api/impl/beacon/blocks/index.ts | 39 +++++---- packages/beacon-node/src/chain/chain.ts | 85 ++++++++++++++++--- packages/beacon-node/src/chain/interface.ts | 4 +- packages/beacon-node/src/chain/options.ts | 4 + .../chain/produceBlock/produceBlockBody.ts | 70 ++++++++++++++- .../validateBlobsAndKzgCommitments.ts | 26 ++++++ packages/beacon-node/src/network/interface.ts | 1 + packages/beacon-node/src/network/network.ts | 16 +++- packages/beacon-node/src/util/promises.ts | 14 +++ .../test/utils/mocks/chain/chain.ts | 4 + 10 files changed, 230 insertions(+), 33 deletions(-) create mode 100644 packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts create mode 100644 packages/beacon-node/src/util/promises.ts diff --git a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts index 6dda9588e859..355493229184 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts @@ -1,11 +1,12 @@ import {routes} from "@lodestar/api"; - import {computeTimeAtSlot} from "@lodestar/state-transition"; -import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params"; +import {ForkSeq, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params"; import {sleep} from "@lodestar/utils"; +import {eip4844} from "@lodestar/types"; import {fromHexString, toHexString} from "@chainsafe/ssz"; -import {BlockError, BlockErrorCode} from "../../../../chain/errors/index.js"; import {getBlockInput} from "../../../../chain/blocks/types.js"; +import {promiseAllMaybeAsync} from "../../../../util/promises.js"; +import {BlockError, BlockErrorCode} from "../../../../chain/errors/index.js"; import {OpSource} from "../../../../metrics/validatorMonitor.js"; import {NetworkEvent} from "../../../../network/index.js"; import {ApiModules, IS_OPTIMISTIC_TEMP} from "../../types.js"; @@ -187,20 +188,28 @@ export function getBeaconBlockApi({ metrics?.registerBeaconBlock(OpSource.api, seenTimestampSec, signedBlock.message); - // TODO EIP-4844: Will throw an error for blocks post EIP-4844 - const blockInput = getBlockInput.preEIP4844(config, signedBlock); - - await Promise.all([ + // TODO EIP-4844: Open question if broadcast to both block topic + block_and_blobs topic + const blockForImport = + config.getForkSeq(signedBlock.message.slot) >= ForkSeq.eip4844 + ? getBlockInput.postEIP4844( + config, + signedBlock, + chain.getBlobsSidecar(signedBlock.message as eip4844.BeaconBlock) + ) + : getBlockInput.preEIP4844(config, signedBlock); + + await promiseAllMaybeAsync([ // Send the block, regardless of whether or not it is valid. The API // specification is very clear that this is the desired behaviour. - network.gossip.publishBeaconBlock(signedBlock), - - chain.processBlock(blockInput).catch((e) => { - if (e instanceof BlockError && e.type.code === BlockErrorCode.PARENT_UNKNOWN) { - network.events.emit(NetworkEvent.unknownBlockParent, blockInput, network.peerId.toString()); - } - throw e; - }), + () => network.publishBeaconBlockMaybeBlobs(blockForImport), + + () => + chain.processBlock(blockForImport).catch((e) => { + if (e instanceof BlockError && e.type.code === BlockErrorCode.PARENT_UNKNOWN) { + network.events.emit(NetworkEvent.unknownBlockParent, blockForImport, network.peerId.toString()); + } + throw e; + }), ]); }, }; diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index e6b26458b617..ae3af312fe49 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -1,4 +1,5 @@ import path from "node:path"; +import {computeAggregateKzgProof} from "c-kzg"; import { BeaconStateAllForks, CachedBeaconStateAllForks, @@ -12,12 +13,12 @@ import { PubkeyIndexMap, } from "@lodestar/state-transition"; import {IBeaconConfig} from "@lodestar/config"; -import {allForks, UintNum64, Root, phase0, Slot, RootHex, Epoch, ValidatorIndex} from "@lodestar/types"; +import {allForks, UintNum64, Root, phase0, Slot, RootHex, Epoch, ValidatorIndex, eip4844} from "@lodestar/types"; import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; import {ProcessShutdownCallback} from "@lodestar/validator"; -import {ILogger, toHex} from "@lodestar/utils"; +import {ILogger, pruneSetToMax, toHex} from "@lodestar/utils"; import {CompositeTypeAny, fromHexString, TreeView, Type} from "@chainsafe/ssz"; -import {SLOTS_PER_EPOCH} from "@lodestar/params"; +import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; import {GENESIS_EPOCH, ZERO_HASH} from "../constants/index.js"; import {IBeaconDb} from "../db/index.js"; @@ -59,11 +60,18 @@ import {SeenAggregatedAttestations} from "./seenCache/seenAggregateAndProof.js"; import {SeenBlockAttesters} from "./seenCache/seenBlockAttesters.js"; import {BeaconProposerCache} from "./beaconProposerCache.js"; import {CheckpointBalancesCache} from "./balancesCache.js"; -import {AssembledBlockType, BlockType} from "./produceBlock/index.js"; +import {AssembledBlockType, BlobsResultType, BlockType} from "./produceBlock/index.js"; import {BlockAttributes, produceBlockBody} from "./produceBlock/produceBlockBody.js"; import {computeNewStateRoot} from "./produceBlock/computeNewStateRoot.js"; import {BlockInput} from "./blocks/types.js"; +/** + * Arbitrary constants, blobs should be consumed immediately in the same slot they are produced. + * A value of 1 would probably be sufficient. However it's sensible to allow some margin if the node overloads. + */ +const DEFAULT_MAX_CACHED_BLOBS_SIDECAR = 8; +const MAX_RETAINED_SLOTS_CACHED_BLOBS_SIDECAR = 8; + export class BeaconChain implements IBeaconChain { readonly genesisTime: UintNum64; readonly genesisValidatorsRoot: Root; @@ -119,6 +127,10 @@ export class BeaconChain implements IBeaconChain { private successfulExchangeTransition = false; private readonly exchangeTransitionConfigurationEverySlots: number; + // TODO EIP-4844: Prune data structure every time period, for both old entries + /** Map keyed by executionPayload.blockHash of the block for those blobs */ + private readonly producedBlobsSidecarCache = new Map(); + private readonly faultInspectionWindow: number; private readonly allowedFaults: number; private processShutdownCallback: ProcessShutdownCallback; @@ -359,27 +371,65 @@ export class BeaconChain implements IBeaconChain { const proposerIndex = state.epochCtx.getBeaconProposer(slot); const proposerPubKey = state.epochCtx.index2pubkey[proposerIndex].toBytes(); + const {body, blobs} = await produceBlockBody.call(this, blockType, state, { + randaoReveal, + graffiti, + slot, + parentSlot: slot - 1, + parentBlockRoot, + proposerIndex, + proposerPubKey, + }); + const block = { slot, proposerIndex, parentRoot: parentBlockRoot, stateRoot: ZERO_HASH, - body: await produceBlockBody.call(this, blockType, state, { - randaoReveal, - graffiti, - slot, - parentSlot: slot - 1, - parentBlockRoot, - proposerIndex, - proposerPubKey, - }), + body, } as AssembledBlockType; block.stateRoot = computeNewStateRoot(this.metrics, state, block); + // Cache for latter broadcasting + if (blobs.type === BlobsResultType.produced) { + // TODO EIP-4844: Prune data structure for max entries + this.producedBlobsSidecarCache.set(blobs.blockHash, { + // TODO EIP-4844: Optimize, hashing the full block is not free. + beaconBlockRoot: this.config.getForkTypes(block.slot).BeaconBlock.hashTreeRoot(block), + beaconBlockSlot: block.slot, + blobs: blobs.blobs, + kzgAggregatedProof: computeAggregateKzgProof(blobs.blobs), + }); + pruneSetToMax( + this.producedBlobsSidecarCache, + this.opts.maxCachedBlobsSidecar ?? DEFAULT_MAX_CACHED_BLOBS_SIDECAR + ); + } + return block; } + /** + * https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/validator.md#sidecar + * def get_blobs_sidecar(block: BeaconBlock, blobs: Sequence[Blob]) -> BlobsSidecar: + * return BlobsSidecar( + * beacon_block_root=hash_tree_root(block), + * beacon_block_slot=block.slot, + * blobs=blobs, + * kzg_aggregated_proof=compute_proof_from_blobs(blobs), + * ) + */ + getBlobsSidecar(beaconBlock: eip4844.BeaconBlock): eip4844.BlobsSidecar { + const blockHash = toHex(beaconBlock.body.executionPayload.blockHash); + const blobsSidecar = this.producedBlobsSidecarCache.get(blockHash); + if (!blobsSidecar) { + throw Error(`No blobsSidecar for executionPayload.blockHash ${blockHash}`); + } + + return blobsSidecar; + } + async processBlock(block: BlockInput, opts?: ImportBlockOpts): Promise { return await this.blockProcessor.processBlocksJob([block], opts); } @@ -591,6 +641,15 @@ export class BeaconChain implements IBeaconChain { this.logger.error("Error on exchangeTransitionConfiguration", {}, e as Error); }); } + + // Prune old blobsSidecar for block production, those are only useful on their slot + if (this.config.getForkSeq(slot) >= ForkSeq.eip4844 && this.producedBlobsSidecarCache.size > 0) { + for (const [key, blobsSidecar] of this.producedBlobsSidecarCache) { + if (slot > blobsSidecar.beaconBlockSlot + MAX_RETAINED_SLOTS_CACHED_BLOBS_SIDECAR) { + this.producedBlobsSidecarCache.delete(key); + } + } + } } private onClockEpoch(epoch: Epoch): void { diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index 053cb9204c3b..50fe3033719f 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -1,4 +1,4 @@ -import {allForks, UintNum64, Root, phase0, Slot, RootHex, Epoch, ValidatorIndex} from "@lodestar/types"; +import {allForks, UintNum64, Root, phase0, Slot, RootHex, Epoch, ValidatorIndex, eip4844} from "@lodestar/types"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {IBeaconConfig} from "@lodestar/config"; import {CompositeTypeAny, TreeView, Type} from "@chainsafe/ssz"; @@ -110,6 +110,8 @@ export interface IBeaconChain { */ getCanonicalBlockAtSlot(slot: Slot): Promise; + getBlobsSidecar(beaconBlock: eip4844.BeaconBlock): eip4844.BlobsSidecar; + produceBlock(blockAttributes: BlockAttributes): Promise; produceBlindedBlock(blockAttributes: BlockAttributes): Promise; diff --git a/packages/beacon-node/src/chain/options.ts b/packages/beacon-node/src/chain/options.ts index e827a3a0b286..0f804dfd46c9 100644 --- a/packages/beacon-node/src/chain/options.ts +++ b/packages/beacon-node/src/chain/options.ts @@ -20,6 +20,10 @@ export type IChainOptions = BlockProcessOpts & faultInspectionWindow?: number; /** Number of missed slots allowed in the faultInspectionWindow for builder circuit*/ allowedFaults?: number; + /** Ensure blobs returned by the execution engine are valid */ + sanityCheckExecutionEngineBlobs?: boolean; + /** Max number of produced blobs by local validators to cache */ + maxCachedBlobsSidecar?: number; }; export type BlockProcessOpts = { diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index 2754ea52a156..d666e8ed7eb9 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -11,6 +11,7 @@ import { BLSPubkey, BLSSignature, capella, + eip4844, } from "@lodestar/types"; import { CachedBeaconStateAllForks, @@ -39,6 +40,7 @@ import { import {ZERO_HASH, ZERO_HASH_HEX} from "../../constants/index.js"; import {IEth1ForBlockProduction} from "../../eth1/index.js"; import {numToQuantity} from "../../eth1/provider/utils.js"; +import {validateBlobsAndKzgCommitments} from "./validateBlobsAndKzgCommitments.js"; // Time to provide the EL to generate a payload from new payload id const PAYLOAD_GENERATION_TIME_MS = 500; @@ -65,6 +67,15 @@ export type AssembledBlockType = T extends BlockType.Full ? allForks.BeaconBlock : allForks.BlindedBeaconBlock; +export enum BlobsResultType { + preEIP4844, + produced, +} + +export type BlobsResult = + | {type: BlobsResultType.preEIP4844} + | {type: BlobsResultType.produced; blobs: eip4844.Blobs; blockHash: RootHex}; + export async function produceBlockBody( this: BeaconChain, blockType: T, @@ -83,7 +94,10 @@ export async function produceBlockBody( proposerIndex: ValidatorIndex; proposerPubKey: BLSPubkey; } -): Promise> { +): Promise<{body: AssembledBodyType; blobs: BlobsResult}> { + // We assign this in an EIP-4844 branch below and return it + let blobs: {blobs: eip4844.Blobs; blockHash: RootHex} | null = null; + // TODO: // Iterate through the naive aggregation pool and ensure all the attestations from there // are included in the operation pool. @@ -153,7 +167,16 @@ export async function produceBlockBody( currentState as CachedBeaconStateBellatrix, proposerPubKey ); - } else { + + // Capella and later forks have withdrawalRoot on their ExecutionPayloadHeader + // TODO Capella: Remove this. It will come from the execution client. + if (ForkSeq[fork] >= ForkSeq.capella) { + throw Error("Builder blinded blocks not supported after capella"); + } + } + + // blockType === BlockType.Full + else { // try catch payload fetch here, because there is still a recovery path possible if we // are pre-merge. We don't care the same for builder segment as the execution block // will takeover if the builder flow was activated and errors @@ -188,6 +211,34 @@ export async function produceBlockBody( if (payload.transactions.length === 0) { this.metrics?.blockPayload.emptyPayloads.inc({prepType}); } + + // Capella and later forks have withdrawals on their ExecutionPayload + if (fork === ForkName.capella || fork === ForkName.eip4844) { + // TODO EIP-4844 Remove this when the EC includes `withdrawals` + (blockBody as capella.BeaconBlockBody).executionPayload.withdrawals = []; + } + + if (fork === ForkName.eip4844) { + // SPEC: https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/validator.md#blob-kzg-commitments + // After retrieving the execution payload from the execution engine as specified in Bellatrix, use the + // payload_id to retrieve blobs and blob_kzg_commitments via get_blobs_and_kzg_commitments(payload_id) + // TODO EIP-4844: getBlobsBundle and getPayload must be either coupled or called in parallel to save time. + const blobsBundle = await this.executionEngine.getBlobsBundle(payloadId); + + // Sanity check consistency between getPayload() and getBlobsBundle() + const blockHash = toHex(payload.blockHash); + if (blobsBundle.blockHash !== blockHash) { + throw Error(`blobsBundle incorrect blockHash ${blobsBundle.blockHash} != ${blockHash}`); + } + + // Optionally sanity-check that the KZG commitments match the versioned hashes in the transactions + if (this.opts.sanityCheckExecutionEngineBlobs) { + validateBlobsAndKzgCommitments(payload, blobsBundle); + } + + (blockBody as eip4844.BeaconBlockBody).blobKzgCommitments = blobsBundle.kzgs; + blobs = {blobs: blobsBundle.blobs, blockHash}; + } } } catch (e) { this.metrics?.blockPayload.payloadFetchErrors.inc(); @@ -217,7 +268,20 @@ export async function produceBlockBody( (blockBody as capella.BeaconBlockBody).blsToExecutionChanges = []; } - return blockBody as AssembledBodyType; + // Type-safe for blobs variable. Translate 'null' value into 'preEIP4844' enum + // TODO: Not ideal, but better than just using null. + // TODO: Does not guarantee that preEIP4844 enum goes with a preEIP4844 block + let blobsResult: BlobsResult; + if (currentState.config.getForkSeq(blockSlot) >= ForkSeq.eip4844) { + if (!blobs) { + throw Error("Blobs are null post eip4844"); + } + blobsResult = {type: BlobsResultType.produced, ...blobs}; + } else { + blobsResult = {type: BlobsResultType.preEIP4844}; + } + + return {body: blockBody as AssembledBodyType, blobs: blobsResult}; } /** diff --git a/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts b/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts new file mode 100644 index 000000000000..9f7d914f5733 --- /dev/null +++ b/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts @@ -0,0 +1,26 @@ +import {blobToKzgCommitment} from "c-kzg"; +import {verifyKzgCommitmentsAgainstTransactions} from "@lodestar/state-transition"; +import {allForks, eip4844} from "@lodestar/types"; +import {toHex} from "@lodestar/utils"; +import {BlobsBundle} from "../../execution/index.js"; +import {byteArrayEquals} from "../../util/bytes.js"; + +/** + * Optionally sanity-check that the KZG commitments match the versioned hashes in the transactions + * https://github.com/ethereum/consensus-specs/blob/11a037fd9227e29ee809c9397b09f8cc3383a8c0/specs/eip4844/validator.md#blob-kzg-commitments + */ +export function validateBlobsAndKzgCommitments(payload: allForks.ExecutionPayload, blobsBundle: BlobsBundle): void { + verifyKzgCommitmentsAgainstTransactions(payload.transactions, blobsBundle.kzgs); + + // Optionally sanity-check that the KZG commitments match the blobs (as produced by the execution engine) + if (blobsBundle.blobs.length !== blobsBundle.kzgs.length) { + throw Error(`Blobs bundle blobs len ${blobsBundle.blobs.length} != kzgs len ${blobsBundle.kzgs.length}`); + } + + for (let i = 0; i < blobsBundle.blobs.length; i++) { + const kzg = blobToKzgCommitment(blobsBundle.blobs[i]) as eip4844.KZGCommitment; + if (!byteArrayEquals(kzg, blobsBundle.kzgs[i])) { + throw Error(`Wrong KZG[${i}] ${toHex(blobsBundle.kzgs[i])} expected ${toHex(kzg)}`); + } + } +} diff --git a/packages/beacon-node/src/network/interface.ts b/packages/beacon-node/src/network/interface.ts index a357b3a9403b..8f9682f41bca 100644 --- a/packages/beacon-node/src/network/interface.ts +++ b/packages/beacon-node/src/network/interface.ts @@ -32,6 +32,7 @@ export interface INetwork { getConnectedPeers(): PeerId[]; hasSomeConnectedPeer(): boolean; + publishBeaconBlockMaybeBlobs(signedBlock: BlockInput): Promise; beaconBlocksMaybeBlobsByRange(peerId: PeerId, request: phase0.BeaconBlocksByRangeRequest): Promise; beaconBlocksMaybeBlobsByRoot(peerId: PeerId, request: phase0.BeaconBlocksByRootRequest): Promise; diff --git a/packages/beacon-node/src/network/network.ts b/packages/beacon-node/src/network/network.ts index b36578361299..254fdae18685 100644 --- a/packages/beacon-node/src/network/network.ts +++ b/packages/beacon-node/src/network/network.ts @@ -11,7 +11,7 @@ import {computeEpochAtSlot, computeTimeAtSlot} from "@lodestar/state-transition" import {altair, Epoch, phase0} from "@lodestar/types"; import {IMetrics} from "../metrics/index.js"; import {ChainEvent, IBeaconChain, IBeaconClock} from "../chain/index.js"; -import {BlockInput, getBlockInput} from "../chain/blocks/types.js"; +import {BlockInput, BlockInputType, getBlockInput} from "../chain/blocks/types.js"; import {INetworkOptions} from "./options.js"; import {INetwork} from "./interface.js"; import {IReqRespBeaconNode, ReqRespBeaconNode, ReqRespHandlers} from "./reqresp/ReqRespBeaconNode.js"; @@ -196,6 +196,20 @@ export class Network implements INetwork { return this.peerManager.hasSomeConnectedPeer(); } + publishBeaconBlockMaybeBlobs(blockImport: BlockInput): Promise { + switch (blockImport.type) { + case BlockInputType.preEIP4844: + return this.gossip.publishBeaconBlock(blockImport.block); + + case BlockInputType.postEIP4844: + // TODO EIP-4844: Implement SignedBeaconBlockAndBlobsSidecar publish topic + throw Error("SignedBeaconBlockAndBlobsSidecar publish not implemented"); + + case BlockInputType.postEIP4844OldBlobs: + throw Error(`Attempting to broadcast old BlockImport slot ${blockImport.block.message.slot}`); + } + } + async beaconBlocksMaybeBlobsByRange( peerId: PeerId, request: phase0.BeaconBlocksByRangeRequest diff --git a/packages/beacon-node/src/util/promises.ts b/packages/beacon-node/src/util/promises.ts new file mode 100644 index 000000000000..e0bba250f310 --- /dev/null +++ b/packages/beacon-node/src/util/promises.ts @@ -0,0 +1,14 @@ +/** + * Promise.all() but allows all functions to run even if one throws syncronously + */ +export function promiseAllMaybeAsync(fns: Array<() => Promise>): Promise { + return Promise.all( + fns.map((fn) => { + try { + return fn(); + } catch (e) { + return Promise.reject(e); + } + }) + ); +} diff --git a/packages/beacon-node/test/utils/mocks/chain/chain.ts b/packages/beacon-node/test/utils/mocks/chain/chain.ts index 780a1f92c3f3..cdea4edd6b42 100644 --- a/packages/beacon-node/test/utils/mocks/chain/chain.ts +++ b/packages/beacon-node/test/utils/mocks/chain/chain.ts @@ -188,6 +188,10 @@ export class MockBeaconChain implements IBeaconChain { throw Error("Not implemented"); } + getBlobsSidecar(): never { + throw Error("Not implemented"); + } + async processBlock(): Promise {} async processChainSegment(): Promise {} From b28dc171b83e62082e5e46e2b0c205cce47aab91 Mon Sep 17 00:00:00 2001 From: gajinder Date: Tue, 6 Dec 2022 18:22:15 +0530 Subject: [PATCH 2/2] remove withdrawals force set empty --- .../beacon-node/src/chain/produceBlock/produceBlockBody.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index d666e8ed7eb9..71b57a198970 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -212,12 +212,6 @@ export async function produceBlockBody( this.metrics?.blockPayload.emptyPayloads.inc({prepType}); } - // Capella and later forks have withdrawals on their ExecutionPayload - if (fork === ForkName.capella || fork === ForkName.eip4844) { - // TODO EIP-4844 Remove this when the EC includes `withdrawals` - (blockBody as capella.BeaconBlockBody).executionPayload.withdrawals = []; - } - if (fork === ForkName.eip4844) { // SPEC: https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/validator.md#blob-kzg-commitments // After retrieving the execution payload from the execution engine as specified in Bellatrix, use the