Skip to content

Commit

Permalink
feat: switch blinded and full block production to produce block v3
Browse files Browse the repository at this point in the history
  • Loading branch information
g11tech committed Aug 13, 2023
1 parent f1a2291 commit e7013fe
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 32 deletions.
124 changes: 99 additions & 25 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, isForkBlobs, isForkExecution} from "@lodestar/params";
import {ForkName, ForkBlobs, isForkBlobs, isForkExecution} from "@lodestar/params";
import {
allForks,
altair,
Expand Down Expand Up @@ -27,12 +27,13 @@ import {
ArrayOf,
Schema,
WithVersion,
WithBlockValue,
WithExecutionPayloadValue,
reqOnlyBody,
ReqSerializers,
jsonType,
ContainerDataExecutionOptimistic,
ContainerData,
TypeJson,
} from "../../utils/index.js";
import {fromU64Str, fromGraffitiHex, toU64Str, U64Str, toGraffitiHex} from "../../utils/serdes.js";
import {
Expand All @@ -43,6 +44,19 @@ import {
} from "../../utils/routes.js";
import {ExecutionOptimistic} from "./beacon/block.js";

type ForkPreBlobs = ForkName.phase0 | ForkName.altair | ForkName.bellatrix | ForkName.capella;

type ProduceBlockOrContentsRes =
| {data: allForks.BeaconBlock; version: ForkPreBlobs}
| {data: BlockContents; version: ForkBlobs};
type ProduceBlindedBlockOrContentsRes =
| {data: allForks.BlindedBeaconBlock; version: ForkPreBlobs}
| {data: BlindedBlockContents; version: ForkBlobs};

type ProduceBlockV3Res =
| (ProduceBlockOrContentsRes & {executionPayloadBlinded: false})
| (ProduceBlindedBlockOrContentsRes & {executionPayloadBlinded: true});

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

export type BeaconCommitteeSubscription = {
Expand Down Expand Up @@ -204,7 +218,7 @@ export type Api = {
graffiti: string
): Promise<
ApiClientResponse<
{[HttpStatusCode.OK]: {data: allForks.BeaconBlock; blockValue: Wei}},
{[HttpStatusCode.OK]: {data: allForks.BeaconBlock}},
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
>
>;
Expand All @@ -225,7 +239,33 @@ export type Api = {
graffiti: string
): Promise<
ApiClientResponse<
{[HttpStatusCode.OK]: {data: allForks.BeaconBlock | BlockContents; version: ForkName; blockValue: Wei}},
{[HttpStatusCode.OK]: {data: allForks.BeaconBlock; version: ForkName}},
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
>
>;

/**
* Requests a beacon node to produce a valid block, which can then be signed by a validator.
* Metadata in the response indicates the type of block produced, and the supported types of block
* will be added to as forks progress.
* @param slot The slot for which the block should be proposed.
* @param randaoReveal The validator's randao reveal value.
* @param graffiti Arbitrary data validator wants to include in block.
* @returns any Success response
* @throws ApiError
*/
produceBlockV3(
slot: Slot,
randaoReveal: BLSSignature,
graffiti: string,
skipRandaoVerification?: boolean
): Promise<
ApiClientResponse<
{
[HttpStatusCode.OK]: ProduceBlockV3Res & {
executionPayloadValue: Wei;
};
},
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
>
>;
Expand All @@ -240,7 +280,6 @@ export type Api = {
[HttpStatusCode.OK]: {
data: allForks.BlindedBeaconBlock | BlindedBlockContents;
version: ForkName;
blockValue: Wei;
};
},
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
Expand Down Expand Up @@ -407,6 +446,7 @@ export const routesData: RoutesData<Api> = {
getSyncCommitteeDuties: {url: "/eth/v1/validator/duties/sync/{epoch}", method: "POST"},
produceBlock: {url: "/eth/v1/validator/blocks/{slot}", method: "GET"},
produceBlockV2: {url: "/eth/v2/validator/blocks/{slot}", method: "GET"},
produceBlockV3: {url: "/eth/v3/validator/blocks/{slot}", method: "GET"},
produceBlindedBlock: {url: "/eth/v1/validator/blinded_blocks/{slot}", method: "GET"},
produceAttestationData: {url: "/eth/v1/validator/attestation_data", method: "GET"},
produceSyncCommitteeContribution: {url: "/eth/v1/validator/sync_committee_contribution", method: "GET"},
Expand All @@ -429,6 +469,10 @@ export type ReqTypes = {
getSyncCommitteeDuties: {params: {epoch: Epoch}; body: U64Str[]};
produceBlock: {params: {slot: number}; query: {randao_reveal: string; graffiti: string}};
produceBlockV2: {params: {slot: number}; query: {randao_reveal: string; graffiti: string}};
produceBlockV3: {
params: {slot: number};
query: {randao_reveal: string; graffiti: string; skip_randao_verification?: boolean};
};
produceBlindedBlock: {params: {slot: number}; query: {randao_reveal: string; graffiti: string}};
produceAttestationData: {query: {slot: number; committee_index: number}};
produceSyncCommitteeContribution: {query: {slot: number; subcommittee_index: number; beacon_block_root: string}};
Expand Down Expand Up @@ -484,15 +528,24 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
{jsonCase: "eth2"}
);

const produceBlock: ReqSerializers<Api, ReqTypes>["produceBlock"] = {
writeReq: (slot, randaoReveal, graffiti) => ({
const produceBlock: ReqSerializers<Api, ReqTypes>["produceBlockV3"] = {
writeReq: (slot, randaoReveal, graffiti, skipRandaoVerification) => ({
params: {slot},
query: {randao_reveal: toHexString(randaoReveal), graffiti: toGraffitiHex(graffiti)},
query: {
randao_reveal: toHexString(randaoReveal),
graffiti: toGraffitiHex(graffiti),
skip_randao_verification: skipRandaoVerification,
},
}),
parseReq: ({params, query}) => [params.slot, fromHexString(query.randao_reveal), fromGraffitiHex(query.graffiti)],
parseReq: ({params, query}) => [
params.slot,
fromHexString(query.randao_reveal),
fromGraffitiHex(query.graffiti),
query.skip_randao_verification,
],
schema: {
params: {slot: Schema.UintRequired},
query: {randao_reveal: Schema.StringRequired, graffiti: Schema.String},
query: {randao_reveal: Schema.StringRequired, graffiti: Schema.String, skip_randao_verification: Schema.Boolean},
},
};

Expand Down Expand Up @@ -523,9 +576,10 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
},
},

produceBlock: produceBlock,
produceBlockV2: produceBlock,
produceBlindedBlock: produceBlock,
produceBlock: produceBlock as ReqSerializers<Api, ReqTypes>["produceBlock"],
produceBlockV2: produceBlock as ReqSerializers<Api, ReqTypes>["produceBlock"],
produceBlockV3: produceBlock,
produceBlindedBlock: produceBlock as ReqSerializers<Api, ReqTypes>["produceBlock"],

produceAttestationData: {
writeReq: (index, slot) => ({query: {slot, committee_index: index}}),
Expand Down Expand Up @@ -633,23 +687,43 @@ export function getReturnTypes(): ReturnTypes<Api> {
{jsonCase: "eth2"}
);

const produceBlockOrContents = WithVersion<allForks.BeaconBlock | BlockContents>((fork: ForkName) =>
isForkBlobs(fork) ? AllForksBlockContentsResSerializer(() => fork) : ssz[fork].BeaconBlock
) as TypeJson<ProduceBlockOrContentsRes>;
const produceBlindedBlockOrContents = WithVersion<allForks.BlindedBeaconBlock | BlindedBlockContents>(
(fork: ForkName) =>
isForkBlobs(fork)
? AllForksBlindedBlockContentsResSerializer(() => fork)
: ssz.allForksBlinded[isForkExecution(fork) ? fork : ForkName.bellatrix].BeaconBlock
) as TypeJson<ProduceBlindedBlockOrContentsRes>;

return {
getAttesterDuties: WithDependentRootExecutionOptimistic(ArrayOf(AttesterDuty)),
getProposerDuties: WithDependentRootExecutionOptimistic(ArrayOf(ProposerDuty)),
getSyncCommitteeDuties: ContainerDataExecutionOptimistic(ArrayOf(SyncDuty)),
produceBlock: WithBlockValue(ContainerData(ssz.phase0.BeaconBlock)),
produceBlockV2: WithBlockValue(
WithVersion<allForks.BeaconBlock | BlockContents>((fork: ForkName) =>
isForkBlobs(fork) ? AllForksBlockContentsResSerializer(() => fork) : ssz[fork].BeaconBlock
)
),
produceBlindedBlock: WithBlockValue(
WithVersion<allForks.BlindedBeaconBlock | BlindedBlockContents>((fork: ForkName) =>
isForkBlobs(fork)
? AllForksBlindedBlockContentsResSerializer(() => fork)
: ssz.allForksBlinded[isForkExecution(fork) ? fork : ForkName.bellatrix].BeaconBlock
)

produceBlock: ContainerData(ssz.phase0.BeaconBlock),
produceBlockV2: WithVersion((fork: ForkName) => ssz[fork].BeaconBlock),
produceBlockV3: WithExecutionPayloadValue<ProduceBlockV3Res>({
toJson: (data) =>
data.executionPayloadBlinded === true
? produceBlindedBlockOrContents.toJson(data)
: produceBlockOrContents.toJson(data),
fromJson: (data) => {
if ((data as {execution_payload_blinded: true}).execution_payload_blinded) {
return {executionPayloadBlinded: true, ...produceBlindedBlockOrContents.fromJson(data)};
} else {
return {executionPayloadBlinded: false, ...produceBlockOrContents.fromJson(data)};
}
// (data as {execution_payload_blinded: true}).execution_payload_blinded
// ? {executionPayloadBlinded: true, ...produceBlindedBlockOrContents.fromJson(data)}
// : {executionPayloadBlinded: false, ...produceBlockOrContents.fromJson(data)},
},
}),
produceBlindedBlock: WithVersion(
(fork: ForkName) => ssz.allForksBlinded[isForkExecution(fork) ? fork : ForkName.bellatrix].BeaconBlock
),

produceAttestationData: ContainerData(ssz.phase0.AttestationData),
produceSyncCommitteeContribution: ContainerData(ssz.altair.SyncCommitteeContribution),
getAggregatedAttestation: ContainerData(ssz.phase0.Attestation),
Expand Down
4 changes: 4 additions & 0 deletions packages/api/src/utils/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export enum Schema {
Object,
ObjectArray,
AnyArray,
Boolean,
}

/**
Expand Down Expand Up @@ -68,6 +69,9 @@ function getJsonSchemaItem(schema: Schema): JsonSchema {

case Schema.AnyArray:
return {type: "array"};

case Schema.Boolean:
return {type: "boolean"};
}
}

Expand Down
16 changes: 9 additions & 7 deletions packages/api/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export type RouteDef = {

export type ReqGeneric = {
params?: Record<string, string | number>;
query?: Record<string, string | number | (string | number)[]>;
query?: Record<string, string | number | boolean | (string | number)[]>;
body?: any;
headers?: Record<string, string[] | string | undefined>;
};
Expand Down Expand Up @@ -179,18 +179,20 @@ export function WithExecutionOptimistic<T extends {data: unknown}>(
}

/**
* SSZ factory helper to wrap an existing type with `{blockValue: Wei}`
* SSZ factory helper to wrap an existing type with `{executionPayloadValue: Wei}`
*/
export function WithBlockValue<T extends {data: unknown}>(type: TypeJson<T>): TypeJson<T & {blockValue: bigint}> {
export function WithExecutionPayloadValue<T extends {data: unknown}>(
type: TypeJson<T>
): TypeJson<T & {executionPayloadValue: bigint}> {
return {
toJson: ({blockValue, ...data}) => ({
toJson: ({executionPayloadValue, ...data}) => ({
...(type.toJson(data as unknown as T) as Record<string, unknown>),
block_value: blockValue.toString(),
execution_payload_value: executionPayloadValue.toString(),
}),
fromJson: ({block_value, ...data}: T & {block_value: string}) => ({
fromJson: ({execution_payload_value, ...data}: T & {execution_payload_value: string}) => ({
...type.fromJson(data),
// For cross client usage where beacon or validator are of separate clients, blockValue could be missing
blockValue: BigInt(block_value ?? "0"),
executionPayloadValue: BigInt(execution_payload_value ?? "0"),
}),
};
}
Expand Down

0 comments on commit e7013fe

Please sign in to comment.