Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions packages/api/src/beacon/routes/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,54 @@ export type Endpoints = {
ProduceBlockV4Meta
>;

/**
* Requests a beacon node to produce a valid block with best-effort inclusion of the supplied
* execution payload bid, which the validator client selected itself among the bids it received.
*
* The beacon node solicits no builder API bids and considers no p2p bids. The supplied bid is
* validated like a builder API bid, with its execution payment counted in full, and weighed by
* `builderBoostFactor` against the local payload. An invalid bid or an active circuit breaker
* falls back to the local payload, the validator client can tell by the bid in the returned block.
*
* The response follows `produceBlockV4` without the `Eth-Builder-Url` header, the validator client
* already knows which builder to submit the signed block to.
* This endpoint is specific to the post-Gloas forks and is not backwards compatible with previous forks.
*/
produceBlockV4WithBid: Endpoint<
"POST",
{
/** The slot for which the block should be proposed */
slot: Slot;
/** The validator's randao reveal value */
randaoReveal: BLSSignature;
/** Arbitrary data validator wants to include in block */
graffiti?: string;
skipRandaoVerification?: boolean;
/** Percentage multiplier applied to the supplied bid when choosing between it and the local payload */
builderBoostFactor: bigint;
/** Include execution payload envelope and blobs in the response when self-building */
includePayload: boolean;
/** The bid selected by the validator client */
signedExecutionPayloadBid: gloas.SignedExecutionPayloadBid;
} & ExtraProduceBlockV4Opts,
{
params: {slot: number};
query: {
randao_reveal: string;
graffiti?: string;
skip_randao_verification?: string;
fee_recipient?: string;
strict_fee_recipient_check?: boolean;
builder_boost_factor: string;
include_payload: boolean;
};
body: unknown;
headers: {[MetaHeader.Version]: string};
},
BeaconBlock<ForkPostGloas> | BlockContents<ForkPostGloas>,
ProduceBlockV4Meta
>;

/**
* Get execution payload envelope.
* Retrieves the cached execution payload envelope for a given slot and beacon block root,
Expand Down Expand Up @@ -1116,6 +1164,131 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
},
},
},
produceBlockV4WithBid: {
url: "/eth/v4/validator/blocks/{slot}/with_bid",
method: "POST",
req: {
writeReqJson: ({
slot,
randaoReveal,
graffiti,
skipRandaoVerification,
feeRecipient,
strictFeeRecipientCheck,
builderBoostFactor,
includePayload,
signedExecutionPayloadBid,
}) => ({
params: {slot},
query: {
randao_reveal: toHex(randaoReveal),
graffiti: toGraffitiHex(graffiti),
skip_randao_verification: writeSkipRandaoVerification(skipRandaoVerification),
fee_recipient: feeRecipient,
strict_fee_recipient_check: strictFeeRecipientCheck,
builder_boost_factor: builderBoostFactor.toString(),
include_payload: includePayload,
},
body: ssz.gloas.SignedExecutionPayloadBid.toJson(signedExecutionPayloadBid),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Select the supplied bid codec by fork

When slot is on Heze, these hard-coded Gloas codecs omit or reject Heze's required inclusionListBits field, which is added to SignedExecutionPayloadBid in packages/types/src/heze/sszTypes.ts:43-57. Both the JSON and SSZ request paths therefore cannot transport a valid Heze bid, even though this endpoint accepts every post-Gloas fork and its response codec already dispatches by version. Resolve the fork from the slot or version header and use its SignedExecutionPayloadBid codec for serialization and parsing.

AGENTS.md reference: AGENTS.md:L202-L210

Useful? React with 👍 / 👎.

headers: {[MetaHeader.Version]: config.getForkName(slot)},
}),
parseReqJson: ({params, query, body, headers}) => {
toForkName(fromHeaders(headers, MetaHeader.Version));
return {
slot: params.slot,
randaoReveal: fromHex(query.randao_reveal),
graffiti: fromGraffitiHex(query.graffiti),
skipRandaoVerification: parseSkipRandaoVerification(query.skip_randao_verification),
feeRecipient: query.fee_recipient,
strictFeeRecipientCheck: query.strict_fee_recipient_check,
builderBoostFactor: BigInt(query.builder_boost_factor),
includePayload: query.include_payload,
signedExecutionPayloadBid: ssz.gloas.SignedExecutionPayloadBid.fromJson(body),
};
},
writeReqSsz: ({
slot,
randaoReveal,
graffiti,
skipRandaoVerification,
feeRecipient,
strictFeeRecipientCheck,
builderBoostFactor,
includePayload,
signedExecutionPayloadBid,
}) => ({
params: {slot},
query: {
randao_reveal: toHex(randaoReveal),
graffiti: toGraffitiHex(graffiti),
skip_randao_verification: writeSkipRandaoVerification(skipRandaoVerification),
fee_recipient: feeRecipient,
strict_fee_recipient_check: strictFeeRecipientCheck,
builder_boost_factor: builderBoostFactor.toString(),
include_payload: includePayload,
},
body: ssz.gloas.SignedExecutionPayloadBid.serialize(signedExecutionPayloadBid),
headers: {[MetaHeader.Version]: config.getForkName(slot)},
}),
parseReqSsz: ({params, query, body, headers}) => {
toForkName(fromHeaders(headers, MetaHeader.Version));
return {
slot: params.slot,
randaoReveal: fromHex(query.randao_reveal),
graffiti: fromGraffitiHex(query.graffiti),
skipRandaoVerification: parseSkipRandaoVerification(query.skip_randao_verification),
feeRecipient: query.fee_recipient,
strictFeeRecipientCheck: query.strict_fee_recipient_check,
builderBoostFactor: BigInt(query.builder_boost_factor),
includePayload: query.include_payload,
signedExecutionPayloadBid: ssz.gloas.SignedExecutionPayloadBid.deserialize(body),
};
},
schema: {
params: {slot: Schema.UintRequired},
query: {
randao_reveal: Schema.StringRequired,
graffiti: Schema.String,
skip_randao_verification: Schema.String,
fee_recipient: Schema.String,
strict_fee_recipient_check: Schema.Boolean,
builder_boost_factor: Schema.StringRequired,
include_payload: Schema.BooleanRequired,
},
body: Schema.Object,
headers: {[MetaHeader.Version]: Schema.String},
},
},
init: {
requestWireFormat: WireFormat.ssz,
},
resp: {
data: WithMeta(
({version, executionPayloadIncluded}) =>
(executionPayloadIncluded
? getPostGloasForkTypes(version).BlockContents
: getPostGloasForkTypes(version).BeaconBlock) as Type<
BeaconBlock<ForkPostGloas> | BlockContents<ForkPostGloas>
>
),
meta: {
toJson: (meta) => ProduceBlockV4MetaType.toJson(meta),
fromJson: (val) => ProduceBlockV4MetaType.fromJson(val),
toHeadersObject: (meta) => ({
[MetaHeader.Version]: meta.version,
[MetaHeader.ConsensusBlockValue]: meta.consensusBlockValue.toString(),
[MetaHeader.ExecutionPayloadValue]: meta.executionPayloadValue.toString(),
[MetaHeader.ExecutionPayloadIncluded]: meta.executionPayloadIncluded.toString(),
}),
fromHeaders: (headers) => ({
version: toForkName(headers.getRequired(MetaHeader.Version)),
consensusBlockValue: BigInt(headers.getRequired(MetaHeader.ConsensusBlockValue)),
executionPayloadValue: BigInt(headers.getRequired(MetaHeader.ExecutionPayloadValue)),
executionPayloadIncluded: toBoolean(headers.getRequired(MetaHeader.ExecutionPayloadIncluded)),
}),
},
},
},
getExecutionPayloadEnvelope: {
url: "/eth/v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}",
method: "GET",
Expand Down
2 changes: 2 additions & 0 deletions packages/api/test/unit/beacon/oapiSpec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ const ignoredOperations = [
// TODO: remove once the pinned beacon-APIs spec version includes beacon-APIs#630
// (GET -> POST with a required BuilderConfig request body)
"produceBlockV4",
// TODO: remove once the pinned beacon-APIs spec version includes beacon-APIs#627
"produceBlockV4WithBid",
// TODO: remove once the pinned beacon-APIs spec version includes beacon-APIs#630
"submitBuilderPreferences",
];
Expand Down
22 changes: 22 additions & 0 deletions packages/api/test/unit/beacon/testData/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,28 @@ export const testData: GenericServerTestCases<Endpoints> = {
},
},
},
produceBlockV4WithBid: {
args: {
slot: 32000,
randaoReveal,
graffiti,
skipRandaoVerification: true,
feeRecipient,
strictFeeRecipientCheck: true,
builderBoostFactor: 100n,
includePayload: true,
signedExecutionPayloadBid: ssz.gloas.SignedExecutionPayloadBid.defaultValue(),
},
res: {
data: ssz.gloas.BlockContents.defaultValue(),
meta: {
version: ForkName.gloas,
consensusBlockValue: ssz.Wei.defaultValue(),
executionPayloadValue: ssz.Wei.defaultValue(),
executionPayloadIncluded: true,
},
},
},
getExecutionPayloadEnvelope: {
args: {slot: 32000, beaconBlockRoot: ZERO_HASH},
res: {
Expand Down
Loading
Loading