Skip to content

Add a POST produceBlockV4#625

Open
potuz wants to merge 1 commit into
ethereum:masterfrom
potuz:builder_preferences
Open

Add a POST produceBlockV4#625
potuz wants to merge 1 commit into
ethereum:masterfrom
potuz:builder_preferences

Conversation

@potuz

@potuz potuz commented Jul 8, 2026

Copy link
Copy Markdown

This PR adds a POST version of produceBlockV4 in which the validator sends a list of BuilderPreferences objects. These object specify a per-builder list of preferences that helps the beacon node chose the right payload bid contained in the block to be produced. This solves issue #620.

@potuz
potuz force-pushed the builder_preferences branch from c28b8b8 to ca2cbed Compare July 8, 2026 16:04
Comment thread apis/validator/block.v4.yaml Outdated
This PR adds a POST version of produceBlockV4 in which the validator
sends a list of `BuilderPreferences` objects. These object specify a
per-builder list of preferences that helps the beacon node chose the
right payload bid contained in the block to be produced. This solves
issue ethereum#620.
@nflaig nflaig added the Gloas api's needed in Gloas fork. label Jul 8, 2026
@potuz
potuz force-pushed the builder_preferences branch from ca2cbed to fea32e4 Compare July 8, 2026 18:12
@eth2353

eth2353 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

I was unable to make ACDC earlier today but here is some feedback from my different points of view.


Vero validator client maintainer:

All per-key configurability in Vero is handled through the Keymanager API which is standardized across all CL/VC clients. This is the only way Vero allows node operators to specify certain things, like different fee recipient per validator key. I hate the idea of adding support for some kind of YAML file (that hasn't even been standardized) instead of doing this in a more standard way using the Keymanager API. We should seriously stop with these CL-client-specific YAML files, we have the standard Keymanager API for a reason.

Setting these various per-key per-builder preferences via the Keymanager API presents some challenges but I'd still prefer solving those over the YAML files.

Switching CL/VC clients should be easy and different YAML configurations make that unnecessarily hard, whereas the Keymanager API makes it trivial.


Staking-as-a-service provider / node operator:

We don't expect to use the advanced functionality described in #620 and I also expect 99% of other node operators not to.

In practice, I expect the vast majority of node operators will be running a much simpler configuration similar to one of these:

A) trustless P2P bids only (no CLI flags, this could be the default clients ship with)
B) trustless P2P bids + specified builder URLs (--builder-urls=http://... , similar to how relays URLs are provided today)
C) specified builder URLs only - I expect entities that censor transactions may want to use this kind of config (--builder-urls=... + perhaps something like --disable-p2p-bids ?). I don't think they'd be using a blocklist of builders for this purpose since it could easily be circumvented by non-censoring builders.

Will these 3 configurations actually cover 99% of the node operators? I don't know. But if they do, we should really avoid overcomplicating things.


I think we should consider making the 1% of super-advanced node operators that MAY want to run a ton of different builder configurations on different subsets of their keys to simply run a corresponding number of validator client instances with different configurations. If they need more than a few, that might require a bit of automation, but so would populating a YAML file, so I don't think that makes things significantly more difficult for them. Whereas supporting these per-key per-builder configuration settings does add a considerable amount of maintenance burden/tech debt across all CL/VC codebases.

Hope this makes sense, ePBS itself is honestly quite complex on its own already so I may have overlooked something.

@JasonVranek

Copy link
Copy Markdown

Agree with @eth2353 on the Keymanager API point, and tried to implement here ethereum/keymanager-APIs/pull/87 with those specs, per-key block-production preferences (fee recipient, gas limit, graffiti, builder list and constraints) as one atomic document per key. The schema follows the BuilderEntry shape from OffchainLabs/prysm#17124 which I think captures the real use cases well (e.g. the optional proxy endpoint, with the builder URL staying the signed identity), and adopts the preference semantics from this PR, including builder_boost_factor and its reserved values.

I understand this PR is for VC to BN but since the Keymanager API sits upstream (user/tool to VC) it needs to carry a superset of what the beacon api will use. So standardizing both makes sense + agreed we dont want to have to support N client-specific YAML dialects.

ofc the big open question is which knobs are truly per-builder, and which can be per-key only? e.g., max_execution_payment makes sense as per-builder but probably min_bid is more suitable as per-key. In the Keymanager PR I just defaulted to maximal flexibility for now.

@shane-moore

Copy link
Copy Markdown
Contributor

Cross-repository sync note from the SSV side: I raised a RequestAuthV1 construction and signing issue in builder-specs #165. Since this PR copies that type, could we make the following corresponding changes here?

I think builder-specs should remain normative for construction and signing. This PR only needs to preserve the resulting signed object across the Validator API boundary:

  1. In types/gloas/request_auth.yaml, specify that data contains the canonical ASCII bytes of the builder identity URL. These are the ByteList contents used by the signed SSZ object and are represented as hex in JSON. Also specify that slot is the proposal slot the authorization applies to, not the slot when it is signed or sent.
  2. In types/builder_preferences.yaml, require the beacon node to forward signed_request_auth without changing its decoded message or signature. In particular, it must not canonicalize or reconstruct message.data.

@potuz

potuz commented Jul 15, 2026

Copy link
Copy Markdown
Author

I think builder-specs should remain normative for construction and signing. This PR only needs to preserve the resulting signed object across the Validator API boundary:

Agree, will update (if this PR is the one adopted) when the builder API is stabilized, but I think the Auth data message should not be specified to be the URL but rather arbitrary data that can be negotiated off-band with the builder. Some builders would want to see here an URL, some others something more generic... there is no need to specify this data to be anything in particular.

@JasonVranek

Copy link
Copy Markdown

[...] I think the Auth data message should not be specified to be the URL but rather arbitrary data that can be negotiated off-band with the builder. Some builders would want to see here an URL, some others something more generic... there is no need to specify this data to be anything in particular.

Agreed with @shane-moore the canonicalizing should happen as the first step, not after a RequestAuthV1 was made. Also agree with @potuz that opaque auth data bytes is more expressive / future proof. An alt proposal is to drop all notions of "canonicalization" and just use auth data bytes as-is. 

Cross referencing to the ethereum/keymanager-APIs/pull/87 PR, a builder entry could look like this, which is unambiguous for VC/BN/ DVT setups etc.

{
  "url": "https://builder-a.example.com",
  "auth_data": "0x123123123....", // new
  "pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
  "proxy": "http://side-car:9001",
  "max_execution_payment": "250000000",
  "min_bid": "10000000",
  "builder_boost_factor": "100"
}

The wrinkle is that before, a proxy could extract the builder url from the auth data to route. Since the SignedRequestAuth wouldn't be required to carry this anymore, either:

  1. the type could include the destination builder url (ugly)
  2. submitBuilderPreferences and getExecutionPayloadBid callers add an optional Eth-Builder-Url​ header. Note since auth_data is bilaterally agreed upon, an unsigned routing header is safe since a mis-routed request fails bc of the wrong auth_data.

wdyt about 2?

@shane-moore

Copy link
Copy Markdown
Contributor

From the SSV/DVT side, option 2 works. The same builder-provided auth_data for a validator-builder relationship would need to be configured across all operators so they construct the same RequestAuthV1 signing root.

The builder URL and proxy destination can remain unsigned routing metadata. If Eth-Builder-Url misroutes the request, the receiving builder should reject it because the exact signed auth_data does not match what it negotiated. So I do not think SSV requires the URL to be included in RequestAuthV1.

@JasonVranek

Copy link
Copy Markdown

Both PRs updated following this convention:

@michaelsproul

michaelsproul commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

A) trustless P2P bids only (no CLI flags, this could be the default clients ship with)
B) trustless P2P bids + specified builder URLs (--builder-urls=http://... , similar to how relays URLs are provided today)
C) specified builder URLs only - I expect entities that censor transactions may want to use this kind of config (--builder-urls=... + perhaps something like --disable-p2p-bids ?). I don't think they'd be using a blocklist of builders for this purpose since it could easily be circumvented by non-censoring builders.

Will these 3 configurations actually cover 99% of the node operators? I don't know. But if they do, we should really avoid overcomplicating things.

Don't we need a POST for use-cases B and C? We need to include the SignedRequestAuthV1, even if no other builder preferences are configured.

This thread on Eth R&D: https://discord.com/channels/595666850260713488/874767108809031740/1522112101714362448

@iurii-ssv

iurii-ssv commented Jul 16, 2026

Copy link
Copy Markdown

Following up on @shane-moore's earlier note from the SSV side — we reviewed the convention this thread converged on, now implemented in builder-specs #165 (opaque pre-agreed data signed exactly as serialized, unsigned Eth-Builder-Url routing, byte-preserving forwarding): LGTM, implementable by SSV without protocol headaches — and of the two models discussed, we prefer this one over deriving data from a canonicalized builder URL.

The reason for that preference: reconstructing a threshold signature requires byte-identical messages from every operator either way, so URL canonicalization only helps if every validator implementation and every builder normalize URLs bit-identically — a silent cross-implementation failure mode. Opaque bytes signed as-is turn it into plain config discipline: distribute the exact per-builder data bytes to all operators, done.

With data a pure function of static config and slot the proposal slot, all operators in an SSV cluster can independently construct the identical RequestAuthV1 at duty-discovery time (thanks to proposer lookahead), threshold-sign it ahead of the slot, and attach the reconstructed signature at proposal time. For us this is one new signing duty, same shape as validator registrations today but without the timestamp-determinism workarounds.

Putting on record the properties this relies on, so future revisions keep them:

  1. RequestAuthV1 contains no per-signer or time-of-signing fields (no pubkey, timestamp, or nonce) — N operators must be able to sign the identical message independently.
  2. Signing and verification operate on the message exactly as serialized, under the pinned DOMAIN_REQUEST_AUTH; no hop (VC, BN, proxy, builder) transforms message.data.
  3. The beacon node forwards signed_request_auth byte-for-byte unchanged — 👍 to mirroring the forwarding MUST from builder-specs Clarify until_epoch is exclusive #165 in types/builder_preferences.yaml when this PR syncs, as discussed above.
  4. The empty-url default entry ("one config applied to all validators") remains a fully supported mode — SSV clusters have no per-validator channel for builder preferences, so cluster-level defaults are how we'd use this API.

One suggestion: it would help to RECOMMEND a default value for data for the case where nothing was agreed out of band — the simplest being the builder's URL exactly as advertised. In practice most proposers will just copy builder URLs from a public list without ever talking to the builder (like relay lists today), and they need to know what bytes to sign. With a spec-suggested default, that plug-and-play case keeps working out of the box, while any builder and proposer that do have a relationship can still agree on whatever custom value they like.

One question: for the ahead-of-time channel (submitBuilderPreferences, which gates max_execution_payment) — is the expectation that VCs call builders directly in the epoch prior to proposing, possibly via the Eth-Builder-Url proxy pattern, or is a BN-mediated endpoint planned? This PR covers the just-in-time attach at produceBlockV4 only. Either works for us; we'd just like to plan the integration.

@chong-he chong-he left a comment

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.

Left a small comment below.

On a side note, this is about replacing the current GET to POST endpoint. Maybe we can rename the title a bit? Something like "replace GET with POST"

@@ -0,0 +1,63 @@
BuilderPreferences:

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.

I wonder should we put this file under the directory types/gloas/builder_preferences.yaml rather than types/builder_preferences.yaml? As BuilderPreferences is a Gloas thing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Gloas api's needed in Gloas fork.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants