Skip to content

Commit a35cbde

Browse files
authored
feat: implement epbs block production (#8838)
Notable changes - implement epbs stateful block production (self-build only) - `GET /eth/v4/validator/blocks/{slot}` added - `GET /eth/v1/validator/execution_payload_envelope/{slot}/{beacon_block_root}` added - `POST /eth/v1/beacon/execution_payload_envelope` added - `POST /eth/v2/beacon/blocks` updated - implement envelope state root computation - update block production cache for gloas (includes state root) - add validator block production flow for gloas - add envelope signing to validator store / remote signer - data column sidecar changes required to wire up new gloas type - update beacon-api spec to `v5.0.0-alpha.0` see ethereum/beacon-APIs#580 for reference
1 parent 97dbe36 commit a35cbde

29 files changed

Lines changed: 1062 additions & 73 deletions

File tree

packages/api/src/beacon/routes/beacon/block.ts

Lines changed: 87 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import {ChainForkConfig} from "@lodestar/config";
33
import {
44
ForkName,
55
ForkPostDeneb,
6+
ForkPostGloas,
67
ForkPreBellatrix,
78
ForkPreDeneb,
89
ForkPreElectra,
910
isForkPostBellatrix,
1011
isForkPostDeneb,
12+
isForkPostGloas,
1113
} from "@lodestar/params";
1214
import {
1315
BeaconBlockBody,
@@ -17,11 +19,12 @@ import {
1719
SignedBlockContents,
1820
Slot,
1921
deneb,
22+
gloas,
2023
ssz,
2124
sszTypesFor,
2225
} from "@lodestar/types";
2326
import {EmptyMeta, EmptyResponseCodec, EmptyResponseData, WithVersion} from "../../../utils/codecs.js";
24-
import {getPostBellatrixForkTypes, toForkName} from "../../../utils/fork.js";
27+
import {getPostBellatrixForkTypes, getPostGloasForkTypes, toForkName} from "../../../utils/fork.js";
2528
import {fromHeaders} from "../../../utils/headers.js";
2629
import {Endpoint, RequestCodec, RouteDefinitions, Schema} from "../../../utils/index.js";
2730
import {
@@ -209,6 +212,20 @@ export type Endpoints = {
209212
EmptyMeta
210213
>;
211214

215+
/**
216+
* Publish signed execution payload envelope.
217+
* Instructs the beacon node to broadcast a signed execution payload envelope to the network,
218+
* to be gossiped for payload validation. A success response (20x) indicates that the envelope
219+
* passed gossip validation and was successfully broadcast onto the network.
220+
*/
221+
publishExecutionPayloadEnvelope: Endpoint<
222+
"POST",
223+
{signedExecutionPayloadEnvelope: gloas.SignedExecutionPayloadEnvelope},
224+
{body: unknown; headers: {[MetaHeader.Version]: string}},
225+
EmptyResponseData,
226+
EmptyMeta
227+
>;
228+
212229
/**
213230
* Get block BlobSidecar
214231
* Retrieves BlobSidecar included in requested block.
@@ -406,11 +423,14 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
406423
const slot = signedBlockContents.signedBlock.message.slot;
407424
const fork = config.getForkName(slot);
408425
return {
409-
body: isForkPostDeneb(fork)
410-
? sszTypesFor(fork).SignedBlockContents.toJson(signedBlockContents as SignedBlockContents<ForkPostDeneb>)
411-
: sszTypesFor(fork).SignedBeaconBlock.toJson(
412-
signedBlockContents.signedBlock as SignedBeaconBlock<ForkPreDeneb>
413-
),
426+
body:
427+
isForkPostDeneb(fork) && !isForkPostGloas(fork)
428+
? sszTypesFor(fork).SignedBlockContents.toJson(
429+
signedBlockContents as SignedBlockContents<ForkPostDeneb>
430+
)
431+
: sszTypesFor(fork).SignedBeaconBlock.toJson(
432+
signedBlockContents.signedBlock as SignedBeaconBlock<ForkPreDeneb | ForkPostGloas>
433+
),
414434
headers: {
415435
[MetaHeader.Version]: fork,
416436
},
@@ -420,9 +440,10 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
420440
parseReqJson: ({body, headers, query}) => {
421441
const forkName = toForkName(fromHeaders(headers, MetaHeader.Version));
422442
return {
423-
signedBlockContents: isForkPostDeneb(forkName)
424-
? sszTypesFor(forkName).SignedBlockContents.fromJson(body)
425-
: {signedBlock: ssz[forkName].SignedBeaconBlock.fromJson(body)},
443+
signedBlockContents:
444+
isForkPostDeneb(forkName) && !isForkPostGloas(forkName)
445+
? sszTypesFor(forkName).SignedBlockContents.fromJson(body)
446+
: {signedBlock: ssz[forkName].SignedBeaconBlock.fromJson(body)},
426447
broadcastValidation: query.broadcast_validation as BroadcastValidation,
427448
};
428449
},
@@ -431,13 +452,14 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
431452
const fork = config.getForkName(slot);
432453

433454
return {
434-
body: isForkPostDeneb(fork)
435-
? sszTypesFor(fork).SignedBlockContents.serialize(
436-
signedBlockContents as SignedBlockContents<ForkPostDeneb>
437-
)
438-
: sszTypesFor(fork).SignedBeaconBlock.serialize(
439-
signedBlockContents.signedBlock as SignedBeaconBlock<ForkPreDeneb>
440-
),
455+
body:
456+
isForkPostDeneb(fork) && !isForkPostGloas(fork)
457+
? sszTypesFor(fork).SignedBlockContents.serialize(
458+
signedBlockContents as SignedBlockContents<ForkPostDeneb>
459+
)
460+
: sszTypesFor(fork).SignedBeaconBlock.serialize(
461+
signedBlockContents.signedBlock as SignedBeaconBlock<ForkPreDeneb | ForkPostGloas>
462+
),
441463
headers: {
442464
[MetaHeader.Version]: fork,
443465
},
@@ -447,9 +469,10 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
447469
parseReqSsz: ({body, headers, query}) => {
448470
const forkName = toForkName(fromHeaders(headers, MetaHeader.Version));
449471
return {
450-
signedBlockContents: isForkPostDeneb(forkName)
451-
? sszTypesFor(forkName).SignedBlockContents.deserialize(body)
452-
: {signedBlock: ssz[forkName].SignedBeaconBlock.deserialize(body)},
472+
signedBlockContents:
473+
isForkPostDeneb(forkName) && !isForkPostGloas(forkName)
474+
? sszTypesFor(forkName).SignedBlockContents.deserialize(body)
475+
: {signedBlock: ssz[forkName].SignedBeaconBlock.deserialize(body)},
453476
broadcastValidation: query.broadcast_validation as BroadcastValidation,
454477
};
455478
},
@@ -566,6 +589,51 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
566589
requestWireFormat: WireFormat.ssz,
567590
},
568591
},
592+
publishExecutionPayloadEnvelope: {
593+
url: "/eth/v1/beacon/execution_payload_envelope",
594+
method: "POST",
595+
req: {
596+
writeReqJson: ({signedExecutionPayloadEnvelope}) => {
597+
const fork = config.getForkName(signedExecutionPayloadEnvelope.message.slot);
598+
return {
599+
body: getPostGloasForkTypes(fork).SignedExecutionPayloadEnvelope.toJson(signedExecutionPayloadEnvelope),
600+
headers: {
601+
[MetaHeader.Version]: fork,
602+
},
603+
};
604+
},
605+
parseReqJson: ({body, headers}) => {
606+
const fork = toForkName(fromHeaders(headers, MetaHeader.Version));
607+
return {
608+
signedExecutionPayloadEnvelope: getPostGloasForkTypes(fork).SignedExecutionPayloadEnvelope.fromJson(body),
609+
};
610+
},
611+
writeReqSsz: ({signedExecutionPayloadEnvelope}) => {
612+
const fork = config.getForkName(signedExecutionPayloadEnvelope.message.slot);
613+
return {
614+
body: getPostGloasForkTypes(fork).SignedExecutionPayloadEnvelope.serialize(signedExecutionPayloadEnvelope),
615+
headers: {
616+
[MetaHeader.Version]: fork,
617+
},
618+
};
619+
},
620+
parseReqSsz: ({body, headers}) => {
621+
const fork = toForkName(fromHeaders(headers, MetaHeader.Version));
622+
return {
623+
signedExecutionPayloadEnvelope:
624+
getPostGloasForkTypes(fork).SignedExecutionPayloadEnvelope.deserialize(body),
625+
};
626+
},
627+
schema: {
628+
body: Schema.Object,
629+
headers: {[MetaHeader.Version]: Schema.String},
630+
},
631+
},
632+
resp: EmptyResponseCodec,
633+
init: {
634+
requestWireFormat: WireFormat.ssz,
635+
},
636+
},
569637
getBlobSidecars: {
570638
url: "/eth/v1/beacon/blob_sidecars/{block_id}",
571639
method: "GET",

packages/api/src/beacon/routes/validator.ts

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {ContainerType, Type, ValueOf} from "@chainsafe/ssz";
22
import {ChainForkConfig} from "@lodestar/config";
33
import {
44
ForkPostDeneb,
5+
ForkPostGloas,
56
ForkPreDeneb,
67
VALIDATOR_REGISTRY_LIMIT,
78
isForkPostDeneb,
@@ -22,6 +23,7 @@ import {
2223
UintBn64,
2324
ValidatorIndex,
2425
altair,
26+
gloas,
2527
phase0,
2628
ssz,
2729
sszTypesFor,
@@ -36,7 +38,7 @@ import {
3638
JsonOnlyReq,
3739
WithVersion,
3840
} from "../../utils/codecs.js";
39-
import {getPostBellatrixForkTypes, toForkName} from "../../utils/fork.js";
41+
import {getPostBellatrixForkTypes, getPostGloasForkTypes, toForkName} from "../../utils/fork.js";
4042
import {fromHeaders} from "../../utils/headers.js";
4143
import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js";
4244
import {
@@ -89,6 +91,17 @@ export type ProduceBlockV3Meta = ValueOf<typeof ProduceBlockV3MetaType> & {
8991
executionPayloadSource: ProducedBlockSource;
9092
};
9193

94+
export const ProduceBlockV4MetaType = new ContainerType(
95+
{
96+
...VersionType.fields,
97+
/** Consensus rewards paid to the proposer for this block, in Wei */
98+
consensusBlockValue: ssz.UintBn64,
99+
},
100+
{jsonCase: "eth2"}
101+
);
102+
103+
export type ProduceBlockV4Meta = ValueOf<typeof ProduceBlockV4MetaType>;
104+
92105
export const AttesterDutyType = new ContainerType(
93106
{
94107
/** The validator's public key, uniquely identifying them */
@@ -357,6 +370,59 @@ export type Endpoints = {
357370
ProduceBlockV3Meta
358371
>;
359372

373+
/**
374+
* Requests a beacon node to produce a valid block, which can then be signed by a validator.
375+
*
376+
* Post-Gloas, proposers submit execution payload bids rather than full execution payloads,
377+
* so there is no longer a concept of blinded or unblinded blocks. Builders release the payload later.
378+
* This endpoint is specific to the post-Gloas forks and is not backwards compatible with previous forks.
379+
*/
380+
produceBlockV4: Endpoint<
381+
"GET",
382+
{
383+
/** The slot for which the block should be proposed */
384+
slot: Slot;
385+
/** The validator's randao reveal value */
386+
randaoReveal: BLSSignature;
387+
/** Arbitrary data validator wants to include in block */
388+
graffiti?: string;
389+
skipRandaoVerification?: boolean;
390+
builderBoostFactor?: UintBn64;
391+
} & Omit<ExtraProduceBlockOpts, "blindedLocal">,
392+
{
393+
params: {slot: number};
394+
query: {
395+
randao_reveal: string;
396+
graffiti?: string;
397+
skip_randao_verification?: string;
398+
fee_recipient?: string;
399+
builder_selection?: string;
400+
builder_boost_factor?: string;
401+
strict_fee_recipient_check?: boolean;
402+
};
403+
},
404+
BeaconBlock<ForkPostGloas>,
405+
ProduceBlockV4Meta
406+
>;
407+
408+
/**
409+
* Get execution payload envelope.
410+
* Retrieves execution payload envelope for a given slot and beacon block root.
411+
* The envelope contains the full execution payload along with associated metadata.
412+
*/
413+
getExecutionPayloadEnvelope: Endpoint<
414+
"GET",
415+
{
416+
/** Slot for which the execution payload envelope is requested */
417+
slot: Slot;
418+
/** Root of the beacon block that this envelope is for */
419+
beaconBlockRoot: Root;
420+
},
421+
{params: {slot: Slot; beacon_block_root: string}},
422+
gloas.ExecutionPayloadEnvelope,
423+
VersionMeta
424+
>;
425+
360426
/**
361427
* Produce an attestation data
362428
* Requests that the beacon node produce an AttestationData.
@@ -763,6 +829,96 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
763829
},
764830
},
765831
},
832+
produceBlockV4: {
833+
url: "/eth/v4/validator/blocks/{slot}",
834+
method: "GET",
835+
req: {
836+
writeReq: ({
837+
slot,
838+
randaoReveal,
839+
graffiti,
840+
skipRandaoVerification,
841+
feeRecipient,
842+
builderSelection,
843+
builderBoostFactor,
844+
strictFeeRecipientCheck,
845+
}) => ({
846+
params: {slot},
847+
query: {
848+
randao_reveal: toHex(randaoReveal),
849+
graffiti: toGraffitiHex(graffiti),
850+
skip_randao_verification: writeSkipRandaoVerification(skipRandaoVerification),
851+
fee_recipient: feeRecipient,
852+
builder_selection: builderSelection,
853+
builder_boost_factor: builderBoostFactor?.toString(),
854+
strict_fee_recipient_check: strictFeeRecipientCheck,
855+
},
856+
}),
857+
parseReq: ({params, query}) => ({
858+
slot: params.slot,
859+
randaoReveal: fromHex(query.randao_reveal),
860+
graffiti: fromGraffitiHex(query.graffiti),
861+
skipRandaoVerification: parseSkipRandaoVerification(query.skip_randao_verification),
862+
feeRecipient: query.fee_recipient,
863+
builderSelection: query.builder_selection as BuilderSelection,
864+
builderBoostFactor: parseBuilderBoostFactor(query.builder_boost_factor),
865+
strictFeeRecipientCheck: query.strict_fee_recipient_check,
866+
}),
867+
schema: {
868+
params: {slot: Schema.UintRequired},
869+
query: {
870+
randao_reveal: Schema.StringRequired,
871+
graffiti: Schema.String,
872+
skip_randao_verification: Schema.String,
873+
fee_recipient: Schema.String,
874+
builder_selection: Schema.String,
875+
builder_boost_factor: Schema.String,
876+
strict_fee_recipient_check: Schema.Boolean,
877+
},
878+
},
879+
},
880+
resp: {
881+
data: WithVersion((fork) => getPostGloasForkTypes(fork).BeaconBlock),
882+
meta: {
883+
toJson: (meta) => ProduceBlockV4MetaType.toJson(meta),
884+
fromJson: (val) => ProduceBlockV4MetaType.fromJson(val),
885+
toHeadersObject: (meta) => ({
886+
[MetaHeader.Version]: meta.version,
887+
[MetaHeader.ConsensusBlockValue]: meta.consensusBlockValue.toString(),
888+
}),
889+
fromHeaders: (headers) => ({
890+
version: toForkName(headers.getRequired(MetaHeader.Version)),
891+
consensusBlockValue: BigInt(headers.getRequired(MetaHeader.ConsensusBlockValue)),
892+
}),
893+
},
894+
},
895+
},
896+
getExecutionPayloadEnvelope: {
897+
url: "/eth/v1/validator/execution_payload_envelope/{slot}/{beacon_block_root}",
898+
method: "GET",
899+
req: {
900+
writeReq: ({slot, beaconBlockRoot}) => ({
901+
params: {
902+
slot,
903+
beacon_block_root: toRootHex(beaconBlockRoot),
904+
},
905+
}),
906+
parseReq: ({params}) => ({
907+
slot: params.slot,
908+
beaconBlockRoot: fromHex(params.beacon_block_root),
909+
}),
910+
schema: {
911+
params: {
912+
slot: Schema.UintRequired,
913+
beacon_block_root: Schema.StringRequired,
914+
},
915+
},
916+
},
917+
resp: {
918+
data: ssz.gloas.ExecutionPayloadEnvelope,
919+
meta: VersionCodec,
920+
},
921+
},
766922
produceAttestationData: {
767923
url: "/eth/v1/validator/attestation_data",
768924
method: "GET",

packages/api/src/utils/fork.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import {
33
ForkPostAltair,
44
ForkPostBellatrix,
55
ForkPostDeneb,
6+
ForkPostGloas,
67
isForkPostAltair,
78
isForkPostBellatrix,
89
isForkPostDeneb,
10+
isForkPostGloas,
911
} from "@lodestar/params";
1012
import {SSZTypesFor, sszTypesFor} from "@lodestar/types";
1113

@@ -42,3 +44,11 @@ export function getPostDenebForkTypes(fork: ForkName): SSZTypesFor<ForkPostDeneb
4244

4345
return sszTypesFor(fork);
4446
}
47+
48+
export function getPostGloasForkTypes(fork: ForkName): SSZTypesFor<ForkPostGloas> {
49+
if (!isForkPostGloas(fork)) {
50+
throw Error(`Invalid fork=${fork} for post-gloas fork types`);
51+
}
52+
53+
return sszTypesFor(fork);
54+
}

0 commit comments

Comments
 (0)