Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
20 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
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
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | | | | | |
Expand Down
36 changes: 26 additions & 10 deletions apis/beacon/blocks/blocks.v2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment thread
nflaig marked this conversation as resolved.
Outdated
parameters:
- name: broadcast_validation
in: query
Expand Down Expand Up @@ -43,6 +43,22 @@ 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
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

in: header
required: false
description: |

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.

it's required false but maybe we should specify that we must echo this and eth-builder-proxy when available if returned by produceBlockv4 just to be explicit

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
by a p2p bid.
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
148 changes: 87 additions & 61 deletions apis/validator/block.v4.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,37 @@
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

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 beacon node always builds a local payload and considers any p2p bid.

hmm I don't think this is the default behavior I want in lodestar, we have a option called executiononly on the validator client and I intend to respect that

I don't think the spec should be opinionated about this, there are valid reasons to not consider p2p bids, eg. a user that just prefers to build local, or for censorship resistance reasons

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

@nflaig nflaig Jul 25, 2026

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.

isn't that documented in the builder-specs already? max_execution_payment shouldn't be documented here

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

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 does this mention anything about blinded or unblinded? sure it doesn't exist anymore but produce block v4 is post-gloas only

what's the intend behind pointing this out explicitly here?

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.

ah realized we had this note before, hmm I don't really like it, but out of scope for this PR in that case

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 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 `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`.

@nflaig nflaig Jul 25, 2026

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 is quite verbose, the flow should be somewhat intuitive from a spec perspective, without having to overexplain it, really need to try and simplify the large text sections in this PR

parameters:
- name: slot
in: path
Expand All @@ -45,59 +57,57 @@ 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:
requestBody:
required: false

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.

while this request body is not required, we should make the Eth-Consensus-Version header required, it's completely missing from the request right now

arguably it's only needed if BuilderConfig is passed in the request body, but I would make the header always required

also worth considering making passing BuilderConfig since it also applies to p2p bids, I don't see a good reason to omit this?

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

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.

there is no cross-entry policy to reconcile

what does this mean?

Malformed entries are ignored rather than failing the request

what does malformed mean in that case, I am guessing the format should still be per schema, especially in case of ssz that must be the case otherwise it fails deserialization

between its local build and any p2p bid.

* `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"
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.

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 we never plan to change BuilderEntry in the future?

also please make sure this somewhat follows how we document stuff in the beacon-api

eg. "MAY be encoded as JSON (Content-Type: application/json) or SSZ (Content-Type: application/octet-stream)", that seems completely unnecessary to point out, the spec already documents that and the content: section is enough

content:
application/json:

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.

wait we should also allow ssz

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
Expand All @@ -108,6 +118,18 @@ 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: |
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 Down Expand Up @@ -139,7 +161,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 or an invalid slot. An
individual malformed `BuilderEntry` is ignored, not a 400.
content:
application/json:
schema:
Expand All @@ -151,6 +175,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
Loading
Loading