Skip to content

Commit

Permalink
feat: enable builder proposals post deneb with blobs
Browse files Browse the repository at this point in the history
  • Loading branch information
g11tech committed Sep 4, 2023
1 parent 615bd59 commit ddba07e
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 45 deletions.
17 changes: 10 additions & 7 deletions packages/beacon-node/src/api/impl/validator/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {fromHexString, toHexString} from "@chainsafe/ssz";
import {routes, ServerApi, BlockContents, isBlindedBlockContents} from "@lodestar/api";
import {routes, ServerApi, BlockContents, BlindedBlockContents} from "@lodestar/api";
import {
CachedBeaconStateAllForks,
computeStartSlotAtEpoch,
Expand Down Expand Up @@ -305,14 +305,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 BlindedBlockContents,
version,
executionPayloadValue,
};
} else {
if (isBlindedBlockContents(block)) {
throw Error(`Invalid BlockContents response at fork=${version}`);
}
return {data: block, version, executionPayloadValue};
}
} finally {
Expand Down
34 changes: 19 additions & 15 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,7 @@ import {
PubkeyIndexMap,
} from "@lodestar/state-transition";
import {BeaconConfig} from "@lodestar/config";
import {
allForks,
UintNum64,
Root,
phase0,
Slot,
RootHex,
Epoch,
ValidatorIndex,
deneb,
Wei,
bellatrix,
} from "@lodestar/types";
import {allForks, UintNum64, Root, phase0, Slot, RootHex, Epoch, ValidatorIndex, deneb, Wei} from "@lodestar/types";
import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
import {ProcessShutdownCallback} from "@lodestar/validator";
import {Logger, isErrorAborted, pruneSetToMax, sleep, toHex} from "@lodestar/utils";
Expand Down Expand Up @@ -135,7 +123,7 @@ export class BeaconChain implements IBeaconChain {
readonly producedBlobSidecarsCache = new Map<BlockHash, {blobSidecars: deneb.BlobSidecars; slot: Slot}>();
readonly producedBlindedBlobSidecarsCache = new Map<
BlockHash,
{blobSidecars: deneb.BlindedBlobSidecars; slot: Slot}
{blindedBlobSidecars: deneb.BlindedBlobSidecars; slot: Slot}
>();

readonly producedBlockRoot = new Set<RootHex>();
Expand Down Expand Up @@ -521,7 +509,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 @@ -535,6 +523,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};
Expand Down
5 changes: 4 additions & 1 deletion packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,10 @@ export interface IBeaconChain {
readonly beaconProposerCache: BeaconProposerCache;
readonly checkpointBalancesCache: CheckpointBalancesCache;
readonly producedBlobSidecarsCache: Map<BlockHash, {blobSidecars: deneb.BlobSidecars; slot: Slot}>;
readonly producedBlindedBlobSidecarsCache: Map<BlockHash, {blobSidecars: deneb.BlindedBlobSidecars; slot: Slot}>;
readonly producedBlindedBlobSidecarsCache: Map<
BlockHash,
{blindedBlobSidecars: deneb.BlindedBlobSidecars; slot: Slot}
>;
readonly producedBlockRoot: Set<RootHex>;
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}`
);
}
}
9 changes: 5 additions & 4 deletions packages/beacon-node/src/execution/builder/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {byteArrayEquals, toHexString} from "@chainsafe/ssz";
import {allForks, bellatrix, Slot, Root, BLSPubkey, ssz, deneb, Wei} 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";
Expand Down Expand Up @@ -88,19 +88,20 @@ 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<allForks.SignedBeaconBlock> {
Expand Down
4 changes: 3 additions & 1 deletion packages/beacon-node/src/execution/builder/interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {allForks, bellatrix, Root, Slot, BLSPubkey, deneb, Wei} from "@lodestar/types";
import {ForkExecution} from "@lodestar/params";

export interface IExecutionBuilder {
/**
Expand All @@ -17,13 +18,14 @@ export interface IExecutionBuilder {
checkStatus(): Promise<void>;
registerValidator(registrations: bellatrix.SignedValidatorRegistrationV1[]): Promise<void>;
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<allForks.SignedBeaconBlock>;
}
11 changes: 10 additions & 1 deletion packages/types/src/deneb/sszTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,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"}
);
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/deneb/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export type SignedBlindedBeaconBlock = ValueOf<typeof ssz.SignedBlindedBeaconBlo

export type FullOrBlindedExecutionPayload = ExecutionPayload | ExecutionPayloadHeader;

export type BlindedBlobsBundle = ValueOf<typeof ssz.BlindedBlobsBundle>;
export type BuilderBid = ValueOf<typeof ssz.BuilderBid>;
export type SignedBuilderBid = ValueOf<typeof ssz.SignedBuilderBid>;
export type SSEPayloadAttributes = ValueOf<typeof ssz.SSEPayloadAttributes>;
Expand Down

0 comments on commit ddba07e

Please sign in to comment.