From 5daf8a734a9b800d8211900f9b4a024fe2d69993 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Mon, 20 Jul 2026 21:15:15 -0700 Subject: [PATCH 01/18] Beacon API parity with builder api and keymanager api --- CHANGES.md | 1 + apis/beacon/blocks/blocks.v2.yaml | 24 ++++ apis/validator/block.v4.yaml | 153 +++++++++++++--------- apis/validator/builder_preferences.yaml | 59 +++++++++ beacon-node-oapi.yaml | 10 ++ types/gloas/builder_entry.yaml | 80 +++++++++++ types/gloas/builder_preference_entry.yaml | 47 +++++++ types/gloas/request_auth.yaml | 31 +++++ wordlist.txt | 1 + 9 files changed, 341 insertions(+), 65 deletions(-) create mode 100644 apis/validator/builder_preferences.yaml create mode 100644 types/gloas/builder_entry.yaml create mode 100644 types/gloas/builder_preference_entry.yaml create mode 100644 types/gloas/request_auth.yaml diff --git a/CHANGES.md b/CHANGES.md index b18d861f..0110520d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -38,6 +38,7 @@ There are likely to be descriptions etc outside of the list below, but new query | [#608](https://github.com/ethereum/beacon-APIs/pull/608) `POST /eth/v1/validator/prepare_beacon_proposer` deprecated | | | | | | | [#608](https://github.com/ethereum/beacon-APIs/pull/608) `POST /eth/v1/validator/register_validator` deprecated | | | | | | | [#608](https://github.com/ethereum/beacon-APIs/pull/608) `POST /eth/v1/validator/beacon_committee_subscriptions` updated | | | | | | +| [#XXX](https://github.com/ethereum/beacon-APIs/pull/XXX) `GET /eth/v4/validator/blocks/{slot}` changed to `POST /eth/v4/validator/blocks/{slot}` | | | | | | The Following are no longer in the Standard API, removed since the latest version. diff --git a/apis/beacon/blocks/blocks.v2.yaml b/apis/beacon/blocks/blocks.v2.yaml index b93e6db9..65771e86 100644 --- a/apis/beacon/blocks/blocks.v2.yaml +++ b/apis/beacon/blocks/blocks.v2.yaml @@ -43,6 +43,30 @@ post: required: true name: Eth-Consensus-Version description: "The active consensus version to which the block being submitted belongs." + - name: Eth-Builder-Url + in: header + required: false + description: | + The `url` of the winning builder, as returned in the `Eth-Builder-Url` response header of + `produceBlockV4`. The beacon node forwards the signed block to this builder via the + builder-API `submitSignedBeaconBlock` so the builder can release the execution payload. + Omitted for a self-built block. + schema: + type: string + format: uri + example: "https://builder.example.com" + - name: Eth-Builder-Proxy + in: header + required: false + description: | + An optional proxy URL for the winning builder, as returned in the `Eth-Builder-Proxy` + response header of `produceBlockV4`. When set, the beacon node sends the request to this URL + and sets the `Eth-Builder-Url` header to the builder's `url` so the proxy can forward the + request to the intended builder; `Eth-Builder-Url` MUST therefore also be present. + schema: + type: string + format: uri + example: "http://side-car:9001" requestBody: description: "The `SignedBeaconBlock` object composed of `BeaconBlock` object (produced by beacon node) and validator signature." required: true diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index df8f08af..1050a64a 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -1,25 +1,36 @@ -get: +post: tags: - Validator - ValidatorRequiredApi operationId: "produceBlockV4" summary: "Produce a new block, without signature." description: | - Requests a beacon node to produce a valid block, which can then be signed by a validator. + Requests a beacon node to produce a valid block, which the validator then signs. - Post-Gloas, proposers submit execution payload bids rather than full execution payloads, - so there is no longer a concept of blinded or unblinded blocks. Builders release the - payload later. This endpoint is specific to the post-Gloas forks and is not backwards compatible - with previous forks. + The beacon node always builds a local payload and considers any p2p bid. The validator optionally + supplies a list of `BuilderEntry` objects in the request body, one per external builder to also + request a bid from; the beacon node adds those bids to the comparison and returns the + highest-value candidate. It biases each builder's bid by the entry's `builder_boost_factor` and + rejects a bid that falls outside the entry's `min_bid` (floor) or `max_execution_payment` (ceiling + on the trusted execution payment). `builder_boost_factor` leans the choice toward the local + payload or a given builder without disabling either source. - When self-building (local execution payload), the response includes the full block contents - (beacon block, execution payload envelope, blobs, and KZG proofs) if `include_payload` is - set to `true`, otherwise only the `BeaconBlock` is returned. - When using an external builder bid, only the `BeaconBlock` is returned as the beacon node - does not have access to the builder's execution payload. + Post-Gloas, proposers submit execution payload bids rather than full execution payloads, so there + is no longer a blinded or unblinded distinction; builders release the payload later. This endpoint + is specific to the post-Gloas forks and is not backwards compatible with previous forks. - The `Eth-Execution-Payload-Included` header and `execution_payload_included` response field - indicate which response type was returned. + The beacon node returns the full block contents (beacon block, execution payload envelope, blobs, + and KZG proofs) only when it self-builds and `include_payload` is `true`; otherwise it returns only + the `BeaconBlock`. The `Eth-Execution-Payload-Included` header and `execution_payload_included` + field report which form was returned. For a block built on an external builder's bid the beacon + node has no payload to return, and the `Eth-Builder-Url` response header (and the optional + `Eth-Builder-Proxy`) identifies the winning builder for the validator to echo when publishing. + + The two outcomes publish differently and never overlap. For an external builder's bid, the + validator publishes the signed block via `publishBlockV2`, echoing the builder headers, and the + winning builder releases the execution payload envelope. For a self-built block, the validator + publishes the signed block via `publishBlockV2` and the execution payload envelope via + `publishExecutionPayloadEnvelope`. parameters: - name: slot in: path @@ -45,59 +56,46 @@ get: in: query required: true description: | - Controls whether the execution payload envelope and blobs are included in the response - when self-building (using local execution payload). + Whether a self-built block's response carries the execution payload inline, or omits it for + the beacon node to cache and the validator to retrieve separately. - When `true`, the response includes the full block contents: beacon block, - execution payload envelope, blobs, and KZG proofs. This enables stateless operation - where the validator client can use multiple beacon nodes (multi-BN setups, distributed validators, failover). + When `true`, a self-built response includes the full block contents (beacon block, execution + payload envelope, blobs, and KZG proofs) and reports `Eth-Execution-Payload-Included: true`. + The validator holds the whole payload, so it can sign and publish the envelope through any + beacon node (stateless operation: multi-BN setups, distributed validators, failover). - When `false`, only the beacon block is returned and the beacon node caches the execution - payload envelope and blobs internally. The validator client must then fetch them separately - via `GET /eth/v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}`. This saves - bandwidth but requires the validator client to publish via the same beacon node that - produced the block (stateful operation). + When `false`, a self-built response returns only the beacon block, reporting + `Eth-Execution-Payload-Included: false`; the beacon node caches the execution payload envelope + and blobs. The validator retrieves the envelope via `getExecutionPayloadEnvelope` + (`GET /eth/v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}`), signs it, and + publishes it via `publishExecutionPayloadEnvelope`, and the producing beacon node attaches its + cached blobs and KZG proofs before broadcasting. This sends fewer bytes but ties publishing to + that one beacon node (stateful operation). - This parameter only affects self-building scenarios. When using an external builder's bid, - only the beacon block is returned regardless of this parameter (the beacon node does not - have access to the builder's execution payload). + This parameter is independent of the request body and only affects the self-built case: the + `BuilderEntry` list selects which external builders to consider, and when an external builder's + bid wins the beacon node returns only the beacon block regardless of this parameter (it does + not have that builder's execution payload). schema: type: boolean - - name: builder_boost_factor - in: query - required: false - description: | - Percentage multiplier to apply to the builder's bid value when choosing between a - builder bid and payload from the paired execution node. This parameter is only relevant - if the beacon node has at least one viable builder `ExecutionPayloadBid` available and - receives a valid response from the paired execution node. When these preconditions are - met, the server MUST act as follows: - - * if `exec_node_payload_value >= builder_boost_factor * (builder_bid_value // 100)` - for the highest builder bid known to the beacon node, then return a block committing - to the local execution node payload (with the payload itself included if - `include_payload` is set to true). - * otherwise, return a block committing to the builder bid (without execution payload, - as it is not yet available). - - Servers must support the following values of the boost factor which encode common - preferences: - - * `builder_boost_factor=0`: prefer the local execution node payload unless an error makes - it unviable. - * `builder_boost_factor=100`: profit maximization mode; choose whichever of the local - execution node payload and the builder bid pays more. - * `builder_boost_factor=2**64 - 1`: prefer the builder bid unless an error or beacon node - health check makes it unviable. - - Servers should use saturating arithmetic or another technique to ensure that large values - of the `builder_boost_factor` do not trigger overflows or errors. If this parameter is - provided and no builder bid is available, the beacon node MUST respond with a block - committing to the local execution node payload, which the caller can choose to reject if - it wishes. If the value is provided but out of range for a 64-bit unsigned integer, then - an error response with status code 400 MUST be returned. - schema: - $ref: "../../beacon-node-oapi.yaml#/components/schemas/Uint64" + requestBody: + required: false + description: | + An optional list of `BuilderEntry` objects, one per external builder to request a bid from, + added to the beacon node's local build and any p2p bid when selecting a payload. Each entry + names a concrete builder `url`, and no two entries may share the same `url`. For each entry the + beacon node contacts the builder at its `proxy` if set, otherwise at its `url`; forwards the + entry's `auth` unchanged; and applies the entry's `max_execution_payment`, `min_bid`, and + `builder_boost_factor` when comparing its bid. Malformed entries are ignored rather than failing + the request, so a single bad entry never blocks block production. When the body is omitted or + empty, or every entry is ignored, the beacon node contacts no external builders and chooses + between its local build and any p2p bid. + content: + application/json: + schema: + type: array + items: + $ref: "../../beacon-node-oapi.yaml#/components/schemas/Gloas.BuilderEntry" responses: "200": description: Success response @@ -108,6 +106,29 @@ get: $ref: '../../beacon-node-oapi.yaml#/components/headers/Eth-Consensus-Block-Value' Eth-Execution-Payload-Included: $ref: '../../beacon-node-oapi.yaml#/components/headers/Eth-Execution-Payload-Included' + Eth-Builder-Url: + description: | + For a block built on an external builder's bid, the `url` of the winning builder. The + validator echoes this value in the `Eth-Builder-Url` request header when publishing the + signed block, so the beacon node can forward the block to the same builder via the + builder-API `submitSignedBeaconBlock`. Absent when the block was self-built (no external + builder bid was used). + required: false + schema: + type: string + format: uri + example: "https://builder.example.com" + Eth-Builder-Proxy: + description: | + The proxy URL the beacon node used to reach the winning builder, when it reached the + builder through a proxy. The validator echoes it in the `Eth-Builder-Proxy` request header + when publishing. Present only alongside `Eth-Builder-Url`. Absent for a builder contacted + directly at its `url`, and for a self-built block. + required: false + schema: + type: string + format: uri + example: "http://side-car:9001" content: application/json: schema: @@ -127,9 +148,9 @@ get: type: boolean description: | Indicates whether the execution payload envelope is included in the response. - When `true`, the `data` field contains a `BlockContents` object with the - beacon block, execution payload envelope, KZG proofs, and blobs. When `false`, - the `data` field contains only a `BeaconBlock`. + When `true`, the `data` field contains a `BlockContents` object with the beacon + block, execution payload envelope, blobs, and KZG proofs. When `false`, the `data` + field contains only a `BeaconBlock`. example: false data: anyOf: @@ -139,7 +160,9 @@ get: schema: description: "SSZ serialized `BeaconBlock` or `BlockContents` bytes. Use Accept header to choose this response type, version string is sent in header `Eth-Consensus-Version` and payload inclusion indicated by `Eth-Execution-Payload-Included` header." "400": - description: "Invalid block production request" + description: | + The request itself is invalid, for example an unparseable body, an invalid slot, or two + entries sharing the same `url`. An individual malformed `BuilderEntry` is ignored, not a 400. content: application/json: schema: diff --git a/apis/validator/builder_preferences.yaml b/apis/validator/builder_preferences.yaml new file mode 100644 index 00000000..afa3afc6 --- /dev/null +++ b/apis/validator/builder_preferences.yaml @@ -0,0 +1,59 @@ +post: + operationId: "submitBuilderPreferences" + summary: "Submit builder preferences via the beacon node." + description: | + Submits a proposer's per-builder preferences to the builders on the validator's behalf, including + the `max_execution_payment` the proposer is willing to accept from each. The validator does not + contact builders directly: for each `BuilderPreferenceEntry` the beacon node makes one + `submitBuilderPreferences` builder-API call, contacting the builder at its `proxy` if set and its + `url` otherwise, forwarding the entry's `auth` unchanged, and submitting its + `max_execution_payment`. The `pubkey` path parameter is passed as the builder-API + `validator_pubkey`, and each entry's `auth.message.slot` is the proposal slot the preferences + apply to. + + Validators MAY call this endpoint in the epoch prior to the epoch in which they will be proposing, + as determined from `state.proposer_lookahead`, so that builders have the preferences before the + bid request arrives. + + `BuilderPreferencesRequestV1` is not fork-versioned, so no `Eth-Consensus-Version` header is + required. + + This API is applicable from the Gloas fork onwards. + parameters: + - name: pubkey + in: path + required: true + description: | + The BLS public key of the proposing validator whose preferences these are. The beacon node + passes it as the `validator_pubkey` of each `submitBuilderPreferences` builder-API call, and + each entry's `auth` is signed by this key. + schema: + $ref: "../../types/primitive.yaml#/Pubkey" + tags: + - Validator + requestBody: + required: true + description: | + An array of `BuilderPreferenceEntry` objects, one per builder the beacon node should submit + preferences to. Each entry names a concrete builder `url`, and no two entries may share the + same `url`. The beacon node submits each entry independently, so a malformed entry does not + prevent the others from being submitted; a 400 response identifies the failing entries by index. + content: + application/json: + schema: + type: array + items: + $ref: "../../beacon-node-oapi.yaml#/components/schemas/Gloas.BuilderPreferenceEntry" + responses: + "200": + description: "The beacon node submitted the preferences to every builder." + "400": + description: "One or more preference submissions were rejected. The index identifies the failing entry." + content: + application/json: + schema: + $ref: "../../beacon-node-oapi.yaml#/components/schemas/IndexedErrorMessage" + "500": + $ref: "../../beacon-node-oapi.yaml#/components/responses/InternalError" + "503": + $ref: "../../beacon-node-oapi.yaml#/components/responses/CurrentlySyncing" diff --git a/beacon-node-oapi.yaml b/beacon-node-oapi.yaml index 369a8f18..26982690 100644 --- a/beacon-node-oapi.yaml +++ b/beacon-node-oapi.yaml @@ -224,6 +224,8 @@ paths: $ref: "./apis/validator/register_validator.yaml" /eth/v1/validator/proposer_preferences: $ref: "./apis/validator/proposer_preferences.yaml" + /eth/v1/validator/builder_preferences/{pubkey}: + $ref: "./apis/validator/builder_preferences.yaml" /eth/v1/validator/liveness/{epoch}: $ref: "./apis/validator/liveness.yaml" /eth/v1/validator/execution_payload_bids/{slot}/{builder_index}: @@ -493,6 +495,14 @@ components: $ref: "./types/fulu/data_column_sidecar.yaml#/Fulu/DataColumnSidecars" Gloas.DataColumnSidecars: $ref: "./types/gloas/data_column_sidecar.yaml#/Gloas/DataColumnSidecars" + Gloas.RequestAuthV1: + $ref: "./types/gloas/request_auth.yaml#/Gloas/RequestAuthV1" + Gloas.SignedRequestAuthV1: + $ref: "./types/gloas/request_auth.yaml#/Gloas/SignedRequestAuthV1" + Gloas.BuilderEntry: + $ref: "./types/gloas/builder_entry.yaml#/Gloas/BuilderEntry" + Gloas.BuilderPreferenceEntry: + $ref: "./types/gloas/builder_preference_entry.yaml#/Gloas/BuilderPreferenceEntry" Node: $ref: './types/fork_choice.yaml#/Node' ExtraData: diff --git a/types/gloas/builder_entry.yaml b/types/gloas/builder_entry.yaml new file mode 100644 index 00000000..8602bf83 --- /dev/null +++ b/types/gloas/builder_entry.yaml @@ -0,0 +1,80 @@ +Gloas: + BuilderEntry: + type: object + description: | + A per-builder input the validator supplies on a block-production request, one entry for each + external builder the beacon node should request a bid from. Each entry is the resolved form of + the validator's persistent `BuilderEntry` configuration from the keymanager + `/eth/v1/validator/config` API (`url`, `proxy`, `pubkey`, `max_execution_payment`, `min_bid`, + and `builder_boost_factor`), together with a per-slot `SignedRequestAuthV1` authenticating the + request to that builder. The validator resolves its configuration, applying any config-level + defaults, before sending, so each entry is self-contained. + + The beacon node contacts each builder (at its `proxy` if set, otherwise its `url`), forwarding + the entry's `auth`, and applies `max_execution_payment`, `min_bid`, and `builder_boost_factor` + when comparing that builder's bid against its local build and any p2p bids. + required: [url] + properties: + url: + type: string + format: uri + description: | + The builder's URL, uniquely identifying it. The beacon node contacts the builder here + unless `proxy` is set, and sends this value as the `Eth-Builder-Url` header when routing + through a proxy. + example: "https://builder.example.com" + proxy: + type: string + format: uri + description: | + An optional proxy URL. When set, the beacon node sends the request to this URL instead of + to `url`, and sets the `Eth-Builder-Url` header to `url` so the proxy can forward the + request to the intended builder. + example: "http://side-car:9001" + auth: + allOf: + - $ref: "./request_auth.yaml#/Gloas/SignedRequestAuthV1" + - description: | + Optional `SignedRequestAuthV1` authenticating the request to this builder. The beacon + node MUST forward its `message` and `signature` byte-for-byte unchanged. Its `data` is + opaque and agreed with the builder out of band, and its `message.slot` is the proposal + slot the request is authorized for. If omitted, requests to this builder are sent + without authentication. + pubkey: + allOf: + - $ref: "../primitive.yaml#/Pubkey" + - description: | + Optional builder BLS public key. When set, binds trusted execution payments to this + builder's on-chain key: bids not signed by it MUST NOT be accepted. + max_execution_payment: + allOf: + - $ref: "../primitive.yaml#/Gwei" + - description: | + Maximum trusted execution-layer payment, in Gwei, accepted from this builder. Bids + promising a trusted payment above this value MUST be rejected. + example: "1000000000" + min_bid: + allOf: + - $ref: "../primitive.yaml#/Gwei" + - description: | + Minimum bid, in Gwei, accepted from this builder. Bids with a value below this amount + MUST be rejected. + example: "10000000" + builder_boost_factor: + allOf: + - $ref: "../primitive.yaml#/Uint64" + - description: | + Percentage multiplier applied to this builder's bid value when choosing between the + builder's bid and the local execution node payload. When the beacon node has a valid + local payload and this builder returns a viable bid, it commits to the local payload if + `exec_node_payload_value >= builder_boost_factor * (builder_payload_value // 100)`, and to + the builder's bid otherwise; the highest-value candidate across the local payload, any + p2p bid, and every builder's boosted bid is chosen. + + The reserved values encode common preferences: `0` prefers the local payload unless an + error makes it unviable; `100` is profit maximization (choose whichever pays more); and + `2**64 - 1` prefers this builder unless an error or beacon node health check makes it + unviable. Servers should use saturating arithmetic so that large values do not overflow. + A value out of range for a 64-bit unsigned integer makes the entry malformed, so the + beacon node ignores that entry. + example: "100" diff --git a/types/gloas/builder_preference_entry.yaml b/types/gloas/builder_preference_entry.yaml new file mode 100644 index 00000000..253d0682 --- /dev/null +++ b/types/gloas/builder_preference_entry.yaml @@ -0,0 +1,47 @@ +Gloas: + BuilderPreferenceEntry: + type: object + description: | + Per-builder preference the validator asks the beacon node to submit to a builder ahead of the + bid request, one entry per builder. Each entry names a concrete builder `url`. For each entry + the beacon node makes one `submitBuilderPreferences` builder-API call: it contacts the builder + at its `proxy` if set, otherwise at its `url`, forwards `auth` unchanged, and submits + `max_execution_payment`. + + Unlike the block-production `BuilderEntry`, this carries only what a builder is allowed to see. + The proposer's private bid-filtering strategy (`min_bid` and `builder_boost_factor`) is never + sent to a builder; the beacon node applies those locally when selecting a payload. + required: [url, auth, max_execution_payment] + properties: + url: + type: string + format: uri + description: | + The builder's URL, uniquely identifying it. The beacon node submits preferences to the + builder here unless `proxy` is set, and sends this value as the `Eth-Builder-Url` header + when routing through a proxy. + example: "https://builder.example.com" + proxy: + type: string + format: uri + description: | + An optional proxy URL. When set, the beacon node sends the request to this URL instead of + to `url`, and sets the `Eth-Builder-Url` header to `url` so the proxy can forward the + request to the intended builder. + example: "http://side-car:9001" + auth: + allOf: + - $ref: "./request_auth.yaml#/Gloas/SignedRequestAuthV1" + - description: | + `SignedRequestAuthV1` authenticating the preference submission to this builder. The + beacon node MUST forward its `message` and `signature` byte-for-byte unchanged. Its + `data` is opaque and agreed with the builder out of band, and its `message.slot` is the + proposal slot the preferences apply to. + max_execution_payment: + allOf: + - $ref: "../primitive.yaml#/Gwei" + - description: | + Maximum trusted execution-layer payment, in Gwei, the proposer will accept from this + builder. It is submitted to the builder so the builder can shape its bid; the beacon + node still enforces the same bound when selecting a payload. + example: "1000000000" diff --git a/types/gloas/request_auth.yaml b/types/gloas/request_auth.yaml new file mode 100644 index 00000000..787a9ffc --- /dev/null +++ b/types/gloas/request_auth.yaml @@ -0,0 +1,31 @@ +Gloas: + RequestAuthV1: + type: object + description: "The `RequestAuthV1` object from the [Builder API](https://ethereum.github.io/builder-specs/) Gloas spec." + required: [data, slot] + properties: + data: + type: string + format: hex + description: | + Opaque authentication data unique to the builder, agreed upon out of band. The meaning of + the up to `MAX_DATA_SIZE` bytes is left to the two parties; the builder verifies the exact + bytes. When no value has been agreed out of band, implementations SHOULD default to the + builder's URL exactly as advertised, so proposers with no prior relationship (and DVT + operators sharing static config) can construct an identical `data` deterministically. + example: "0x1234567890abcdef" + pattern: "^0x[a-fA-F0-9]{0,8192}$" + slot: + allOf: + - $ref: "../primitive.yaml#/Uint64" + - description: "The proposal slot this request is authorized for." + + SignedRequestAuthV1: + type: object + description: "The `SignedRequestAuthV1` object from the [Builder API](https://ethereum.github.io/builder-specs/) Gloas spec." + required: [message, signature] + properties: + message: + $ref: "#/Gloas/RequestAuthV1" + signature: + $ref: "../primitive.yaml#/Signature" diff --git a/wordlist.txt b/wordlist.txt index b03ee999..e16374f4 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -47,3 +47,4 @@ produceBlockV stateful CGC RLP +auth From 21750d85108ca5771b5f5c66cdbcc0f4d42edf67 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Mon, 20 Jul 2026 21:30:24 -0700 Subject: [PATCH 02/18] Update CHANGES.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 0110520d..d740bdbe 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -38,7 +38,7 @@ There are likely to be descriptions etc outside of the list below, but new query | [#608](https://github.com/ethereum/beacon-APIs/pull/608) `POST /eth/v1/validator/prepare_beacon_proposer` deprecated | | | | | | | [#608](https://github.com/ethereum/beacon-APIs/pull/608) `POST /eth/v1/validator/register_validator` deprecated | | | | | | | [#608](https://github.com/ethereum/beacon-APIs/pull/608) `POST /eth/v1/validator/beacon_committee_subscriptions` updated | | | | | | -| [#XXX](https://github.com/ethereum/beacon-APIs/pull/XXX) `GET /eth/v4/validator/blocks/{slot}` changed to `POST /eth/v4/validator/blocks/{slot}` | | | | | | +| [#630](https://github.com/ethereum/beacon-APIs/pull/630) `GET /eth/v4/validator/blocks/{slot}` changed to `POST /eth/v4/validator/blocks/{slot}` | | | | | | The Following are no longer in the Standard API, removed since the latest version. From f8d2943a4b0f159110a03eaf4b7fc2195fa985ae Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Tue, 21 Jul 2026 09:50:39 -0700 Subject: [PATCH 03/18] address review comments --- CHANGES.md | 3 +-- apis/validator/block.v4.yaml | 12 ++++++------ apis/validator/builder_preferences.yaml | 7 ++++--- types/gloas/builder_entry.yaml | 8 +++++--- types/gloas/builder_preference_entry.yaml | 8 +++++--- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d740bdbe..3461614a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ There are likely to be descriptions etc outside of the list below, but new query | Endpoint | [Lighthouse](https://github.com/sigp/lighthouse) | [Lodestar](https://github.com/ChainSafe/lodestar) | [Nimbus](https://github.com/status-im/nimbus-eth2) | [Prysm](https://github.com/prysmaticlabs/prysm) | [Teku](https://github.com/ConsenSys/teku) | |---------------------------------------------------------------------------------------------------------------------|--------------------------------------------------|---------------------------------------------------|----------------------------------------------------|-------------------------------------------------|-------------------------------------------| -| [#580](https://github.com/ethereum/beacon-APIs/pull/580) / [#624](https://github.com/ethereum/beacon-APIs/pull/624) `GET /eth/v4/validator/blocks/{slot}` added | | | | | | +| [#580](https://github.com/ethereum/beacon-APIs/pull/580) / [#624](https://github.com/ethereum/beacon-APIs/pull/624) / [#630](https://github.com/ethereum/beacon-APIs/pull/630) `POST /eth/v4/validator/blocks/{slot}` added | | | | | | | [#552](https://github.com/ethereum/beacon-APIs/pull/552) `GET /eth/v1/validator/execution_payload_bids/{slot}/{builder_index}` added | | | | | | | [#580](https://github.com/ethereum/beacon-APIs/pull/580) / [#624](https://github.com/ethereum/beacon-APIs/pull/624) `GET /eth/v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}` added | | | | | | | [#552](https://github.com/ethereum/beacon-APIs/pull/552) `GET /eth/v1/validator/payload_attestation_data/{slot}` added | | | | | | @@ -38,7 +38,6 @@ There are likely to be descriptions etc outside of the list below, but new query | [#608](https://github.com/ethereum/beacon-APIs/pull/608) `POST /eth/v1/validator/prepare_beacon_proposer` deprecated | | | | | | | [#608](https://github.com/ethereum/beacon-APIs/pull/608) `POST /eth/v1/validator/register_validator` deprecated | | | | | | | [#608](https://github.com/ethereum/beacon-APIs/pull/608) `POST /eth/v1/validator/beacon_committee_subscriptions` updated | | | | | | -| [#630](https://github.com/ethereum/beacon-APIs/pull/630) `GET /eth/v4/validator/blocks/{slot}` changed to `POST /eth/v4/validator/blocks/{slot}` | | | | | | The Following are no longer in the Standard API, removed since the latest version. diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index 1050a64a..1ab8937e 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -108,11 +108,11 @@ post: $ref: '../../beacon-node-oapi.yaml#/components/headers/Eth-Execution-Payload-Included' Eth-Builder-Url: description: | - For a block built on an external builder's bid, the `url` of the winning builder. The + When the winning bid came through the builder-API channel, the `url` of that builder. The validator echoes this value in the `Eth-Builder-Url` request header when publishing the signed block, so the beacon node can forward the block to the same builder via the - builder-API `submitSignedBeaconBlock`. Absent when the block was self-built (no external - builder bid was used). + builder-API `submitSignedBeaconBlock`. Absent when the block was self-built or the winning + bid came from the p2p network. required: false schema: type: string @@ -148,9 +148,9 @@ post: type: boolean description: | Indicates whether the execution payload envelope is included in the response. - When `true`, the `data` field contains a `BlockContents` object with the beacon - block, execution payload envelope, blobs, and KZG proofs. When `false`, the `data` - field contains only a `BeaconBlock`. + When `true`, the `data` field contains a `BlockContents` object with the + beacon block, execution payload envelope, KZG proofs, and blobs. When `false`, + the `data` field contains only a `BeaconBlock`. example: false data: anyOf: diff --git a/apis/validator/builder_preferences.yaml b/apis/validator/builder_preferences.yaml index afa3afc6..ea70f935 100644 --- a/apis/validator/builder_preferences.yaml +++ b/apis/validator/builder_preferences.yaml @@ -36,8 +36,9 @@ post: description: | An array of `BuilderPreferenceEntry` objects, one per builder the beacon node should submit preferences to. Each entry names a concrete builder `url`, and no two entries may share the - same `url`. The beacon node submits each entry independently, so a malformed entry does not - prevent the others from being submitted; a 400 response identifies the failing entries by index. + same `url`. Inclusion is best-effort: the beacon node submits each entry independently and skips + any entry it cannot submit rather than failing the whole request. Entries that were not + submitted are reported by index in a 400 response. content: application/json: schema: @@ -48,7 +49,7 @@ post: "200": description: "The beacon node submitted the preferences to every builder." "400": - description: "One or more preference submissions were rejected. The index identifies the failing entry." + description: "Errors with one or more preference submissions; the failing entries are identified by index. Well-formed entries are still submitted." content: application/json: schema: diff --git a/types/gloas/builder_entry.yaml b/types/gloas/builder_entry.yaml index 8602bf83..acf2a83b 100644 --- a/types/gloas/builder_entry.yaml +++ b/types/gloas/builder_entry.yaml @@ -27,9 +27,11 @@ Gloas: type: string format: uri description: | - An optional proxy URL. When set, the beacon node sends the request to this URL instead of - to `url`, and sets the `Eth-Builder-Url` header to `url` so the proxy can forward the - request to the intended builder. + An optional proxy URL for reaching this builder. Some deployments place a sidecar between + the beacon node and its builders; when `proxy` is set the beacon node sends the request to + this URL and sets the `Eth-Builder-Url` header to `url` so the sidecar can forward it to the + intended builder. When `proxy` is unset the beacon node contacts the builder directly at + `url`. example: "http://side-car:9001" auth: allOf: diff --git a/types/gloas/builder_preference_entry.yaml b/types/gloas/builder_preference_entry.yaml index 253d0682..19c76251 100644 --- a/types/gloas/builder_preference_entry.yaml +++ b/types/gloas/builder_preference_entry.yaml @@ -25,9 +25,11 @@ Gloas: type: string format: uri description: | - An optional proxy URL. When set, the beacon node sends the request to this URL instead of - to `url`, and sets the `Eth-Builder-Url` header to `url` so the proxy can forward the - request to the intended builder. + An optional proxy URL for reaching this builder. Some deployments place a sidecar between + the beacon node and its builders; when `proxy` is set the beacon node sends the request to + this URL and sets the `Eth-Builder-Url` header to `url` so the sidecar can forward it to the + intended builder. When `proxy` is unset the beacon node contacts the builder directly at + `url`. example: "http://side-car:9001" auth: allOf: From 0ea8a06f1fe8331e01810fc0a2a5eee598ae05f0 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Tue, 21 Jul 2026 14:40:48 -0700 Subject: [PATCH 04/18] address review comments --- apis/beacon/blocks/blocks.v2.yaml | 10 ++++++---- apis/validator/block.v4.yaml | 10 ++++++---- types/gloas/builder_entry.yaml | 7 +++++-- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/apis/beacon/blocks/blocks.v2.yaml b/apis/beacon/blocks/blocks.v2.yaml index 65771e86..ee84fc04 100644 --- a/apis/beacon/blocks/blocks.v2.yaml +++ b/apis/beacon/blocks/blocks.v2.yaml @@ -48,9 +48,10 @@ post: required: false description: | The `url` of the winning builder, as returned in the `Eth-Builder-Url` response header of - `produceBlockV4`. The beacon node forwards the signed block to this builder via the - builder-API `submitSignedBeaconBlock` so the builder can release the execution payload. - Omitted for a self-built block. + `produceBlockV4`. When `produceBlockV4` returns this header (and `Eth-Builder-Proxy` if + present), the validator MUST echo them here so the beacon node can forward the signed block to + the winning builder via the builder-API `submitSignedBeaconBlock`; without it the builder + never releases the execution payload. Omitted for a self-built block. schema: type: string format: uri @@ -62,7 +63,8 @@ post: An optional proxy URL for the winning builder, as returned in the `Eth-Builder-Proxy` response header of `produceBlockV4`. When set, the beacon node sends the request to this URL and sets the `Eth-Builder-Url` header to the builder's `url` so the proxy can forward the - request to the intended builder; `Eth-Builder-Url` MUST therefore also be present. + request to the intended builder; `Eth-Builder-Url` MUST therefore also be present. The + validator echoes this value whenever `produceBlockV4` returned an `Eth-Builder-Proxy`. schema: type: string format: uri diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index 1ab8937e..7e9ca43a 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -86,10 +86,12 @@ post: names a concrete builder `url`, and no two entries may share the same `url`. For each entry the beacon node contacts the builder at its `proxy` if set, otherwise at its `url`; forwards the entry's `auth` unchanged; and applies the entry's `max_execution_payment`, `min_bid`, and - `builder_boost_factor` when comparing its bid. Malformed entries are ignored rather than failing - the request, so a single bad entry never blocks block production. When the body is omitted or - empty, or every entry is ignored, the beacon node contacts no external builders and chooses - between its local build and any p2p bid. + `builder_boost_factor` to that builder's own bid only. Each builder's bid is evaluated + independently under its own entry, and the highest-value candidate across those, the local + build, and any p2p bid wins; there is no cross-entry policy to reconcile. Malformed entries are + ignored rather than failing the request, so a single bad entry never blocks block production. + When the body is omitted or empty, or every entry is ignored, the beacon node contacts no + external builders and chooses between its local build and any p2p bid. content: application/json: schema: diff --git a/types/gloas/builder_entry.yaml b/types/gloas/builder_entry.yaml index acf2a83b..02cef540 100644 --- a/types/gloas/builder_entry.yaml +++ b/types/gloas/builder_entry.yaml @@ -7,8 +7,11 @@ Gloas: the validator's persistent `BuilderEntry` configuration from the keymanager `/eth/v1/validator/config` API (`url`, `proxy`, `pubkey`, `max_execution_payment`, `min_bid`, and `builder_boost_factor`), together with a per-slot `SignedRequestAuthV1` authenticating the - request to that builder. The validator resolves its configuration, applying any config-level - defaults, before sending, so each entry is self-contained. + request to that builder. The validator resolves its configuration up the keymanager order + (`BuilderEntry` over `BuilderConfig` over `default_config`, per field) before sending, so each + entry is self-contained and every field, including `min_bid`, `max_execution_payment`, and + `builder_boost_factor`, applies only to this builder's bid. There is no global preference at + the bid-selection layer; any `default_config` value has already been resolved into each entry. The beacon node contacts each builder (at its `proxy` if set, otherwise its `url`), forwarding the entry's `auth`, and applies `max_execution_payment`, `min_bid`, and `builder_boost_factor` From adc1ae17bfca43aab307af03184cd8f65f665739 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Wed, 22 Jul 2026 17:16:40 -0700 Subject: [PATCH 05/18] require fully resolved builder entries, add SSZ request bodies, drop proxy routing - BuilderEntry mirrors the keymanager config: url, auth, max_execution_payment, min_bid, and builder_boost_factor required, pubkey the sole optional verification pin; entries arrive fully resolved and the beacon node never substitutes defaults - no uniqueness rule here: multiple entries may share a url (the keymanager enforces the pair rule at write time), one getExecutionPayloadBid call per entry, malformed entries ignored - produceBlockV4 and submitBuilderPreferences accept SSZ request bodies: BuilderEntryV1 / BuilderPreferenceEntryV1 containers with MAX_BUILDER_ENTRIES (64) and MAX_BUILDER_URL_SIZE (2048) defined on the BuilderEntry type, all-zero pubkey means unset in either encoding, maxItems mirrors the SSZ cap, 415 for unsupported media types - bid evaluation made precise: min_bid floors the bid's total payment (value plus execution_payment), max_execution_payment caps the trusted component, and builder_boost_factor applies to the total after both checks - proxy fields and Eth-Builder-Proxy headers removed; the Eth-Builder-Url winner echo stays, with the stateless-setup rationale stated - request_auth data matches the builder-specs twin (UTF-8 URL default, whole-byte hex pattern); builder-API path parameter renamed to proposer_pubkey --- apis/beacon/blocks/blocks.v2.yaml | 44 ++++---- apis/validator/block.v4.yaml | 117 +++++++++++----------- apis/validator/builder_preferences.yaml | 49 +++++---- dictionary.dic | Bin 1456 -> 2288 bytes types/gloas/builder_entry.yaml | 109 +++++++++++--------- types/gloas/builder_preference_entry.yaml | 51 +++++----- types/gloas/request_auth.yaml | 11 +- wordlist.txt | 7 ++ 8 files changed, 204 insertions(+), 184 deletions(-) diff --git a/apis/beacon/blocks/blocks.v2.yaml b/apis/beacon/blocks/blocks.v2.yaml index ee84fc04..6e9eac83 100644 --- a/apis/beacon/blocks/blocks.v2.yaml +++ b/apis/beacon/blocks/blocks.v2.yaml @@ -5,16 +5,16 @@ post: summary: "Publish a signed block." operationId: "publishBlockV2" description: | - Instructs the beacon node to broadcast a newly signed beacon block to the beacon network, - to be included in the beacon chain. A success response (20x) indicates that the block - passed gossip validation and was successfully broadcast onto the network. - The beacon node is also expected to integrate the block into the state, but may broadcast it - before doing so, so as to aid timely delivery of the block. Should the block fail full - validation, a separate success response code (202) is used to indicate that the block was - successfully broadcast but failed integration. For Deneb/Electra/Fulu, this additionally instructs - the beacon node to broadcast all given blobs. For Gloas and later, blobs are broadcast as part - of the `ExecutionPayloadEnvelope` and are not submitted with the block. The broadcast behaviour - may be adjusted via the `broadcast_validation` query parameter. + Instructs the beacon node to broadcast a newly signed beacon block to the beacon network, to be + included in the beacon chain. A success response (20x) indicates that the block passed gossip + validation and was successfully broadcast onto the network. The beacon node is also expected to + integrate the block into the state, but may broadcast it before doing so, so as to aid timely + delivery of the block. Should the block fail full validation, a separate success response code + (202) is used to indicate that the block was successfully broadcast but failed integration. For + Deneb/Electra/Fulu, this additionally instructs the beacon node to broadcast all given blobs. + For Gloas and later, blobs are broadcast as part of the `ExecutionPayloadEnvelope` and are not + submitted with the block. The broadcast behaviour may be adjusted via the `broadcast_validation` + query parameter. parameters: - name: broadcast_validation in: query @@ -48,27 +48,17 @@ post: required: false description: | The `url` of the winning builder, as returned in the `Eth-Builder-Url` response header of - `produceBlockV4`. When `produceBlockV4` returns this header (and `Eth-Builder-Proxy` if - present), the validator MUST echo them here so the beacon node can forward the signed block to - the winning builder via the builder-API `submitSignedBeaconBlock`; without it the builder - never releases the execution payload. Omitted for a self-built block. + `produceBlockV4`. When `produceBlockV4` returns this header, the validator MUST echo it here + so the beacon node can push the signed block directly to the winning builder via the + builder-API `submitSignedBeaconBlock`, letting the builder release the execution payload + without waiting for the block to arrive over gossip. The echo is what makes this work for + stateless setups: a beacon node that did not serve the `produceBlockV4` request has no other + way to map the winning bid to a builder `url`. Omitted for a self-built block or a block won + by a p2p bid. schema: type: string format: uri example: "https://builder.example.com" - - name: Eth-Builder-Proxy - in: header - required: false - description: | - An optional proxy URL for the winning builder, as returned in the `Eth-Builder-Proxy` - response header of `produceBlockV4`. When set, the beacon node sends the request to this URL - and sets the `Eth-Builder-Url` header to the builder's `url` so the proxy can forward the - request to the intended builder; `Eth-Builder-Url` MUST therefore also be present. The - validator echoes this value whenever `produceBlockV4` returned an `Eth-Builder-Proxy`. - schema: - type: string - format: uri - example: "http://side-car:9001" requestBody: description: "The `SignedBeaconBlock` object composed of `BeaconBlock` object (produced by beacon node) and validator signature." required: true diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index 7e9ca43a..c25311d2 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -7,30 +7,31 @@ post: description: | Requests a beacon node to produce a valid block, which the validator then signs. - The beacon node always builds a local payload and considers any p2p bid. The validator optionally - supplies a list of `BuilderEntry` objects in the request body, one per external builder to also - request a bid from; the beacon node adds those bids to the comparison and returns the - highest-value candidate. It biases each builder's bid by the entry's `builder_boost_factor` and - rejects a bid that falls outside the entry's `min_bid` (floor) or `max_execution_payment` (ceiling - on the trusted execution payment). `builder_boost_factor` leans the choice toward the local - payload or a given builder without disabling either source. + The beacon node always builds a local payload and considers any p2p bid. The validator + optionally supplies a list of `BuilderEntry` objects in the request body, one per builder-API + `getExecutionPayloadBid` call to make; the beacon node adds those bids to the comparison and + returns the highest-value candidate. It biases each builder's bid by the entry's + `builder_boost_factor` and rejects a bid that falls outside the entry's `min_bid` (floor) or + `max_execution_payment` (ceiling on the trusted execution payment). `builder_boost_factor` leans + the choice toward the local payload or a given builder without disabling either source. - Post-Gloas, proposers submit execution payload bids rather than full execution payloads, so there - is no longer a blinded or unblinded distinction; builders release the payload later. This endpoint - is specific to the post-Gloas forks and is not backwards compatible with previous forks. + Post-Gloas, proposers submit execution payload bids rather than full execution payloads, so + there is no longer a blinded or unblinded distinction; builders release the payload later. This + endpoint is specific to the post-Gloas forks and is not backwards compatible with previous + forks. - The beacon node returns the full block contents (beacon block, execution payload envelope, blobs, - and KZG proofs) only when it self-builds and `include_payload` is `true`; otherwise it returns only - the `BeaconBlock`. The `Eth-Execution-Payload-Included` header and `execution_payload_included` - field report which form was returned. For a block built on an external builder's bid the beacon - node has no payload to return, and the `Eth-Builder-Url` response header (and the optional - `Eth-Builder-Proxy`) identifies the winning builder for the validator to echo when publishing. + The beacon node returns the full block contents (beacon block, execution payload envelope, + blobs, and KZG proofs) only when it self-builds and `include_payload` is `true`; otherwise it + returns only the `BeaconBlock`. The `Eth-Execution-Payload-Included` header and + `execution_payload_included` field report which form was returned. For a block built on an + external builder's bid the beacon node has no payload to return, and the `Eth-Builder-Url` + response header identifies the winning builder for the validator to echo when publishing. The two outcomes publish differently and never overlap. For an external builder's bid, the - validator publishes the signed block via `publishBlockV2`, echoing the builder headers, and the - winning builder releases the execution payload envelope. For a self-built block, the validator - publishes the signed block via `publishBlockV2` and the execution payload envelope via - `publishExecutionPayloadEnvelope`. + validator publishes the signed block via `publishBlockV2`, echoing the `Eth-Builder-Url` header, + and the winning builder releases the execution payload envelope. For a self-built block, the + validator publishes the signed block via `publishBlockV2` and the execution payload envelope + via `publishExecutionPayloadEnvelope`. parameters: - name: slot in: path @@ -65,39 +66,48 @@ post: beacon node (stateless operation: multi-BN setups, distributed validators, failover). When `false`, a self-built response returns only the beacon block, reporting - `Eth-Execution-Payload-Included: false`; the beacon node caches the execution payload envelope - and blobs. The validator retrieves the envelope via `getExecutionPayloadEnvelope` - (`GET /eth/v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}`), signs it, and - publishes it via `publishExecutionPayloadEnvelope`, and the producing beacon node attaches its - cached blobs and KZG proofs before broadcasting. This sends fewer bytes but ties publishing to - that one beacon node (stateful operation). + `Eth-Execution-Payload-Included: false`; the beacon node caches the execution payload + envelope and blobs. The validator retrieves the envelope via `getExecutionPayloadEnvelope` + (`GET /eth/v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}`), signs it, + and publishes it via `publishExecutionPayloadEnvelope`, and the producing beacon node + attaches its cached blobs and KZG proofs before broadcasting. This sends fewer bytes but + ties publishing to that one beacon node (stateful operation). This parameter is independent of the request body and only affects the self-built case: the - `BuilderEntry` list selects which external builders to consider, and when an external builder's - bid wins the beacon node returns only the beacon block regardless of this parameter (it does - not have that builder's execution payload). + `BuilderEntry` list selects which external builders to consider, and when an external + builder's bid wins the beacon node returns only the beacon block regardless of this + parameter (it does not have that builder's execution payload). schema: type: boolean requestBody: required: false description: | - An optional list of `BuilderEntry` objects, one per external builder to request a bid from, - added to the beacon node's local build and any p2p bid when selecting a payload. Each entry - names a concrete builder `url`, and no two entries may share the same `url`. For each entry the - beacon node contacts the builder at its `proxy` if set, otherwise at its `url`; forwards the - entry's `auth` unchanged; and applies the entry's `max_execution_payment`, `min_bid`, and - `builder_boost_factor` to that builder's own bid only. Each builder's bid is evaluated - independently under its own entry, and the highest-value candidate across those, the local - build, and any p2p bid wins; there is no cross-entry policy to reconcile. Malformed entries are - ignored rather than failing the request, so a single bad entry never blocks block production. - When the body is omitted or empty, or every entry is ignored, the beacon node contacts no - external builders and chooses between its local build and any p2p bid. + An optional list of `BuilderEntry` objects, one per builder-API `getExecutionPayloadBid` call + the beacon node should make, added to the beacon node's local build and any p2p bid when + selecting a bid. Each entry names the builder `url` to contact; multiple entries MAY share a + `url`, and the beacon node makes one `getExecutionPayloadBid` call per entry. For each entry + the beacon node contacts the builder at its `url`; forwards the entry's `auth` unchanged; and + applies the entry's `max_execution_payment`, `min_bid`, and `builder_boost_factor` to that + builder's own bid only. Each builder's bid is evaluated independently under its own entry, and + the highest-value candidate across those, the local build, and any p2p bid wins; there is no + cross-entry policy to reconcile. Malformed entries are ignored rather than failing the + request, so a single bad entry never blocks block production. When the body is omitted or + empty, or every entry is ignored, the beacon node contacts no external builders and chooses + between its local build and any p2p bid. + + The body MAY be encoded as JSON (`Content-Type: application/json`) or SSZ + (`Content-Type: application/octet-stream`); the `BuilderEntry` list is not fork-versioned, so + no `Eth-Consensus-Version` header is required. content: application/json: schema: type: array + maxItems: 64 # MAX_BUILDER_ENTRIES items: $ref: "../../beacon-node-oapi.yaml#/components/schemas/Gloas.BuilderEntry" + application/octet-stream: + schema: + description: "SSZ serialized `List[BuilderEntryV1, MAX_BUILDER_ENTRIES]` bytes (see the `BuilderEntry` type description for the container and constants). Use Content-Type header to indicate that SSZ data is contained in the request body." responses: "200": description: Success response @@ -110,27 +120,16 @@ post: $ref: '../../beacon-node-oapi.yaml#/components/headers/Eth-Execution-Payload-Included' Eth-Builder-Url: description: | - When the winning bid came through the builder-API channel, the `url` of that builder. The - validator echoes this value in the `Eth-Builder-Url` request header when publishing the - signed block, so the beacon node can forward the block to the same builder via the - builder-API `submitSignedBeaconBlock`. Absent when the block was self-built or the winning - bid came from the p2p network. + When the winning bid came through the builder-API channel, the `url` of that builder. + The validator echoes this value in the `Eth-Builder-Url` request header when publishing + the signed block, so the beacon node can forward the block to the same builder via the + builder-API `submitSignedBeaconBlock`. Absent when the block was self-built or the + winning bid came from the p2p network. required: false schema: type: string format: uri example: "https://builder.example.com" - Eth-Builder-Proxy: - description: | - The proxy URL the beacon node used to reach the winning builder, when it reached the - builder through a proxy. The validator echoes it in the `Eth-Builder-Proxy` request header - when publishing. Present only alongside `Eth-Builder-Url`. Absent for a builder contacted - directly at its `url`, and for a self-built block. - required: false - schema: - type: string - format: uri - example: "http://side-car:9001" content: application/json: schema: @@ -163,8 +162,8 @@ post: description: "SSZ serialized `BeaconBlock` or `BlockContents` bytes. Use Accept header to choose this response type, version string is sent in header `Eth-Consensus-Version` and payload inclusion indicated by `Eth-Execution-Payload-Included` header." "400": description: | - The request itself is invalid, for example an unparseable body, an invalid slot, or two - entries sharing the same `url`. An individual malformed `BuilderEntry` is ignored, not a 400. + The request itself is invalid, for example an unparseable body or an invalid slot. An + individual malformed `BuilderEntry` is ignored, not a 400. content: application/json: schema: @@ -176,6 +175,8 @@ post: message: "Invalid request to produce a block" "406": $ref: "../../beacon-node-oapi.yaml#/components/responses/NotAcceptable" + "415": + $ref: "../../beacon-node-oapi.yaml#/components/responses/UnsupportedMediaType" "500": $ref: '../../beacon-node-oapi.yaml#/components/responses/InternalError' "503": diff --git a/apis/validator/builder_preferences.yaml b/apis/validator/builder_preferences.yaml index ea70f935..c3321880 100644 --- a/apis/validator/builder_preferences.yaml +++ b/apis/validator/builder_preferences.yaml @@ -2,21 +2,21 @@ post: operationId: "submitBuilderPreferences" summary: "Submit builder preferences via the beacon node." description: | - Submits a proposer's per-builder preferences to the builders on the validator's behalf, including - the `max_execution_payment` the proposer is willing to accept from each. The validator does not - contact builders directly: for each `BuilderPreferenceEntry` the beacon node makes one - `submitBuilderPreferences` builder-API call, contacting the builder at its `proxy` if set and its - `url` otherwise, forwarding the entry's `auth` unchanged, and submitting its - `max_execution_payment`. The `pubkey` path parameter is passed as the builder-API - `validator_pubkey`, and each entry's `auth.message.slot` is the proposal slot the preferences - apply to. + Submits a proposer's per-builder preferences to the builders on the validator's behalf, + including the `max_execution_payment` the proposer is willing to accept from each. The validator + does not contact builders directly: for each `BuilderPreferenceEntry` the beacon node makes one + `submitBuilderPreferences` builder-API call, contacting the builder at its `url`, forwarding the + entry's `auth` unchanged, and submitting its `max_execution_payment`. The `pubkey` path + parameter is passed as the builder-API `proposer_pubkey`, and each entry's `auth.message.slot` + is the proposal slot the preferences apply to. - Validators MAY call this endpoint in the epoch prior to the epoch in which they will be proposing, - as determined from `state.proposer_lookahead`, so that builders have the preferences before the - bid request arrives. + Validators MAY call this endpoint in the epoch prior to the epoch in which they will be + proposing, as determined from `state.proposer_lookahead`, so that builders have the preferences + before the bid request arrives. - `BuilderPreferencesRequestV1` is not fork-versioned, so no `Eth-Consensus-Version` header is - required. + The request body MAY be encoded as JSON (`Content-Type: application/json`) or SSZ + (`Content-Type: application/octet-stream`). It is not fork-versioned, so no + `Eth-Consensus-Version` header is required. This API is applicable from the Gloas fork onwards. parameters: @@ -25,7 +25,7 @@ post: required: true description: | The BLS public key of the proposing validator whose preferences these are. The beacon node - passes it as the `validator_pubkey` of each `submitBuilderPreferences` builder-API call, and + passes it as the `proposer_pubkey` of each `submitBuilderPreferences` builder-API call, and each entry's `auth` is signed by this key. schema: $ref: "../../types/primitive.yaml#/Pubkey" @@ -34,26 +34,33 @@ post: requestBody: required: true description: | - An array of `BuilderPreferenceEntry` objects, one per builder the beacon node should submit - preferences to. Each entry names a concrete builder `url`, and no two entries may share the - same `url`. Inclusion is best-effort: the beacon node submits each entry independently and skips - any entry it cannot submit rather than failing the whole request. Entries that were not - submitted are reported by index in a 400 response. + An array of `BuilderPreferenceEntry` objects, one per preference submission the beacon node + should make. Each entry names the builder `url` to contact; multiple entries MAY share a + `url`, and the beacon node makes one submission per entry. Inclusion is best-effort: the + beacon node submits each entry independently and skips any entry it cannot submit rather than + failing the whole request. Entries that were not submitted are reported by index in a 400 + response. content: application/json: schema: type: array + maxItems: 64 # MAX_BUILDER_ENTRIES items: $ref: "../../beacon-node-oapi.yaml#/components/schemas/Gloas.BuilderPreferenceEntry" + application/octet-stream: + schema: + description: "SSZ serialized `List[BuilderPreferenceEntryV1, MAX_BUILDER_ENTRIES]` bytes (see the `BuilderPreferenceEntry` type description for the container). Use Content-Type header to indicate that SSZ data is contained in the request body." responses: "200": - description: "The beacon node submitted the preferences to every builder." + description: "The beacon node completed every preference submission." "400": - description: "Errors with one or more preference submissions; the failing entries are identified by index. Well-formed entries are still submitted." + description: "The request itself is invalid (for example an unparseable body), or errors occurred with one or more preference submissions; failing entries are identified by index. Well-formed entries are still submitted." content: application/json: schema: $ref: "../../beacon-node-oapi.yaml#/components/schemas/IndexedErrorMessage" + "415": + $ref: "../../beacon-node-oapi.yaml#/components/responses/UnsupportedMediaType" "500": $ref: "../../beacon-node-oapi.yaml#/components/responses/InternalError" "503": diff --git a/dictionary.dic b/dictionary.dic index 0924f5dadf249b157792226a692f7920c79e8d1f..cfb5cde5f637a60f4f61e7e717858075376e3746 100644 GIT binary patch literal 2288 zcmai$KWrOy6vtnEcRt7Qot-#|14)G&XcJsoQB@#dfW%Inq_!_7->+`ePLBOcVsbf~ z^Bo~bEG!iRFaQH$sgMv%PzkYtgoL`#i4BPX7+^r10Eqz!_&)oB(iYT{E+7Bi@BQBI z{k7BV_oVCQ+j6TJxImLv09u%;@ zz=R)d zkn^mfz%>}kfRnQ9m%&JWa8nBLn zWtet_@=WNR0iB9P&oPnrO{Ng!fubnrRVMs;b3j4wF_G_&m?-3DAm{%k_$F% zu}f=|jiDB1LT!e@4w)%M0?8QaVy0r!rL4lG$?3`kfVVxLkJzL`v}TiOsNH6-L!&X2 z%runH?@&tNvgv8%5-_fzcETZZOm7AOhN_Q!E~B0s;IvV!3cFseTaG2_WztUNieblX zOW*e8mh`39k{7(dKWNZQ0;Fb`6)l0=)7nlD z^!RRW4LzllR!VRhWi`i@mfNA(M5Stp@`eTD(`L!-NH1tezu)P44KkGqJcf~Nx7+NK zp>Y40S!MU-Z8|=JMWrTcD5ZANs9aeob@%o*g7!Zy^4&N>Hq^AM{cWZdueV1T4GXlZnbEL(zE2qyT@ME) z<5ssVFZNLnwEsr2S}Buy+~|dyZl}Lfbi1wH2AvqeNQin-I0$%V+-@EqbM2)d@H?Af zAo~qCf?+|&^nAXU12(fN7B8~hx^RYeb4Jhaw!@Z;>`o{=O6W&hl-Sc=&_atrh5^wg zbUSD*hrt0&$1!PQksBpdmoJ2}A1tB48^{HZ9^z-D6`nsm6R$5>%S-FLi8<6wphROT zh7s*Ct^2awZMg?Dm&90#L&G!UPG{Sb?K{km#xSwye8;&$Sq)RmtvH_O2hBjDA8`Xu zFiIhcTnfwwGSEiMkdy}-%MF1-B1SE(3c1T2jGjlgnOfk7UW<$vhFg!v8m0dj?oaXE zf}aTT@ctb=WylurUG^4uC}UoO>`BPp$M?h!;2StI7oFS09_!5Cb><7`egK_sv41t% zn}=*F(&sbpV0{zoCqQ1`?|BV+xg+=db;MTkh@993A`2Q{Rr~^5qwt-HeQM3IecD^_bCCmd8q~iif zA^Rsum;F;h511xB&QA$hH$xuxSU5|^=K*tMU<3Pv96T@N-~}NEERrS0`}>*caG<1m z2e^@SJS+=2@r*EVz*(>hK5h$nVw+qKTs$U4(XPND zCDL(=xFh6^o{(-KxQ>_NcsYC|J!<|ebo2NsWZ_Rzl>H_HJAk78S7r?vAT6Y#!$Q8z zDk%!r)P5#yMU^bfxO(Lh;%fG6xT~!iNxe~S*1{j!!jW-JB*s(z>ceoySei+it!kyQRqyfyiZc_M?_Ue|jJ24h^75vkZl3RF zQoX-iH~7NxzqrW<_(c0f+n~r}-1|fSE}3DxMD8%=j|1sb=wF~cqa9^_jP{= builder_boost_factor * (builder_payload_value // 100)`, and to - the builder's bid otherwise; the highest-value candidate across the local payload, any - p2p bid, and every builder's boosted bid is chosen. + Percentage multiplier applied to this builder's bid when choosing between the + builder's bid and the local execution node payload. `builder_payload_value` is the + bid's total payment to the proposer, its `value` plus its `execution_payment`, and the + factor applies only to bids that passed the `max_execution_payment` and `min_bid` + checks. When the beacon node has a valid local payload and this builder returns a + viable bid, it commits to the local payload if `exec_node_payload_value >= + builder_boost_factor * (builder_payload_value // 100)`, and to the builder's bid + otherwise; the highest-value candidate across the local payload, any p2p bid, and + every builder's boosted bid is chosen. The reserved values encode common preferences: `0` prefers the local payload unless an - error makes it unviable; `100` is profit maximization (choose whichever pays more); and - `2**64 - 1` prefers this builder unless an error or beacon node health check makes it - unviable. Servers should use saturating arithmetic so that large values do not overflow. - A value out of range for a 64-bit unsigned integer makes the entry malformed, so the - beacon node ignores that entry. + error makes it unviable; `100` is profit maximization (choose whichever pays more); + and `2**64 - 1` prefers this builder unless an error or beacon node health check makes + it unviable. Servers should use saturating arithmetic so that large values do not + overflow. A value out of range for a 64-bit unsigned integer makes the entry + malformed, so the beacon node ignores that entry. example: "100" diff --git a/types/gloas/builder_preference_entry.yaml b/types/gloas/builder_preference_entry.yaml index 19c76251..0a3f39e1 100644 --- a/types/gloas/builder_preference_entry.yaml +++ b/types/gloas/builder_preference_entry.yaml @@ -3,47 +3,48 @@ Gloas: type: object description: | Per-builder preference the validator asks the beacon node to submit to a builder ahead of the - bid request, one entry per builder. Each entry names a concrete builder `url`. For each entry - the beacon node makes one `submitBuilderPreferences` builder-API call: it contacts the builder - at its `proxy` if set, otherwise at its `url`, forwards `auth` unchanged, and submits + bid request, one entry per preference submission. Each entry names the builder `url` to + contact. For each entry the beacon node makes one `submitBuilderPreferences` builder-API + call: it contacts the builder at its `url`, forwards `auth` unchanged, and submits `max_execution_payment`. - Unlike the block-production `BuilderEntry`, this carries only what a builder is allowed to see. - The proposer's private bid-filtering strategy (`min_bid` and `builder_boost_factor`) is never - sent to a builder; the beacon node applies those locally when selecting a payload. + Unlike the block-production `BuilderEntry`, this carries only what a builder is allowed to + see. The proposer's private bid-filtering strategy (`min_bid` and `builder_boost_factor`) is + never sent to a builder; the beacon node applies those locally when selecting a bid. + + SSZ request encoding. The `application/octet-stream` request body of the beacon-node + `submitBuilderPreferences` endpoint is the SSZ serialization of + `List[BuilderPreferenceEntryV1, MAX_BUILDER_ENTRIES]`, using the constants defined on + `BuilderEntry` and the `SignedRequestAuthV1` container from the [Builder + API](https://ethereum.github.io/builder-specs/) Gloas spec: + + class BuilderPreferenceEntryV1(Container): + url: ByteList[MAX_BUILDER_URL_SIZE] # UTF-8 bytes of `url`, exactly as in JSON + auth: SignedRequestAuthV1 + max_execution_payment: Gwei required: [url, auth, max_execution_payment] properties: url: type: string format: uri description: | - The builder's URL, uniquely identifying it. The beacon node submits preferences to the - builder here unless `proxy` is set, and sends this value as the `Eth-Builder-Url` header - when routing through a proxy. + The URL the beacon node submits this entry's preferences to. Multiple entries MAY share a + `url`; the beacon node makes one submission per entry. example: "https://builder.example.com" - proxy: - type: string - format: uri - description: | - An optional proxy URL for reaching this builder. Some deployments place a sidecar between - the beacon node and its builders; when `proxy` is set the beacon node sends the request to - this URL and sets the `Eth-Builder-Url` header to `url` so the sidecar can forward it to the - intended builder. When `proxy` is unset the beacon node contacts the builder directly at - `url`. - example: "http://side-car:9001" auth: + description: | + `SignedRequestAuthV1` authenticating the preference submission to this builder. The + beacon node MUST forward its `message` and `signature` byte-for-byte unchanged; the + builder verifies them. Its `data` is opaque and agreed with the builder out of band, + and its `message.slot` is the proposal slot the preferences apply to. allOf: - $ref: "./request_auth.yaml#/Gloas/SignedRequestAuthV1" - - description: | - `SignedRequestAuthV1` authenticating the preference submission to this builder. The - beacon node MUST forward its `message` and `signature` byte-for-byte unchanged. Its - `data` is opaque and agreed with the builder out of band, and its `message.slot` is the - proposal slot the preferences apply to. max_execution_payment: allOf: - $ref: "../primitive.yaml#/Gwei" - description: | Maximum trusted execution-layer payment, in Gwei, the proposer will accept from this builder. It is submitted to the builder so the builder can shape its bid; the beacon - node still enforces the same bound when selecting a payload. + node enforces the `max_execution_payment` carried in the block-production + `BuilderEntry` when selecting a bid. example: "1000000000" diff --git a/types/gloas/request_auth.yaml b/types/gloas/request_auth.yaml index 787a9ffc..4c79684c 100644 --- a/types/gloas/request_auth.yaml +++ b/types/gloas/request_auth.yaml @@ -9,12 +9,13 @@ Gloas: format: hex description: | Opaque authentication data unique to the builder, agreed upon out of band. The meaning of - the up to `MAX_DATA_SIZE` bytes is left to the two parties; the builder verifies the exact - bytes. When no value has been agreed out of band, implementations SHOULD default to the - builder's URL exactly as advertised, so proposers with no prior relationship (and DVT - operators sharing static config) can construct an identical `data` deterministically. + the up to `MAX_DATA_SIZE` (4096) bytes is left to the two parties; the builder checks the + exact bytes when it verifies. When no value has been agreed out of band, implementations + SHOULD default to the UTF-8 bytes of the builder's own advertised URL, exactly as + advertised, so proposers with no prior relationship can construct an identical `data` + deterministically. example: "0x1234567890abcdef" - pattern: "^0x[a-fA-F0-9]{0,8192}$" + pattern: "^0x(?:[a-fA-F0-9]{2}){0,4096}$" slot: allOf: - $ref: "../primitive.yaml#/Uint64" diff --git a/wordlist.txt b/wordlist.txt index e16374f4..b899c4dd 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -48,3 +48,10 @@ stateful CGC RLP auth +RequestAuthV +SignedRequestAuthV +BuilderEntryV +BuilderPreferenceEntryV +UTF +uri +publishBlockV From 1028934dcadfa42ff748a569fa8723aaa606854a Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Sat, 25 Jul 2026 16:31:05 -0700 Subject: [PATCH 06/18] address review feedback: trim to beacon-API conventions, fix bid selection Reference the Builder API containers at a pinned commit with anchors instead of restating them (will need to bump pin later); BuilderEntryV1 has no Builder API definition, so it stays defined here and now says why. Bid selection: all candidates are compared in Gwei and the highest boosted value wins. Behaviour: the beacon node MAY consider a p2p bid rather than always doing so; an entry the beacon node cannot use never fails the request; each entry's auth.message.slot is the slot the request is for. Also: revert the accidental dictionary.dic edit, add the new endpoint and the Eth-Builder-Url header to CHANGES.md, and document the builder-bid path in validator-flow.md. --- CHANGES.md | 3 +- apis/beacon/blocks/blocks.v2.yaml | 32 ++++---- apis/validator/block.v4.yaml | 86 +++++++++------------- apis/validator/builder_preferences.yaml | 33 +++------ dictionary.dic | Bin 2288 -> 1456 bytes types/gloas/builder_entry.yaml | 82 +++++++-------------- types/gloas/builder_preference_entry.yaml | 39 +++------- types/gloas/request_auth.yaml | 4 +- validator-flow.md | 14 +++- 9 files changed, 115 insertions(+), 178 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3461614a..d65e6634 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ There are likely to be descriptions etc outside of the list below, but new query | Endpoint | [Lighthouse](https://github.com/sigp/lighthouse) | [Lodestar](https://github.com/ChainSafe/lodestar) | [Nimbus](https://github.com/status-im/nimbus-eth2) | [Prysm](https://github.com/prysmaticlabs/prysm) | [Teku](https://github.com/ConsenSys/teku) | |---------------------------------------------------------------------------------------------------------------------|--------------------------------------------------|---------------------------------------------------|----------------------------------------------------|-------------------------------------------------|-------------------------------------------| | [#580](https://github.com/ethereum/beacon-APIs/pull/580) / [#624](https://github.com/ethereum/beacon-APIs/pull/624) / [#630](https://github.com/ethereum/beacon-APIs/pull/630) `POST /eth/v4/validator/blocks/{slot}` added | | | | | | +| [#630](https://github.com/ethereum/beacon-APIs/pull/630) `POST /eth/v1/validator/builder_preferences/{pubkey}` added | | | | | | | [#552](https://github.com/ethereum/beacon-APIs/pull/552) `GET /eth/v1/validator/execution_payload_bids/{slot}/{builder_index}` added | | | | | | | [#580](https://github.com/ethereum/beacon-APIs/pull/580) / [#624](https://github.com/ethereum/beacon-APIs/pull/624) `GET /eth/v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}` added | | | | | | | [#552](https://github.com/ethereum/beacon-APIs/pull/552) `GET /eth/v1/validator/payload_attestation_data/{slot}` added | | | | | | @@ -20,7 +21,7 @@ There are likely to be descriptions etc outside of the list below, but new query | [#552](https://github.com/ethereum/beacon-APIs/pull/552) `POST /eth/v1/beacon/pool/payload_attestations` added | | | | | | | [#614](https://github.com/ethereum/beacon-APIs/pull/614) `POST /eth/v1/beacon/states/{state_id}/builders` added | | | | | | | [#552](https://github.com/ethereum/beacon-APIs/pull/552) `GET /eth/v2/beacon/blocks/{block_id}` updated | | | | | | -| [#552](https://github.com/ethereum/beacon-APIs/pull/552) `POST /eth/v2/beacon/blocks` updated | | | | | | +| [#552](https://github.com/ethereum/beacon-APIs/pull/552) / [#630](https://github.com/ethereum/beacon-APIs/pull/630) `POST /eth/v2/beacon/blocks` updated | | | | | | | [#552](https://github.com/ethereum/beacon-APIs/pull/552) `execution_payload_available EVENT` added | | | | | | | [#552](https://github.com/ethereum/beacon-APIs/pull/552) `execution_payload_bid EVENT` added | | | | | | | [#552](https://github.com/ethereum/beacon-APIs/pull/552) `payload_attestation_message EVENT` added | | | | | | diff --git a/apis/beacon/blocks/blocks.v2.yaml b/apis/beacon/blocks/blocks.v2.yaml index 6e9eac83..f561b3d5 100644 --- a/apis/beacon/blocks/blocks.v2.yaml +++ b/apis/beacon/blocks/blocks.v2.yaml @@ -5,16 +5,16 @@ post: summary: "Publish a signed block." operationId: "publishBlockV2" description: | - Instructs the beacon node to broadcast a newly signed beacon block to the beacon network, to be - included in the beacon chain. A success response (20x) indicates that the block passed gossip - validation and was successfully broadcast onto the network. The beacon node is also expected to - integrate the block into the state, but may broadcast it before doing so, so as to aid timely - delivery of the block. Should the block fail full validation, a separate success response code - (202) is used to indicate that the block was successfully broadcast but failed integration. For - Deneb/Electra/Fulu, this additionally instructs the beacon node to broadcast all given blobs. - For Gloas and later, blobs are broadcast as part of the `ExecutionPayloadEnvelope` and are not - submitted with the block. The broadcast behaviour may be adjusted via the `broadcast_validation` - query parameter. + Instructs the beacon node to broadcast a newly signed beacon block to the beacon network, + to be included in the beacon chain. A success response (20x) indicates that the block + passed gossip validation and was successfully broadcast onto the network. + The beacon node is also expected to integrate the block into the state, but may broadcast it + before doing so, so as to aid timely delivery of the block. Should the block fail full + validation, a separate success response code (202) is used to indicate that the block was + successfully broadcast but failed integration. For Deneb/Electra/Fulu, this additionally instructs + the beacon node to broadcast all given blobs. For Gloas and later, blobs are broadcast as part + of the `ExecutionPayloadEnvelope` and are not submitted with the block. The broadcast behaviour + may be adjusted via the `broadcast_validation` query parameter. parameters: - name: broadcast_validation in: query @@ -47,13 +47,11 @@ post: in: header required: false description: | - The `url` of the winning builder, as returned in the `Eth-Builder-Url` response header of - `produceBlockV4`. When `produceBlockV4` returns this header, the validator MUST echo it here - so the beacon node can push the signed block directly to the winning builder via the - builder-API `submitSignedBeaconBlock`, letting the builder release the execution payload - without waiting for the block to arrive over gossip. The echo is what makes this work for - stateless setups: a beacon node that did not serve the `produceBlockV4` request has no other - way to map the winning bid to a builder `url`. Omitted for a self-built block or a block won + The `url` of the winning builder, as returned by `POST /eth/v4/validator/blocks/{slot}`. + When that endpoint returns this header the validator client MUST echo it here, so the beacon + node can forward the signed block to the same builder and the builder can release the + payload without waiting for gossip. The echo is what lets a beacon node that did not serve + the block-production request still forward it. Omitted for a self-built block or a block won by a p2p bid. schema: type: string diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index c25311d2..61ad0746 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -7,31 +7,28 @@ post: description: | Requests a beacon node to produce a valid block, which the validator then signs. - The beacon node always builds a local payload and considers any p2p bid. The validator - optionally supplies a list of `BuilderEntry` objects in the request body, one per builder-API - `getExecutionPayloadBid` call to make; the beacon node adds those bids to the comparison and - returns the highest-value candidate. It biases each builder's bid by the entry's - `builder_boost_factor` and rejects a bid that falls outside the entry's `min_bid` (floor) or - `max_execution_payment` (ceiling on the trusted execution payment). `builder_boost_factor` leans - the choice toward the local payload or a given builder without disabling either source. + The beacon node always builds a local payload and MAY consider a p2p bid, so a block is returned + even when no bid is available. The validator client optionally supplies a list of `BuilderEntry` + objects in the request body, one per builder-API bid request to make. Each builder's bid is + rejected if it falls outside its own entry's `min_bid` or `max_execution_payment`, and is then + boosted by that entry's `builder_boost_factor`. The highest boosted bid, the local build and any + p2p bid are compared in Gwei, and the highest wins. - Post-Gloas, proposers submit execution payload bids rather than full execution payloads, so - there is no longer a blinded or unblinded distinction; builders release the payload later. This - endpoint is specific to the post-Gloas forks and is not backwards compatible with previous - forks. + The response carries the full block contents only when the beacon node self-built the block and + `include_payload` is `true`; in every other case, including any bid win, it carries only the + `BeaconBlock`. Response metadata reports which form was returned and, when a builder-API bid + won, which builder won it. - The beacon node returns the full block contents (beacon block, execution payload envelope, - blobs, and KZG proofs) only when it self-builds and `include_payload` is `true`; otherwise it - returns only the `BeaconBlock`. The `Eth-Execution-Payload-Included` header and - `execution_payload_included` field report which form was returned. For a block built on an - external builder's bid the beacon node has no payload to return, and the `Eth-Builder-Url` - response header identifies the winning builder for the validator to echo when publishing. + Notes: + - A self-built block is published via `POST /eth/v2/beacon/blocks`, and its envelope via + `POST /eth/v1/beacon/execution_payload_envelopes`. + - A block won through the builder-API channel is published via `POST /eth/v2/beacon/blocks` + echoing `Eth-Builder-Url`, and the winning builder releases the envelope. + - A block won by a p2p bid is published via `POST /eth/v2/beacon/blocks` without the header, + and the bidding builder releases the envelope. - The two outcomes publish differently and never overlap. For an external builder's bid, the - validator publishes the signed block via `publishBlockV2`, echoing the `Eth-Builder-Url` header, - and the winning builder releases the execution payload envelope. For a self-built block, the - validator publishes the signed block via `publishBlockV2` and the execution payload envelope - via `publishExecutionPayloadEnvelope`. + This endpoint is specific to the post-Gloas forks and is not backwards compatible with previous + forks. parameters: - name: slot in: path @@ -58,46 +55,33 @@ post: required: true description: | Whether a self-built block's response carries the execution payload inline, or omits it for - the beacon node to cache and the validator to retrieve separately. + the beacon node to cache and the validator client to retrieve separately. When `true`, a self-built response includes the full block contents (beacon block, execution payload envelope, blobs, and KZG proofs) and reports `Eth-Execution-Payload-Included: true`. - The validator holds the whole payload, so it can sign and publish the envelope through any - beacon node (stateless operation: multi-BN setups, distributed validators, failover). + The validator client holds the whole payload, so it can sign and publish the envelope through + any beacon node (stateless operation: multi-BN setups, distributed validators, failover). When `false`, a self-built response returns only the beacon block, reporting `Eth-Execution-Payload-Included: false`; the beacon node caches the execution payload - envelope and blobs. The validator retrieves the envelope via `getExecutionPayloadEnvelope` - (`GET /eth/v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}`), signs it, - and publishes it via `publishExecutionPayloadEnvelope`, and the producing beacon node - attaches its cached blobs and KZG proofs before broadcasting. This sends fewer bytes but + envelope and blobs. The validator client retrieves the envelope via + `GET /eth/v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}`, signs it, and + publishes it via `POST /eth/v1/beacon/execution_payload_envelopes`, and the producing beacon + node attaches its cached blobs and KZG proofs before broadcasting. This sends fewer bytes but ties publishing to that one beacon node (stateful operation). - This parameter is independent of the request body and only affects the self-built case: the - `BuilderEntry` list selects which external builders to consider, and when an external - builder's bid wins the beacon node returns only the beacon block regardless of this - parameter (it does not have that builder's execution payload). + This parameter affects the self-built case only. When a bid wins, the beacon node does not + hold that payload, so it returns only the beacon block regardless of this parameter. schema: type: boolean requestBody: required: false description: | - An optional list of `BuilderEntry` objects, one per builder-API `getExecutionPayloadBid` call - the beacon node should make, added to the beacon node's local build and any p2p bid when - selecting a bid. Each entry names the builder `url` to contact; multiple entries MAY share a - `url`, and the beacon node makes one `getExecutionPayloadBid` call per entry. For each entry - the beacon node contacts the builder at its `url`; forwards the entry's `auth` unchanged; and - applies the entry's `max_execution_payment`, `min_bid`, and `builder_boost_factor` to that - builder's own bid only. Each builder's bid is evaluated independently under its own entry, and - the highest-value candidate across those, the local build, and any p2p bid wins; there is no - cross-entry policy to reconcile. Malformed entries are ignored rather than failing the - request, so a single bad entry never blocks block production. When the body is omitted or - empty, or every entry is ignored, the beacon node contacts no external builders and chooses - between its local build and any p2p bid. - - The body MAY be encoded as JSON (`Content-Type: application/json`) or SSZ - (`Content-Type: application/octet-stream`); the `BuilderEntry` list is not fork-versioned, so - no `Eth-Consensus-Version` header is required. + The `BuilderEntry` objects to solicit bids from. Each entry's `auth.message.slot` is the + `slot` this request is for; the builder rejects a mismatch, so an entry naming a different slot + is one the beacon node does not use. An entry the beacon node cannot use, for any reason, MUST + NOT fail the request; it simply yields no bid, so one bad entry never costs the proposer its + slot. When the body is omitted or empty, no external builders are contacted. content: application/json: schema: @@ -161,9 +145,7 @@ post: schema: description: "SSZ serialized `BeaconBlock` or `BlockContents` bytes. Use Accept header to choose this response type, version string is sent in header `Eth-Consensus-Version` and payload inclusion indicated by `Eth-Execution-Payload-Included` header." "400": - description: | - The request itself is invalid, for example an unparseable body or an invalid slot. An - individual malformed `BuilderEntry` is ignored, not a 400. + description: "Invalid block production request" content: application/json: schema: diff --git a/apis/validator/builder_preferences.yaml b/apis/validator/builder_preferences.yaml index c3321880..f9ba356e 100644 --- a/apis/validator/builder_preferences.yaml +++ b/apis/validator/builder_preferences.yaml @@ -1,23 +1,15 @@ post: operationId: "submitBuilderPreferences" - summary: "Submit builder preferences via the beacon node." + summary: Submit builder preferences description: | - Submits a proposer's per-builder preferences to the builders on the validator's behalf, - including the `max_execution_payment` the proposer is willing to accept from each. The validator - does not contact builders directly: for each `BuilderPreferenceEntry` the beacon node makes one - `submitBuilderPreferences` builder-API call, contacting the builder at its `url`, forwarding the - entry's `auth` unchanged, and submitting its `max_execution_payment`. The `pubkey` path - parameter is passed as the builder-API `proposer_pubkey`, and each entry's `auth.message.slot` - is the proposal slot the preferences apply to. + Submits a proposer's per-builder preferences on the validator client's behalf, one builder-API + preference submission per entry. The validator client does not contact builders directly. Each + entry's `auth.message.slot` is the proposal slot the preferences apply to. Validators MAY call this endpoint in the epoch prior to the epoch in which they will be proposing, as determined from `state.proposer_lookahead`, so that builders have the preferences before the bid request arrives. - The request body MAY be encoded as JSON (`Content-Type: application/json`) or SSZ - (`Content-Type: application/octet-stream`). It is not fork-versioned, so no - `Eth-Consensus-Version` header is required. - This API is applicable from the Gloas fork onwards. parameters: - name: pubkey @@ -25,8 +17,8 @@ post: required: true description: | The BLS public key of the proposing validator whose preferences these are. The beacon node - passes it as the `proposer_pubkey` of each `submitBuilderPreferences` builder-API call, and - each entry's `auth` is signed by this key. + passes it as the `proposer_pubkey` of each builder-API preference submission, and each + entry's `auth` is signed by this key. schema: $ref: "../../types/primitive.yaml#/Pubkey" tags: @@ -34,12 +26,9 @@ post: requestBody: required: true description: | - An array of `BuilderPreferenceEntry` objects, one per preference submission the beacon node - should make. Each entry names the builder `url` to contact; multiple entries MAY share a - `url`, and the beacon node makes one submission per entry. Inclusion is best-effort: the - beacon node submits each entry independently and skips any entry it cannot submit rather than - failing the whole request. Entries that were not submitted are reported by index in a 400 - response. + The `BuilderPreferenceEntry` objects to submit. Inclusion is best-effort: the beacon node + submits each entry independently and skips any entry it cannot submit rather than failing the + whole request. Entries that were not submitted are reported by index in a 400 response. content: application/json: schema: @@ -52,9 +41,9 @@ post: description: "SSZ serialized `List[BuilderPreferenceEntryV1, MAX_BUILDER_ENTRIES]` bytes (see the `BuilderPreferenceEntry` type description for the container). Use Content-Type header to indicate that SSZ data is contained in the request body." responses: "200": - description: "The beacon node completed every preference submission." + description: "Every entry was submitted" "400": - description: "The request itself is invalid (for example an unparseable body), or errors occurred with one or more preference submissions; failing entries are identified by index. Well-formed entries are still submitted." + description: "Invalid request, or errors with one or more preference submissions" content: application/json: schema: diff --git a/dictionary.dic b/dictionary.dic index cfb5cde5f637a60f4f61e7e717858075376e3746..0924f5dadf249b157792226a692f7920c79e8d1f 100644 GIT binary patch delta 816 zcmXYvzi$#@7{|Z&gS!IdN?Y1mS`&$;CRB%-8Xb&3&Wg3b<-9wKQDcGyEeQ$&Y8Wb$ zZZ?uL4h|;9f51T#H#av|69)(5=uj3X2mL_bCCmd8q~iif zA^Rsum;F;h511xB&QA$hH$xuxSU5|^=K*tMU<3Pv96T@N-~}NEERrS0`}>*caG<1m z2e^@SJS+=2@r*EVz*(>hK5h$nVw+qKTs$U4(XPND zCDL(=xFh6^o{(-KxQ>_NcsYC|J!<|ebo2NsWZ_Rzl>H_HJAk78S7r?vAT6Y#!$Q8z zDk%!r)P5#yMU^bfxO(Lh;%fG6xT~!iNxe~S*1{j!!jW-JB*s(z>ceoySei+it!kyQRqyfyiZc_M?_Ue|jJ24h^75vkZl3RF zQoX-iH~7NxzqrW<_(c0f+n~r}-1|fSE}3DxMD8%=j|1sb=wF~cqa9^_jP{+`ePLBOcVsbf~ z^Bo~bEG!iRFaQH$sgMv%PzkYtgoL`#i4BPX7+^r10Eqz!_&)oB(iYT{E+7Bi@BQBI z{k7BV_oVCQ+j6TJxImLv09u%;@ zz=R)d zkn^mfz%>}kfRnQ9m%&JWa8nBLn zWtet_@=WNR0iB9P&oPnrO{Ng!fubnrRVMs;b3j4wF_G_&m?-3DAm{%k_$F% zu}f=|jiDB1LT!e@4w)%M0?8QaVy0r!rL4lG$?3`kfVVxLkJzL`v}TiOsNH6-L!&X2 z%runH?@&tNvgv8%5-_fzcETZZOm7AOhN_Q!E~B0s;IvV!3cFseTaG2_WztUNieblX zOW*e8mh`39k{7(dKWNZQ0;Fb`6)l0=)7nlD z^!RRW4LzllR!VRhWi`i@mfNA(M5Stp@`eTD(`L!-NH1tezu)P44KkGqJcf~Nx7+NK zp>Y40S!MU-Z8|=JMWrTcD5ZANs9aeob@%o*g7!Zy^4&N>Hq^AM{cWZdueV1T4GXlZnbEL(zE2qyT@ME) z<5ssVFZNLnwEsr2S}Buy+~|dyZl}Lfbi1wH2AvqeNQin-I0$%V+-@EqbM2)d@H?Af zAo~qCf?+|&^nAXU12(fN7B8~hx^RYeb4Jhaw!@Z;>`o{=O6W&hl-Sc=&_atrh5^wg zbUSD*hrt0&$1!PQksBpdmoJ2}A1tB48^{HZ9^z-D6`nsm6R$5>%S-FLi8<6wphROT zh7s*Ct^2awZMg?Dm&90#L&G!UPG{Sb?K{km#xSwye8;&$Sq)RmtvH_O2hBjDA8`Xu zFiIhcTnfwwGSEiMkdy}-%MF1-B1SE(3c1T2jGjlgnOfk7UW<$vhFg!v8m0dj?oaXE zf}aTT@ctb=WylurUG^4uC}UoO>`BPp$M?h!;2StI7oFS09_!5Cb><7`egK_sv41t% zn}=*F(&sbpV0{zoCqQ1`?|BV+xg+=db;MTkh@993A`2Q{Rr~^5qwt-HeQM3IecD^= - builder_boost_factor * (builder_payload_value // 100)`, and to the builder's bid - otherwise; the highest-value candidate across the local payload, any p2p bid, and - every builder's boosted bid is chosen. + Percentage multiplier applied to this builder's bid once it has passed the + `max_execution_payment` and `min_bid` checks, giving + `builder_boost_factor * (builder_payload_value // 100)`, where + `builder_payload_value` is the bid's `value` plus its `execution_payment`. All + candidates are compared in Gwei and the highest boosted value wins, so a factor below + `100` favours the local payload and one above `100` favours this builder. + + Servers must support the following values, which encode common preferences: + + * `builder_boost_factor=0`: prefer the local payload unless an error makes it unviable. + * `builder_boost_factor=100`: profit maximization mode; choose whichever pays more. + * `builder_boost_factor=2**64 - 1`: prefer this builder unless an error or health check + makes it unviable. - The reserved values encode common preferences: `0` prefers the local payload unless an - error makes it unviable; `100` is profit maximization (choose whichever pays more); - and `2**64 - 1` prefers this builder unless an error or beacon node health check makes - it unviable. Servers should use saturating arithmetic so that large values do not - overflow. A value out of range for a 64-bit unsigned integer makes the entry - malformed, so the beacon node ignores that entry. + Servers should use saturating arithmetic so that large values do not overflow. example: "100" diff --git a/types/gloas/builder_preference_entry.yaml b/types/gloas/builder_preference_entry.yaml index 0a3f39e1..4b3fecf4 100644 --- a/types/gloas/builder_preference_entry.yaml +++ b/types/gloas/builder_preference_entry.yaml @@ -2,21 +2,15 @@ Gloas: BuilderPreferenceEntry: type: object description: | - Per-builder preference the validator asks the beacon node to submit to a builder ahead of the - bid request, one entry per preference submission. Each entry names the builder `url` to - contact. For each entry the beacon node makes one `submitBuilderPreferences` builder-API - call: it contacts the builder at its `url`, forwards `auth` unchanged, and submits - `max_execution_payment`. + A per-builder preference the validator client asks the beacon node to submit ahead of the bid + request, one entry per submission. Unlike the block-production `BuilderEntry` it carries only + what a builder may see: `min_bid` and `builder_boost_factor` are the proposer's private + bid-filtering strategy and are never sent to a builder. - Unlike the block-production `BuilderEntry`, this carries only what a builder is allowed to - see. The proposer's private bid-filtering strategy (`min_bid` and `builder_boost_factor`) is - never sent to a builder; the beacon node applies those locally when selecting a bid. - - SSZ request encoding. The `application/octet-stream` request body of the beacon-node - `submitBuilderPreferences` endpoint is the SSZ serialization of - `List[BuilderPreferenceEntryV1, MAX_BUILDER_ENTRIES]`, using the constants defined on - `BuilderEntry` and the `SignedRequestAuthV1` container from the [Builder - API](https://ethereum.github.io/builder-specs/) Gloas spec: + The entry pairs the beacon node's routing with the payload the builder receives, so like + `BuilderEntry` it has no Builder API definition. Its SSZ container uses that type's constants + and the + [`SignedRequestAuthV1`](https://github.com/ethereum/builder-specs/blob/78a5546d9d8253beabf7db8baf988a58abdec87f/specs/gloas/validator.md#signedrequestauthv1) object from the Builder API Gloas spec: class BuilderPreferenceEntryV1(Container): url: ByteList[MAX_BUILDER_URL_SIZE] # UTF-8 bytes of `url`, exactly as in JSON @@ -27,24 +21,15 @@ Gloas: url: type: string format: uri - description: | - The URL the beacon node submits this entry's preferences to. Multiple entries MAY share a - `url`; the beacon node makes one submission per entry. + maxLength: 2048 # MAX_BUILDER_URL_SIZE + description: "The URL this entry's preferences are submitted to. Multiple entries MAY share a `url`; the beacon node makes one submission per entry." example: "https://builder.example.com" auth: - description: | - `SignedRequestAuthV1` authenticating the preference submission to this builder. The - beacon node MUST forward its `message` and `signature` byte-for-byte unchanged; the - builder verifies them. Its `data` is opaque and agreed with the builder out of band, - and its `message.slot` is the proposal slot the preferences apply to. + description: "Authenticates this submission to the builder. The beacon node MUST forward `message` and `signature` byte-for-byte unchanged." allOf: - $ref: "./request_auth.yaml#/Gloas/SignedRequestAuthV1" max_execution_payment: allOf: - $ref: "../primitive.yaml#/Gwei" - - description: | - Maximum trusted execution-layer payment, in Gwei, the proposer will accept from this - builder. It is submitted to the builder so the builder can shape its bid; the beacon - node enforces the `max_execution_payment` carried in the block-production - `BuilderEntry` when selecting a bid. + - description: "Maximum trusted execution-layer payment, in Gwei, submitted to this builder so it can shape its bid. The beacon node enforces the block-production `BuilderEntry` value when selecting a bid." example: "1000000000" diff --git a/types/gloas/request_auth.yaml b/types/gloas/request_auth.yaml index 4c79684c..2d598239 100644 --- a/types/gloas/request_auth.yaml +++ b/types/gloas/request_auth.yaml @@ -1,7 +1,7 @@ Gloas: RequestAuthV1: type: object - description: "The `RequestAuthV1` object from the [Builder API](https://ethereum.github.io/builder-specs/) Gloas spec." + description: "The [`RequestAuthV1`](https://github.com/ethereum/builder-specs/blob/78a5546d9d8253beabf7db8baf988a58abdec87f/specs/gloas/validator.md#requestauthv1) object from the Builder API Gloas spec." required: [data, slot] properties: data: @@ -23,7 +23,7 @@ Gloas: SignedRequestAuthV1: type: object - description: "The `SignedRequestAuthV1` object from the [Builder API](https://ethereum.github.io/builder-specs/) Gloas spec." + description: "The [`SignedRequestAuthV1`](https://github.com/ethereum/builder-specs/blob/78a5546d9d8253beabf7db8baf988a58abdec87f/specs/gloas/validator.md#signedrequestauthv1) object from the Builder API Gloas spec." required: [message, signature] properties: message: diff --git a/validator-flow.md b/validator-flow.md index 79dc94e1..105b1a62 100644 --- a/validator-flow.md +++ b/validator-flow.md @@ -14,17 +14,24 @@ Detail explanation how validator should utilize this API to perform his regular On start of every epoch, validator should [fetch proposer duties](#/Validator/getProposerDuties). Result is array of objects, each containing proposer pubkey and slot at which he is suppose to propose. +Post-Gloas, in the epoch prior to a proposal the validator MAY +[submit builder preferences](#/Validator/submitBuilderPreferences) so builders hold them before the +bid request arrives. + If proposing block, then at immediate start of slot: 1. Ask Beacon Node for BeaconBlock object: - Pre-Gloas forks: [produceBlockV3](#/Validator/produceBlockV3) - Post-Gloas fork: [produceBlockV4](#/Validator/produceBlockV4) + - Optionally supply `BuilderEntry` objects in the request body to solicit builder-API bids. - `include_payload=true`: returns `BlockContents` (beacon block, execution payload envelope, blobs, and KZG proofs). Enables stateless operation (multi-BN setups, distributed validators, failover). - `include_payload=false`: returns only the `BeaconBlock`. The beacon node caches the execution payload envelope and blobs internally (stateful operation, must publish via the same beacon node). - - When using an external builder's bid, only the `BeaconBlock` is returned regardless of `include_payload`. + - When a bid wins, only the `BeaconBlock` is returned regardless of `include_payload`, and + `Eth-Builder-Url` names the builder if the bid came through the builder-API channel. 2. Sign block -3. [Submit SignedBeaconBlock](#/ValidatorRequiredApi/publishBlock) (BeaconBlock + signature) +3. [Submit SignedBeaconBlock](#/ValidatorRequiredApi/publishBlock) (BeaconBlock + signature), + echoing the `Eth-Builder-Url` header if one was returned 4. Post-Gloas, if self-building (proposer's own bid included in block): - Stateless (`include_payload=true`): envelope and blobs are already available from step 1. Sign envelope and [submit `SignedExecutionPayloadEnvelopeContents`](#/Beacon/publishExecutionPayloadEnvelope) @@ -34,6 +41,9 @@ If proposing block, then at immediate start of slot: (beacon node attaches blobs and KZG proofs from its cache). - Must submit before [PAYLOAD_DUE_BPS](https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/gloas/validator.md#time-parameters) of slot duration for the PTC to attest the payload as present +5. Post-Gloas, if a bid won: nothing further. The winning builder releases the execution payload + envelope. + Monitor chain block reorganization events (TBD) as they could change block proposers. If reorg is detected, ask for new proposer duties and proceed from 1. From 657d39096df76f55c3ea8fe16f05e3956a6edca4 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Sun, 26 Jul 2026 20:59:57 -0700 Subject: [PATCH 07/18] report the payload source, add default bid policy params, fix entry semantics New Eth-Execution-Payload-Source response header (local, builder, p2p). A self-built block with include_payload=false and a p2p bid win were indistinguishable: both return a bare BeaconBlock with no Eth-Builder-Url, but the validator publishes the envelope itself on the first and must not on the second. Defaults: default_min_bid and default_builder_boost_factor query parameters cover bids that match no BuilderEntry. No default value is asserted. Bid selection is one rule over both channels: reject on min_bid or max_execution_payment, boost every survivor, the highest boosted bid competes with the local build, and the local build wins a tie. Entries: url and builder_pubkey act independently, so the three legal combinations are enumerated rather than left to inference. --- apis/validator/block.v4.yaml | 73 +++++++++++++++++++++++++--------- beacon-node-oapi.yaml | 9 +++++ types/gloas/builder_entry.yaml | 41 ++++++++++++------- validator-flow.md | 11 +++-- 4 files changed, 97 insertions(+), 37 deletions(-) diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index 61ad0746..89125681 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -9,23 +9,32 @@ post: The beacon node always builds a local payload and MAY consider a p2p bid, so a block is returned even when no bid is available. The validator client optionally supplies a list of `BuilderEntry` - objects in the request body, one per builder-API bid request to make. Each builder's bid is - rejected if it falls outside its own entry's `min_bid` or `max_execution_payment`, and is then - boosted by that entry's `builder_boost_factor`. The highest boosted bid, the local build and any - p2p bid are compared in Gwei, and the highest wins. + objects in the request body, one per builder. Each entry contains `url`, `builder_pubkey`, or + both: + - `url` only: a bid is requested from that address, and this entry applies to the bid it + returns. That builder's p2p bids are not matched to this entry. + - `builder_pubkey` only: no bid is requested, and this entry applies to that builder's p2p bids. + - both: a bid is requested, this entry applies to the bid it returns and to that builder's p2p + bids, and a returned bid not signed by `builder_pubkey` MUST NOT be accepted. + + A bid is rejected if its total payment falls below the `min_bid` that applies to it, or if its + trusted `execution_payment` exceeds the `max_execution_payment` that applies to it. Every + surviving bid is then boosted by the `builder_boost_factor` that applies to it, and the highest + boosted bid competes with the local build in Gwei. The local build wins a tie. + + A p2p bid is matched to an entry by resolving its `builder_index` through the builder registry. + A bid matching no entry takes its `min_bid` and `builder_boost_factor` from the + `default_min_bid` and `default_builder_boost_factor` query parameters. A p2p bid promises no + trusted `execution_payment`, so `max_execution_payment` never constrains one. The response carries the full block contents only when the beacon node self-built the block and `include_payload` is `true`; in every other case, including any bid win, it carries only the - `BeaconBlock`. Response metadata reports which form was returned and, when a builder-API bid - won, which builder won it. + `BeaconBlock`. - Notes: - - A self-built block is published via `POST /eth/v2/beacon/blocks`, and its envelope via - `POST /eth/v1/beacon/execution_payload_envelopes`. - - A block won through the builder-API channel is published via `POST /eth/v2/beacon/blocks` - echoing `Eth-Builder-Url`, and the winning builder releases the envelope. - - A block won by a p2p bid is published via `POST /eth/v2/beacon/blocks` without the header, - and the bidding builder releases the envelope. + Every block is published via `POST /eth/v2/beacon/blocks`. `Eth-Execution-Payload-Source` says + what else the validator client must do: on `local` it publishes the execution payload envelope + via `POST /eth/v1/beacon/execution_payload_envelopes`, on `builder` it echoes `Eth-Builder-Url` + and the winning builder releases the envelope, and on `p2p` the bidding builder releases it. This endpoint is specific to the post-Gloas forks and is not backwards compatible with previous forks. @@ -50,6 +59,27 @@ post: $ref: '../../beacon-node-oapi.yaml#/components/schemas/Graffiti' - name: skip_randao_verification $ref: '../../beacon-node-oapi.yaml#/components/parameters/SkipRandaoVerification' + - name: default_min_bid + in: query + required: false + description: | + Minimum total payment, in Gwei, accepted from a bid that matches no `BuilderEntry`, which in + practice is a bid received over p2p. A bid whose `value` plus `execution_payment` is below + this MUST be rejected. When omitted, the beacon node applies its own configured value. If + the value is provided but out of range for a 64-bit unsigned integer, then an error response + with status code 400 MUST be returned. + schema: + $ref: "../../beacon-node-oapi.yaml#/components/schemas/Uint64" + - name: default_builder_boost_factor + in: query + required: false + description: | + Percentage multiplier applied to a bid that matches no `BuilderEntry`, exactly as a + `BuilderEntry`'s `builder_boost_factor` is applied to a bid from that entry. When omitted, + the beacon node applies its own configured value. If the value is provided but out of range + for a 64-bit unsigned integer, then an error response with status code 400 MUST be returned. + schema: + $ref: "../../beacon-node-oapi.yaml#/components/schemas/Uint64" - name: include_payload in: query required: true @@ -77,11 +107,14 @@ post: requestBody: required: false description: | - The `BuilderEntry` objects to solicit bids from. Each entry's `auth.message.slot` is the - `slot` this request is for; the builder rejects a mismatch, so an entry naming a different slot - is one the beacon node does not use. An entry the beacon node cannot use, for any reason, MUST - NOT fail the request; it simply yields no bid, so one bad entry never costs the proposer its - slot. When the body is omitted or empty, no external builders are contacted. + The `BuilderEntry` objects for this request. When an entry contains an `auth`, its + `auth.message.slot` is the `slot` this request is for; the builder rejects a mismatch, so an + entry naming a different slot is one the beacon node does not use for a bid request. A body + that cannot be decoded is invalid and MUST fail with a 400. Every other failure is per entry: + an entry the beacon node rejects, or whose builder does not answer, yields no bid and MUST + NOT fail the request, so one bad entry never costs the proposer its slot. An entry that + yields no bid still supplies policy for its builder's p2p bids when it contains a + `builder_pubkey`. When the body is omitted or empty, no external builders are contacted. content: application/json: schema: @@ -102,6 +135,8 @@ post: $ref: '../../beacon-node-oapi.yaml#/components/headers/Eth-Consensus-Block-Value' Eth-Execution-Payload-Included: $ref: '../../beacon-node-oapi.yaml#/components/headers/Eth-Execution-Payload-Included' + Eth-Execution-Payload-Source: + $ref: '../../beacon-node-oapi.yaml#/components/headers/Eth-Execution-Payload-Source' Eth-Builder-Url: description: | When the winning bid came through the builder-API channel, the `url` of that builder. @@ -143,7 +178,7 @@ post: - $ref: "../../beacon-node-oapi.yaml#/components/schemas/Gloas.BlockContents" application/octet-stream: schema: - description: "SSZ serialized `BeaconBlock` or `BlockContents` bytes. Use Accept header to choose this response type, version string is sent in header `Eth-Consensus-Version` and payload inclusion indicated by `Eth-Execution-Payload-Included` header." + description: "SSZ serialized `BeaconBlock` or `BlockContents` bytes. Use Accept header to choose this response type, version string is sent in header `Eth-Consensus-Version`, payload inclusion indicated by `Eth-Execution-Payload-Included` header and payload origin by `Eth-Execution-Payload-Source` header." "400": description: "Invalid block production request" content: diff --git a/beacon-node-oapi.yaml b/beacon-node-oapi.yaml index 26982690..a69b62b7 100644 --- a/beacon-node-oapi.yaml +++ b/beacon-node-oapi.yaml @@ -576,3 +576,12 @@ components: required: true schema: type: boolean + Eth-Execution-Payload-Source: + description: | + Where this block's execution payload comes from: `local` when the beacon node built it, + `builder` when a builder-API bid won, and `p2p` when a p2p bid won. Required in response so + client can determine whether it must publish the execution payload envelope itself. + required: true + schema: + type: string + enum: [local, builder, p2p] diff --git a/types/gloas/builder_entry.yaml b/types/gloas/builder_entry.yaml index fc851c0d..7c8049c6 100644 --- a/types/gloas/builder_entry.yaml +++ b/types/gloas/builder_entry.yaml @@ -2,9 +2,21 @@ Gloas: BuilderEntry: type: object description: | - A per-builder input the validator client supplies on a block-production request, one entry per - builder-API bid request the beacon node should make. `auth` is the - [`SignedRequestAuthV1`](https://github.com/ethereum/builder-specs/blob/78a5546d9d8253beabf7db8baf988a58abdec87f/specs/gloas/validator.md#signedrequestauthv1) object from the Builder API Gloas spec. + A per-builder input the validator client supplies on a block-production request. At least one + of `url` and `builder_pubkey` MUST be present. + + An entry that contains a `url` MUST also contain an `auth`, which the builder requires on + every request; the beacon node MUST NOT send a bid request for an entry that contains a + `url` without one. + + Entries are distinguished by `url`, `auth`'s `data` and `builder_pubkey` taken together, where + an absent field matches only another absent field. Several entries MAY share a `url` with + different `data`, and one request is made per entry. A `builder_pubkey` MUST NOT appear on + more than one entry, since two entries naming the same builder would leave its p2p bids + subject to both; if one does, the beacon node applies the first such entry and ignores the + rest. + + `auth` is the [`SignedRequestAuthV1`](https://github.com/ethereum/builder-specs/blob/78a5546d9d8253beabf7db8baf988a58abdec87f/specs/gloas/validator.md#signedrequestauthv1) object from the Builder API Gloas spec. A `BuilderEntry` never reaches a builder, so it has no Builder API definition. Its SSZ container and constants are: @@ -15,27 +27,28 @@ Gloas: class BuilderEntryV1(Container): url: ByteList[MAX_BUILDER_URL_SIZE] # UTF-8 bytes of `url`, exactly as in JSON auth: SignedRequestAuthV1 - pubkey: BLSPubkey + builder_pubkey: BLSPubkey max_execution_payment: Gwei min_bid: Gwei builder_boost_factor: uint64 - SSZ cannot express absence, so an all-zero `pubkey`, which is not a valid BLS public key, is - treated as unset in either encoding. - required: [url, auth, max_execution_payment, min_bid, builder_boost_factor] + SSZ cannot express absence, so each optional field has a value that means unset in either + encoding: a zero-length `url`, an all-zero `builder_pubkey`, which is not a valid BLS public + key, and an `auth` whose `data` is zero-length and whose `signature` is all-zero. + required: [max_execution_payment, min_bid, builder_boost_factor] properties: url: type: string format: uri maxLength: 2048 # MAX_BUILDER_URL_SIZE - description: "The URL this entry's bid request is sent to. Multiple entries MAY share a `url`; the beacon node makes one request per entry." + description: "Where this entry's bid request is sent. Omit it to supply policy for this builder's p2p bids without requesting one." example: "https://builder.example.com" auth: - description: "Authenticates this request to the builder. The beacon node MUST forward `message` and `signature` byte-for-byte unchanged." + description: "Authenticates this entry's bid request to the builder. The beacon node MUST forward `message` and `signature` byte-for-byte unchanged. Required when `url` is present; omit it only on an entry that requests no bid." allOf: - $ref: "./request_auth.yaml#/Gloas/SignedRequestAuthV1" - pubkey: - description: "Optional builder BLS public key. When set, bids not signed by it MUST NOT be accepted." + builder_pubkey: + description: "The builder's BLS public key. When set, a builder-API bid not signed by it MUST NOT be accepted, and this entry also applies to that builder's p2p bids." allOf: - $ref: "../primitive.yaml#/Pubkey" max_execution_payment: @@ -55,9 +68,9 @@ Gloas: Percentage multiplier applied to this builder's bid once it has passed the `max_execution_payment` and `min_bid` checks, giving `builder_boost_factor * (builder_payload_value // 100)`, where - `builder_payload_value` is the bid's `value` plus its `execution_payment`. All - candidates are compared in Gwei and the highest boosted value wins, so a factor below - `100` favours the local payload and one above `100` favours this builder. + `builder_payload_value` is the bid's `value` plus its `execution_payment`. The + highest boosted bid competes with the local payload in Gwei, so a factor below `100` + favors the local payload and one above `100` favors this builder. Servers must support the following values, which encode common preferences: diff --git a/validator-flow.md b/validator-flow.md index 105b1a62..a909f814 100644 --- a/validator-flow.md +++ b/validator-flow.md @@ -22,17 +22,20 @@ If proposing block, then at immediate start of slot: 1. Ask Beacon Node for BeaconBlock object: - Pre-Gloas forks: [produceBlockV3](#/Validator/produceBlockV3) - Post-Gloas fork: [produceBlockV4](#/Validator/produceBlockV4) - - Optionally supply `BuilderEntry` objects in the request body to solicit builder-API bids. + - Optionally supply `BuilderEntry` objects in the request body to solicit builder-API bids and + to set per-builder policy, including for p2p bids. - `include_payload=true`: returns `BlockContents` (beacon block, execution payload envelope, blobs, and KZG proofs). Enables stateless operation (multi-BN setups, distributed validators, failover). - `include_payload=false`: returns only the `BeaconBlock`. The beacon node caches the execution payload envelope and blobs internally (stateful operation, must publish via the same beacon node). - When a bid wins, only the `BeaconBlock` is returned regardless of `include_payload`, and `Eth-Builder-Url` names the builder if the bid came through the builder-API channel. + - `Eth-Execution-Payload-Source` is `local`, `builder`, or `p2p`, and determines which of + steps 4 and 5 applies. 2. Sign block 3. [Submit SignedBeaconBlock](#/ValidatorRequiredApi/publishBlock) (BeaconBlock + signature), echoing the `Eth-Builder-Url` header if one was returned -4. Post-Gloas, if self-building (proposer's own bid included in block): +4. Post-Gloas, if self-building (`Eth-Execution-Payload-Source: local`): - Stateless (`include_payload=true`): envelope and blobs are already available from step 1. Sign envelope and [submit `SignedExecutionPayloadEnvelopeContents`](#/Beacon/publishExecutionPayloadEnvelope) (envelope + blobs + KZG proofs). @@ -41,8 +44,8 @@ If proposing block, then at immediate start of slot: (beacon node attaches blobs and KZG proofs from its cache). - Must submit before [PAYLOAD_DUE_BPS](https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/gloas/validator.md#time-parameters) of slot duration for the PTC to attest the payload as present -5. Post-Gloas, if a bid won: nothing further. The winning builder releases the execution payload - envelope. +5. Post-Gloas, if a bid won (`Eth-Execution-Payload-Source: builder` or `p2p`): nothing further. + The winning builder releases the execution payload envelope. Monitor chain block reorganization events (TBD) as they could change block proposers. If reorg is detected, ask for new proposer duties and proceed from 1. From 727f6b315701580c438b29469e1cb53bb3cbd4c5 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Mon, 27 Jul 2026 06:51:51 -0700 Subject: [PATCH 08/18] remove Eth-Execution-Payload-Source header --- apis/validator/block.v4.yaml | 13 ++++++------- beacon-node-oapi.yaml | 9 --------- validator-flow.md | 8 +++----- 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index 89125681..3e9252a7 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -31,10 +31,11 @@ post: `include_payload` is `true`; in every other case, including any bid win, it carries only the `BeaconBlock`. - Every block is published via `POST /eth/v2/beacon/blocks`. `Eth-Execution-Payload-Source` says - what else the validator client must do: on `local` it publishes the execution payload envelope - via `POST /eth/v1/beacon/execution_payload_envelopes`, on `builder` it echoes `Eth-Builder-Url` - and the winning builder releases the envelope, and on `p2p` the bidding builder releases it. + Every block is published via `POST /eth/v2/beacon/blocks`. A self-built block carries + `BUILDER_INDEX_SELF_BUILD` as its `builder_index`, and the validator client publishes its + execution payload envelope via `POST /eth/v1/beacon/execution_payload_envelopes`. When a bid + won, the winning builder releases the envelope instead, and the validator client echoes + `Eth-Builder-Url` if one was returned. This endpoint is specific to the post-Gloas forks and is not backwards compatible with previous forks. @@ -135,8 +136,6 @@ post: $ref: '../../beacon-node-oapi.yaml#/components/headers/Eth-Consensus-Block-Value' Eth-Execution-Payload-Included: $ref: '../../beacon-node-oapi.yaml#/components/headers/Eth-Execution-Payload-Included' - Eth-Execution-Payload-Source: - $ref: '../../beacon-node-oapi.yaml#/components/headers/Eth-Execution-Payload-Source' Eth-Builder-Url: description: | When the winning bid came through the builder-API channel, the `url` of that builder. @@ -178,7 +177,7 @@ post: - $ref: "../../beacon-node-oapi.yaml#/components/schemas/Gloas.BlockContents" application/octet-stream: schema: - description: "SSZ serialized `BeaconBlock` or `BlockContents` bytes. Use Accept header to choose this response type, version string is sent in header `Eth-Consensus-Version`, payload inclusion indicated by `Eth-Execution-Payload-Included` header and payload origin by `Eth-Execution-Payload-Source` header." + description: "SSZ serialized `BeaconBlock` or `BlockContents` bytes. Use Accept header to choose this response type, version string is sent in header `Eth-Consensus-Version` and payload inclusion indicated by `Eth-Execution-Payload-Included` header." "400": description: "Invalid block production request" content: diff --git a/beacon-node-oapi.yaml b/beacon-node-oapi.yaml index a69b62b7..26982690 100644 --- a/beacon-node-oapi.yaml +++ b/beacon-node-oapi.yaml @@ -576,12 +576,3 @@ components: required: true schema: type: boolean - Eth-Execution-Payload-Source: - description: | - Where this block's execution payload comes from: `local` when the beacon node built it, - `builder` when a builder-API bid won, and `p2p` when a p2p bid won. Required in response so - client can determine whether it must publish the execution payload envelope itself. - required: true - schema: - type: string - enum: [local, builder, p2p] diff --git a/validator-flow.md b/validator-flow.md index a909f814..c931b463 100644 --- a/validator-flow.md +++ b/validator-flow.md @@ -30,12 +30,10 @@ If proposing block, then at immediate start of slot: envelope and blobs internally (stateful operation, must publish via the same beacon node). - When a bid wins, only the `BeaconBlock` is returned regardless of `include_payload`, and `Eth-Builder-Url` names the builder if the bid came through the builder-API channel. - - `Eth-Execution-Payload-Source` is `local`, `builder`, or `p2p`, and determines which of - steps 4 and 5 applies. 2. Sign block 3. [Submit SignedBeaconBlock](#/ValidatorRequiredApi/publishBlock) (BeaconBlock + signature), echoing the `Eth-Builder-Url` header if one was returned -4. Post-Gloas, if self-building (`Eth-Execution-Payload-Source: local`): +4. Post-Gloas, if self-building (the block's `builder_index` is [BUILDER_INDEX_SELF_BUILD](https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/gloas/beacon-chain.md#misc)): - Stateless (`include_payload=true`): envelope and blobs are already available from step 1. Sign envelope and [submit `SignedExecutionPayloadEnvelopeContents`](#/Beacon/publishExecutionPayloadEnvelope) (envelope + blobs + KZG proofs). @@ -44,8 +42,8 @@ If proposing block, then at immediate start of slot: (beacon node attaches blobs and KZG proofs from its cache). - Must submit before [PAYLOAD_DUE_BPS](https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.12/specs/gloas/validator.md#time-parameters) of slot duration for the PTC to attest the payload as present -5. Post-Gloas, if a bid won (`Eth-Execution-Payload-Source: builder` or `p2p`): nothing further. - The winning builder releases the execution payload envelope. +5. Post-Gloas, if a bid won (any other `builder_index`): nothing further. The winning builder + releases the execution payload envelope. Monitor chain block reorganization events (TBD) as they could change block proposers. If reorg is detected, ask for new proposer duties and proceed from 1. From 23b555534a8ab0f638f93736b65c3b6f9e3b3704 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Mon, 27 Jul 2026 11:32:41 -0700 Subject: [PATCH 09/18] scope entry uniqueness by role: request identity vs p2p policy `builder_pubkey` does two different jobs depending on whether the entry has a `url`: on a url entry it filters the returned bid (a builder-API bid not signed by it is rejected), on a url-less entry it names the builder whose p2p bids the entry applies to. The old rule gave an entry one combined identity and forbade a `builder_pubkey` appearing twice, which was wrong both ways: it rejected a builder bidding into two relays (two entries naming it), and accepted two entries sharing a url and auth but expecting different pubkeys, which produce identical requests. Split into two scoped rules. An entry with a url is a bid request, unique by (url, auth.data); several may share a url with different data. An entry with no url supplies p2p policy, unique by builder_pubkey. p2p policy now comes only from url-less entries, so p2p matching is scoped to them and the requestBody note follows suit. --- apis/validator/block.v4.yaml | 22 ++++++++++++---------- types/gloas/builder_entry.yaml | 18 ++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index 3e9252a7..4a86511f 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -12,20 +12,22 @@ post: objects in the request body, one per builder. Each entry contains `url`, `builder_pubkey`, or both: - `url` only: a bid is requested from that address, and this entry applies to the bid it - returns. That builder's p2p bids are not matched to this entry. + returns. - `builder_pubkey` only: no bid is requested, and this entry applies to that builder's p2p bids. - - both: a bid is requested, this entry applies to the bid it returns and to that builder's p2p - bids, and a returned bid not signed by `builder_pubkey` MUST NOT be accepted. + - both: a bid is requested and this entry applies to the bid it returns, which MUST NOT be + accepted unless signed by `builder_pubkey`. + + p2p policy comes only from entries with no `url`. A bid is rejected if its total payment falls below the `min_bid` that applies to it, or if its trusted `execution_payment` exceeds the `max_execution_payment` that applies to it. Every surviving bid is then boosted by the `builder_boost_factor` that applies to it, and the highest boosted bid competes with the local build in Gwei. The local build wins a tie. - A p2p bid is matched to an entry by resolving its `builder_index` through the builder registry. - A bid matching no entry takes its `min_bid` and `builder_boost_factor` from the - `default_min_bid` and `default_builder_boost_factor` query parameters. A p2p bid promises no - trusted `execution_payment`, so `max_execution_payment` never constrains one. + A p2p bid is matched to an entry with no `url` by resolving its `builder_index` through the + builder registry. A bid matching no entry takes its `min_bid` and `builder_boost_factor` from + the `default_min_bid` and `default_builder_boost_factor` query parameters. A p2p bid promises + no trusted `execution_payment`, so `max_execution_payment` never constrains one. The response carries the full block contents only when the beacon node self-built the block and `include_payload` is `true`; in every other case, including any bid win, it carries only the @@ -113,9 +115,9 @@ post: entry naming a different slot is one the beacon node does not use for a bid request. A body that cannot be decoded is invalid and MUST fail with a 400. Every other failure is per entry: an entry the beacon node rejects, or whose builder does not answer, yields no bid and MUST - NOT fail the request, so one bad entry never costs the proposer its slot. An entry that - yields no bid still supplies policy for its builder's p2p bids when it contains a - `builder_pubkey`. When the body is omitted or empty, no external builders are contacted. + NOT fail the request, so one bad entry never costs the proposer its slot. An entry with no + `url` yields no bid by design and still supplies policy for its builder's p2p bids. When the + body is omitted or empty, no external builders are contacted. content: application/json: schema: diff --git a/types/gloas/builder_entry.yaml b/types/gloas/builder_entry.yaml index 7c8049c6..4e000310 100644 --- a/types/gloas/builder_entry.yaml +++ b/types/gloas/builder_entry.yaml @@ -5,16 +5,14 @@ Gloas: A per-builder input the validator client supplies on a block-production request. At least one of `url` and `builder_pubkey` MUST be present. - An entry that contains a `url` MUST also contain an `auth`, which the builder requires on - every request; the beacon node MUST NOT send a bid request for an entry that contains a - `url` without one. + An entry that contains a `url` is a bid request. It MUST also contain an `auth`, which the + builder requires on every request, and the beacon node MUST NOT send a request for an entry + that has a `url` without one. No two such entries may share both that `url` and their + `auth`'s `data`, and one request is made per entry, so several MAY share a `url` with + different `data`. - Entries are distinguished by `url`, `auth`'s `data` and `builder_pubkey` taken together, where - an absent field matches only another absent field. Several entries MAY share a `url` with - different `data`, and one request is made per entry. A `builder_pubkey` MUST NOT appear on - more than one entry, since two entries naming the same builder would leave its p2p bids - subject to both; if one does, the beacon node applies the first such entry and ignores the - rest. + An entry with no `url` supplies p2p policy instead, and no two of them may share a + `builder_pubkey`; if they do, the beacon node applies the first and ignores the rest. `auth` is the [`SignedRequestAuthV1`](https://github.com/ethereum/builder-specs/blob/78a5546d9d8253beabf7db8baf988a58abdec87f/specs/gloas/validator.md#signedrequestauthv1) object from the Builder API Gloas spec. @@ -48,7 +46,7 @@ Gloas: allOf: - $ref: "./request_auth.yaml#/Gloas/SignedRequestAuthV1" builder_pubkey: - description: "The builder's BLS public key. When set, a builder-API bid not signed by it MUST NOT be accepted, and this entry also applies to that builder's p2p bids." + description: "The builder's BLS public key. On an entry with a `url` it filters the response: a builder-API bid not signed by it MUST NOT be accepted. On an entry without one it identifies the builder whose p2p bids this entry applies to." allOf: - $ref: "../primitive.yaml#/Pubkey" max_execution_payment: From 3f886ebe8100993011e1ea4f6b06f1c5c3be85d1 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Tue, 28 Jul 2026 10:39:28 -0700 Subject: [PATCH 10/18] A fully resolved body already carries default_min_bid and default_builder_boost_factor values, so drop the two query parameters. --- apis/validator/block.v4.yaml | 49 ++++++++++------------------------ beacon-node-oapi.yaml | 2 ++ types/gloas/builder_entry.yaml | 32 ++++++++++++++++++++++ validator-flow.md | 5 ++-- 4 files changed, 51 insertions(+), 37 deletions(-) diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index 4a86511f..e1527149 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -26,7 +26,7 @@ post: A p2p bid is matched to an entry with no `url` by resolving its `builder_index` through the builder registry. A bid matching no entry takes its `min_bid` and `builder_boost_factor` from - the `default_min_bid` and `default_builder_boost_factor` query parameters. A p2p bid promises + the body's top-level `min_bid` and `builder_boost_factor`. A p2p bid promises no trusted `execution_payment`, so `max_execution_payment` never constrains one. The response carries the full block contents only when the beacon node self-built the block and @@ -62,27 +62,6 @@ post: $ref: '../../beacon-node-oapi.yaml#/components/schemas/Graffiti' - name: skip_randao_verification $ref: '../../beacon-node-oapi.yaml#/components/parameters/SkipRandaoVerification' - - name: default_min_bid - in: query - required: false - description: | - Minimum total payment, in Gwei, accepted from a bid that matches no `BuilderEntry`, which in - practice is a bid received over p2p. A bid whose `value` plus `execution_payment` is below - this MUST be rejected. When omitted, the beacon node applies its own configured value. If - the value is provided but out of range for a 64-bit unsigned integer, then an error response - with status code 400 MUST be returned. - schema: - $ref: "../../beacon-node-oapi.yaml#/components/schemas/Uint64" - - name: default_builder_boost_factor - in: query - required: false - description: | - Percentage multiplier applied to a bid that matches no `BuilderEntry`, exactly as a - `BuilderEntry`'s `builder_boost_factor` is applied to a bid from that entry. When omitted, - the beacon node applies its own configured value. If the value is provided but out of range - for a 64-bit unsigned integer, then an error response with status code 400 MUST be returned. - schema: - $ref: "../../beacon-node-oapi.yaml#/components/schemas/Uint64" - name: include_payload in: query required: true @@ -110,24 +89,24 @@ post: requestBody: required: false description: | - The `BuilderEntry` objects for this request. When an entry contains an `auth`, its - `auth.message.slot` is the `slot` this request is for; the builder rejects a mismatch, so an - entry naming a different slot is one the beacon node does not use for a bid request. A body - that cannot be decoded is invalid and MUST fail with a 400. Every other failure is per entry: - an entry the beacon node rejects, or whose builder does not answer, yields no bid and MUST - NOT fail the request, so one bad entry never costs the proposer its slot. An entry with no - `url` yields no bid by design and still supplies policy for its builder's p2p bids. When the - body is omitted or empty, no external builders are contacted. + A `BuilderConfig` for this request: the builder entries to solicit bids from, plus the + top-level `min_bid` and `builder_boost_factor` that apply to a p2p bid matching no entry. + When an entry contains an `auth`, its `auth.message.slot` is the `slot` this request is for; + the builder rejects a mismatch, so an entry naming a different slot is one the beacon node + does not use for a bid request. A body that cannot be decoded is invalid and MUST fail with a + 400. Every other failure is per entry: an entry the beacon node rejects, or whose builder does + not answer, yields no bid and MUST NOT fail the request, so one bad entry never costs the + proposer its slot. An entry with no `url` requests no bid by design but supplies policy + for its builder's p2p bids. When the body is omitted the validator client has supplied no + builder config, so the beacon node requests no bids and returns its locally built block. A + client that wants a bid considered, over the builder API or p2p, supplies a `BuilderConfig`. content: application/json: schema: - type: array - maxItems: 64 # MAX_BUILDER_ENTRIES - items: - $ref: "../../beacon-node-oapi.yaml#/components/schemas/Gloas.BuilderEntry" + $ref: "../../beacon-node-oapi.yaml#/components/schemas/Gloas.BuilderConfig" application/octet-stream: schema: - description: "SSZ serialized `List[BuilderEntryV1, MAX_BUILDER_ENTRIES]` bytes (see the `BuilderEntry` type description for the container and constants). Use Content-Type header to indicate that SSZ data is contained in the request body." + description: "SSZ serialized `BuilderConfigV1` bytes (see the `BuilderConfig` type description for the container and constants). Use Content-Type header to indicate that SSZ data is contained in the request body." responses: "200": description: Success response diff --git a/beacon-node-oapi.yaml b/beacon-node-oapi.yaml index 26982690..eb031755 100644 --- a/beacon-node-oapi.yaml +++ b/beacon-node-oapi.yaml @@ -501,6 +501,8 @@ components: $ref: "./types/gloas/request_auth.yaml#/Gloas/SignedRequestAuthV1" Gloas.BuilderEntry: $ref: "./types/gloas/builder_entry.yaml#/Gloas/BuilderEntry" + Gloas.BuilderConfig: + $ref: "./types/gloas/builder_entry.yaml#/Gloas/BuilderConfig" Gloas.BuilderPreferenceEntry: $ref: "./types/gloas/builder_preference_entry.yaml#/Gloas/BuilderPreferenceEntry" Node: diff --git a/types/gloas/builder_entry.yaml b/types/gloas/builder_entry.yaml index 4e000310..40b881ec 100644 --- a/types/gloas/builder_entry.yaml +++ b/types/gloas/builder_entry.yaml @@ -79,3 +79,35 @@ Gloas: Servers should use saturating arithmetic so that large values do not overflow. example: "100" + BuilderConfig: + type: object + description: | + The resolved per-key builder config the validator client sends on a block-production request. + Entries arrive fully resolved, so a requested bid is governed by its own `BuilderEntry`; the + top-level `min_bid` and `builder_boost_factor` apply only to a bid that matches no entry, + which in practice is a bid received over p2p. + + Its SSZ container is: + + class BuilderConfigV1(Container): + builders: List[BuilderEntryV1, MAX_BUILDER_ENTRIES] + min_bid: Gwei + builder_boost_factor: uint64 + required: [builders, min_bid, builder_boost_factor] + properties: + builders: + type: array + maxItems: 64 # MAX_BUILDER_ENTRIES + description: "The builders to request bids from, one `BuilderEntry` each. Empty means request none, so only p2p bids are considered." + items: + $ref: "#/Gloas/BuilderEntry" + min_bid: + allOf: + - $ref: "../primitive.yaml#/Gwei" + - description: "Minimum total payment, in Gwei, accepted from a bid that matches no entry, which in practice is a p2p bid. A bid whose `value` plus `execution_payment` is below this MUST be rejected." + example: "10000000" + builder_boost_factor: + allOf: + - $ref: "../primitive.yaml#/Uint64" + - description: "Percentage multiplier applied to a bid that matches no entry, exactly as a `BuilderEntry`'s `builder_boost_factor` is applied to a bid from that entry." + example: "100" diff --git a/validator-flow.md b/validator-flow.md index c931b463..91e89403 100644 --- a/validator-flow.md +++ b/validator-flow.md @@ -22,8 +22,9 @@ If proposing block, then at immediate start of slot: 1. Ask Beacon Node for BeaconBlock object: - Pre-Gloas forks: [produceBlockV3](#/Validator/produceBlockV3) - Post-Gloas fork: [produceBlockV4](#/Validator/produceBlockV4) - - Optionally supply `BuilderEntry` objects in the request body to solicit builder-API bids and - to set per-builder policy, including for p2p bids. + - Optionally supply a `BuilderConfig` in the request body: the builder entries to solicit + builder-API bids and set per-builder policy, plus the top-level `min_bid` and + `builder_boost_factor` that apply to p2p bids matching no entry. - `include_payload=true`: returns `BlockContents` (beacon block, execution payload envelope, blobs, and KZG proofs). Enables stateless operation (multi-BN setups, distributed validators, failover). - `include_payload=false`: returns only the `BeaconBlock`. The beacon node caches the execution payload From 979d0384f020354c9a0bf3c9e291fd5ef8d25c5d Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Tue, 28 Jul 2026 10:59:54 -0700 Subject: [PATCH 11/18] align the produceBlockV4 description with the BuilderConfig body The opening paragraph still called the request body a list of BuilderEntry objects; the schema and the rest of the description already describe a BuilderConfig. Update the intro to match. --- apis/validator/block.v4.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index e1527149..840af343 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -8,9 +8,10 @@ post: Requests a beacon node to produce a valid block, which the validator then signs. The beacon node always builds a local payload and MAY consider a p2p bid, so a block is returned - even when no bid is available. The validator client optionally supplies a list of `BuilderEntry` - objects in the request body, one per builder. Each entry contains `url`, `builder_pubkey`, or - both: + even when no bid is available. The validator client optionally supplies a `BuilderConfig` in the + request body: a list of `BuilderEntry` objects in its `builders` field, one per builder, plus a + top-level `min_bid` and `builder_boost_factor` that apply to a bid matching no entry. Each entry + contains `url`, `builder_pubkey`, or both: - `url` only: a bid is requested from that address, and this entry applies to the bid it returns. - `builder_pubkey` only: no bid is requested, and this entry applies to that builder's p2p bids. From ea9bafbc45c194e9944ba8f730bf275d1bdc26e3 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Tue, 28 Jul 2026 14:17:02 -0700 Subject: [PATCH 12/18] move max_execution_payment selection semantics to the builder-specs --- apis/validator/block.v4.yaml | 6 ++---- types/gloas/builder_entry.yaml | 4 ++-- types/gloas/builder_preference_entry.yaml | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index 840af343..ab61a18b 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -20,15 +20,13 @@ post: p2p policy comes only from entries with no `url`. - A bid is rejected if its total payment falls below the `min_bid` that applies to it, or if its - trusted `execution_payment` exceeds the `max_execution_payment` that applies to it. Every + A bid is rejected if its total payment falls below the `min_bid` that applies to it. Every surviving bid is then boosted by the `builder_boost_factor` that applies to it, and the highest boosted bid competes with the local build in Gwei. The local build wins a tie. A p2p bid is matched to an entry with no `url` by resolving its `builder_index` through the builder registry. A bid matching no entry takes its `min_bid` and `builder_boost_factor` from - the body's top-level `min_bid` and `builder_boost_factor`. A p2p bid promises - no trusted `execution_payment`, so `max_execution_payment` never constrains one. + the body's top-level `min_bid` and `builder_boost_factor`. The response carries the full block contents only when the beacon node self-built the block and `include_payload` is `true`; in every other case, including any bid win, it carries only the diff --git a/types/gloas/builder_entry.yaml b/types/gloas/builder_entry.yaml index 40b881ec..e2118185 100644 --- a/types/gloas/builder_entry.yaml +++ b/types/gloas/builder_entry.yaml @@ -52,7 +52,7 @@ Gloas: max_execution_payment: allOf: - $ref: "../primitive.yaml#/Gwei" - - description: "Maximum trusted execution-layer payment, in Gwei, accepted from this builder. A bid promising more MUST be rejected." + - description: "Maximum execution-layer payment, in Gwei, the proposer will accept from this builder. Its effect on bid selection is specified in the builder-specs." example: "1000000000" min_bid: allOf: @@ -64,7 +64,7 @@ Gloas: - $ref: "../primitive.yaml#/Uint64" - description: | Percentage multiplier applied to this builder's bid once it has passed the - `max_execution_payment` and `min_bid` checks, giving + `min_bid` check, giving `builder_boost_factor * (builder_payload_value // 100)`, where `builder_payload_value` is the bid's `value` plus its `execution_payment`. The highest boosted bid competes with the local payload in Gwei, so a factor below `100` diff --git a/types/gloas/builder_preference_entry.yaml b/types/gloas/builder_preference_entry.yaml index 4b3fecf4..7d3cebce 100644 --- a/types/gloas/builder_preference_entry.yaml +++ b/types/gloas/builder_preference_entry.yaml @@ -31,5 +31,5 @@ Gloas: max_execution_payment: allOf: - $ref: "../primitive.yaml#/Gwei" - - description: "Maximum trusted execution-layer payment, in Gwei, submitted to this builder so it can shape its bid. The beacon node enforces the block-production `BuilderEntry` value when selecting a bid." + - description: "Maximum execution-layer payment, in Gwei, submitted to this builder so it can shape its bid." example: "1000000000" From bb1e790715bee9a7ab70638f3b621626a3a9cc17 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Fri, 31 Jul 2026 15:25:34 -0700 Subject: [PATCH 13/18] batched submitBuilderPreferences call The validator client submits a flat array of BuilderPreferencesEntry, one entry per builder per proposer, each with proposer_pubkey, url, auth and max_execution_payment. The beacon node returns 200, or 400 with an IndexedErrorMessage naming each failed entry, and fans out one builder-API submission per entry. --- CHANGES.md | 2 +- apis/validator/block.v4.yaml | 2 +- apis/validator/builder_preferences.yaml | 50 +++++++++---------- beacon-node-oapi.yaml | 14 +++--- types/gloas/builder_entry.yaml | 10 ++-- ...ry.yaml => builder_preferences_entry.yaml} | 21 +++++--- types/gloas/request_auth.yaml | 6 +-- wordlist.txt | 4 +- 8 files changed, 57 insertions(+), 52 deletions(-) rename types/gloas/{builder_preference_entry.yaml => builder_preferences_entry.yaml} (64%) diff --git a/CHANGES.md b/CHANGES.md index d65e6634..b1ab4654 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,7 +9,7 @@ There are likely to be descriptions etc outside of the list below, but new query | Endpoint | [Lighthouse](https://github.com/sigp/lighthouse) | [Lodestar](https://github.com/ChainSafe/lodestar) | [Nimbus](https://github.com/status-im/nimbus-eth2) | [Prysm](https://github.com/prysmaticlabs/prysm) | [Teku](https://github.com/ConsenSys/teku) | |---------------------------------------------------------------------------------------------------------------------|--------------------------------------------------|---------------------------------------------------|----------------------------------------------------|-------------------------------------------------|-------------------------------------------| | [#580](https://github.com/ethereum/beacon-APIs/pull/580) / [#624](https://github.com/ethereum/beacon-APIs/pull/624) / [#630](https://github.com/ethereum/beacon-APIs/pull/630) `POST /eth/v4/validator/blocks/{slot}` added | | | | | | -| [#630](https://github.com/ethereum/beacon-APIs/pull/630) `POST /eth/v1/validator/builder_preferences/{pubkey}` added | | | | | | +| [#630](https://github.com/ethereum/beacon-APIs/pull/630) `POST /eth/v1/validator/builder_preferences` added | | | | | | | [#552](https://github.com/ethereum/beacon-APIs/pull/552) `GET /eth/v1/validator/execution_payload_bids/{slot}/{builder_index}` added | | | | | | | [#580](https://github.com/ethereum/beacon-APIs/pull/580) / [#624](https://github.com/ethereum/beacon-APIs/pull/624) `GET /eth/v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}` added | | | | | | | [#552](https://github.com/ethereum/beacon-APIs/pull/552) `GET /eth/v1/validator/payload_attestation_data/{slot}` added | | | | | | diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index ab61a18b..106fb459 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -105,7 +105,7 @@ post: $ref: "../../beacon-node-oapi.yaml#/components/schemas/Gloas.BuilderConfig" application/octet-stream: schema: - description: "SSZ serialized `BuilderConfigV1` bytes (see the `BuilderConfig` type description for the container and constants). Use Content-Type header to indicate that SSZ data is contained in the request body." + description: "SSZ serialized `BuilderConfig` bytes (see the `BuilderConfig` type description for the container and constants). Use Content-Type header to indicate that SSZ data is contained in the request body." responses: "200": description: Success response diff --git a/apis/validator/builder_preferences.yaml b/apis/validator/builder_preferences.yaml index f9ba356e..715a74c9 100644 --- a/apis/validator/builder_preferences.yaml +++ b/apis/validator/builder_preferences.yaml @@ -2,48 +2,48 @@ post: operationId: "submitBuilderPreferences" summary: Submit builder preferences description: | - Submits a proposer's per-builder preferences on the validator client's behalf, one builder-API - preference submission per entry. The validator client does not contact builders directly. Each - entry's `auth.message.slot` is the proposal slot the preferences apply to. + Submits per-builder preferences for one or more proposers. The request body is a flat list of + `BuilderPreferencesEntry` objects, each naming the proposer in `proposer_pubkey`; the beacon + node submits each to the builder-API `submitBuilderPreferences` endpoint at the entry's `url`. + Applicable from the Gloas fork onwards. - Validators MAY call this endpoint in the epoch prior to the epoch in which they will be - proposing, as determined from `state.proposer_lookahead`, so that builders have the preferences - before the bid request arrives. - - This API is applicable from the Gloas fork onwards. + Notes: + - Each entry targets one builder `url`, to which the beacon node makes one submission. + - Entries are identified by `proposer_pubkey`, so several proposers MAY submit to the same `url`. + - The beacon node routes each entry by its `url` and submits to that builder's + `submitBuilderPreferences` for the entry's `proposer_pubkey`, forwarding the `auth` and + `max_execution_payment`. + - Validators MAY submit in the epoch prior to proposing (from `state.proposer_lookahead`), so + builders hold the preferences before the bid request arrives. parameters: - - name: pubkey - in: path - required: true - description: | - The BLS public key of the proposing validator whose preferences these are. The beacon node - passes it as the `proposer_pubkey` of each builder-API preference submission, and each - entry's `auth` is signed by this key. + - in: header schema: - $ref: "../../types/primitive.yaml#/Pubkey" + $ref: "../../beacon-node-oapi.yaml#/components/schemas/ConsensusVersion" + required: true + name: Eth-Consensus-Version + description: "The active consensus version to which the submitted builder preferences belong." tags: - Validator requestBody: required: true - description: | - The `BuilderPreferenceEntry` objects to submit. Inclusion is best-effort: the beacon node - submits each entry independently and skips any entry it cannot submit rather than failing the - whole request. Entries that were not submitted are reported by index in a 400 response. + description: "Array of `BuilderPreferencesEntry` objects to submit, one per builder per proposer." content: application/json: schema: type: array - maxItems: 64 # MAX_BUILDER_ENTRIES + maxItems: 4096 # MAX_BUILDER_ENTRIES * (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH items: - $ref: "../../beacon-node-oapi.yaml#/components/schemas/Gloas.BuilderPreferenceEntry" + $ref: "../../beacon-node-oapi.yaml#/components/schemas/Gloas.BuilderPreferencesEntry" application/octet-stream: schema: - description: "SSZ serialized `List[BuilderPreferenceEntryV1, MAX_BUILDER_ENTRIES]` bytes (see the `BuilderPreferenceEntry` type description for the container). Use Content-Type header to indicate that SSZ data is contained in the request body." + description: "SSZ serialized `List[BuilderPreferencesEntry, MAX_BUILDER_ENTRIES * (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH]` bytes. Use Content-Type header to indicate that SSZ data is contained in the request body." responses: "200": - description: "Every entry was submitted" + description: "Every entry was submitted and its builder accepted it" "400": - description: "Invalid request, or errors with one or more preference submissions" + description: | + One or more entries failed; the others were still submitted. Each failure is reported by the + entry's index via `IndexedErrorMessage`, proxying the builder's error for that entry. content: application/json: schema: diff --git a/beacon-node-oapi.yaml b/beacon-node-oapi.yaml index eb031755..d24e44b4 100644 --- a/beacon-node-oapi.yaml +++ b/beacon-node-oapi.yaml @@ -224,7 +224,7 @@ paths: $ref: "./apis/validator/register_validator.yaml" /eth/v1/validator/proposer_preferences: $ref: "./apis/validator/proposer_preferences.yaml" - /eth/v1/validator/builder_preferences/{pubkey}: + /eth/v1/validator/builder_preferences: $ref: "./apis/validator/builder_preferences.yaml" /eth/v1/validator/liveness/{epoch}: $ref: "./apis/validator/liveness.yaml" @@ -495,16 +495,16 @@ components: $ref: "./types/fulu/data_column_sidecar.yaml#/Fulu/DataColumnSidecars" Gloas.DataColumnSidecars: $ref: "./types/gloas/data_column_sidecar.yaml#/Gloas/DataColumnSidecars" - Gloas.RequestAuthV1: - $ref: "./types/gloas/request_auth.yaml#/Gloas/RequestAuthV1" - Gloas.SignedRequestAuthV1: - $ref: "./types/gloas/request_auth.yaml#/Gloas/SignedRequestAuthV1" + Gloas.RequestAuth: + $ref: "./types/gloas/request_auth.yaml#/Gloas/RequestAuth" + Gloas.SignedRequestAuth: + $ref: "./types/gloas/request_auth.yaml#/Gloas/SignedRequestAuth" Gloas.BuilderEntry: $ref: "./types/gloas/builder_entry.yaml#/Gloas/BuilderEntry" Gloas.BuilderConfig: $ref: "./types/gloas/builder_entry.yaml#/Gloas/BuilderConfig" - Gloas.BuilderPreferenceEntry: - $ref: "./types/gloas/builder_preference_entry.yaml#/Gloas/BuilderPreferenceEntry" + Gloas.BuilderPreferencesEntry: + $ref: "./types/gloas/builder_preferences_entry.yaml#/Gloas/BuilderPreferencesEntry" Node: $ref: './types/fork_choice.yaml#/Node' ExtraData: diff --git a/types/gloas/builder_entry.yaml b/types/gloas/builder_entry.yaml index e2118185..3e4d1729 100644 --- a/types/gloas/builder_entry.yaml +++ b/types/gloas/builder_entry.yaml @@ -22,9 +22,9 @@ Gloas: MAX_BUILDER_ENTRIES = 64 MAX_BUILDER_URL_SIZE = 2048 - class BuilderEntryV1(Container): + class BuilderEntry(Container): url: ByteList[MAX_BUILDER_URL_SIZE] # UTF-8 bytes of `url`, exactly as in JSON - auth: SignedRequestAuthV1 + auth: SignedRequestAuth builder_pubkey: BLSPubkey max_execution_payment: Gwei min_bid: Gwei @@ -44,7 +44,7 @@ Gloas: auth: description: "Authenticates this entry's bid request to the builder. The beacon node MUST forward `message` and `signature` byte-for-byte unchanged. Required when `url` is present; omit it only on an entry that requests no bid." allOf: - - $ref: "./request_auth.yaml#/Gloas/SignedRequestAuthV1" + - $ref: "./request_auth.yaml#/Gloas/SignedRequestAuth" builder_pubkey: description: "The builder's BLS public key. On an entry with a `url` it filters the response: a builder-API bid not signed by it MUST NOT be accepted. On an entry without one it identifies the builder whose p2p bids this entry applies to." allOf: @@ -89,8 +89,8 @@ Gloas: Its SSZ container is: - class BuilderConfigV1(Container): - builders: List[BuilderEntryV1, MAX_BUILDER_ENTRIES] + class BuilderConfig(Container): + builders: List[BuilderEntry, MAX_BUILDER_ENTRIES] min_bid: Gwei builder_boost_factor: uint64 required: [builders, min_bid, builder_boost_factor] diff --git a/types/gloas/builder_preference_entry.yaml b/types/gloas/builder_preferences_entry.yaml similarity index 64% rename from types/gloas/builder_preference_entry.yaml rename to types/gloas/builder_preferences_entry.yaml index 7d3cebce..69abbdc6 100644 --- a/types/gloas/builder_preference_entry.yaml +++ b/types/gloas/builder_preferences_entry.yaml @@ -1,23 +1,28 @@ Gloas: - BuilderPreferenceEntry: + BuilderPreferencesEntry: type: object description: | A per-builder preference the validator client asks the beacon node to submit ahead of the bid - request, one entry per submission. Unlike the block-production `BuilderEntry` it carries only - what a builder may see: `min_bid` and `builder_boost_factor` are the proposer's private - bid-filtering strategy and are never sent to a builder. + request, one entry per builder per proposer. A single submission batches these across the + validator client's proposers, so each entry names the proposer it belongs to in + `proposer_pubkey`. It is a dedicated type, distinct from the block-production `BuilderEntry`. The entry pairs the beacon node's routing with the payload the builder receives, so like `BuilderEntry` it has no Builder API definition. Its SSZ container uses that type's constants and the [`SignedRequestAuthV1`](https://github.com/ethereum/builder-specs/blob/78a5546d9d8253beabf7db8baf988a58abdec87f/specs/gloas/validator.md#signedrequestauthv1) object from the Builder API Gloas spec: - class BuilderPreferenceEntryV1(Container): + class BuilderPreferencesEntry(Container): + proposer_pubkey: BLSPubkey # the proposer these preferences belong to url: ByteList[MAX_BUILDER_URL_SIZE] # UTF-8 bytes of `url`, exactly as in JSON - auth: SignedRequestAuthV1 + auth: SignedRequestAuth max_execution_payment: Gwei - required: [url, auth, max_execution_payment] + required: [proposer_pubkey, url, auth, max_execution_payment] properties: + proposer_pubkey: + description: "The BLS public key of the proposer these preferences apply to." + allOf: + - $ref: "../primitive.yaml#/Pubkey" url: type: string format: uri @@ -27,7 +32,7 @@ Gloas: auth: description: "Authenticates this submission to the builder. The beacon node MUST forward `message` and `signature` byte-for-byte unchanged." allOf: - - $ref: "./request_auth.yaml#/Gloas/SignedRequestAuthV1" + - $ref: "./request_auth.yaml#/Gloas/SignedRequestAuth" max_execution_payment: allOf: - $ref: "../primitive.yaml#/Gwei" diff --git a/types/gloas/request_auth.yaml b/types/gloas/request_auth.yaml index 2d598239..a7bc5a05 100644 --- a/types/gloas/request_auth.yaml +++ b/types/gloas/request_auth.yaml @@ -1,5 +1,5 @@ Gloas: - RequestAuthV1: + RequestAuth: type: object description: "The [`RequestAuthV1`](https://github.com/ethereum/builder-specs/blob/78a5546d9d8253beabf7db8baf988a58abdec87f/specs/gloas/validator.md#requestauthv1) object from the Builder API Gloas spec." required: [data, slot] @@ -21,12 +21,12 @@ Gloas: - $ref: "../primitive.yaml#/Uint64" - description: "The proposal slot this request is authorized for." - SignedRequestAuthV1: + SignedRequestAuth: type: object description: "The [`SignedRequestAuthV1`](https://github.com/ethereum/builder-specs/blob/78a5546d9d8253beabf7db8baf988a58abdec87f/specs/gloas/validator.md#signedrequestauthv1) object from the Builder API Gloas spec." required: [message, signature] properties: message: - $ref: "#/Gloas/RequestAuthV1" + $ref: "#/Gloas/RequestAuth" signature: $ref: "../primitive.yaml#/Signature" diff --git a/wordlist.txt b/wordlist.txt index b899c4dd..6336057f 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -48,10 +48,10 @@ stateful CGC RLP auth +RequestAuth RequestAuthV +SignedRequestAuth SignedRequestAuthV -BuilderEntryV -BuilderPreferenceEntryV UTF uri publishBlockV From 91db6c8416b7961406da2436623d4b07a87eaeb9 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Sun, 2 Aug 2026 12:31:35 -0700 Subject: [PATCH 14/18] address review nits on BuilderConfig and produceBlockV4 Order the global min_bid and builder_boost_factor before the builders list in BuilderConfig. Rename builder_payload_value to builder_bid_value to match the term cleaned up in #624. Revert the include_payload description to the master wording, keeping only the note that the flag is ignored when a bid wins. --- apis/validator/block.v4.yaml | 23 ++++++++++------------- types/gloas/builder_entry.yaml | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index c8a75af2..72352f63 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -65,21 +65,18 @@ post: in: query required: true description: | - Whether a self-built block's response carries the execution payload inline, or omits it for - the beacon node to cache and the validator client to retrieve separately. + Controls whether the execution payload envelope and blobs are included in the response + when self-building (using local execution payload). - When `true`, a self-built response includes the full block contents (beacon block, execution - payload envelope, blobs, and KZG proofs) and reports `Eth-Execution-Payload-Included: true`. - The validator client holds the whole payload, so it can sign and publish the envelope through - any beacon node (stateless operation: multi-BN setups, distributed validators, failover). + When `true`, the response includes the full block contents: beacon block, + execution payload envelope, blobs, and KZG proofs. This enables stateless operation + where the validator client can use multiple beacon nodes (multi-BN setups, distributed validators, failover). - When `false`, a self-built response returns only the beacon block, reporting - `Eth-Execution-Payload-Included: false`; the beacon node caches the execution payload - envelope and blobs. The validator client retrieves the envelope via - `GET /eth/v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}`, signs it, and - publishes it via `POST /eth/v1/beacon/execution_payload_envelopes`, and the producing beacon - node attaches its cached blobs and KZG proofs before broadcasting. This sends fewer bytes but - ties publishing to that one beacon node (stateful operation). + When `false`, only the beacon block is returned and the beacon node caches the execution + payload envelope and blobs internally. The validator client must then fetch them separately + via `GET /eth/v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}`. This saves + bandwidth but requires the validator client to publish via the same beacon node that + produced the block (stateful operation). This parameter affects the self-built case only. When a bid wins, the beacon node does not hold that payload, so it returns only the beacon block regardless of this parameter. diff --git a/types/gloas/builder_entry.yaml b/types/gloas/builder_entry.yaml index 3e4d1729..243384ea 100644 --- a/types/gloas/builder_entry.yaml +++ b/types/gloas/builder_entry.yaml @@ -65,8 +65,8 @@ Gloas: - description: | Percentage multiplier applied to this builder's bid once it has passed the `min_bid` check, giving - `builder_boost_factor * (builder_payload_value // 100)`, where - `builder_payload_value` is the bid's `value` plus its `execution_payment`. The + `builder_boost_factor * (builder_bid_value // 100)`, where + `builder_bid_value` is the bid's `value` plus its `execution_payment`. The highest boosted bid competes with the local payload in Gwei, so a factor below `100` favors the local payload and one above `100` favors this builder. @@ -90,17 +90,11 @@ Gloas: Its SSZ container is: class BuilderConfig(Container): - builders: List[BuilderEntry, MAX_BUILDER_ENTRIES] min_bid: Gwei builder_boost_factor: uint64 - required: [builders, min_bid, builder_boost_factor] + builders: List[BuilderEntry, MAX_BUILDER_ENTRIES] + required: [min_bid, builder_boost_factor, builders] properties: - builders: - type: array - maxItems: 64 # MAX_BUILDER_ENTRIES - description: "The builders to request bids from, one `BuilderEntry` each. Empty means request none, so only p2p bids are considered." - items: - $ref: "#/Gloas/BuilderEntry" min_bid: allOf: - $ref: "../primitive.yaml#/Gwei" @@ -111,3 +105,9 @@ Gloas: - $ref: "../primitive.yaml#/Uint64" - description: "Percentage multiplier applied to a bid that matches no entry, exactly as a `BuilderEntry`'s `builder_boost_factor` is applied to a bid from that entry." example: "100" + builders: + type: array + maxItems: 64 # MAX_BUILDER_ENTRIES + description: "The builders to request bids from, one `BuilderEntry` each. Empty means request none, so only p2p bids are considered." + items: + $ref: "#/Gloas/BuilderEntry" From 97c1cd93bbd6ff2a36a9402d69a88905ef60d9b7 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Mon, 3 Aug 2026 12:42:46 -0700 Subject: [PATCH 15/18] drop per-builder p2p bid preferences Every BuilderEntry is now a bid request: url and auth are required, and an entry no longer supplies p2p policy for a builder's gossiped bids. The top-level min_bid and builder_boost_factor apply to p2p bids --- apis/validator/block.v4.yaml | 25 +++++++------------------ types/gloas/builder_entry.yaml | 32 +++++++++++++------------------- validator-flow.md | 4 ++-- 3 files changed, 22 insertions(+), 39 deletions(-) diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index 72352f63..12699c3f 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -9,25 +9,15 @@ post: The beacon node always builds a local payload and MAY consider a p2p bid, so a block is returned even when no bid is available. The validator client optionally supplies a `BuilderConfig` in the - request body: a list of `BuilderEntry` objects in its `builders` field, one per builder, plus a - top-level `min_bid` and `builder_boost_factor` that apply to a bid matching no entry. Each entry - contains `url`, `builder_pubkey`, or both: - - `url` only: a bid is requested from that address, and this entry applies to the bid it - returns. - - `builder_pubkey` only: no bid is requested, and this entry applies to that builder's p2p bids. - - both: a bid is requested and this entry applies to the bid it returns, which MUST NOT be - accepted unless signed by `builder_pubkey`. - - p2p policy comes only from entries with no `url`. + request body: a list of `BuilderEntry` objects in its `builders` field, one per bid request, + plus a top-level `min_bid` and `builder_boost_factor` that apply to p2p bids. Each entry + requests a bid from its `url`, and the entry applies to the bid that request returns; when the + entry also contains a `builder_pubkey`, that bid MUST NOT be accepted unless signed by it. A bid is rejected if its total payment falls below the `min_bid` that applies to it. Every surviving bid is then boosted by the `builder_boost_factor` that applies to it, and the highest boosted bid competes with the local build in Gwei. The local build wins a tie. - A p2p bid is matched to an entry with no `url` by resolving its `builder_index` through the - builder registry. A bid matching no entry takes its `min_bid` and `builder_boost_factor` from - the body's top-level `min_bid` and `builder_boost_factor`. - The response carries the full block contents only when the beacon node self-built the block and `include_payload` is `true`; in every other case, including any bid win, it carries only the `BeaconBlock`. @@ -86,14 +76,13 @@ post: required: false description: | A `BuilderConfig` for this request: the builder entries to solicit bids from, plus the - top-level `min_bid` and `builder_boost_factor` that apply to a p2p bid matching no entry. - When an entry contains an `auth`, its `auth.message.slot` is the `slot` this request is for; + top-level `min_bid` and `builder_boost_factor` that apply to p2p bids. + Each entry's `auth.message.slot` is the `slot` this request is for; the builder rejects a mismatch, so an entry naming a different slot is one the beacon node does not use for a bid request. A body that cannot be decoded is invalid and MUST fail with a 400. Every other failure is per entry: an entry the beacon node rejects, or whose builder does not answer, yields no bid and MUST NOT fail the request, so one bad entry never costs the - proposer its slot. An entry with no `url` requests no bid by design but supplies policy - for its builder's p2p bids. When the body is omitted the validator client has supplied no + proposer its slot. When the body is omitted the validator client has supplied no builder config, so the beacon node requests no bids and returns its locally built block. A client that wants a bid considered, over the builder API or p2p, supplies a `BuilderConfig`. content: diff --git a/types/gloas/builder_entry.yaml b/types/gloas/builder_entry.yaml index 243384ea..ad2b6cb2 100644 --- a/types/gloas/builder_entry.yaml +++ b/types/gloas/builder_entry.yaml @@ -2,18 +2,13 @@ Gloas: BuilderEntry: type: object description: | - A per-builder input the validator client supplies on a block-production request. At least one - of `url` and `builder_pubkey` MUST be present. + A per-builder input the validator client supplies on a block-production request. - An entry that contains a `url` is a bid request. It MUST also contain an `auth`, which the - builder requires on every request, and the beacon node MUST NOT send a request for an entry - that has a `url` without one. No two such entries may share both that `url` and their + Each entry is a bid request sent to its `url`, authenticated by its `auth`, which the + builder requires on every request. No two entries may share both their `url` and their `auth`'s `data`, and one request is made per entry, so several MAY share a `url` with different `data`. - An entry with no `url` supplies p2p policy instead, and no two of them may share a - `builder_pubkey`; if they do, the beacon node applies the first and ignores the rest. - `auth` is the [`SignedRequestAuthV1`](https://github.com/ethereum/builder-specs/blob/78a5546d9d8253beabf7db8baf988a58abdec87f/specs/gloas/validator.md#signedrequestauthv1) object from the Builder API Gloas spec. A `BuilderEntry` never reaches a builder, so it has no Builder API definition. Its SSZ @@ -30,23 +25,23 @@ Gloas: min_bid: Gwei builder_boost_factor: uint64 - SSZ cannot express absence, so each optional field has a value that means unset in either - encoding: a zero-length `url`, an all-zero `builder_pubkey`, which is not a valid BLS public - key, and an `auth` whose `data` is zero-length and whose `signature` is all-zero. - required: [max_execution_payment, min_bid, builder_boost_factor] + SSZ cannot express absence, so an unset `builder_pubkey` is all-zero, which is not a valid + BLS public key. A zero-length `url` is equivalent to omitting it, so a body containing one is + invalid. + required: [url, auth, max_execution_payment, min_bid, builder_boost_factor] properties: url: type: string format: uri maxLength: 2048 # MAX_BUILDER_URL_SIZE - description: "Where this entry's bid request is sent. Omit it to supply policy for this builder's p2p bids without requesting one." + description: "Where this entry's bid request is sent." example: "https://builder.example.com" auth: - description: "Authenticates this entry's bid request to the builder. The beacon node MUST forward `message` and `signature` byte-for-byte unchanged. Required when `url` is present; omit it only on an entry that requests no bid." + description: "Authenticates this entry's bid request to the builder. The beacon node MUST forward `message` and `signature` byte-for-byte unchanged." allOf: - $ref: "./request_auth.yaml#/Gloas/SignedRequestAuth" builder_pubkey: - description: "The builder's BLS public key. On an entry with a `url` it filters the response: a builder-API bid not signed by it MUST NOT be accepted. On an entry without one it identifies the builder whose p2p bids this entry applies to." + description: "The builder's BLS public key. When present it filters the response: a builder-API bid not signed by it MUST NOT be accepted." allOf: - $ref: "../primitive.yaml#/Pubkey" max_execution_payment: @@ -84,8 +79,7 @@ Gloas: description: | The resolved per-key builder config the validator client sends on a block-production request. Entries arrive fully resolved, so a requested bid is governed by its own `BuilderEntry`; the - top-level `min_bid` and `builder_boost_factor` apply only to a bid that matches no entry, - which in practice is a bid received over p2p. + top-level `min_bid` and `builder_boost_factor` apply to bids received over p2p. Its SSZ container is: @@ -98,12 +92,12 @@ Gloas: min_bid: allOf: - $ref: "../primitive.yaml#/Gwei" - - description: "Minimum total payment, in Gwei, accepted from a bid that matches no entry, which in practice is a p2p bid. A bid whose `value` plus `execution_payment` is below this MUST be rejected." + - description: "Minimum total payment, in Gwei, accepted from a p2p bid. A bid whose `value` plus `execution_payment` is below this MUST be rejected." example: "10000000" builder_boost_factor: allOf: - $ref: "../primitive.yaml#/Uint64" - - description: "Percentage multiplier applied to a bid that matches no entry, exactly as a `BuilderEntry`'s `builder_boost_factor` is applied to a bid from that entry." + - description: "Percentage multiplier applied to a p2p bid, exactly as a `BuilderEntry`'s `builder_boost_factor` is applied to a bid from that entry." example: "100" builders: type: array diff --git a/validator-flow.md b/validator-flow.md index 91e89403..c13c63de 100644 --- a/validator-flow.md +++ b/validator-flow.md @@ -23,8 +23,8 @@ If proposing block, then at immediate start of slot: - Pre-Gloas forks: [produceBlockV3](#/Validator/produceBlockV3) - Post-Gloas fork: [produceBlockV4](#/Validator/produceBlockV4) - Optionally supply a `BuilderConfig` in the request body: the builder entries to solicit - builder-API bids and set per-builder policy, plus the top-level `min_bid` and - `builder_boost_factor` that apply to p2p bids matching no entry. + builder-API bids, plus the top-level `min_bid` and `builder_boost_factor` that apply to + p2p bids. - `include_payload=true`: returns `BlockContents` (beacon block, execution payload envelope, blobs, and KZG proofs). Enables stateless operation (multi-BN setups, distributed validators, failover). - `include_payload=false`: returns only the `BeaconBlock`. The beacon node caches the execution payload From eb752a182d70f735d5b63740f085e38e23420039 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Mon, 10 Aug 2026 14:47:38 -0700 Subject: [PATCH 16/18] Make BuilderConfig body and fork version header required --- apis/validator/block.v4.yaml | 22 ++++++++++++++-------- types/gloas/builder_entry.yaml | 7 ++++++- validator-flow.md | 6 +++--- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index 12699c3f..24a57466 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -8,7 +8,7 @@ post: Requests a beacon node to produce a valid block, which the validator then signs. The beacon node always builds a local payload and MAY consider a p2p bid, so a block is returned - even when no bid is available. The validator client optionally supplies a `BuilderConfig` in the + even when no bid is available. The validator client supplies a `BuilderConfig` in the request body: a list of `BuilderEntry` objects in its `builders` field, one per bid request, plus a top-level `min_bid` and `builder_boost_factor` that apply to p2p bids. Each entry requests a bid from its `url`, and the entry applies to the bid that request returns; when the @@ -72,19 +72,25 @@ post: hold that payload, so it returns only the beacon block regardless of this parameter. schema: type: boolean + - in: header + schema: + $ref: "../../beacon-node-oapi.yaml#/components/schemas/ConsensusVersion" + required: true + name: Eth-Consensus-Version + description: "The active consensus version to which the request belongs." requestBody: - required: false + required: true description: | A `BuilderConfig` for this request: the builder entries to solicit bids from, plus the top-level `min_bid` and `builder_boost_factor` that apply to p2p bids. Each entry's `auth.message.slot` is the `slot` this request is for; the builder rejects a mismatch, so an entry naming a different slot is one the beacon node - does not use for a bid request. A body that cannot be decoded is invalid and MUST fail with a - 400. Every other failure is per entry: an entry the beacon node rejects, or whose builder does - not answer, yields no bid and MUST NOT fail the request, so one bad entry never costs the - proposer its slot. When the body is omitted the validator client has supplied no - builder config, so the beacon node requests no bids and returns its locally built block. A - client that wants a bid considered, over the builder API or p2p, supplies a `BuilderConfig`. + does not use for a bid request. A missing body, or one that cannot be decoded, is invalid and + MUST fail with a 400. Every other failure is per entry: an entry the beacon node rejects, or + whose builder does not answer, yields no bid and MUST NOT fail the request, so one bad entry + never costs the proposer its slot. A local-preferred build is requested with an empty + `builders` list and a `builder_boost_factor` of `0`, which prefers the local payload unless + an error makes it unviable. content: application/json: schema: diff --git a/types/gloas/builder_entry.yaml b/types/gloas/builder_entry.yaml index ad2b6cb2..c43e5761 100644 --- a/types/gloas/builder_entry.yaml +++ b/types/gloas/builder_entry.yaml @@ -97,7 +97,12 @@ Gloas: builder_boost_factor: allOf: - $ref: "../primitive.yaml#/Uint64" - - description: "Percentage multiplier applied to a p2p bid, exactly as a `BuilderEntry`'s `builder_boost_factor` is applied to a bid from that entry." + - description: | + Percentage multiplier applied to a p2p bid, exactly as a `BuilderEntry`'s + `builder_boost_factor` is applied to a bid from that entry: `0` prefers the local + payload, `100` is profit maximization, and `2**64 - 1` prefers the bid, each unless + an error or health check makes the preferred payload unviable. Servers should use + saturating arithmetic so that large values do not overflow. example: "100" builders: type: array diff --git a/validator-flow.md b/validator-flow.md index c13c63de..15ebc406 100644 --- a/validator-flow.md +++ b/validator-flow.md @@ -22,9 +22,9 @@ If proposing block, then at immediate start of slot: 1. Ask Beacon Node for BeaconBlock object: - Pre-Gloas forks: [produceBlockV3](#/Validator/produceBlockV3) - Post-Gloas fork: [produceBlockV4](#/Validator/produceBlockV4) - - Optionally supply a `BuilderConfig` in the request body: the builder entries to solicit - builder-API bids, plus the top-level `min_bid` and `builder_boost_factor` that apply to - p2p bids. + - Supply a `BuilderConfig` in the required request body, with the `Eth-Consensus-Version` + header: the builder entries to solicit builder-API bids, plus the top-level `min_bid` and + `builder_boost_factor` that apply to p2p bids. - `include_payload=true`: returns `BlockContents` (beacon block, execution payload envelope, blobs, and KZG proofs). Enables stateless operation (multi-BN setups, distributed validators, failover). - `include_payload=false`: returns only the `BeaconBlock`. The beacon node caches the execution payload From 1dda02dc0570380f5e42e249136d39a6793a929d Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Mon, 10 Aug 2026 15:24:04 -0700 Subject: [PATCH 17/18] accept bids from a list of builder pubkeys builder_pubkey becomes builder_pubkeys, a List[BLSPubkey, MAX_BUILDER_PUBKEYS]: an empty list accepts any builder, and a bid MUST be signed by one of a non-empty list. Every BuilderEntry field is now required and the SSZ absence sentinels are gone. Also says builder bid where a builder bid is meant, per review. --- apis/validator/block.v4.yaml | 31 +++++++++++++++++-------------- types/gloas/builder_entry.yaml | 23 +++++++++++++---------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/apis/validator/block.v4.yaml b/apis/validator/block.v4.yaml index 24a57466..b23ee031 100644 --- a/apis/validator/block.v4.yaml +++ b/apis/validator/block.v4.yaml @@ -8,25 +8,27 @@ post: Requests a beacon node to produce a valid block, which the validator then signs. The beacon node always builds a local payload and MAY consider a p2p bid, so a block is returned - even when no bid is available. The validator client supplies a `BuilderConfig` in the + even when no builder bid is available. The validator client supplies a `BuilderConfig` in the request body: a list of `BuilderEntry` objects in its `builders` field, one per bid request, plus a top-level `min_bid` and `builder_boost_factor` that apply to p2p bids. Each entry - requests a bid from its `url`, and the entry applies to the bid that request returns; when the - entry also contains a `builder_pubkey`, that bid MUST NOT be accepted unless signed by it. + requests a bid from its `url`, and the entry applies to the bid that request returns; an empty + `builder_pubkeys` list accepts any builder, and a bid MUST NOT be accepted unless signed by + one of a non-empty list. - A bid is rejected if its total payment falls below the `min_bid` that applies to it. Every - surviving bid is then boosted by the `builder_boost_factor` that applies to it, and the highest - boosted bid competes with the local build in Gwei. The local build wins a tie. + A builder bid, over the builder API or p2p, is rejected if its total payment falls below the + `min_bid` that applies to it. Every surviving bid is then boosted by the + `builder_boost_factor` that applies to it, and the + highest boosted bid competes with the local build in Gwei. The local build wins a tie. The response carries the full block contents only when the beacon node self-built the block and - `include_payload` is `true`; in every other case, including any bid win, it carries only the - `BeaconBlock`. + `include_payload` is `true`; in every other case, including any builder bid win, it carries + only the `BeaconBlock`. Every block is published via `POST /eth/v2/beacon/blocks`. A self-built block carries `BUILDER_INDEX_SELF_BUILD` as its `builder_index`, and the validator client publishes its - execution payload envelope via `POST /eth/v1/beacon/execution_payload_envelopes`. When a bid - won, the winning builder releases the envelope instead, and the validator client echoes - `Eth-Builder-Url` if one was returned. + execution payload envelope via `POST /eth/v1/beacon/execution_payload_envelopes`. When a + builder bid won, the winning builder releases the envelope instead, and the validator client + echoes `Eth-Builder-Url` if one was returned. This endpoint is specific to the post-Gloas forks and is not backwards compatible with previous forks. @@ -68,8 +70,9 @@ post: bandwidth but requires the validator client to publish via the same beacon node that produced the block (stateful operation). - This parameter affects the self-built case only. When a bid wins, the beacon node does not - hold that payload, so it returns only the beacon block regardless of this parameter. + This parameter affects the self-built case only. When a builder bid wins, the beacon node + does not hold that payload, so it returns only the beacon block regardless of this + parameter. schema: type: boolean - in: header @@ -142,7 +145,7 @@ post: example: "12345" description: | Execution payload value in Wei. The value of the local execution payload when - self-building, or the total value of the bid when committing to a builder bid. + self-building, or the total value of the builder bid when committing to one. execution_payload_included: type: boolean description: | diff --git a/types/gloas/builder_entry.yaml b/types/gloas/builder_entry.yaml index c43e5761..16a5254a 100644 --- a/types/gloas/builder_entry.yaml +++ b/types/gloas/builder_entry.yaml @@ -16,19 +16,18 @@ Gloas: MAX_BUILDER_ENTRIES = 64 MAX_BUILDER_URL_SIZE = 2048 + MAX_BUILDER_PUBKEYS = 64 class BuilderEntry(Container): url: ByteList[MAX_BUILDER_URL_SIZE] # UTF-8 bytes of `url`, exactly as in JSON auth: SignedRequestAuth - builder_pubkey: BLSPubkey + builder_pubkeys: List[BLSPubkey, MAX_BUILDER_PUBKEYS] max_execution_payment: Gwei min_bid: Gwei builder_boost_factor: uint64 - SSZ cannot express absence, so an unset `builder_pubkey` is all-zero, which is not a valid - BLS public key. A zero-length `url` is equivalent to omitting it, so a body containing one is - invalid. - required: [url, auth, max_execution_payment, min_bid, builder_boost_factor] + In either encoding, a zero-length `url` is invalid. + required: [url, auth, builder_pubkeys, max_execution_payment, min_bid, builder_boost_factor] properties: url: type: string @@ -37,13 +36,17 @@ Gloas: description: "Where this entry's bid request is sent." example: "https://builder.example.com" auth: - description: "Authenticates this entry's bid request to the builder. The beacon node MUST forward `message` and `signature` byte-for-byte unchanged." allOf: - $ref: "./request_auth.yaml#/Gloas/SignedRequestAuth" - builder_pubkey: - description: "The builder's BLS public key. When present it filters the response: a builder-API bid not signed by it MUST NOT be accepted." - allOf: - - $ref: "../primitive.yaml#/Pubkey" + - description: "Authenticates this entry's bid request to the builder. The beacon node MUST forward `message` and `signature` byte-for-byte unchanged." + builder_pubkeys: + type: array + maxItems: 64 # MAX_BUILDER_PUBKEYS + description: "The builder BLS public keys this entry accepts bids from. Empty accepts any builder; otherwise a bid not signed by one of them MUST NOT be accepted." + items: + allOf: + - $ref: "../primitive.yaml#/Pubkey" + - description: "A builder's BLS public key." max_execution_payment: allOf: - $ref: "../primitive.yaml#/Gwei" From 71c73154836a95dd724d009ccdd6ad59620ce63e Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Mon, 10 Aug 2026 15:56:16 -0700 Subject: [PATCH 18/18] a zero-length auth data is invalid Removes the last SSZ absence ambiguity: with the builder_pubkey sentinel gone, an empty data ByteList was the one remaining wire value that could read as "unset". It had no defined meaning either way, since omission already expresses derive from the builder's URL and auth is required on every request. Also state the zero-length url rule on BuilderPreferencesEntry. --- types/gloas/builder_preferences_entry.yaml | 2 ++ types/gloas/request_auth.yaml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/types/gloas/builder_preferences_entry.yaml b/types/gloas/builder_preferences_entry.yaml index 69abbdc6..c55cc27f 100644 --- a/types/gloas/builder_preferences_entry.yaml +++ b/types/gloas/builder_preferences_entry.yaml @@ -17,6 +17,8 @@ Gloas: url: ByteList[MAX_BUILDER_URL_SIZE] # UTF-8 bytes of `url`, exactly as in JSON auth: SignedRequestAuth max_execution_payment: Gwei + + In either encoding, a zero-length `url` is invalid. required: [proposer_pubkey, url, auth, max_execution_payment] properties: proposer_pubkey: diff --git a/types/gloas/request_auth.yaml b/types/gloas/request_auth.yaml index a7bc5a05..4561e588 100644 --- a/types/gloas/request_auth.yaml +++ b/types/gloas/request_auth.yaml @@ -13,9 +13,9 @@ Gloas: exact bytes when it verifies. When no value has been agreed out of band, implementations SHOULD default to the UTF-8 bytes of the builder's own advertised URL, exactly as advertised, so proposers with no prior relationship can construct an identical `data` - deterministically. + deterministically. A zero-length `data` is invalid. example: "0x1234567890abcdef" - pattern: "^0x(?:[a-fA-F0-9]{2}){0,4096}$" + pattern: "^0x(?:[a-fA-F0-9]{2}){1,4096}$" slot: allOf: - $ref: "../primitive.yaml#/Uint64"