Skip to content

Commit

Permalink
feat: add broadcast validation to the block publishing
Browse files Browse the repository at this point in the history
  • Loading branch information
g11tech committed Jul 16, 2023
1 parent cdca9fb commit 29344ee
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 4 deletions.
65 changes: 65 additions & 0 deletions packages/api/src/beacon/routes/beacon/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export type BlockHeaderResponse = {
header: phase0.SignedBeaconBlockHeader;
};

export enum BroadcastValidation {
gossip = "gossip",
consensus = "consensus",
consensusAndEquivocation = "consensus_and_equivocation",
}

export type Api = {
/**
* Get block
Expand Down Expand Up @@ -167,6 +173,20 @@ export type Api = {
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
>
>;

publishBlockV2(
blockOrContents: allForks.SignedBeaconBlock | SignedBlockContents,
opts: {broadcastValidation: BroadcastValidation}
): Promise<
ApiClientResponse<
{
[HttpStatusCode.OK]: void;
[HttpStatusCode.ACCEPTED]: void;
},
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
>
>;

/**
* Publish a signed blinded block by submitting it to the mev relay and patching in the block
* transactions beacon node gets in response.
Expand All @@ -180,6 +200,19 @@ export type Api = {
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
>
>;

publishBlindedBlockV2(
blindedBlockOrContents: allForks.SignedBlindedBeaconBlock | SignedBlindedBlockContents,
opts: {broadcastValidation: BroadcastValidation}
): Promise<
ApiClientResponse<
{
[HttpStatusCode.OK]: void;
[HttpStatusCode.ACCEPTED]: void;
},
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
>
>;
/**
* Get block BlobSidecar
* Retrieves BlobSidecar included in requested block.
Expand All @@ -204,7 +237,9 @@ export const routesData: RoutesData<Api> = {
getBlockHeaders: {url: "/eth/v1/beacon/headers", method: "GET"},
getBlockRoot: {url: "/eth/v1/beacon/blocks/{block_id}/root", method: "GET"},
publishBlock: {url: "/eth/v1/beacon/blocks", method: "POST"},
publishBlockV2: {url: "/eth/v2/beacon/blocks", method: "POST"},
publishBlindedBlock: {url: "/eth/v1/beacon/blinded_blocks", method: "POST"},
publishBlindedBlockV2: {url: "/eth/v2/beacon/blinded_blocks", method: "POST"},
getBlobSidecars: {url: "/eth/v1/beacon/blob_sidecars/{block_id}", method: "GET"},
};

Expand All @@ -220,7 +255,9 @@ export type ReqTypes = {
getBlockHeaders: {query: {slot?: number; parent_root?: string}};
getBlockRoot: BlockIdOnlyReq;
publishBlock: {body: unknown};
publishBlockV2: {body: unknown; query: {broadcast_validation: string}};
publishBlindedBlock: {body: unknown};
publishBlindedBlockV2: {body: unknown; query: {broadcast_validation: string}};
getBlobSidecars: BlockIdOnlyReq;
};

Expand Down Expand Up @@ -277,7 +314,35 @@ export function getReqSerializers(config: ChainForkConfig): ReqSerializers<Api,
},
getBlockRoot: blockIdOnlyReq,
publishBlock: reqOnlyBody(AllForksSignedBlockOrContents, Schema.Object),
publishBlockV2: {
writeReq: (item, {broadcastValidation}) => ({
body: AllForksSignedBlockOrContents.toJson(item),
query: {broadcast_validation: broadcastValidation},
}),
parseReq: ({body, query}) => [
AllForksSignedBlockOrContents.fromJson(body),
{broadcastValidation: query.broadcast_validation as BroadcastValidation},
],
schema: {
body: Schema.Object,
query: {broadcast_validation: Schema.String},
},
},
publishBlindedBlock: reqOnlyBody(AllForksSignedBlindedBlockOrContents, Schema.Object),
publishBlindedBlockV2: {
writeReq: (item, {broadcastValidation}) => ({
body: AllForksSignedBlindedBlockOrContents.toJson(item),
query: {broadcast_validation: broadcastValidation},
}),
parseReq: ({body, query}) => [
AllForksSignedBlindedBlockOrContents.fromJson(body),
{broadcastValidation: query.broadcast_validation as BroadcastValidation},
],
schema: {
body: Schema.Object,
query: {broadcast_validation: Schema.String},
},
},
getBlobSidecars: blockIdOnlyReq,
};
}
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/beacon/routes/beacon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import * as state from "./state.js";
export * as block from "./block.js";
export * as pool from "./pool.js";
export * as state from "./state.js";
export {BlockId, BlockHeaderResponse} from "./block.js";
export {BlockId, BlockHeaderResponse, BroadcastValidation} from "./block.js";
export {AttestationFilters} from "./pool.js";
// TODO: Review if re-exporting all these types is necessary
export {
Expand Down
8 changes: 8 additions & 0 deletions packages/api/test/unit/beacon/testData/beacon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,18 @@ export const testData: GenericServerTestCases<Api> = {
args: [ssz.phase0.SignedBeaconBlock.defaultValue()],
res: undefined,
},
publishBlockV2: {
args: [ssz.phase0.SignedBeaconBlock.defaultValue(), "consensus"],
res: undefined,
},
publishBlindedBlock: {
args: [getDefaultBlindedBlock(64)],
res: undefined,
},
publishBlindedBlockV2: {
args: [getDefaultBlindedBlock(64), "consensus"],
res: undefined,
},
getBlobSidecars: {
args: ["head"],
res: {executionOptimistic: true, data: ssz.deneb.BlobSidecars.defaultValue()},
Expand Down
22 changes: 19 additions & 3 deletions packages/beacon-node/src/api/impl/beacon/blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import {NetworkEvent} from "../../../../network/index.js";
import {ApiModules} from "../../types.js";
import {resolveBlockId, toBeaconHeaderResponse} from "./utils.js";

type PublishBlockOpts = ImportBlockOpts & {broadcastValidation?: routes.beacon.BroadcastValidation};
const defaultPublishOpts = {broadcastValidation: routes.beacon.BroadcastValidation.consensus};

/**
* Validator clock may be advanced from beacon's clock. If the validator requests a resource in a
* future slot, wait some time instead of rejecting the request because it's in the future
Expand Down Expand Up @@ -189,7 +192,11 @@ export function getBeaconBlockApi({
};
},

async publishBlindedBlock(signedBlindedBlockOrContents) {
async publishBlindedBlockV2(signedBlindedBlockOrContents, opts) {
await this.publishBlindedBlock(signedBlindedBlockOrContents, opts);
},

async publishBlindedBlock(signedBlindedBlockOrContents, opts: PublishBlockOpts = defaultPublishOpts) {
const executionBuilder = chain.executionBuilder;
if (!executionBuilder) throw Error("exeutionBuilder required to publish SignedBlindedBeaconBlock");
// Mechanism for blobs & blocks on builder is not yet finalized
Expand All @@ -199,14 +206,23 @@ export function getBeaconBlockApi({
const signedBlockOrContents = await executionBuilder.submitBlindedBlock(signedBlindedBlockOrContents);
// the full block is published by relay and it's possible that the block is already known to us by gossip
// see https://github.com/ChainSafe/lodestar/issues/5404
return this.publishBlock(signedBlockOrContents, {ignoreIfKnown: true});
return this.publishBlock(signedBlockOrContents, {...opts, ignoreIfKnown: true});
}
},

async publishBlock(signedBlockOrContents, opts: ImportBlockOpts = {}) {
async publishBlockV2(signedBlockOrContents, opts: PublishBlockOpts = defaultPublishOpts) {
await this.publishBlock(signedBlockOrContents, opts);
},

async publishBlock(signedBlockOrContents, opts: PublishBlockOpts = defaultPublishOpts) {
const seenTimestampSec = Date.now() / 1000;
let blockForImport: BlockInput, signedBlock: allForks.SignedBeaconBlock, signedBlobs: deneb.SignedBlobSidecars;

const {broadcastValidation} = opts;
if (broadcastValidation !== routes.beacon.BroadcastValidation.consensus) {
throw Error("Broadcast Validation of consensus type accepted only.");
}

if (isSignedBlockContents(signedBlockOrContents)) {
({signedBlock, signedBlobSidecars: signedBlobs} = signedBlockOrContents);
blockForImport = getBlockInput.postDeneb(
Expand Down

0 comments on commit 29344ee

Please sign in to comment.