Skip to content

add per-key builders endpoints - #88

Open
JasonVranek wants to merge 6 commits into
ethereum:masterfrom
Commit-Boost:validator-builders
Open

add per-key builders endpoints#88
JasonVranek wants to merge 6 commits into
ethereum:masterfrom
Commit-Boost:validator-builders

Conversation

@JasonVranek

Copy link
Copy Markdown

Adds GET/POST/DELETE on /eth/v1/validator/{pubkey}/builders, letting an operator configure which external builders a validator key sources bids from, and with what per-builder limits.

An alternative to #87, which managed one atomic per-key document at /eth/v1/validator/config and deprecated the fee recipient, gas limit and graffiti endpoints. This branch is cut from master, so those three are untouched, and each concern keeps its own endpoint.

Adds GET/POST/DELETE on /eth/v1/validator/{pubkey}/builders, letting an
operator configure which external builders a validator key sources bids
from, and with what per-builder limits.

An alternative to ethereum#87, which managed one atomic per-key document at
/eth/v1/validator/config and deprecated the fee recipient, gas limit and
graffiti endpoints. This branch is cut from master, so those three are
untouched, and each concern keeps its own endpoint.

POST replaces the key's list and requires at least one entry; DELETE
removes the configuration so the key follows the validator client's own,
exactly as a key that was never configured does. Entries are identified
by their (url, auth_data) pair, unique per key.

The entry describes what each value means to the operator. The bid
selection rule itself lives in beacon-APIs, since only the beacon node
can implement it.

@eth2353 eth2353 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.

Thanks for creating this alternate PR Jason, I strongly prefer this approach over #87 .

Comment thread types/builder_entry.yaml Outdated
builder_pubkey:
description: |
The builder's BLS public key, distinct from the validator `pubkey` in the request path. When
set, it binds this builder's trusted execution payments to its on-chain key. _48-bytes, hex

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 binds this builder's trusted execution payments to its on-chain key

I honestly have no idea what this "binding" means, what it is for, or when a Keymanager API client would want to submit this. What does the validator client do with this value? Can you make that clearer or point me somewhere where this is explained? Thanks!

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 came from Terrence's implementation. The idea was the signed auth lets a proposer authenticate to a builder. But a MITM (or even a relay) could return arbitrary bids. The builder_pubkey was another optional layer for the BN to validate that a bid came from a specific builder (and reject otherwise). Note this really only makes sense in the context of receiving a trusted bid.

I'm personally not too sure how much this would be used / strongly opinionated on keeping it, but the functionality makes sense to me.

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 took a look at the linked implementation, I think I understand now. So this is to protect against e.g. a relay returning a bid with an execution payment from a builder that the validator doesn't trust to perform that payment.

That only seems needed when using a combination of relay/proxy/sidecar + execution payments. (other forms of MITM don't seem relevant with https?).

It makes sense to me even though I share your concern of it not being widely used. The alternative to this would be to only allow execution payments for direct builder connections but I don't know how you'd enforce that in practice.

Comment thread types/builder_entry.yaml
Comment on lines +30 to +35
The keymanager treats this value as opaque bytes: it only ever compares `auth_data` values
for equality, and never parses or inspects them. When no value has been agreed with the
builder out of band, the caller SHOULD set `auth_data` to the UTF-8 bytes of the builder's
own advertised URL, exactly as advertised, hex encoded: it is deterministic, distinct per
builder, and requires no coordination between the signers of a distributed validator. That
is purely a convention about what tooling writes into the field.

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.

Wouldn't it be simpler/better to just treat this auth_data field as optional? I assume custom auth data will not be set in the vast majority of cases.

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.

The thinking was if the key manager API required fully resolved fields, it simplified everything downstream since the request auth could be required by beacon api -> builder api and remove the need to reason about the cases where it's absent. If everyone used the default auth data they could just supply auth_data = builder_url as part of the keymanager call.

some motivation was talking with @james-prysm here where it was hard to reason about how to resolve configs when VCs have different defaults + keymanager fields were all optional

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 auth_data need to be required in the config though? we could still make it required in the call to the beacon node and default it to url if it's not explicitly set

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.

If it isn't in the config, how could a node operator set their auth_data if they aren't using the default? I assumed it would have to be by manually inserting into the config which is what this PR helps make programmatic

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.

operators can still set the auth_data if they wanna override the default (based on the url), but why does it need to be a required field to be configured by the user?

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.

It definitely doesn't need to be required. I'm happy to make it and all of the fields that can as optional. It was just simpler if the key manager API supplied the VC with fully resolved values derived from the caller's config, so we don't have to reason about how missing fields in the key manager request body have to be resolved differently across all the different VC implementations

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 see your point but most users are just fine with the default that validator clients provide and each client will have their own defaults, eg. we currently don't force users to provide a gas limit in their config, and most don't and just go with whatever the client sets as default. so the vc can easily fill missing fields with default and supply all fields to the beacon node via api. but when it comes to configuring the validator client from a user perspective, I would prefer if users only have to configure the values they are interested in instead of having to provide the full set of options

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.

From your comment here, if we use builder_pubkey + the absence of a url as the way to tell the BN "use these config values when evaluating p2p bids from this builder" then I think it makes sense to switch all of these keymanager fields to optional, since you need to show the absence of something.

I'm pretty in favor of this. Not too sure who will want to set per-key, per-builder p2p bid settings but it is elegant that we can support both p2p bids / builder api bids symmetrically. (will build towards this if it sounds good)

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.

if we use builder_pubkey + the absence of a url as the way to tell the BN "use these config values when evaluating p2p bids from this builder"

if we want the mutually exclusive it could even be a config field like builder_id, which could be a url, or a pubkey, or even a builder index. but it's probably better to have separate fields, also when transmitting this to the beacon node, it would be good if we can support ssz encoding, and union or optional isn't widely supported and not part of standard spec, but for missing pubkey we can just set zero bytes for example

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.

Agreed separate fields over builder_id since url and pubkey are complementary rather than strictly alternatives. In Terence's PoC the url is where you send and the pubkey is what you check came back, and a union can only carry one of those.

What I'm thinking is optional fields in keymanager and VC has to fully resolve before sending to BN. The one thing is we'll still need per-key min_bid and builder_boost_factor that apply to p2p bids from builders with no entry. From Luca's comment:

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.

So thinking to also add a way for the keymanager api to express the per-key p2p defaults.

Comment thread types/builder_entry.yaml Outdated
Comment thread types/builder_entry.yaml Outdated
Comment on lines +6 to +7
so hex case does not distinguish entries. Several entries MAY share a `url`, each with different
`auth_data`, and one request is made per entry.

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.

Several entries MAY share a url, each with different auth_data, and one request is made per entry.

Why is that? Why is a builder not identified only by their URL? What is the use case for fetching bids from the same builder URL multiple times with differing auth data?

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.

In the case where you connect to a relay/proxy/sidecar/etc fronting multiple builders, the URL is fixed but the auth_data may differ. This came up after removing the original PR's proxy field

Co-authored-by: Luca | Serenita <70237279+eth2353@users.noreply.github.com>
Comment thread apis/validator_builders.yaml Outdated
minItems: 1
maxItems: 64 # MAX_BUILDER_ENTRIES
items:
$ref: "../keymanager-oapi.yaml#/components/schemas/BuilderEntry"

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 this allows to configure per-builder, but how would I configure a min_bid or builder_boost_factor that applies to all builders (even from p2p) for this validator? this relates to the comment here ethereum/beacon-APIs#630 (comment), I feel like we add this really complex per-builder config, but at the same time we lose the simple config

Comment thread types/builder_entry.yaml
selects a bid; see [produceBlockV4](https://ethereum.github.io/beacon-APIs/#/Validator/produceBlockV4) for the selection rule.
required: [url, auth_data, max_execution_payment, min_bid, builder_boost_factor]
properties:
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.

this configuration seems to be fully tailored towards builders connected via builder-api, based on the original intend from ethereum/beacon-APIs#620, this doesn't seem to match up, I left a related comment here ethereum/beacon-APIs#625 (comment)

we also lose functionality we currently have, eg. the builder_boost_factor query param as defined in the beacon-api (on master branch) allows to specify how local payload vs. bids (from api or p2p) should be selected, but with this change, we have 0 ways to configure any bid selection strategy from p2p

maybe @potuz, @terence, or @james-prysm can clarify this, I thought this follows the prysm implementation but it doesn't at all match what potuz documented in ethereum/beacon-APIs#620 which I do prefer since it at least supported the full spectrum of builders, and not just the builder-api

Comment thread types/builder_entry.yaml Outdated
Every field except `builder_pubkey` is required: an entry carries every preference that applies
to this builder, and none are inherited. The beacon node applies these preferences when it
selects a bid; see [produceBlockV4](https://ethereum.github.io/beacon-APIs/#/Validator/produceBlockV4) for the selection rule.
required: [url, auth_data, max_execution_payment, min_bid, builder_boost_factor]

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 anything besides url required here?

Comment thread keymanager-oapi.yaml
$ref: './apis/local_keystores.yaml'
/eth/v1/remotekeys:
$ref: './apis/remote_keystores.yaml'
/eth/v1/validator/{pubkey}/builders:

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.

maybe if we do allow a default config per pubkey that applies to all builders we should rename this to builder_config since builders would be a field of the request body @eth2353 suggested that naming earlier here #87 (comment)

Defaults: default_min_bid and default_builder_boost_factor on
BuilderConfig. An entry that omits min_bid or builder_boost_factor takes
the key's default, so one value covers every builder without being
repeated per entry, and the same values apply to bids from builders no
entry identifies. Resolution order is stated as entry value, then the
key's default, then the validator client's own configuration. Without
the middle step an operator who set a default and omitted the field on
entries would silently get the validator client's value instead.

GET returns the effective configuration with omitted values resolved,
matching getGasLimit and getGraffiti
Comment thread apis/validator_builders.yaml Outdated
Comment on lines +73 to +74
Each entry MUST contain at least one of `url` and `builder_pubkey`. No two entries may share the
same `url`, `auth_data` and `builder_pubkey`, and no `builder_pubkey` may appear twice. The

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.

no builder_pubkey may appear twice

What if a builder submits bids to 2+ different relays?

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.

good catch, the rule would need to be:

# a request is identified by where it goes, not by who we expect back
if entry.url:
    assert (entry.url, entry.auth_data) not in seen_requests

# a p2p policy is identified by the builder it names
if not entry.url:
    assert entry.builder_pubkey not in seen_p2p_policies

So, you can support 2+ different relays bc if the url is supplied, the only check is unique url/auth pairs and you can reuse the builder_pubkey.

If url is missing, the entry is explicitly for p2p config and that's the case you need to block duplicates

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.

addressed by 52ce114 and 23b5555

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.

This is getting so complex, it feels like we should write up some reference test cases for implementers 😅

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'll share a hack md implementation guide to try to cover all these confusing edge cases tomorrow

Comment thread types/builder_entry.yaml Outdated
Comment on lines +99 to +103
builder_pubkey:
description: |
The builder's BLS public key, distinct from the validator `pubkey` in the request path. 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. _48-bytes, hex encoded with 0x prefix, case insensitive._

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.

from #88 (comment)

In Terence's PoC the url is where you send and the pubkey is what you check came back, and a union can only carry one of those.

why is that, the relay or builder will return a SignedExecutionPayloadBid so you can always verify it's signature, the CL should apply the same validations as on gossip on those bids with a few exceptions.

is there another scenario where a separate builder_pubkey should be used rather than the builder_index inside the bid itself?

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.

It's not about checking the bid is self-consistent but verifying that this bid came from the exact builder the user expected.

# check bid is correctly signed at all
assert validate_signature(signed_bid)

# check bid was signed by expected
assert signed_bid.pubkey == builder_pubkey

And I think the main use case is a url fronting multiple builders and you accept a trusted payment. You might not equally trust all the builder indices behind the url. So this is one extra check to filter out bids from unexpected builders.

As to using pubkey vs index I'm not opinionated but I think the reasoning was pubkey is more static

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.

ok but for that can the pubkey just be part of the URL as it is today? might be easier than having to set it explicitly, I really just wanna have users set --builder.urls in the simple setup case which will be sufficient for most users

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.

think this makes it difficult for the p2p bid config case since you would omit 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.

but for p2p builders you set the pubkey explicitly, but if operators/relays want this to be connected to the url then why does it need to be a separate parameter instead of using the current format which has the pubkey in the url?

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.

Been thinking about this and think the change makes configs a little simpler but the wire more complicated.

first, for the users you're talking about who just want to set --builder.urls, I'd argue that either builder_pubkey as a field or embedded in userinfo mev-boost style isn't something this category of users actually want to set. adding the builder_pubkey is a provenance check only needed when you are receiving trusted bids over builder api which I assume the set and forget users won't be optimizing for. what follows is if you restrict yourself to something like https://B@relay.host you deny yourself trustless bids from other pubkeys behind that host / must update your config if B ever rotates keys. so if the user doesn't care about trusted payments, they can just set --builder.urls https://relay.host and not be losing anything / be able to set and forget.

now regarding the change. the advantage is definitely that the builder_pubkey field explicitly becomes the toggle for p2p bid preferences and the "provenance check" happens only if you supply the target builder's pubkey in your url's userinfo. you can reason about what's happening right away by looking at the config entry.

the con is that the VC will still have to send an optional builder_pubkey (zero'd out in SSZ) and now an optional pubkey in the userinfo. that's two places a builder key can live, and the userinfo copy is just bytes in the url, so it sidesteps the schema validation the field already gives us + BN has to validate by hand

james-prysm added a commit to OffchainLabs/prysm that referenced this pull request Jul 27, 2026
Mirror of the #630 beacon-APIs change. `builder_pubkey` filters the
response
on an entry with a `url` and names the p2p builder on one without, so
the POST 400 rules and the identity paragraph split in two: entries with
a url are unique by (url, auth_data), entries without one by
builder_pubkey. This unblocks a builder bidding into several relays,
which the old "builder_pubkey may not appear twice" rule rejected.
Comment thread apis/validator_builders.yaml Outdated
Comment on lines +37 to +38
default_min_bid: "10000000"
default_builder_boost_factor: "100"

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 do we need to prefix that with default_? that seems obvious from the hierachy

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

agreed with nico we can remove default_

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.

chagned in e2213e6

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants