Skip to content

Commit

Permalink
Enable builder api spec tests (#4496)
Browse files Browse the repository at this point in the history
* Enable builder api spec tests

* Update status name
  • Loading branch information
dapplion authored Sep 2, 2022
1 parent 3aad672 commit 04030be
Show file tree
Hide file tree
Showing 9 changed files with 47 additions and 50 deletions.
5 changes: 1 addition & 4 deletions packages/api/src/beacon/routes/beacon/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,7 @@ export function getReturnTypes(): ReturnTypes<Api> {

return {
getBlock: ContainerData(ssz.phase0.SignedBeaconBlock),
// Teku returns fork as UPPERCASE
getBlockV2: WithExecutionOptimistic(
WithVersion((fork: ForkName) => ssz[fork.toLowerCase() as ForkName].SignedBeaconBlock)
),
getBlockV2: WithExecutionOptimistic(WithVersion((fork) => ssz[fork].SignedBeaconBlock)),
getBlockAttestations: ContainerDataExecutionOptimistic(ArrayOf(ssz.phase0.Attestation)),
getBlockHeader: ContainerDataExecutionOptimistic(BeaconHeaderResType),
getBlockHeaders: ContainerDataExecutionOptimistic(ArrayOf(BeaconHeaderResType)),
Expand Down
36 changes: 19 additions & 17 deletions packages/api/src/builder/routes.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,55 @@
import {ssz, bellatrix, Slot, Root, BLSPubkey} from "@lodestar/types";
import {fromHexString, toHexString} from "@chainsafe/ssz";
import {ForkName} from "@lodestar/params";
import {
ReturnTypes,
RoutesData,
Schema,
ReqSerializers,
reqOnlyBody,
ContainerData,
reqEmpty,
ReqEmpty,
ArrayOf,
WithVersion,
} from "../utils/index.js";
// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes

export type Api = {
checkStatus(): Promise<void>;
status(): Promise<void>;
registerValidator(registrations: bellatrix.SignedValidatorRegistrationV1[]): Promise<void>;
getPayloadHeader(
getHeader(
slot: Slot,
parentHash: Root,
proposerPubKey: BLSPubkey
): Promise<{data: bellatrix.SignedBuilderBid}>;
submitSignedBlindedBlock(
): Promise<{version: ForkName; data: bellatrix.SignedBuilderBid}>;
submitBlindedBlock(
signedBlock: bellatrix.SignedBlindedBeaconBlock
): Promise<{data: bellatrix.ExecutionPayload}>;
): Promise<{version: ForkName; data: bellatrix.ExecutionPayload}>;
};

/**
* Define javascript values for each route
*/
export const routesData: RoutesData<Api> = {
checkStatus: {url: "/eth/v1/builder/status", method: "GET"},
status: {url: "/eth/v1/builder/status", method: "GET"},
registerValidator: {url: "/eth/v1/builder/validators", method: "POST"},
getPayloadHeader: {url: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}", method: "GET"},
submitSignedBlindedBlock: {url: "/eth/v1/builder/blinded_blocks", method: "POST"},
getHeader: {url: "/eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}", method: "GET"},
submitBlindedBlock: {url: "/eth/v1/builder/blinded_blocks", method: "POST"},
};

/* eslint-disable @typescript-eslint/naming-convention */
export type ReqTypes = {
checkStatus: ReqEmpty;
status: ReqEmpty;
registerValidator: {body: unknown};
getPayloadHeader: {params: {slot: Slot; parent_hash: string; pubkey: string}};
submitSignedBlindedBlock: {body: unknown};
getHeader: {params: {slot: Slot; parent_hash: string; pubkey: string}};
submitBlindedBlock: {body: unknown};
};

export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
return {
checkStatus: reqEmpty,
status: reqEmpty,
registerValidator: reqOnlyBody(ArrayOf(ssz.bellatrix.SignedValidatorRegistrationV1), Schema.ObjectArray),
getPayloadHeader: {
getHeader: {
writeReq: (slot, parentHash, proposerPubKey) => ({
params: {slot, parent_hash: toHexString(parentHash), pubkey: toHexString(proposerPubKey)},
}),
Expand All @@ -57,13 +58,14 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
params: {slot: Schema.UintRequired, parent_hash: Schema.StringRequired, pubkey: Schema.StringRequired},
},
},
submitSignedBlindedBlock: reqOnlyBody(ssz.bellatrix.SignedBlindedBeaconBlock, Schema.Object),
submitBlindedBlock: reqOnlyBody(ssz.bellatrix.SignedBlindedBeaconBlock, Schema.Object),
};
}

export function getReturnTypes(): ReturnTypes<Api> {
return {
getPayloadHeader: ContainerData(ssz.bellatrix.SignedBuilderBid),
submitSignedBlindedBlock: ContainerData(ssz.bellatrix.ExecutionPayload),
// TODO: Generalize to allForks
getHeader: WithVersion(() => ssz.bellatrix.SignedBuilderBid),
submitBlindedBlock: WithVersion(() => ssz.bellatrix.ExecutionPayload),
};
}
3 changes: 3 additions & 0 deletions packages/api/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ export function WithVersion<T>(getType: (fork: ForkName) => TypeJson<T>): TypeJs
version,
}),
fromJson: ({data, version}: {data: unknown; version: string}) => {
// Teku returns fork as UPPERCASE
version = version.toLowerCase();

// Un-safe external data, validate version is known ForkName value
if (!ForkName[version as ForkName]) throw Error(`Invalid version ${version}`);

Expand Down
12 changes: 4 additions & 8 deletions packages/api/test/unit/builder/oapiSpec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {testData} from "./testData.js";
// eslint-disable-next-line @typescript-eslint/naming-convention
const __dirname = path.dirname(fileURLToPath(import.meta.url));

const skip = true;
const version = "v0.2.0";
const openApiFile: OpenApiFile = {
url: `https://github.com/ethereum/builder-specs/releases/download/${version}/builder-oapi.json`,
Expand All @@ -21,11 +20,8 @@ const openApiFile: OpenApiFile = {
version: RegExp(/.*/),
};

// TODO: un-skip in follow-up PR, this PR only adds basic infra for spec testing
if (!skip) {
const reqSerializers = getReqSerializers();
const returnTypes = getReturnTypes();
const reqSerializers = getReqSerializers();
const returnTypes = getReturnTypes();

const openApiJson = await fetchOpenApiSpec(openApiFile);
runTestCheckAgainstSpec(openApiJson, routesData, reqSerializers, returnTypes, testData);
}
const openApiJson = await fetchOpenApiSpec(openApiFile);
runTestCheckAgainstSpec(openApiJson, routesData, reqSerializers, returnTypes, testData);
17 changes: 11 additions & 6 deletions packages/api/test/unit/builder/testData.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import {ssz} from "@lodestar/types";
import {fromHexString} from "@chainsafe/ssz";
import {ForkName} from "@lodestar/params";

import {Api} from "../../../src/builder/routes.js";
import {GenericServerTestCases} from "../../utils/genericServerTest.js";

// randomly pregenerated pubkey
const pubkeyRand = "0x84105a985058fc8740a48bf1ede9d223ef09e8c6b1735ba0a55cf4a9ff2ff92376b778798365e488dab07a652eb04576";
const root = Buffer.alloc(32, 1);

export const testData: GenericServerTestCases<Api> = {
checkStatus: {
status: {
args: [],
res: undefined,
},
registerValidator: {
args: [[ssz.bellatrix.SignedValidatorRegistrationV1.defaultValue()]],
res: undefined,
},
getPayloadHeader: {
args: [1, fromHexString("0x00000000000000000000000000000000"), fromHexString("0x1234")],
res: {data: ssz.bellatrix.SignedBuilderBid.defaultValue()},
getHeader: {
args: [1, root, fromHexString(pubkeyRand)],
res: {version: ForkName.bellatrix, data: ssz.bellatrix.SignedBuilderBid.defaultValue()},
},
submitSignedBlindedBlock: {
submitBlindedBlock: {
args: [ssz.bellatrix.SignedBlindedBeaconBlock.defaultValue()],
res: {data: ssz.bellatrix.ExecutionPayload.defaultValue()},
res: {version: ForkName.bellatrix, data: ssz.bellatrix.ExecutionPayload.defaultValue()},
},
};
2 changes: 1 addition & 1 deletion packages/beacon-node/src/api/impl/beacon/blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export function getBeaconBlockApi({
async publishBlindedBlock(signedBlindedBlock) {
const executionBuilder = chain.executionBuilder;
if (!executionBuilder) throw Error("exeutionBuilder required to publish SignedBlindedBeaconBlock");
const signedBlock = await executionBuilder.submitSignedBlindedBlock(signedBlindedBlock);
const signedBlock = await executionBuilder.submitBlindedBlock(signedBlindedBlock);
return await this.publishBlock(signedBlock);
},

Expand Down
2 changes: 1 addition & 1 deletion packages/beacon-node/src/chain/factory/block/body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ async function prepareExecutionPayloadHeader(
}

const {parentHash} = parentHashRes;
return chain.executionBuilder.getPayloadHeader(state.slot, parentHash, proposerPubKey);
return chain.executionBuilder.getHeader(state.slot, parentHash, proposerPubKey);
}

async function getExecutionPayloadParentHash(
Expand Down
16 changes: 5 additions & 11 deletions packages/beacon-node/src/execution/builder/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class ExecutionBuilderHttp implements IExecutionBuilder {

async checkStatus(): Promise<void> {
try {
await this.api.checkStatus();
await this.api.status();
} catch (e) {
// Disable if the status was enabled
this.status = false;
Expand All @@ -50,20 +50,14 @@ export class ExecutionBuilderHttp implements IExecutionBuilder {
return this.api.registerValidator(registrations);
}

async getPayloadHeader(
slot: Slot,
parentHash: Root,
proposerPubKey: BLSPubkey
): Promise<bellatrix.ExecutionPayloadHeader> {
const {data: signedBid} = await this.api.getPayloadHeader(slot, parentHash, proposerPubKey);
async getHeader(slot: Slot, parentHash: Root, proposerPubKey: BLSPubkey): Promise<bellatrix.ExecutionPayloadHeader> {
const {data: signedBid} = await this.api.getHeader(slot, parentHash, proposerPubKey);
const executionPayloadHeader = signedBid.message.header;
return executionPayloadHeader;
}

async submitSignedBlindedBlock(
signedBlock: bellatrix.SignedBlindedBeaconBlock
): Promise<bellatrix.SignedBeaconBlock> {
const {data: executionPayload} = await this.api.submitSignedBlindedBlock(signedBlock);
async submitBlindedBlock(signedBlock: bellatrix.SignedBlindedBeaconBlock): Promise<bellatrix.SignedBeaconBlock> {
const {data: executionPayload} = await this.api.submitBlindedBlock(signedBlock);
const expectedTransactionsRoot = signedBlock.message.body.executionPayloadHeader.transactionsRoot;
const actualTransactionsRoot = ssz.bellatrix.Transactions.hashTreeRoot(executionPayload.transactions);
if (!byteArrayEquals(expectedTransactionsRoot, actualTransactionsRoot)) {
Expand Down
4 changes: 2 additions & 2 deletions packages/beacon-node/src/execution/builder/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ export interface IExecutionBuilder {
updateStatus(shouldEnable: boolean): void;
checkStatus(): Promise<void>;
registerValidator(registrations: bellatrix.SignedValidatorRegistrationV1[]): Promise<void>;
getPayloadHeader(slot: Slot, parentHash: Root, proposerPubKey: BLSPubkey): Promise<bellatrix.ExecutionPayloadHeader>;
submitSignedBlindedBlock(signedBlock: bellatrix.SignedBlindedBeaconBlock): Promise<bellatrix.SignedBeaconBlock>;
getHeader(slot: Slot, parentHash: Root, proposerPubKey: BLSPubkey): Promise<bellatrix.ExecutionPayloadHeader>;
submitBlindedBlock(signedBlock: bellatrix.SignedBlindedBeaconBlock): Promise<bellatrix.SignedBeaconBlock>;
}

0 comments on commit 04030be

Please sign in to comment.