diff --git a/packages/api/src/builder/routes.ts b/packages/api/src/builder/routes.ts index dcff20705c17..0136f1deeac4 100644 --- a/packages/api/src/builder/routes.ts +++ b/packages/api/src/builder/routes.ts @@ -1,6 +1,6 @@ import {fromHexString, toHexString} from "@chainsafe/ssz"; import {ssz, allForks, bellatrix, Slot, Root, BLSPubkey} from "@lodestar/types"; -import {ForkName, isForkExecution} from "@lodestar/params"; +import {ForkName, isForkExecution, isForkBlobs} from "@lodestar/params"; import {ChainForkConfig} from "@lodestar/config"; import { @@ -34,11 +34,14 @@ export type Api = { HttpStatusCode.NOT_FOUND | HttpStatusCode.BAD_REQUEST > >; - submitBlindedBlock( - signedBlock: allForks.SignedBlindedBeaconBlockOrContents - ): Promise< + submitBlindedBlock(signedBlock: allForks.SignedBlindedBeaconBlockOrContents): Promise< ApiClientResponse< - {[HttpStatusCode.OK]: {data: allForks.ExecutionPayload; version: ForkName}}, + { + [HttpStatusCode.OK]: { + data: allForks.ExecutionPayload | allForks.ExecutionPayloadAndBlobsBundle; + version: ForkName; + }; + }, HttpStatusCode.SERVICE_UNAVAILABLE > >; @@ -84,8 +87,13 @@ export function getReturnTypes(): ReturnTypes { getHeader: WithVersion((fork: ForkName) => isForkExecution(fork) ? ssz.allForksExecution[fork].SignedBuilderBid : ssz.bellatrix.SignedBuilderBid ), - submitBlindedBlock: WithVersion((fork: ForkName) => - isForkExecution(fork) ? ssz.allForksExecution[fork].ExecutionPayload : ssz.bellatrix.ExecutionPayload + submitBlindedBlock: WithVersion( + (fork: ForkName) => + isForkBlobs(fork) + ? ssz.allForksBlobs[fork].ExecutionPayloadAndBlobsBundle + : isForkExecution(fork) + ? ssz.allForksExecution[fork].ExecutionPayload + : ssz.bellatrix.ExecutionPayload ), }; } diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 1406848c5fe4..d1dc1717cbd4 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -310,14 +310,17 @@ export function getValidatorApi({ const version = config.getForkName(block.slot); if (isForkBlobs(version)) { - if (!isBlindedBlockContents(block)) { - throw Error(`Expected BlockContents response at fork=${version}`); + const blockHash = toHex((block as bellatrix.BlindedBeaconBlock).body.executionPayloadHeader.blockHash); + const {blindedBlobSidecars} = chain.producedBlindedBlobSidecarsCache.get(blockHash) ?? {}; + if (blindedBlobSidecars === undefined) { + throw Error("blobSidecars missing in cache"); } - return {data: block, version, executionPayloadValue}; + return { + data: {blindedBlock: block, blindedBlobSidecars} as allForks.BlindedBlockContents, + version, + executionPayloadValue, + }; } else { - if (isBlindedBlockContents(block)) { - throw Error(`Invalid BlockContents response at fork=${version}`); - } return {data: block, version, executionPayloadValue}; } } finally { diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 6d1fb945198f..d261e79bd974 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -135,7 +135,7 @@ export class BeaconChain implements IBeaconChain { readonly producedBlobSidecarsCache = new Map(); readonly producedBlindedBlobSidecarsCache = new Map< BlockHash, - {blobSidecars: deneb.BlindedBlobSidecars; slot: Slot} + {blindedBlobSidecars: deneb.BlindedBlobSidecars; slot: Slot} >(); // Cache payload from the local execution so that produceBlindedBlock or produceBlockV3 and @@ -531,7 +531,7 @@ export class BeaconChain implements IBeaconChain { // publishing the blinded block's full version if (blobs.type === BlobsResultType.produced) { // body is of full type here - const blockHash = toHex((block as bellatrix.BeaconBlock).body.executionPayload.blockHash); + const blockHash = blobs.blockHash; const blobSidecars = blobs.blobSidecars.map((blobSidecar) => ({ ...blobSidecar, blockRoot, @@ -545,6 +545,22 @@ export class BeaconChain implements IBeaconChain { this.producedBlobSidecarsCache, this.opts.maxCachedBlobSidecars ?? DEFAULT_MAX_CACHED_BLOB_SIDECARS ); + } else if (blobs.type === BlobsResultType.blinded) { + // body is of blinded type here + const blockHash = blobs.blockHash; + const blindedBlobSidecars = blobs.blobSidecars.map((blindedBlobSidecar) => ({ + ...blindedBlobSidecar, + blockRoot, + slot, + blockParentRoot: parentBlockRoot, + proposerIndex, + })); + + this.producedBlindedBlobSidecarsCache.set(blockHash, {blindedBlobSidecars, slot}); + pruneSetToMax( + this.producedBlindedBlobSidecarsCache, + this.opts.maxCachedBlobSidecars ?? DEFAULT_MAX_CACHED_BLOB_SIDECARS + ); } return {block, executionPayloadValue}; diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index c2d2abc8f2fb..bc643aa78d76 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -94,8 +94,11 @@ export interface IBeaconChain { readonly beaconProposerCache: BeaconProposerCache; readonly checkpointBalancesCache: CheckpointBalancesCache; readonly producedBlobSidecarsCache: Map; - readonly producedBlindedBlobSidecarsCache: Map; readonly producedBlockRoot: Map; + readonly producedBlindedBlobSidecarsCache: Map< + BlockHash, + {blindedBlobSidecars: deneb.BlindedBlobSidecars; slot: Slot} + >; readonly producedBlindedBlockRoot: Set; readonly opts: IChainOptions; diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index 5224aae65035..acefbbf765a1 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -35,7 +35,10 @@ import {PayloadId, IExecutionEngine, IExecutionBuilder, PayloadAttributes} from 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"; +import { + validateBlobsAndKzgCommitments, + validateBlindedBlobsAndKzgCommitments, +} from "./validateBlobsAndKzgCommitments.js"; // Time to provide the EL to generate a payload from new payload id const PAYLOAD_GENERATION_TIME_MS = 500; @@ -70,8 +73,9 @@ export enum BlobsResultType { } export type BlobsResult = - | {type: BlobsResultType.preDeneb | BlobsResultType.blinded} - | {type: BlobsResultType.produced; blobSidecars: deneb.BlobSidecars; blockHash: RootHex}; + | {type: BlobsResultType.preDeneb} + | {type: BlobsResultType.produced; blobSidecars: deneb.BlobSidecars; blockHash: RootHex} + | {type: BlobsResultType.blinded; blobSidecars: deneb.BlindedBlobSidecars; blockHash: RootHex}; export async function produceBlockBody( this: BeaconChain, @@ -195,16 +199,47 @@ export async function produceBlockBody( ); (blockBody as allForks.BlindedBeaconBlockBody).executionPayloadHeader = builderRes.header; executionPayloadValue = builderRes.executionPayloadValue; - this.logger.verbose("Fetched execution payload header from builder", {slot: blockSlot, executionPayloadValue}); + + const fetchedTime = Date.now() / 1000 - computeTimeAtSlot(this.config, blockSlot, this.genesisTime); + const prepType = "blinded"; + this.metrics?.blockPayload.payloadFetchedTime.observe({prepType}, fetchedTime); + this.logger.verbose("Fetched execution payload header from builder", { + slot: blockSlot, + executionPayloadValue, + prepType, + fetchedTime, + }); + if (ForkSeq[fork] >= ForkSeq.deneb) { - const {blobKzgCommitments} = builderRes; - if (blobKzgCommitments === undefined) { - throw Error(`Invalid builder getHeader response for fork=${fork}, missing blobKzgCommitments`); + const {blindedBlobsBundle} = builderRes; + if (blindedBlobsBundle === undefined) { + throw Error(`Invalid builder getHeader response for fork=${fork}, missing blindedBlobsBundle`); } - (blockBody as deneb.BlindedBeaconBlockBody).blobKzgCommitments = blobKzgCommitments; - blobsResult = {type: BlobsResultType.blinded}; - Object.assign(logMeta, {blobs: blobKzgCommitments.length}); + // validate blindedBlobsBundle + if (this.opts.sanityCheckExecutionEngineBlobs) { + validateBlindedBlobsAndKzgCommitments(builderRes.header, blindedBlobsBundle); + } + + (blockBody as deneb.BlindedBeaconBlockBody).blobKzgCommitments = blindedBlobsBundle.commitments; + const blockHash = toHex(builderRes.header.blockHash); + + const blobSidecars = Array.from({length: blindedBlobsBundle.blobRoots.length}, (_v, index) => { + const blobRoot = blindedBlobsBundle.blobRoots[index]; + const commitment = blindedBlobsBundle.commitments[index]; + const proof = blindedBlobsBundle.proofs[index]; + const blindedBlobSidecar = { + index, + blobRoot, + kzgProof: proof, + kzgCommitment: commitment, + }; + // Other fields will be injected after postState is calculated + return blindedBlobSidecar; + }) as deneb.BlindedBlobSidecars; + blobsResult = {type: BlobsResultType.blinded, blobSidecars, blockHash}; + + Object.assign(logMeta, {blobs: blindedBlobsBundle.commitments.length}); } else { blobsResult = {type: BlobsResultType.preDeneb}; } @@ -270,7 +305,7 @@ export async function produceBlockBody( throw Error(`Missing blobsBundle response from getPayload at fork=${fork}`); } - // Optionally sanity-check that the KZG commitments match the versioned hashes in the transactions + // validate blindedBlobsBundle if (this.opts.sanityCheckExecutionEngineBlobs) { validateBlobsAndKzgCommitments(executionPayload, blobsBundle); } @@ -288,6 +323,7 @@ export async function produceBlockBody( kzgProof: proof, kzgCommitment: commitment, }; + // Other fields will be injected after postState is calculated return blobSidecar; }) as deneb.BlobSidecars; blobsResult = {type: BlobsResultType.produced, blobSidecars, blockHash}; @@ -443,21 +479,19 @@ async function prepareExecutionPayloadHeader( ): Promise<{ header: allForks.ExecutionPayloadHeader; executionPayloadValue: Wei; - blobKzgCommitments?: deneb.BlobKzgCommitments; + blindedBlobsBundle?: deneb.BlindedBlobsBundle; }> { if (!chain.executionBuilder) { throw Error("executionBuilder required"); } const parentHashRes = await getExecutionPayloadParentHash(chain, state); - if (parentHashRes.isPremerge) { - // TODO: Is this okay? throw Error("Execution builder disabled pre-merge"); } const {parentHash} = parentHashRes; - return chain.executionBuilder.getHeader(state.slot, parentHash, proposerPubKey); + return chain.executionBuilder.getHeader(fork, state.slot, parentHash, proposerPubKey); } export async function getExecutionPayloadParentHash( diff --git a/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts b/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts index 54e90672d189..0d00d0c8bd72 100644 --- a/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts +++ b/packages/beacon-node/src/chain/produceBlock/validateBlobsAndKzgCommitments.ts @@ -1,4 +1,4 @@ -import {allForks} from "@lodestar/types"; +import {allForks, deneb} from "@lodestar/types"; import {BlobsBundle} from "../../execution/index.js"; /** @@ -13,3 +13,15 @@ export function validateBlobsAndKzgCommitments(payload: allForks.ExecutionPayloa ); } } + +export function validateBlindedBlobsAndKzgCommitments( + payload: allForks.ExecutionPayloadHeader, + blindedBlobsBundle: deneb.BlindedBlobsBundle +): void { + // sanity-check that the KZG commitments match the blobs (as produced by the execution engine) + if (blindedBlobsBundle.blobRoots.length !== blindedBlobsBundle.commitments.length) { + throw Error( + `BlindedBlobs bundle blobs len ${blindedBlobsBundle.blobRoots.length} != commitments len ${blindedBlobsBundle.commitments.length}` + ); + } +} diff --git a/packages/beacon-node/src/execution/builder/http.ts b/packages/beacon-node/src/execution/builder/http.ts index 9a423e0f832d..5ee9c271facb 100644 --- a/packages/beacon-node/src/execution/builder/http.ts +++ b/packages/beacon-node/src/execution/builder/http.ts @@ -1,8 +1,19 @@ import {byteArrayEquals, toHexString} from "@chainsafe/ssz"; -import {allForks, bellatrix, Slot, Root, BLSPubkey, ssz, deneb, Wei} from "@lodestar/types"; +import { + allForks, + bellatrix, + Slot, + Root, + BLSPubkey, + ssz, + deneb, + Wei, + isExecutionPayloadAndBlobsBundle, + isSignedBlindedBlockContents, +} from "@lodestar/types"; import {ChainForkConfig} from "@lodestar/config"; import {getClient, Api as BuilderApi} from "@lodestar/api/builder"; -import {SLOTS_PER_EPOCH} from "@lodestar/params"; +import {SLOTS_PER_EPOCH, ForkExecution} from "@lodestar/params"; import {ApiError} from "@lodestar/api"; import {Metrics} from "../../metrics/metrics.js"; @@ -91,27 +102,53 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { } async getHeader( + fork: ForkExecution, slot: Slot, parentHash: Root, proposerPubKey: BLSPubkey ): Promise<{ header: allForks.ExecutionPayloadHeader; executionPayloadValue: Wei; - blobKzgCommitments?: deneb.BlobKzgCommitments; + blindedBlobsBundle?: deneb.BlindedBlobsBundle; }> { const res = await this.api.getHeader(slot, parentHash, proposerPubKey); ApiError.assert(res, "execution.builder.getheader"); const {header, value: executionPayloadValue} = res.response.data.message; - const {blobKzgCommitments} = res.response.data.message as {blobKzgCommitments?: deneb.BlobKzgCommitments}; - return {header, executionPayloadValue, blobKzgCommitments}; + const {blindedBlobsBundle} = res.response.data.message as deneb.BuilderBid; + return {header, executionPayloadValue, blindedBlobsBundle}; } - async submitBlindedBlock(signedBlock: allForks.SignedBlindedBeaconBlock): Promise { - const res = await this.api.submitBlindedBlock(signedBlock); + async submitBlindedBlock( + signedBlindedBlockOrContents: allForks.SignedBlindedBeaconBlockOrContents + ): Promise { + const res = await this.api.submitBlindedBlock(signedBlindedBlockOrContents); ApiError.assert(res, "execution.builder.submitBlindedBlock"); - const executionPayload = res.response.data; - const expectedTransactionsRoot = signedBlock.message.body.executionPayloadHeader.transactionsRoot; - const actualTransactionsRoot = ssz.bellatrix.Transactions.hashTreeRoot(res.response.data.transactions); + const {data} = res.response; + + let executionPayload: allForks.ExecutionPayload; + let blobsBundle: deneb.BlobsBundle | null; + + if (isExecutionPayloadAndBlobsBundle(data)) { + executionPayload = data.executionPayload; + blobsBundle = data.blobsBundle; + } else { + executionPayload = data; + blobsBundle = null; + } + + let signedBlindedBlock: allForks.SignedBlindedBeaconBlock; + let signedBlindedBlobSidecars: deneb.SignedBlindedBlobSidecars | null; + if (isSignedBlindedBlockContents(signedBlindedBlockOrContents)) { + signedBlindedBlock = signedBlindedBlockOrContents.signedBlindedBlock; + signedBlindedBlobSidecars = signedBlindedBlockOrContents.signedBlindedBlobSidecars; + } else { + signedBlindedBlock = signedBlindedBlockOrContents; + signedBlindedBlobSidecars = null; + } + + // some validations for execution payload + const expectedTransactionsRoot = signedBlindedBlock.message.body.executionPayloadHeader.transactionsRoot; + const actualTransactionsRoot = ssz.bellatrix.Transactions.hashTreeRoot(executionPayload.transactions); if (!byteArrayEquals(expectedTransactionsRoot, actualTransactionsRoot)) { throw Error( `Invalid transactionsRoot of the builder payload, expected=${toHexString( @@ -119,10 +156,38 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { )}, actual=${toHexString(actualTransactionsRoot)}` ); } - const fullySignedBlock: bellatrix.SignedBeaconBlock = { - ...signedBlock, - message: {...signedBlock.message, body: {...signedBlock.message.body, executionPayload}}, + + const signedBlock: bellatrix.SignedBeaconBlock = { + ...signedBlindedBlock, + message: {...signedBlindedBlock.message, body: {...signedBlindedBlock.message.body, executionPayload}}, }; - return fullySignedBlock; + + if (signedBlindedBlobSidecars !== null) { + if (blobsBundle === null) { + throw Error("Invalid Builder response with missing blobsBundle for deneb+ forks"); + } + if (signedBlindedBlobSidecars.length !== blobsBundle.blobs.length) { + throw Error( + `Invalid number of blobs returned by builder, expected=$${signedBlindedBlobSidecars.length} received=${blobsBundle.blobs.length}` + ); + } + const signedBlobSidecars = signedBlindedBlobSidecars.map((_v, i) => { + // signedBlindedBlobSidecars and blobsBundle can't be null as we checked above but + // typescript can't seem to figure that out + if (signedBlindedBlobSidecars === null || blobsBundle === null) { + throw Error("Internal Error - signedBlindedBlobSidecars or blobsBundle is null"); + } + + const signedBlindedBlobSidecar = signedBlindedBlobSidecars[i]; + const blob = blobsBundle.blobs[i]; + return {signature: signedBlindedBlobSidecar.signature, message: {...signedBlindedBlobSidecar.message, blob}}; + }); + return {signedBlock, signedBlobSidecars}; + } else { + if (blobsBundle !== null) { + throw Error("Invalid Builder response with blobsBundle for deneb- forks"); + } + return signedBlock; + } } } diff --git a/packages/beacon-node/src/execution/builder/interface.ts b/packages/beacon-node/src/execution/builder/interface.ts index 2bc7a19765a0..e9a2cabb69ef 100644 --- a/packages/beacon-node/src/execution/builder/interface.ts +++ b/packages/beacon-node/src/execution/builder/interface.ts @@ -1,4 +1,5 @@ import {allForks, bellatrix, Root, Slot, BLSPubkey, deneb, Wei} from "@lodestar/types"; +import {ForkExecution} from "@lodestar/params"; export interface IExecutionBuilder { /** @@ -17,13 +18,16 @@ export interface IExecutionBuilder { checkStatus(): Promise; registerValidator(registrations: bellatrix.SignedValidatorRegistrationV1[]): Promise; getHeader( + fork: ForkExecution, slot: Slot, parentHash: Root, proposerPubKey: BLSPubkey ): Promise<{ header: allForks.ExecutionPayloadHeader; executionPayloadValue: Wei; - blobKzgCommitments?: deneb.BlobKzgCommitments; + blindedBlobsBundle?: deneb.BlindedBlobsBundle; }>; - submitBlindedBlock(signedBlock: allForks.SignedBlindedBeaconBlock): Promise; + submitBlindedBlock( + signedBlock: allForks.SignedBlindedBeaconBlockOrContents + ): Promise; } diff --git a/packages/types/src/allForks/sszTypes.ts b/packages/types/src/allForks/sszTypes.ts index 023d7bc86369..463e5c57bd0d 100644 --- a/packages/types/src/allForks/sszTypes.ts +++ b/packages/types/src/allForks/sszTypes.ts @@ -156,5 +156,6 @@ export const allForksBlobs = { deneb: { BlobSidecar: deneb.BlobSidecar, BlindedBlobSidecar: deneb.BlindedBlobSidecar, + ExecutionPayloadAndBlobsBundle: deneb.ExecutionPayloadAndBlobsBundle, }, }; diff --git a/packages/types/src/allForks/types.ts b/packages/types/src/allForks/types.ts index a525820aac02..01c597b8a245 100644 --- a/packages/types/src/allForks/types.ts +++ b/packages/types/src/allForks/types.ts @@ -96,6 +96,7 @@ export type SignedBlindedBeaconBlockOrContents = SignedBlindedBeaconBlock | Sign export type BuilderBid = bellatrix.BuilderBid | capella.BuilderBid | deneb.BuilderBid; export type SignedBuilderBid = bellatrix.SignedBuilderBid | capella.SignedBuilderBid | deneb.SignedBuilderBid; +export type ExecutionPayloadAndBlobsBundle = deneb.ExecutionPayloadAndBlobsBundle; export type LightClientHeader = altair.LightClientHeader | capella.LightClientHeader | deneb.LightClientHeader; export type LightClientBootstrap = @@ -308,4 +309,5 @@ export type AllForksLightClientSSZTypes = { export type AllForksBlobsSSZTypes = { BlobSidecar: AllForksTypeOf; BlindedBlobSidecar: AllForksTypeOf; + ExecutionPayloadAndBlobsBundle: AllForksTypeOf; }; diff --git a/packages/types/src/deneb/sszTypes.ts b/packages/types/src/deneb/sszTypes.ts index a527cf3b4f48..96509d1d898b 100644 --- a/packages/types/src/deneb/sszTypes.ts +++ b/packages/types/src/deneb/sszTypes.ts @@ -149,6 +149,15 @@ export const SignedBlobSidecar = new ContainerType( ); export const SignedBlobSidecars = new ListCompositeType(SignedBlobSidecar, MAX_BLOB_COMMITMENTS_PER_BLOCK); +export const BlobsBundle = new ContainerType( + { + commitments: BlobKzgCommitments, + proofs: KZGProofs, + blobs: Blobs, + }, + {typeName: "BlobsBundle", jsonCase: "eth2"} +); + export const BlindedBlobSidecar = new ContainerType( { blockRoot: Root, @@ -204,12 +213,21 @@ export const SignedBlindedBeaconBlock = new ContainerType( {typeName: "SignedBlindedBeaconBlock", jsonCase: "eth2"} ); +export const BlindedBlobsBundle = new ContainerType( + { + commitments: BlobKzgCommitments, + proofs: KZGProofs, + blobRoots: BlindedBlobs, + }, + {typeName: "BlindedBlobsBundle", jsonCase: "eth2"} +); + export const BuilderBid = new ContainerType( { header: ExecutionPayloadHeader, + blindedBlobsBundle: BlindedBlobsBundle, value: UintBn256, pubkey: BLSPubkey, - blobKzgCommitments: BlobKzgCommitments, }, {typeName: "BuilderBid", jsonCase: "eth2"} ); @@ -222,6 +240,14 @@ export const SignedBuilderBid = new ContainerType( {typeName: "SignedBuilderBid", jsonCase: "eth2"} ); +export const ExecutionPayloadAndBlobsBundle = new ContainerType( + { + executionPayload: ExecutionPayload, + blobsBundle: BlobsBundle, + }, + {typeName: "ExecutionPayloadAndBlobsBundle", jsonCase: "eth2"} +); + // We don't spread capella.BeaconState fields since we need to replace // latestExecutionPayloadHeader and we cannot keep order doing that export const BeaconState = new ContainerType( diff --git a/packages/types/src/deneb/types.ts b/packages/types/src/deneb/types.ts index 93ea514aea75..1d6eb5fca5aa 100644 --- a/packages/types/src/deneb/types.ts +++ b/packages/types/src/deneb/types.ts @@ -16,6 +16,8 @@ export type SignedBlobSidecar = ValueOf; export type SignedBlobSidecars = ValueOf; export type SignedBlindedBlobSidecar = ValueOf; export type SignedBlindedBlobSidecars = ValueOf; +export type ExecutionPayloadAndBlobsBundle = ValueOf; +export type BlobsBundle = ValueOf; export type BlobKzgCommitments = ValueOf; export type KZGProofs = ValueOf; @@ -40,6 +42,7 @@ export type SignedBlindedBeaconBlock = ValueOf; export type BuilderBid = ValueOf; export type SignedBuilderBid = ValueOf; export type SSEPayloadAttributes = ValueOf; diff --git a/packages/types/src/utils/typeguards.ts b/packages/types/src/utils/typeguards.ts index a3e4393c51cb..0b9bee97d17a 100644 --- a/packages/types/src/utils/typeguards.ts +++ b/packages/types/src/utils/typeguards.ts @@ -16,6 +16,8 @@ import { SignedBlockContents, SignedBeaconBlock, SignedBlindedBeaconBlockOrContents, + ExecutionPayload, + ExecutionPayloadAndBlobsBundle, } from "../allForks/types.js"; import {ts as deneb} from "../deneb/index.js"; @@ -67,3 +69,9 @@ export function isSignedBlindedBlockContents( ): data is SignedBlindedBlockContents { return (data as SignedBlindedBlockContents).signedBlindedBlobSidecars !== undefined; } + +export function isExecutionPayloadAndBlobsBundle( + data: ExecutionPayload | ExecutionPayloadAndBlobsBundle +): data is ExecutionPayloadAndBlobsBundle { + return (data as ExecutionPayloadAndBlobsBundle).blobsBundle !== undefined; +}