Skip to content

Commit

Permalink
Mev builder api integration
Browse files Browse the repository at this point in the history
 * extract builder in its own folder space

 * fix engine path

 * rebase fixes

 * refactor the builder with latest beacon state transtion processing changes

 * fix the registration signature

 * make typescript happy

 * remove the early return

 * rebase fixes

 * gas limit option

 * builder enabling on validator, gaslimit and other refac

 * fixes

 * Rebase fixes with the api refac

 * some cleanup fixes

 * comment explaining the immediate registration call

 * use already defined batchItems

 * serialize the validator registration chunks

 * validate transactions root on payload response

 * removing the BlockType from global types and sandboxing it just inside block factory

 * make builder optional in dev validator setup

 * option builder with dev val init with keystores

 * add builder api test routes

 * fix validator tests

 * fix block assembly scripts

 * handle the mergemock customization

 * fix body extraction

 * remove linter superession for boolean expressions
  • Loading branch information
g11tech committed Jun 30, 2022
1 parent f15a8a9 commit e23a3dd
Show file tree
Hide file tree
Showing 63 changed files with 909 additions and 201 deletions.
11 changes: 10 additions & 1 deletion packages/api/src/beacon/routes/beacon/block.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {ContainerType} from "@chainsafe/ssz";
import {ForkName} from "@chainsafe/lodestar-params";
import {IChainForkConfig} from "@chainsafe/lodestar-config";
import {phase0, allForks, Slot, Root, ssz} from "@chainsafe/lodestar-types";
import {phase0, allForks, Slot, Root, ssz, bellatrix} from "@chainsafe/lodestar-types";

import {
RoutesData,
ReturnTypes,
Expand Down Expand Up @@ -89,6 +90,11 @@ export type Api = {
* @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<void>;
/**
* 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: bellatrix.SignedBlindedBeaconBlock): Promise<void>;
};

/**
Expand All @@ -102,6 +108,7 @@ export const routesData: RoutesData<Api> = {
getBlockHeaders: {url: "/eth/v1/beacon/headers", method: "GET"},
getBlockRoot: {url: "/eth/v1/beacon/blocks/:blockId/root", method: "GET"},
publishBlock: {url: "/eth/v1/beacon/blocks", method: "POST"},
publishBlindedBlock: {url: "/eth/v1/beacon/blinded_blocks", method: "POST"},
};

type BlockIdOnlyReq = {params: {blockId: string | number}};
Expand All @@ -115,6 +122,7 @@ export type ReqTypes = {
getBlockHeaders: {query: {slot?: number; parent_root?: string}};
getBlockRoot: BlockIdOnlyReq;
publishBlock: {body: unknown};
publishBlindedBlock: {body: unknown};
};

export function getReqSerializers(config: IChainForkConfig): ReqSerializers<Api, ReqTypes> {
Expand Down Expand Up @@ -145,6 +153,7 @@ export function getReqSerializers(config: IChainForkConfig): ReqSerializers<Api,
},
getBlockRoot: blockIdOnlyReq,
publishBlock: reqOnlyBody(AllForksSignedBeaconBlock, Schema.Object),
publishBlindedBlock: reqOnlyBody(ssz.bellatrix.SignedBlindedBeaconBlock, Schema.Object),
};
}

Expand Down
16 changes: 16 additions & 0 deletions packages/api/src/beacon/routes/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
CommitteeIndex,
Epoch,
phase0,
bellatrix,
Root,
Slot,
ssz,
Expand Down Expand Up @@ -151,6 +152,12 @@ export type Api = {
graffiti: string
): Promise<{data: allForks.BeaconBlock; version: ForkName}>;

produceBlindedBlock(
slot: Slot,
randaoReveal: BLSSignature,
graffiti: string
): Promise<{data: bellatrix.BlindedBeaconBlock; version: ForkName}>;

/**
* Produce an attestation data
* Requests that the beacon node produce an AttestationData.
Expand Down Expand Up @@ -209,6 +216,8 @@ export type Api = {
prepareSyncCommitteeSubnets(subscriptions: SyncCommitteeSubscription[]): Promise<void>;

prepareBeaconProposer(proposers: ProposerPreparationData[]): Promise<void>;

registerValidator(registrations: bellatrix.SignedValidatorRegistrationV1[]): Promise<void>;
};

/**
Expand All @@ -220,6 +229,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"},
produceBlindedBlock: {url: "/eth/v2/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"},
getAggregatedAttestation: {url: "/eth/v1/validator/aggregate_attestation", method: "GET"},
Expand All @@ -228,6 +238,7 @@ export const routesData: RoutesData<Api> = {
prepareBeaconCommitteeSubnet: {url: "/eth/v1/validator/beacon_committee_subscriptions", method: "POST"},
prepareSyncCommitteeSubnets: {url: "/eth/v1/validator/sync_committee_subscriptions", method: "POST"},
prepareBeaconProposer: {url: "/eth/v1/validator/prepare_beacon_proposer", method: "POST"},
registerValidator: {url: "/eth/v1/validator/register_validator", method: "POST"},
};

/* eslint-disable @typescript-eslint/naming-convention */
Expand All @@ -237,6 +248,7 @@ export type ReqTypes = {
getSyncCommitteeDuties: {params: {epoch: Epoch}; body: ValidatorIndex[]};
produceBlock: {params: {slot: number}; query: {randao_reveal: string; grafitti: string}};
produceBlockV2: {params: {slot: number}; query: {randao_reveal: string; grafitti: string}};
produceBlindedBlock: {params: {slot: number}; query: {randao_reveal: string; grafitti: string}};
produceAttestationData: {query: {slot: number; committee_index: number}};
produceSyncCommitteeContribution: {query: {slot: number; subcommittee_index: number; beacon_block_root: string}};
getAggregatedAttestation: {query: {attestation_data_root: string; slot: number}};
Expand All @@ -245,6 +257,7 @@ export type ReqTypes = {
prepareBeaconCommitteeSubnet: {body: unknown};
prepareSyncCommitteeSubnets: {body: unknown};
prepareBeaconProposer: {body: unknown};
registerValidator: {body: unknown};
};

export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
Expand Down Expand Up @@ -309,6 +322,7 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {

produceBlock: produceBlock,
produceBlockV2: produceBlock,
produceBlindedBlock: produceBlock,

produceAttestationData: {
writeReq: (index, slot) => ({query: {slot, committee_index: index}}),
Expand Down Expand Up @@ -351,6 +365,7 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
],
schema: {body: Schema.ObjectArray},
},
registerValidator: reqOnlyBody(ArrayOf(ssz.bellatrix.SignedValidatorRegistrationV1), Schema.ObjectArray),
};
}

Expand Down Expand Up @@ -396,6 +411,7 @@ export function getReturnTypes(): ReturnTypes<Api> {
getSyncCommitteeDuties: WithDependentRoot(ArrayOf(SyncDuty)),
produceBlock: ContainerData(ssz.phase0.BeaconBlock),
produceBlockV2: WithVersion((fork: ForkName) => ssz[fork].BeaconBlock),
produceBlindedBlock: WithVersion((_fork: ForkName) => ssz.bellatrix.BlindedBeaconBlock),
produceAttestationData: ContainerData(ssz.phase0.AttestationData),
produceSyncCommitteeContribution: ContainerData(ssz.altair.SyncCommitteeContribution),
getAggregatedAttestation: ContainerData(ssz.phase0.Attestation),
Expand Down
45 changes: 43 additions & 2 deletions packages/api/src/builder/routes.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,69 @@
import {ReturnTypes, RoutesData, ReqSerializers, reqEmpty, ReqEmpty} from "../utils/index.js";
import {ssz, bellatrix, Slot, Root, BLSPubkey} from "@chainsafe/lodestar-types";
import {fromHexString, toHexString} from "@chainsafe/ssz";
import {
ReturnTypes,
RoutesData,
Schema,
ReqSerializers,
reqOnlyBody,
ContainerData,
reqEmpty,
ReqEmpty,
ArrayOf,
} 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>;
registerValidator(registrations: bellatrix.SignedValidatorRegistrationV1[]): Promise<void>;
getPayloadHeader(
slot: Slot,
parentHash: Root,
proposerPubKey: BLSPubkey
): Promise<{data: bellatrix.SignedBuilderBid}>;
submitSignedBlindedBlock(
signedBlock: bellatrix.SignedBlindedBeaconBlock
): Promise<{data: bellatrix.ExecutionPayload}>;
};

/**
* Define javascript values for each route
*/
export const routesData: RoutesData<Api> = {
checkStatus: {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"},
};

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

export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
return {
checkStatus: reqEmpty,
registerValidator: reqOnlyBody(ArrayOf(ssz.bellatrix.SignedValidatorRegistrationV1), Schema.ObjectArray),
getPayloadHeader: {
writeReq: (slot, parentHash, proposerPubKey) => ({
params: {slot, parent_hash: toHexString(parentHash), pubkey: toHexString(proposerPubKey)},
}),
parseReq: ({params}) => [params.slot, fromHexString(params.parent_hash), fromHexString(params.pubkey)],
schema: {
params: {slot: Schema.UintRequired, parent_hash: Schema.StringRequired, pubkey: Schema.StringRequired},
},
},
submitSignedBlindedBlock: reqOnlyBody(ssz.bellatrix.SignedBlindedBeaconBlock, Schema.Object),
};
}

export function getReturnTypes(): ReturnTypes<Api> {
return {};
return {
getPayloadHeader: ContainerData(ssz.bellatrix.SignedBuilderBid),
submitSignedBlindedBlock: ContainerData(ssz.bellatrix.ExecutionPayload),
};
}
4 changes: 4 additions & 0 deletions packages/api/test/unit/beacon/beacon.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ describe("beacon / beacon", () => {
args: [ssz.phase0.SignedBeaconBlock.defaultValue()],
res: undefined,
},
publishBlindedBlock: {
args: [ssz.bellatrix.SignedBlindedBeaconBlock.defaultValue()],
res: undefined,
},

// pool

Expand Down
8 changes: 8 additions & 0 deletions packages/api/test/unit/beacon/validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ describe("beacon / validator", () => {
args: [32000, Buffer.alloc(96, 1), "graffiti"],
res: {data: ssz.altair.BeaconBlock.defaultValue(), version: ForkName.altair},
},
produceBlindedBlock: {
args: [32000, Buffer.alloc(96, 1), "graffiti"],
res: {data: ssz.bellatrix.BlindedBeaconBlock.defaultValue(), version: ForkName.bellatrix},
},
produceAttestationData: {
args: [2, 32000],
res: {data: ssz.phase0.AttestationData.defaultValue()},
Expand Down Expand Up @@ -78,6 +82,10 @@ describe("beacon / validator", () => {
args: [[{validatorIndex: "1", feeRecipient: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"}]],
res: undefined,
},
registerValidator: {
args: [[ssz.bellatrix.SignedValidatorRegistrationV1.defaultValue()]],
res: undefined,
},
});

// TODO: Extra tests to implement maybe
Expand Down
15 changes: 15 additions & 0 deletions packages/api/test/unit/builder/builder.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import {config} from "@chainsafe/lodestar-config/default";
import {ssz} from "@chainsafe/lodestar-types";
import {fromHexString} from "@chainsafe/ssz";

import {Api, ReqTypes} from "../../../src/builder/routes.js";
import {getClient} from "../../../src/builder/client.js";
import {getRoutes} from "../../../src/builder/server/index.js";
Expand All @@ -10,5 +13,17 @@ describe("builder", () => {
args: [],
res: undefined,
},
registerValidator: {
args: [[ssz.bellatrix.SignedValidatorRegistrationV1.defaultValue()]],
res: undefined,
},
getPayloadHeader: {
args: [1, fromHexString("0x00000000000000000000000000000000"), fromHexString("0x1234")],
res: {data: ssz.bellatrix.SignedBuilderBid.defaultValue()},
},
submitSignedBlindedBlock: {
args: [ssz.bellatrix.SignedBlindedBeaconBlock.defaultValue()],
res: {data: ssz.bellatrix.ExecutionPayload.defaultValue()},
},
});
});
16 changes: 10 additions & 6 deletions packages/beacon-state-transition/src/block/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export * from "./isValidIndexedAttestation.js";
export function processBlock(
fork: ForkSeq,
state: CachedBeaconStateAllForks,
block: allForks.BeaconBlock,
block: allForks.FullOrBlindedBeaconBlock,
verifySignatures = true,
executionEngine: ExecutionEngine | null
): void {
Expand All @@ -29,12 +29,16 @@ export function processBlock(
// The call to the process_execution_payload must happen before the call to the process_randao as the former depends
// on the randao_mix computed with the reveal of the previous block.
if (fork >= ForkSeq.bellatrix) {
if (isExecutionEnabled(state as CachedBeaconStateBellatrix, (block as bellatrix.BeaconBlock).body)) {
processExecutionPayload(
const fullOrBlindedPayload = ((block as bellatrix.BlindedBeaconBlock).body.executionPayloadHeader ??
(block as bellatrix.BeaconBlock).body.executionPayload) as allForks.FullOrBlindedExecutionPayload;

if (
isExecutionEnabled(
state as CachedBeaconStateBellatrix,
(block as bellatrix.BeaconBlock).body.executionPayload,
executionEngine
);
block.body as allForks.FullOrBlindedBellatrixBeaconBlockBody
)
) {
processExecutionPayload(state as CachedBeaconStateBellatrix, fullOrBlindedPayload, executionEngine);
}
}

Expand Down
11 changes: 8 additions & 3 deletions packages/beacon-state-transition/src/block/processBlockHeader.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {toHexString, byteArrayEquals} from "@chainsafe/ssz";
import {allForks, ssz} from "@chainsafe/lodestar-types";
import {allForks, ssz, bellatrix} from "@chainsafe/lodestar-types";
import {CachedBeaconStateAllForks} from "../types.js";
import {ZERO_HASH} from "../constants/index.js";

Expand All @@ -9,7 +9,7 @@ import {ZERO_HASH} from "../constants/index.js";
* PERF: Fixed work independent of block contents.
* NOTE: `block` body root MUST be pre-cached.
*/
export function processBlockHeader(state: CachedBeaconStateAllForks, block: allForks.BeaconBlock): void {
export function processBlockHeader(state: CachedBeaconStateAllForks, block: allForks.FullOrBlindedBeaconBlock): void {
const slot = state.slot;
// verify that the slots match
if (block.slot !== slot) {
Expand Down Expand Up @@ -37,13 +37,18 @@ export function processBlockHeader(state: CachedBeaconStateAllForks, block: allF
);
}

const bodyRoot =
(block.body as bellatrix.BlindedBeaconBlockBody).executionPayloadHeader !== undefined
? ssz.bellatrix.BlindedBeaconBlockBody.hashTreeRoot(block.body as bellatrix.BlindedBeaconBlockBody)
: types.BeaconBlockBody.hashTreeRoot(block.body as bellatrix.BeaconBlockBody);

// cache current block as the new latest block
state.latestBlockHeader = ssz.phase0.BeaconBlockHeader.toViewDU({
slot: slot,
proposerIndex: block.proposerIndex,
parentRoot: block.parentRoot,
stateRoot: ZERO_HASH,
bodyRoot: types.BeaconBlockBody.hashTreeRoot(block.body),
bodyRoot,
});

// verify proposer is not slashed. Only once per block, may use the slower read from tree
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {bellatrix, ssz} from "@chainsafe/lodestar-types";
import {bellatrix, ssz, allForks} from "@chainsafe/lodestar-types";
import {toHexString, byteArrayEquals} from "@chainsafe/ssz";
import {CachedBeaconStateBellatrix} from "../types.js";
import {getRandaoMix} from "../util/index.js";
Expand All @@ -7,7 +7,7 @@ import {isMergeTransitionComplete} from "../util/bellatrix.js";

export function processExecutionPayload(
state: CachedBeaconStateBellatrix,
payload: bellatrix.ExecutionPayload,
payload: allForks.FullOrBlindedExecutionPayload,
executionEngine: ExecutionEngine | null
): void {
// Verify consistency of the parent hash, block number, base fee per gas and gas limit
Expand Down Expand Up @@ -46,10 +46,18 @@ export function processExecutionPayload(
// if executionEngine is null, executionEngine.onPayload MUST be called after running processBlock to get the
// correct randao mix. Since executionEngine will be an async call in most cases it is called afterwards to keep
// the state transition sync
if (executionEngine && !executionEngine.notifyNewPayload(payload)) {
if (
(payload as bellatrix.ExecutionPayload).transactions !== undefined &&
executionEngine &&
!executionEngine.notifyNewPayload(payload as bellatrix.ExecutionPayload)
) {
throw Error("Invalid execution payload");
}

const transactionsRoot =
(payload as bellatrix.ExecutionPayloadHeader).transactionsRoot ??
ssz.bellatrix.Transactions.hashTreeRoot((payload as bellatrix.ExecutionPayload).transactions);

// Cache execution payload header
state.latestExecutionPayloadHeader = ssz.bellatrix.ExecutionPayloadHeader.toViewDU({
parentHash: payload.parentHash,
Expand All @@ -65,6 +73,6 @@ export function processExecutionPayload(
extraData: payload.extraData,
baseFeePerGas: payload.baseFeePerGas,
blockHash: payload.blockHash,
transactionsRoot: ssz.bellatrix.Transactions.hashTreeRoot(payload.transactions),
transactionsRoot,
});
}
Loading

0 comments on commit e23a3dd

Please sign in to comment.