Skip to content

Commit

Permalink
feat: enable builder proposals post deneb with blobs
Browse files Browse the repository at this point in the history
implement missing blindedblock publishing

remove the throw
  • Loading branch information
g11tech committed Oct 28, 2023
1 parent 2b5935a commit a5f1ef4
Show file tree
Hide file tree
Showing 15 changed files with 242 additions and 51 deletions.
22 changes: 15 additions & 7 deletions packages/api/src/builder/routes.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
>
>;
Expand Down Expand Up @@ -84,8 +87,13 @@ export function getReturnTypes(): ReturnTypes<Api> {
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<allForks.ExecutionPayload | allForks.ExecutionPayloadAndBlobsBundle>(
(fork: ForkName) =>
isForkBlobs(fork)
? ssz.allForksBlobs[fork].ExecutionPayloadAndBlobsBundle
: isForkExecution(fork)
? ssz.allForksExecution[fork].ExecutionPayload
: ssz.bellatrix.ExecutionPayload
),
};
}
4 changes: 0 additions & 4 deletions packages/beacon-node/src/api/impl/beacon/blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,10 +422,6 @@ async function reconstructBuilderBlockOrContents(
signedBlindedBlockOrContents: allForks.SignedBlindedBeaconBlockOrContents,
logCtx: Record<string, LogDataBasic>
): Promise<allForks.SignedBeaconBlockOrContents> {
// Mechanism for blobs & blocks on builder is implemenented separately in a followup deneb-builder PR
if (isSignedBlindedBlockContents(signedBlindedBlockOrContents)) {
throw Error("exeutionBuilder not yet implemented for deneb+ forks");
}
const executionBuilder = chain.executionBuilder;
if (!executionBuilder) {
throw Error("exeutionBuilder required to publish SignedBlindedBeaconBlock");
Expand Down
15 changes: 9 additions & 6 deletions packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
26 changes: 25 additions & 1 deletion packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export class BeaconChain implements IBeaconChain {
readonly checkpointBalancesCache: CheckpointBalancesCache;
/** Map keyed by executionPayload.blockHash of the block for those blobs */
readonly producedBlobSidecarsCache = new Map<BlockHash, deneb.BlobSidecars>();
readonly producedBlindedBlobSidecarsCache = new Map<BlockHash, deneb.BlindedBlobSidecars>();

// Cache payload from the local execution so that produceBlindedBlock or produceBlockV3 and
// send and get signed/published blinded versions which beacon can assemble into full before
Expand Down Expand Up @@ -522,7 +523,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,
Expand All @@ -533,6 +534,21 @@ export class BeaconChain implements IBeaconChain {

this.producedBlobSidecarsCache.set(blockHash, blobSidecars);
this.metrics?.blockProductionCaches.producedBlobSidecarsCache.set(this.producedBlobSidecarsCache.size);
} 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);
this.metrics?.blockProductionCaches.producedBlindedBlobSidecarsCache.set(
this.producedBlindedBlobSidecarsCache.size
);
}

return {block, executionPayloadValue};
Expand Down Expand Up @@ -792,6 +808,14 @@ export class BeaconChain implements IBeaconChain {
this.opts.maxCachedBlobSidecars ?? DEFAULT_MAX_CACHED_BLOB_SIDECARS
);
this.metrics?.blockProductionCaches.producedBlobSidecarsCache.set(this.producedBlobSidecarsCache.size);

pruneSetToMax(
this.producedBlindedBlobSidecarsCache,
this.opts.maxCachedBlobSidecars ?? DEFAULT_MAX_CACHED_BLOB_SIDECARS
);
this.metrics?.blockProductionCaches.producedBlindedBlobSidecarsCache.set(
this.producedBlindedBlobSidecarsCache.size
);
}

const metrics = this.metrics;
Expand Down
1 change: 1 addition & 0 deletions packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export interface IBeaconChain {
readonly checkpointBalancesCache: CheckpointBalancesCache;
readonly producedBlobSidecarsCache: Map<BlockHash, deneb.BlobSidecars>;
readonly producedBlockRoot: Map<RootHex, allForks.ExecutionPayload | null>;
readonly producedBlindedBlobSidecarsCache: Map<BlockHash, deneb.BlindedBlobSidecars>;
readonly producedBlindedBlockRoot: Set<RootHex>;
readonly opts: IChainOptions;

Expand Down
64 changes: 49 additions & 15 deletions packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<T extends BlockType>(
this: BeaconChain,
Expand Down Expand Up @@ -195,16 +199,47 @@ export async function produceBlockBody<T extends BlockType>(
);
(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};
}
Expand Down Expand Up @@ -270,7 +305,7 @@ export async function produceBlockBody<T extends BlockType>(
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);
}
Expand All @@ -288,6 +323,7 @@ export async function produceBlockBody<T extends BlockType>(
kzgProof: proof,
kzgCommitment: commitment,
};
// Other fields will be injected after postState is calculated
return blobSidecar;
}) as deneb.BlobSidecars;
blobsResult = {type: BlobsResultType.produced, blobSidecars, blockHash};
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {allForks} from "@lodestar/types";
import {allForks, deneb} from "@lodestar/types";
import {BlobsBundle} from "../../execution/index.js";

/**
Expand All @@ -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}`
);
}
}
Loading

0 comments on commit a5f1ef4

Please sign in to comment.