Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3566c77
fix one typo and some inprecise links
JasonVranek Jul 8, 2026
d23c708
clarify RequestAuthV1.slot as the proposal slot on both channels
JasonVranek Jul 13, 2026
ea472f5
define the canonical byte form of the builder URL in RequestAuthV1.data
JasonVranek Jul 13, 2026
bd892d6
Clarify request auth signing/verifying
JasonVranek Jul 14, 2026
3dadb3a
Define canonicalize() and apply it when signing/verifying RequestAuth…
JasonVranek Jul 14, 2026
5078eab
Adopt opaque RequestAuthV1.data, drop URL canonicalization, add
JasonVranek Jul 15, 2026
31712da
SHOULD use builder URL as the default request auth data
JasonVranek Jul 20, 2026
e6f34b2
remove Eth-Consensus-Version header requirement if type isn't fork
JasonVranek Jul 20, 2026
4a60214
housekeeping:
JasonVranek Jul 20, 2026
06ac935
add missing example
JasonVranek Jul 21, 2026
51545cf
require and verify the SignedRequestAuthV1, drop explicit proxy support
JasonVranek Jul 22, 2026
8eec2eb
Require Date-Milliseconds and X-Timeout-Ms headers
JasonVranek Jul 23, 2026
07b94de
clarify the request auth signing root
JasonVranek Jul 24, 2026
f99faf8
wording nitpicks
JasonVranek Jul 27, 2026
e27dfe3
Don't require Eth-Consensus-Version if JSON. Consistent with prior
JasonVranek Jul 29, 2026
bc91f6f
Revert "Don't require Eth-Consensus-Version if JSON. Consistent with …
JasonVranek Jul 29, 2026
25fa22b
update wordlist.txt for CI
JasonVranek Jul 30, 2026
36cefe4
fork-version request auth and slim the builder preferences request
JasonVranek Jul 31, 2026
9a58e07
preferences should be stored as (proposer, slot)
JasonVranek Aug 10, 2026
72165af
missing 415 error type; clarity if fork header is missing or unknown
JasonVranek Aug 10, 2026
a5552d6
a zero-length auth data is invalid
JasonVranek Aug 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions apis/builder/beacon_blocks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ post:
must return an error response (400) with a description of the validation
failure.

After receiving 202, the proposer takes no further action on the block; the
builder is responsible for publishing the execution payload envelope.
After the builder returns 202, the proposer takes no further action on the
block; the builder is responsible for publishing the execution payload
envelope.

This API is applicable from Gloas fork onwards.
tags:
Expand Down
56 changes: 32 additions & 24 deletions apis/builder/builder_preferences.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@ post:
operationId: "submitBuilderPreferences"
summary: Submit builder preferences for a proposer.
description: |
Submits a proposer's `BuilderPreferencesRequestV1` to the builder, including
Submits a proposer's `BuilderPreferencesRequest` to the builder, including
the `max_execution_payment` that the proposer is willing to accept from this
builder, authenticated via a `SignedRequestAuthV1`.
builder, authenticated via a `SignedRequestAuth`.

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 builder MUST verify the BLS signature in `auth` against
`validator_pubkey`, and check that `auth.message.data`
matches its own URL. If the signature check fails, the builder MUST
return a 401 response. If the URL or slot check fails, the builder MUST
return a 400 response.
The body MAY be encoded as JSON (`Content-Type: application/json`) or SSZ
(`Content-Type: application/octet-stream`); `BuilderPreferencesRequest`
is fork-versioned, so the `Eth-Consensus-Version` header is required.

The builder MUST verify the BLS signature in `auth` against the
`proposer_pubkey` path parameter, and check that `auth.message.data` matches
the value it agreed with the proposer. `auth.message.slot` is the proposal slot the
preferences apply to; the builder MUST reject preferences whose slot has
already passed. If the signature check fails, the builder MUST return a 401
response. If the data or slot check fails, the builder MUST return a 400
response.

A success response (202) indicates that the preferences were accepted. If
the preferences are invalid, then the builder MUST return an error response
Expand All @@ -24,31 +30,31 @@ post:
tags:
- Builder
parameters:
- name: validator_pubkey
- name: proposer_pubkey

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

related to ethereum/beacon-APIs#630 (comment), maybe there was some discussion around this but I don't really understand why we wanna sent a single request per pubkey, maybe someone can explain this

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I don't think I understood the question the first time. Is the assumption if a node operator has more than one proposer in the lookahead they can save on a call if batching was supported?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yes, you only do a single call no matter how many validators are proposers in the lookahead, that matches how the proposer preferences api works

as far as I am aware all apis support batching, this one seems like an exception that decided to do a request per pubkey, but I don't see why it needs to be

to be clear, I don't feel strongly about this, on mainnet that will not make a large difference, but it would be kinda nice if the builder preferences work alongside the proposer preferences more closely unless there is a good reason to have a different api design

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

after thinking about this more and reading up on the current spec, although I haven't found that mentioned as an explicit argument during the initial spec design, I believe it makes sense due to signed auth requests since we allow pubkeys to sign over different auth, so there can be partial failures, however per the spec here this should return a 401 which is communicated back via http status code which doesn't really allow reporting partial failures. It seems favorable to send individual requests per-pubkey to me.

in: path
required: true
description: "The BLS public key of the validator expressing these preferences."
description: BLS public key of the proposer.
schema:
$ref: "../../beacon-apis/types/primitive.yaml#/Pubkey"
- name: Eth-Consensus-Version
in: header
required: true
description: "The active consensus version to which the request body belongs. Required if the request body is SSZ encoded."
- in: header
schema:
$ref: "../../builder-oapi.yaml#/components/schemas/ConsensusVersion"
required: true
name: Eth-Consensus-Version
description: "The active consensus version to which the submitted preferences belong."
requestBody:
description: A `BuilderPreferencesRequestV1` containing the proposer's preferences and a `SignedRequestAuthV1` for authentication.
description: A `BuilderPreferencesRequest` containing the proposer's preferences and a `SignedRequestAuth` for authentication.
required: true
content:
application/json:
schema:
$ref: "../../types/gloas/builder_preferences.yaml#/Gloas/BuilderPreferencesRequestV1"
$ref: "../../types/gloas/builder_preferences.yaml#/Gloas/BuilderPreferencesRequest"
examples:
BuilderPreferencesRequestV1:
$ref: "../../builder-oapi.yaml#/components/examples/Gloas.BuilderPreferencesRequestV1"
BuilderPreferencesRequest:
$ref: "../../builder-oapi.yaml#/components/examples/Gloas.BuilderPreferencesRequest"
application/octet-stream:
schema:
description: "SSZ serialized `BuilderPreferencesRequestV1` bytes. Use content type header to indicate that SSZ data is contained in the request body."
description: "SSZ serialized `BuilderPreferencesRequest` bytes. Use content type header to indicate that SSZ data is contained in the request body."
responses:
"202":
description: Success response.
Expand All @@ -62,25 +68,27 @@ post:
SlotMismatch:
value:
code: 400
message: "Invalid SignedRequestAuthV1: auth.message.slot does not match the requested slot"
WrongBuilder:
message: "Invalid SignedRequestAuth: auth.message.slot has already passed"
DataMismatch:
value:
code: 400
message: "auth.message.data does not match this builder's URL"
message: "Invalid SignedRequestAuth: auth.message.data does not match the value agreed with this builder"
InvalidPreferences:
value:
code: 400
message: "Invalid builder preferences: max_execution_payment malformed"
"401":
description: Authentication required.
description: Authentication failed.
content:
application/json:
schema:
$ref: "../../builder-oapi.yaml#/components/schemas/ErrorMessage"
examples:
SignatureVerificationFailed:
InvalidAuth:
value:
code: 401
message: "Invalid SignedRequestAuthV1: signature verification failed"
message: "Invalid SignedRequestAuth: signature verification failed"
"415":
$ref: "../../builder-oapi.yaml#/components/responses/UnsupportedMediaType"
"500":
$ref: "../../builder-oapi.yaml#/components/responses/InternalError"
101 changes: 54 additions & 47 deletions apis/builder/execution_payload_bid.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,35 @@ post:
Requests a builder node to produce a valid execution payload bid, which
can be integrated into a beacon block and signed.

The proposer sends a POST request to the builder with the following information:
The proposer's beacon node sends a POST request to the builder with the following information:
- The slot for which the block should be proposed.
- The hash of the execution layer block the proposer will build on.
- The root of the beacon block the proposer will build on.
- The index of the proposer.
- Optionally, a `SignedRequestAuthV1` in the request body that
authenticates the request. The body MAY be encoded as JSON or SSZ.
- The public key of the proposer.
- A `SignedRequestAuth` in the request body that authenticates the
request. The body MAY be encoded as JSON or SSZ.
- A `Date-Milliseconds` header with the send time and an `X-Timeout-Ms`
header with the proposer's timeout for the request.

The proposer's `max_execution_payment` is communicated exclusively via the
`submitBuilderPreferences` endpoint. If no `BuilderPreferencesV1` have been
submitted for the proposer, the builder MUST treat `max_execution_payment` as `0`
and MUST NOT include an execution layer payment in the bid.
`submitBuilderPreferences` endpoint. Any bid the builder serves MUST honor
the `max_execution_payment` cap from stored preferences. Without stored
preferences the builder MAY serve a bid with any `execution_payment`; the
proposer's locally configured per-builder limits are the backstop: the
proposer discards any bid that exceeds them.

The `SignedRequestAuthV1` body is optional. If it is present but malformed
or fails signature verification, the builder MAY return a 401 response.
If it is absent, the builder MAY still serve a bid, but builders MAY
use the presence and validity of the `SignedRequestAuthV1` to apply
per-validator policy (e.g. rate-limiting, prioritization, or refusing
unauthenticated requests).
The `SignedRequestAuth` body is required. If the body is missing or
malformed, the request is invalid and the builder MUST return a 400
response. If its `auth.message.data` or `auth.message.slot` does not match,
the builder MUST return a 400 response; if its signature fails to verify,
the builder MUST return a 401 response. Builders MAY use the authenticated
identity to apply per-validator policy (e.g. rate-limiting).

The builder responds with a 200 response containing an execution payload bid if it can provide one.

If the builder is unable to produce a valid execution payload bid, then
the builder MUST return a 204 response. If the request is invalid, then the
builder MUST return an error response (400) with a description of the
validation failure.
A builder that does not serve a bid MUST return a 204 response. If the
request is invalid, then the builder MUST return an error response (400)
with a description of the validation failure.

This API is applicable from Gloas fork onwards.
tags:
Expand Down Expand Up @@ -62,48 +65,48 @@ post:
$ref: "../../beacon-apis/types/primitive.yaml#/Pubkey"
- name: Date-Milliseconds
in: header
required: false
required: true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why is this change in this PR? it seems already quite convoluted with different changes

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I can put it back but this was a simplification born from having the keymanager api expect fully resolved fields including the request auth -> it's always going to be able to be supplied. The original protest (largely by me) of having required request auths was when it was difficult/unclear how a proxy could get/use them.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't understand the relation, making Date-Milliseconds header required seems to be completely separate to me, but seems like you referring to the auth param, for that I agree is should be required and probably default to builder url if not supplied

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

oops I misread. I was talking about request auths. for the timing headers I folded in based on the convo from discord https://discord.com/channels/595666850260713488/1528987631474446466/1529900444288155709

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yep I know the discord discussion and agree with making them required in gloas, but a separate PR seems like the better way to go about just to get some approvals on those changes easier, also not quite sure how to merge anything in this repo, there is no maintainer since Bharath is gone

description: |
Optional header containing a Unix timestamp in milliseconds representing
the point-in-time the request was sent. This header can be used to measure
latency.
Unix timestamp in milliseconds at which the request was sent. The
builder compares it with its own receive time to estimate the transit
delay from proposer to builder.
schema:
type: integer
format: int64
example: 1710338135000
- name: X-Timeout-Ms
in: header
required: false
required: true
description: |
Optional header containing the proposer's timeout for the request in milliseconds.
The proposer's timeout for the request in milliseconds, measured from
`Date-Milliseconds`: the builder MUST respond by `Date-Milliseconds`
plus `X-Timeout-Ms`, and the proposer discards later responses.
schema:
type: integer
format: int64
example: 10000
- name: Eth-Consensus-Version
in: header
required: true
description: |
The active consensus version to which the `SignedRequestAuthV1` in the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

so in the future if we wanna have a SignedRequestAuthV2 we bump the whole api version? I do agree this isn't fork versioned per se, but the mechanism could still be used as such

I am still fine with bumping the whole api though, just wanna be clear what versioning strategy we wanna use here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Not opinionated on this I just carried over the conventions from Bharath's original version

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the conventions from Bharath's original version

so we bump the api from v1 to v2 if we wanna introduce a SignedRequestAuthV2?

request body belongs. Required if the request body is SSZ encoded.
- in: header
schema:
$ref: "../../builder-oapi.yaml#/components/schemas/ConsensusVersion"
required: true
name: Eth-Consensus-Version
description: "The active consensus version to which the request body belongs."
requestBody:
description: |
Optional `SignedRequestAuthV1` authenticating the request. If provided,
the builder MAY verify the BLS signature against the validator pubkey
resolved from the `proposer_pubkey` path parameter, and check that
`data` matches its own URL and that `slot` matches the
requested slot. If absent, the builder MAY still serve a bid subject
to its own policy.
required: false
A `SignedRequestAuth` authenticating the request. The builder MUST
verify the BLS signature against the validator pubkey resolved from the
`proposer_pubkey` path parameter, and MUST check that `auth.message.data`
matches the value it agreed with the proposer and that
`auth.message.slot` matches the `slot` path parameter (the proposal
slot).
required: true
content:
application/json:
schema:
$ref: "../../types/gloas/request_auth.yaml#/Gloas/SignedRequestAuthV1"
$ref: "../../types/gloas/request_auth.yaml#/Gloas/SignedRequestAuth"
application/octet-stream:
schema:
description: "SSZ serialized `SignedRequestAuthV1` bytes. Use Content-Type header to indicate that SSZ data is contained in the request body."
description: "SSZ serialized `SignedRequestAuth` bytes. Use Content-Type header to indicate that SSZ data is contained in the request body."
responses:
"200":
description: Success response.
Expand Down Expand Up @@ -143,26 +146,30 @@ post:
SlotMismatch:
value:
code: 400
message: "Invalid SignedRequestAuthV1: auth.message.slot does not match the requested slot"
WrongBuilder:
message: "Invalid SignedRequestAuth: auth.message.slot does not match the proposal slot in the request path"
DataMismatch:
value:
code: 400
message: "Invalid SignedRequestAuthV1: auth.message.data does not match this builder's URL"
message: "Invalid SignedRequestAuth: auth.message.data does not match the value agreed with this builder"
MissingBody:
value:
code: 400
message: "Invalid request: SignedRequestAuth body is required"
MissingHeader:
value:
code: 400
message: "Invalid request: Date-Milliseconds and X-Timeout-Ms headers are required"
"401":
description: Authentication required.
description: Authentication failed.
content:
application/json:
schema:
$ref: "../../builder-oapi.yaml#/components/schemas/ErrorMessage"
examples:
MissingAuth:
value:
code: 401
message: "Missing SignedRequestAuthV1: this builder requires authenticated requests"
InvalidAuth:
value:
code: 401
message: "Invalid SignedRequestAuthV1: signature verification failed"
message: "Invalid SignedRequestAuth: signature verification failed"
"406":
$ref: "../../builder-oapi.yaml#/components/responses/NotAcceptable"
"415":
Expand Down
24 changes: 12 additions & 12 deletions builder-oapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ paths:
$ref: "./apis/builder/blinded_blocks.yaml"
/eth/v2/builder/blinded_blocks:
$ref: "./apis/builder/blinded_blocks_v2.yaml"
/eth/v1/builder/builder_preferences/{validator_pubkey}:
/eth/v1/builder/builder_preferences/{proposer_pubkey}:
$ref: "./apis/builder/builder_preferences.yaml"
/eth/v1/builder/status:
$ref: "./apis/builder/status.yaml"
Expand Down Expand Up @@ -108,14 +108,14 @@ components:
$ref: "./types/fulu/blobs_bundle.yaml#/Fulu/BlobsBundle"
Fulu.ExecutionPayloadAndBlobsBundle:
$ref: "./types/fulu/execution_payload_and_blobs_bundle.yaml#/Fulu/ExecutionPayloadAndBlobsBundle"
Gloas.RequestAuthV1:
$ref: "./types/gloas/request_auth.yaml#/Gloas/RequestAuthV1"
Gloas.SignedRequestAuthV1:
$ref: "./types/gloas/request_auth.yaml#/Gloas/SignedRequestAuthV1"
Gloas.BuilderPreferencesV1:
$ref: "./types/gloas/builder_preferences.yaml#/Gloas/BuilderPreferencesV1"
Gloas.BuilderPreferencesRequestV1:
$ref: "./types/gloas/builder_preferences.yaml#/Gloas/BuilderPreferencesRequestV1"
Gloas.RequestAuth:
$ref: "./types/gloas/request_auth.yaml#/Gloas/RequestAuth"
Gloas.SignedRequestAuth:
$ref: "./types/gloas/request_auth.yaml#/Gloas/SignedRequestAuth"
Gloas.BuilderPreferences:
$ref: "./types/gloas/builder_preferences.yaml#/Gloas/BuilderPreferences"
Gloas.BuilderPreferencesRequest:
$ref: "./types/gloas/builder_preferences.yaml#/Gloas/BuilderPreferencesRequest"

responses:
InternalError:
Expand Down Expand Up @@ -165,9 +165,9 @@ components:
$ref: "./examples/fulu/signed_blinded_beacon_block.json"
Fulu.SignedBuilderBid:
$ref: "./examples/fulu/signed_builder_bid.json"
Gloas.SignedRequestAuthV1:
Gloas.SignedRequestAuth:
$ref: "./examples/gloas/signed_request_auth.json"
Gloas.BuilderPreferencesV1:
Gloas.BuilderPreferences:
$ref: "./examples/gloas/builder_preferences.json"
Gloas.BuilderPreferencesRequestV1:
Gloas.BuilderPreferencesRequest:
$ref: "./examples/gloas/builder_preferences_request.json"
2 changes: 1 addition & 1 deletion examples/gloas/builder_preferences_request.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
},
"auth": {
"message": {
"data": "0x68747470733a2f2f6275696c6465722e6578616d706c652e636f6d",
"data": "0x1234567890abcdef",
"slot": "1"
},
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
Expand Down
2 changes: 1 addition & 1 deletion examples/gloas/signed_request_auth.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"value": {
"message": {
"data": "0x68747470733a2f2f6275696c6465722e6578616d706c652e636f6d",
"data": "0x1234567890abcdef",
"slot": "1"
},
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
Expand Down
Loading