Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5daf8a7
Beacon API parity with builder api and keymanager api
JasonVranek Jul 21, 2026
21750d8
Update CHANGES.md
JasonVranek Jul 21, 2026
f8d2943
address review comments
JasonVranek Jul 21, 2026
0ea8a06
address review comments
JasonVranek Jul 21, 2026
adc1ae1
require fully resolved builder entries, add SSZ request bodies, drop
JasonVranek Jul 23, 2026
1028934
address review feedback: trim to beacon-API conventions, fix bid
JasonVranek Jul 25, 2026
657d390
report the payload source, add default bid policy params, fix entry
JasonVranek Jul 27, 2026
727f6b3
remove Eth-Execution-Payload-Source header
JasonVranek Jul 27, 2026
23b5555
scope entry uniqueness by role: request identity vs p2p policy
JasonVranek Jul 27, 2026
3f886eb
A fully resolved body already carries default_min_bid and
JasonVranek Jul 28, 2026
979d038
align the produceBlockV4 description with the BuilderConfig body
JasonVranek Jul 28, 2026
ea9bafb
move max_execution_payment selection semantics to the builder-specs
JasonVranek Jul 28, 2026
bb1e790
batched submitBuilderPreferences call
JasonVranek Jul 31, 2026
bd99f03
Merge branch 'master' into update-produceBlockV4
JasonVranek Aug 1, 2026
91db6c8
address review nits on BuilderConfig and produceBlockV4
JasonVranek Aug 2, 2026
97c1cd9
drop per-builder p2p bid preferences
JasonVranek Aug 3, 2026
f71b1b8
Merge branch 'master' into update-produceBlockV4
JasonVranek Aug 7, 2026
eb752a1
Make BuilderConfig body and fork version header required
JasonVranek Aug 10, 2026
1dda02d
accept bids from a list of builder pubkeys
JasonVranek Aug 10, 2026
71c7315
a zero-length auth data is invalid
JasonVranek Aug 10, 2026
698d05f
float builder-specs links on master with the renamed types
JasonVranek Aug 15, 2026
e7280c2
Merge branch 'master' into update-produceBlockV4
JasonVranek Aug 18, 2026
8197ba0
pemdas for boost formula precision
JasonVranek Aug 20, 2026
376c9f0
clarify min(execution_payment, max_execution_payment) clamp
JasonVranek Aug 21, 2026
a221af8
- rename to BuilderRequestAuth
JasonVranek Aug 23, 2026
2d6a38e
clean up wordlist
nflaig Aug 23, 2026
a5d0dfa
formatting
nflaig Aug 23, 2026
b65a8de
fix builder spec references
nflaig Aug 23, 2026
1e8f160
nits
nflaig Aug 24, 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 CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ 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 | | | | | |
| [#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 | | | | | |
Expand All @@ -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 | | | | | |
Expand Down
20 changes: 20 additions & 0 deletions apis/beacon/blocks/blocks.v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,26 @@ post:
required: true
name: Eth-Consensus-Version
description: "The active consensus version to which the block being submitted belongs."
- name: Eth-Builder-Url

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.

do we really need this header? if I understand correctly the sole purpose of this is so that the beacon node knows where to submit it via submitSignedBeaconBlock but the beacon node should know this anyways, since it got the bid from that builder via api

the only case where the beacon node doesn't know this is if it's a different node (eg. in a multi-node setup) if the primary node that sourced the bid from the builder via api went offline during the proposal flow but this seems like an edge case and even then, publishing only via p2p is a fine fallback behavior that is sufficient anyways most of the time

that said, we can keep the header if people wanna have it, just questionable to me if it's useful enough

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this was solely for the multi-node support, not opinionated on it but it seemed like DVT teams wanted. Will leave for now and address most of the fat trimming comments

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 do not feel strongly about removing it, also I think when I commented there was in addition a Eth-Proxy-Url header which is now gone, so this single header, if useful for DVT teams, might be worth keeping

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1 questioning the usefulness of this header, both here and in the produceBlockV4 response. I'd personally prefer removing it unless there is a good reason to keep it.

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 implementing it, the header makes it easier on the submission side on the beacon node, avoids having to implement a lookup cache to "remember" to which builder to submit the bid to, it also acts as a signal whether the beacon node should forward the beacon block to a builder

in: header
required: false
description: |
Comment thread
nflaig marked this conversation as resolved.
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.
Comment on lines +46 to +55

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.

this can be implementation specific but we should probably not blindly call any url that is provided here, otherwise this creates a potential SSRF path

this isn't that critical since generally the beacon api is not to be exposed publicly but there are providers like rescue node or in general rpc nodes that could be vulnerable to this if not handled correctly

cc @jshufro might be relevant for you


The URL is untrusted input and a server-side request forgery (SSRF) risk: the beacon

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.

as a side note, lodestar will only forward urls that have previously been authenticated by a proposer, I expect even in DVT setups that either produceBlockV4 or submitBuilderPreferences has authenticated the url beforehand on all beacon nodes

node issues only the builder-API `submitSignedBeaconBlock` request to it, over http(s),
and MUST NOT follow redirects. Deployments serving callers beyond their own validator
clients SHOULD also reject URLs that resolve to private address space, since a caller
can otherwise use the forwarded request to reach internal endpoints.
schema:
type: string
format: uri
example: "https://builder.example.com"
requestBody:
description: "The `SignedBeaconBlock` object composed of `BeaconBlock` object (produced by beacon node) and validator signature."
required: true
Expand Down
122 changes: 71 additions & 51 deletions apis/validator/block.v4.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
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 MAY consider a p2p bid, so a block is returned
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; an empty
`builder_pubkeys` list accepts any builder, and a bid MUST NOT be accepted unless signed by
one of a non-empty list.

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.
A bid's total payment counts its `execution_payment` at most at the `max_execution_payment`
that applies to it; payment above that cap adds nothing to the bid's chances, though the
proposer still receives it if the builder honors the bid. 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 weighted by the `builder_boost_factor` that applies to it, and the
highest weighted value competes with the local build's value weighted by `100`. The local
build wins a tie.

The `Eth-Execution-Payload-Included` header and `execution_payload_included` response field
indicate which response type was returned.
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 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
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.
parameters:
- name: slot
in: path
Expand Down Expand Up @@ -58,46 +73,37 @@ get:
bandwidth but requires the validator client to publish via the same beacon node that
produced the block (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 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
- 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.
- in: header
schema:
$ref: "../../beacon-node-oapi.yaml#/components/schemas/Uint64"
$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: 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 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:
Comment thread
nflaig marked this conversation as resolved.
schema:
$ref: "../../beacon-node-oapi.yaml#/components/schemas/Gloas.BuilderConfig"
application/octet-stream:
schema:
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
Expand All @@ -110,6 +116,18 @@ get:
$ref: '../../beacon-node-oapi.yaml#/components/headers/Eth-Execution-Payload-Value'
Eth-Execution-Payload-Included:
$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.
required: false
schema:
type: string
format: uri
example: "https://builder.example.com"
content:
application/json:
schema:
Expand All @@ -130,7 +148,7 @@ get:
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: |
Expand Down Expand Up @@ -159,6 +177,8 @@ get:
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":
Expand Down
56 changes: 56 additions & 0 deletions apis/validator/builder_preferences.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
post:
operationId: "submitBuilderPreferences"
summary: Submit builder preferences
description: |
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.

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:
- 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 submitted builder preferences belong."
tags:
- Validator
requestBody:
required: true
description: "Array of `BuilderPreferencesEntry` objects to submit, one per builder per proposer."
content:
Comment thread
nflaig marked this conversation as resolved.
application/json:
schema:
type: array
maxItems: 4096 # MAX_BUILDER_ENTRIES * (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH
items:
$ref: "../../beacon-node-oapi.yaml#/components/schemas/Gloas.BuilderPreferencesEntry"
application/octet-stream:
schema:
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 and its builder accepted it"
"400":
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:
$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":
$ref: "../../beacon-node-oapi.yaml#/components/responses/CurrentlySyncing"
Loading
Loading