Skip to content

Commit

Permalink
feat: decouple the deneb blob and block production (#5492)
Browse files Browse the repository at this point in the history
* refactor: decouple the deneb blob and block production

add some breathing room

reduce diff

apply feedback

use typeguards

fix typo

update comment

* rebase fixes

* refactor blockcontent types and serializers

* add missing file
  • Loading branch information
g11tech committed May 19, 2023
1 parent 812ac92 commit 402c46f
Show file tree
Hide file tree
Showing 27 changed files with 616 additions and 199 deletions.
64 changes: 44 additions & 20 deletions packages/api/src/beacon/routes/beacon/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ import {
} from "../../../utils/index.js";
import {HttpStatusCode} from "../../../utils/client/httpStatusCode.js";
import {ApiClientResponse} from "../../../interfaces.js";
import {
SignedBlockContents,
SignedBlindedBlockContents,
isSignedBlockContents,
isSignedBlindedBlockContents,
AllForksSignedBlockContentsReqSerializer,
AllForksSignedBlindedBlockContentsReqSerializer,
} from "../../../utils/routes.js";

// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes

Expand Down Expand Up @@ -150,7 +158,7 @@ export type Api = {
* @param requestBody The `SignedBeaconBlock` object composed of `BeaconBlock` object (produced by beacon node) and validator signature.
* @returns any The block was validated successfully and has been broadcast. It has also been integrated into the beacon node's database.
*/
publishBlock(block: allForks.SignedBeaconBlock): Promise<
publishBlock(blockOrContents: allForks.SignedBeaconBlock | SignedBlockContents): Promise<
ApiClientResponse<
{
[HttpStatusCode.OK]: void;
Expand All @@ -163,7 +171,7 @@ export type Api = {
* Publish a signed blinded block by submitting it to the mev relay and patching in the block
* transactions beacon node gets in response.
*/
publishBlindedBlock(block: allForks.SignedBlindedBeaconBlock): Promise<
publishBlindedBlock(blindedBlockOrContents: allForks.SignedBlindedBeaconBlock | SignedBlindedBlockContents): Promise<
ApiClientResponse<
{
[HttpStatusCode.OK]: void;
Expand All @@ -173,14 +181,14 @@ export type Api = {
>
>;
/**
* Get block BlobsSidecar
* Retrieves BlobsSidecar included in requested block.
* Get block BlobSidecar
* Retrieves BlobSidecar included in requested block.
* @param blockId Block identifier.
* Can be one of: "head" (canonical head in node's view), "genesis", "finalized", \<slot\>, \<hex encoded blockRoot with 0x prefix\>.
*/
getBlobsSidecar(blockId: BlockId): Promise<
getBlobSidecars(blockId: BlockId): Promise<
ApiClientResponse<{
[HttpStatusCode.OK]: {executionOptimistic: ExecutionOptimistic; data: deneb.BlobsSidecar};
[HttpStatusCode.OK]: {executionOptimistic: ExecutionOptimistic; data: deneb.BlobSidecars};
}>
>;
};
Expand All @@ -197,7 +205,7 @@ export const routesData: RoutesData<Api> = {
getBlockRoot: {url: "/eth/v1/beacon/blocks/{block_id}/root", method: "GET"},
publishBlock: {url: "/eth/v1/beacon/blocks", method: "POST"},
publishBlindedBlock: {url: "/eth/v1/beacon/blinded_blocks", method: "POST"},
getBlobsSidecar: {url: "/eth/v1/beacon/blobs_sidecars/{block_id}", method: "GET"},
getBlobSidecars: {url: "/eth/v1/beacon/blob_sidecars/{block_id}", method: "GET"},
};

/* eslint-disable @typescript-eslint/naming-convention */
Expand All @@ -213,7 +221,7 @@ export type ReqTypes = {
getBlockRoot: BlockIdOnlyReq;
publishBlock: {body: unknown};
publishBlindedBlock: {body: unknown};
getBlobsSidecar: BlockIdOnlyReq;
getBlobSidecars: BlockIdOnlyReq;
};

export function getReqSerializers(config: ChainForkConfig): ReqSerializers<Api, ReqTypes> {
Expand All @@ -227,21 +235,37 @@ export function getReqSerializers(config: ChainForkConfig): ReqSerializers<Api,
const getSignedBeaconBlockType = (data: allForks.SignedBeaconBlock): allForks.AllForksSSZTypes["SignedBeaconBlock"] =>
config.getForkTypes(data.message.slot).SignedBeaconBlock;

const AllForksSignedBeaconBlock: TypeJson<allForks.SignedBeaconBlock> = {
toJson: (data) => getSignedBeaconBlockType(data).toJson(data),
fromJson: (data) => getSignedBeaconBlockType(data as unknown as allForks.SignedBeaconBlock).fromJson(data),
const AllForksSignedBlockOrContents: TypeJson<allForks.SignedBeaconBlock | SignedBlockContents> = {
toJson: (data) =>
isSignedBlockContents(data)
? AllForksSignedBlockContentsReqSerializer(getSignedBeaconBlockType).toJson(data)
: getSignedBeaconBlockType(data as allForks.SignedBeaconBlock).toJson(data as allForks.SignedBeaconBlock),

fromJson: (data) =>
(data as {signed_block: unknown}).signed_block !== undefined
? AllForksSignedBlockContentsReqSerializer(getSignedBeaconBlockType).fromJson(data)
: getSignedBeaconBlockType(data as allForks.SignedBeaconBlock).fromJson(data),
};

const getSignedBlindedBeaconBlockType = (
data: allForks.SignedBlindedBeaconBlock
): allForks.AllForksBlindedSSZTypes["SignedBeaconBlock"] =>
config.getBlindedForkTypes(data.message.slot).SignedBeaconBlock;

const AllForksSignedBlindedBeaconBlock: TypeJson<allForks.SignedBlindedBeaconBlock> = {
toJson: (data) => getSignedBlindedBeaconBlockType(data).toJson(data),
fromJson: (data) =>
getSignedBlindedBeaconBlockType(data as unknown as allForks.SignedBlindedBeaconBlock).fromJson(data),
};
const AllForksSignedBlindedBlockOrContents: TypeJson<allForks.SignedBlindedBeaconBlock | SignedBlindedBlockContents> =
{
toJson: (data) =>
isSignedBlindedBlockContents(data)
? AllForksSignedBlindedBlockContentsReqSerializer(getSignedBlindedBeaconBlockType).toJson(data)
: getSignedBlindedBeaconBlockType(data as allForks.SignedBlindedBeaconBlock).toJson(
data as allForks.SignedBlindedBeaconBlock
),

fromJson: (data) =>
(data as {signed_blinded_block: unknown}).signed_blinded_block !== undefined
? AllForksSignedBlindedBlockContentsReqSerializer(getSignedBlindedBeaconBlockType).fromJson(data)
: getSignedBlindedBeaconBlockType(data as allForks.SignedBlindedBeaconBlock).fromJson(data),
};

return {
getBlock: blockIdOnlyReq,
Expand All @@ -254,9 +278,9 @@ export function getReqSerializers(config: ChainForkConfig): ReqSerializers<Api,
schema: {query: {slot: Schema.Uint, parent_root: Schema.String}},
},
getBlockRoot: blockIdOnlyReq,
publishBlock: reqOnlyBody(AllForksSignedBeaconBlock, Schema.Object),
publishBlindedBlock: reqOnlyBody(AllForksSignedBlindedBeaconBlock, Schema.Object),
getBlobsSidecar: blockIdOnlyReq,
publishBlock: reqOnlyBody(AllForksSignedBlockOrContents, Schema.Object),
publishBlindedBlock: reqOnlyBody(AllForksSignedBlindedBlockOrContents, Schema.Object),
getBlobSidecars: blockIdOnlyReq,
};
}

Expand All @@ -278,6 +302,6 @@ export function getReturnTypes(): ReturnTypes<Api> {
getBlockHeader: ContainerDataExecutionOptimistic(BeaconHeaderResType),
getBlockHeaders: ContainerDataExecutionOptimistic(ArrayOf(BeaconHeaderResType)),
getBlockRoot: ContainerDataExecutionOptimistic(RootContainer),
getBlobsSidecar: ContainerDataExecutionOptimistic(ssz.deneb.BlobsSidecar),
getBlobSidecars: ContainerDataExecutionOptimistic(ssz.deneb.BlobSidecars),
};
}
35 changes: 25 additions & 10 deletions packages/api/src/beacon/routes/validator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ContainerType, fromHexString, toHexString, Type} from "@chainsafe/ssz";
import {ForkName} from "@lodestar/params";
import {ForkName, isForkBlobs, isForkExecution} from "@lodestar/params";
import {
allForks,
altair,
Expand Down Expand Up @@ -35,6 +35,12 @@ import {
ContainerData,
} from "../../utils/index.js";
import {fromU64Str, fromGraffitiHex, toU64Str, U64Str, toGraffitiHex} from "../../utils/serdes.js";
import {
BlockContents,
BlindedBlockContents,
AllForksBlockContentsResSerializer,
AllForksBlindedBlockContentsResSerializer,
} from "../../utils/routes.js";
import {ExecutionOptimistic} from "./beacon/block.js";

// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes
Expand Down Expand Up @@ -220,7 +226,7 @@ export type Api = {
graffiti: string
): Promise<
ApiClientResponse<
{[HttpStatusCode.OK]: {data: allForks.BeaconBlock; version: ForkName; blockValue: Wei}},
{[HttpStatusCode.OK]: {data: allForks.BeaconBlock | BlockContents; version: ForkName; blockValue: Wei}},
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
>
>;
Expand All @@ -231,7 +237,13 @@ export type Api = {
graffiti: string
): Promise<
ApiClientResponse<
{[HttpStatusCode.OK]: {data: allForks.BlindedBeaconBlock; version: ForkName; blockValue: Wei}},
{
[HttpStatusCode.OK]: {
data: allForks.BlindedBeaconBlock | BlindedBlockContents;
version: ForkName;
blockValue: Wei;
};
},
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
>
>;
Expand Down Expand Up @@ -624,14 +636,17 @@ export function getReturnTypes(): ReturnTypes<Api> {
getProposerDuties: WithDependentRootExecutionOptimistic(ArrayOf(ProposerDuty)),
getSyncCommitteeDuties: ContainerDataExecutionOptimistic(ArrayOf(SyncDuty)),
produceBlock: WithBlockValue(ContainerData(ssz.phase0.BeaconBlock)),
produceBlockV2: WithBlockValue(WithVersion((fork: ForkName) => ssz[fork].BeaconBlock)),
produceBlockV2: WithBlockValue(
WithVersion<allForks.BeaconBlock | BlockContents>((fork: ForkName) =>
isForkBlobs(fork) ? AllForksBlockContentsResSerializer(() => fork) : ssz[fork].BeaconBlock
)
),
produceBlindedBlock: WithBlockValue(
WithVersion((fork: ForkName) => {
if (fork === ForkName.phase0 || fork === ForkName.altair) {
throw Error(`No BlindedBlock for fork ${fork} previous to bellatrix`);
}
return ssz[fork].BlindedBeaconBlock;
})
WithVersion<allForks.BlindedBeaconBlock | BlindedBlockContents>((fork: ForkName) =>
isForkBlobs(fork)
? AllForksBlindedBlockContentsResSerializer(() => fork)
: ssz.allForksBlinded[isForkExecution(fork) ? fork : ForkName.bellatrix].BeaconBlock
)
),
produceAttestationData: ContainerData(ssz.phase0.AttestationData),
produceSyncCommitteeContribution: ContainerData(ssz.altair.SyncCommitteeContribution),
Expand Down
3 changes: 2 additions & 1 deletion packages/api/src/builder/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
import {getReqSerializers as getBeaconReqSerializers} from "../beacon/routes/beacon/block.js";
import {HttpStatusCode} from "../utils/client/httpStatusCode.js";
import {ApiClientResponse} from "../interfaces.js";
import {SignedBlindedBlockContents} from "../utils/routes.js";

export type Api = {
status(): Promise<ApiClientResponse<{[HttpStatusCode.OK]: void}, HttpStatusCode.SERVICE_UNAVAILABLE>>;
Expand All @@ -35,7 +36,7 @@ export type Api = {
>
>;
submitBlindedBlock(
signedBlock: allForks.SignedBlindedBeaconBlock
signedBlock: allForks.SignedBlindedBeaconBlock | SignedBlindedBlockContents
): Promise<
ApiClientResponse<
{[HttpStatusCode.OK]: {data: allForks.ExecutionPayload; version: ForkName}},
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ export {
ApiError,
Metrics,
} from "./utils/client/index.js";
export * from "./utils/routes.js";

// NOTE: Don't export server here so it's not bundled to all consumers
105 changes: 105 additions & 0 deletions packages/api/src/utils/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {allForks, deneb, ssz} from "@lodestar/types";
import {ForkBlobs} from "@lodestar/params";

import {TypeJson} from "./types.js";

export type BlockContents = {block: allForks.BeaconBlock; blobSidecars: deneb.BlobSidecars};
export type SignedBlockContents = {
signedBlock: allForks.SignedBeaconBlock;
signedBlobSidecars: deneb.SignedBlobSidecars;
};

export type BlindedBlockContents = {
blindedBlock: allForks.BlindedBeaconBlock;
blindedBlobSidecars: deneb.BlindedBlobSidecars;
};
export type SignedBlindedBlockContents = {
signedBlindedBlock: allForks.SignedBlindedBeaconBlock;
signedBlindedBlobSidecars: deneb.SignedBlindedBlobSidecars;
};

export function isBlockContents(data: allForks.BeaconBlock | BlockContents): data is BlockContents {
return (data as BlockContents).blobSidecars !== undefined;
}

export function isSignedBlockContents(
data: allForks.SignedBeaconBlock | SignedBlockContents
): data is SignedBlockContents {
return (data as SignedBlockContents).signedBlobSidecars !== undefined;
}

export function isBlindedBlockContents(
data: allForks.BlindedBeaconBlock | BlindedBlockContents
): data is BlindedBlockContents {
return (data as BlindedBlockContents).blindedBlobSidecars !== undefined;
}

export function isSignedBlindedBlockContents(
data: allForks.SignedBlindedBeaconBlock | SignedBlindedBlockContents
): data is SignedBlindedBlockContents {
return (data as SignedBlindedBlockContents).signedBlindedBlobSidecars !== undefined;
}

/* eslint-disable @typescript-eslint/naming-convention */

export function AllForksSignedBlockContentsReqSerializer(
blockSerializer: (data: allForks.SignedBeaconBlock) => TypeJson<allForks.SignedBeaconBlock>
): TypeJson<SignedBlockContents> {
return {
toJson: (data) => ({
signed_block: blockSerializer(data.signedBlock).toJson(data.signedBlock),
signed_blob_sidecars: ssz.deneb.SignedBlobSidecars.toJson(data.signedBlobSidecars),
}),

fromJson: (data: {signed_block: unknown; signed_blob_sidecars: unknown}) => ({
signedBlock: blockSerializer(data.signed_block as allForks.SignedBeaconBlock).fromJson(data.signed_block),
signedBlobSidecars: ssz.deneb.SignedBlobSidecars.fromJson(data.signed_blob_sidecars),
}),
};
}

export function AllForksBlockContentsResSerializer(getType: () => ForkBlobs): TypeJson<BlockContents> {
return {
toJson: (data) => ({
block: (ssz.allForks[getType()].BeaconBlock as allForks.AllForksSSZTypes["BeaconBlock"]).toJson(data.block),
blob_sidecars: ssz.deneb.BlobSidecars.toJson(data.blobSidecars),
}),
fromJson: (data: {block: unknown; blob_sidecars: unknown}) => ({
block: ssz.allForks[getType()].BeaconBlock.fromJson(data.block),
blobSidecars: ssz.deneb.BlobSidecars.fromJson(data.blob_sidecars),
}),
};
}

export function AllForksSignedBlindedBlockContentsReqSerializer(
blockSerializer: (data: allForks.SignedBlindedBeaconBlock) => TypeJson<allForks.SignedBlindedBeaconBlock>
): TypeJson<SignedBlindedBlockContents> {
return {
toJson: (data) => ({
signed_blinded_block: blockSerializer(data.signedBlindedBlock).toJson(data.signedBlindedBlock),
signed_blinded_blob_sidecars: ssz.deneb.SignedBlindedBlobSidecars.toJson(data.signedBlindedBlobSidecars),
}),

fromJson: (data: {signed_blinded_block: unknown; signed_blinded_blob_sidecars: unknown}) => ({
signedBlindedBlock: blockSerializer(data.signed_blinded_block as allForks.SignedBlindedBeaconBlock).fromJson(
data.signed_blinded_block
),
signedBlindedBlobSidecars: ssz.deneb.SignedBlindedBlobSidecars.fromJson(data.signed_blinded_blob_sidecars),
}),
};
}

export function AllForksBlindedBlockContentsResSerializer(getType: () => ForkBlobs): TypeJson<BlindedBlockContents> {
return {
toJson: (data) => ({
blinded_block: (
ssz.allForksBlinded[getType()].BeaconBlock as allForks.AllForksBlindedSSZTypes["BeaconBlock"]
).toJson(data.blindedBlock),
blinded_blob_sidecars: ssz.deneb.BlindedBlobSidecars.toJson(data.blindedBlobSidecars),
}),
fromJson: (data: {blinded_block: unknown; blinded_blob_sidecars: unknown}) => ({
blindedBlock: ssz.allForksBlinded[getType()].BeaconBlock.fromJson(data.blinded_block),
blindedBlobSidecars: ssz.deneb.BlindedBlobSidecars.fromJson(data.blinded_blob_sidecars),
}),
};
}
4 changes: 2 additions & 2 deletions packages/api/test/unit/beacon/testData/beacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ export const testData: GenericServerTestCases<Api> = {
args: [getDefaultBlindedBlock(64)],
res: undefined,
},
getBlobsSidecar: {
getBlobSidecars: {
args: ["head"],
res: {executionOptimistic: true, data: ssz.deneb.BlobsSidecar.defaultValue()},
res: {executionOptimistic: true, data: ssz.deneb.BlobSidecars.defaultValue()},
},

// pool
Expand Down

0 comments on commit 402c46f

Please sign in to comment.