Skip to content

Commit a434c7d

Browse files
committed
Migrate the validator client to the Gloas builder API (Gloas builder API 5/5)
Final PR of the Gloas builder API stack: - sign builder request-auth and preferences (REQUEST_AUTH signing domain, web3signer message type) - add the builder configuration store (`builder_definitions.yml`) and book documentation - add the builder-preferences service and switch block production to `POST` produceBlockV4 with a `BuilderConfig` body, threading the `Eth-Builder-Url` header through block publication - remove the now-unused legacy `GET` produceBlockV4 client methods Change-Id: Iaeeaf6205a024e4fb9fd11aae6ac6a75978a8320
1 parent 81335ac commit a434c7d

25 files changed

Lines changed: 1186 additions & 214 deletions

File tree

Cargo.lock

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ members = [
7878
"testing/web3signer_tests",
7979
"validator_client",
8080
"validator_client/beacon_node_fallback",
81+
"validator_client/builder_store",
8182
"validator_client/doppelganger_service",
8283
"validator_client/graffiti_file",
8384
"validator_client/http_api",
@@ -120,6 +121,7 @@ bincode = "1"
120121
bitvec = "1"
121122
bls = { path = "crypto/bls" }
122123
builder_client = { path = "beacon_node/builder_client" }
124+
builder_store = { path = "validator_client/builder_store" }
123125
builder_types = { path = "common/builder_types" }
124126
byteorder = "1"
125127
bytes = "1.11.1"

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
* [Redundancy](./advanced_redundancy.md)
5050
* [Release Candidates](./advanced_release_candidates.md)
5151
* [MEV](./advanced_builders.md)
52+
* [Gloas Builder Configuration](./gloas_builder_config.md)
5253
* [Late Block Re-orgs](./advanced_re-orgs.md)
5354
* [Blobs](./advanced_blobs.md)
5455
* [Command Line Reference (CLI)](./help_general.md)

book/src/gloas_builder_config.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Builder Configuration
2+
3+
> This applies from the **Gloas** fork onwards. It configures how the validator client sources
4+
> execution-payload bids from external builders under ePBS.
5+
6+
The validator client reads its external-builder settings from a YAML file named
7+
`builder_definitions.yml` in the validator directory
8+
(`<datadir>/validators/builder_definitions.yml`). The file holds two things:
9+
10+
- **A global bid policy**`min_bid` and `builder_boost_factor`, applied to bids received over p2p
11+
(gossip) and used as the default for any builder that does not set its own.
12+
- **A list of builders** to request bids from directly, each with optional per-builder overrides of
13+
the global policy.
14+
15+
## Example
16+
17+
```yaml
18+
# Global bid policy: applies to p2p (gossip) bids, and is the default for any
19+
# builder below that omits the corresponding field.
20+
min_bid: 0 # gwei — bids below this rank last; one wins only if nothing else is viable
21+
builder_boost_factor: 100 # percent — 100 = neutral, >100 favors builders, 0 = prefer local
22+
23+
builders:
24+
# Minimal builder — inherits the global policy.
25+
- enabled: true
26+
url: "https://builder-a.example.com"
27+
max_execution_payment: 1000000000 # gwei — cap on the trusted execution payment
28+
29+
# Builder overriding the globals and pinning the expected builder key.
30+
- enabled: true
31+
url: "https://builder-b.example.com"
32+
max_execution_payment: 1000000000
33+
min_bid: 500000000 # override the global for this builder
34+
builder_boost_factor: 120 # override the global for this builder
35+
builder_pubkeys: # optional — reject a bid not signed by one of these keys
36+
- "0xa1b2c3d4..."
37+
# auth_data: "0x68747470..." # optional — defaults to the UTF-8 bytes of `url`
38+
```
39+
40+
> **Comments are not preserved.** The validator client rewrites this file when builders are added or
41+
> removed (for example via the keymanager API), which strips YAML comments. Keep an annotated copy
42+
> elsewhere if you rely on inline notes.
43+
44+
## Fields
45+
46+
### Top level (global bid policy)
47+
48+
| Field | Required | Default | Meaning |
49+
| ------- | ---------- | --------- | --------- |
50+
| `min_bid` | no | `0` | Minimum total payment, in gwei, for a p2p bid. A bid below the floor is ranked behind any floor-clearing candidate (including the local block) and only wins when nothing else is viable. Also the default `min_bid` for any builder that omits it. |
51+
| `builder_boost_factor` | no | `100` | Percentage multiplier applied to p2p bids when comparing against the local block. Also the default for any builder that omits it. |
52+
| `builders` | no | `[]` | The list of builders to request bids from directly. |
53+
54+
### Per builder (each entry under `builders`)
55+
56+
| Field | Required | Default | Meaning |
57+
| ------- | ---------- | --------- | --------- |
58+
| `enabled` | **yes** || Whether this builder is used. Disabled builders are ignored. |
59+
| `url` | **yes** || The builder's `http`/`https` URL. Bids are requested from here at block-production time. |
60+
| `max_execution_payment` | **yes** || Cap, in gwei, on the *trusted* execution payment accepted from this builder. |
61+
| `min_bid` | no | *(global)* | Override the global minimum bid for this builder. |
62+
| `builder_boost_factor` | no | *(global)* | Override the global boost factor for this builder. |
63+
| `builder_pubkeys` | no | *(empty)* | The builder's BLS public keys, hex-encoded. If non-empty, a returned bid **not** signed by one of them is rejected. |
64+
| `auth_data` | no | *(UTF-8 of `url`)* | Opaque authentication data, hex-encoded, agreed with the builder out of band. Signed into the request. Must be non-empty when set. Defaults to the UTF-8 bytes of `url`. |
65+
66+
All byte fields (`builder_pubkeys` entries, `auth_data`) are `0x`-prefixed hex strings. All payment values
67+
(`min_bid`, `max_execution_payment`) are in gwei.
68+
69+
## How bids are selected
70+
71+
At block-production time the validator client requests a bid from each enabled builder with a `url`,
72+
and also considers bids seen over p2p. For each candidate bid:
73+
74+
- **`min_bid`** — a bid whose total value is below the applicable `min_bid` is ranked behind any
75+
floor-clearing candidate (including the local block) rather than dropped, so it wins only when
76+
nothing else is viable (e.g. the local build failed). Direct builders use their own (or the
77+
inherited global) value; p2p bids use the global value.
78+
- **`builder_boost_factor`** — the surviving bid's value is scaled by its boost factor
79+
(`boost × value ÷ 100`) before being compared against the locally-built block. A factor below
80+
`100` favors the local block; above `100` favors the builder; `0` always prefers local;
81+
`2^64 − 1` strongly favors the builder. The factor is a multiplier, not an absolute override, so a
82+
zero-value bid still ranks `0` and loses to any non-zero local block.
83+
- **`max_execution_payment`** — bounds how much of a builder's (off-chain) execution payment counts
84+
toward its bid value. This applies only to direct builders; p2p bids carry no trusted execution
85+
payment.
86+
- **`builder_pubkeys`** — for a direct builder, if non-empty, the returned bid must be signed by
87+
one of these keys or it is discarded.
88+
89+
The highest-value bid after these rules wins. Per-builder `min_bid`/`builder_boost_factor` apply
90+
only to bids requested directly by URL; p2p bids are governed by the global values.

common/eth2/src/lib.rs

Lines changed: 0 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -3048,188 +3048,6 @@ impl BeaconNodeHttpClient {
30483048
opt_response.ok_or(Error::StatusCode(StatusCode::NOT_FOUND))
30493049
}
30503050

3051-
// The legacy `GET v4/validator/blocks/{slot}` client methods below are kept alongside the new
3052-
// POST variants until the validator client migrates to POST (later in this PR stack), at which
3053-
// point they are removed.
3054-
3055-
/// `GET v4/validator/blocks/{slot}`
3056-
pub async fn get_validator_blocks_v4<E: EthSpec>(
3057-
&self,
3058-
slot: Slot,
3059-
randao_reveal: &SignatureBytes,
3060-
graffiti: Option<&Graffiti>,
3061-
include_payload: bool,
3062-
builder_booster_factor: Option<u64>,
3063-
graffiti_policy: Option<GraffitiPolicy>,
3064-
) -> Result<(ProduceBlockV4Response<E>, ProduceBlockV4Metadata), Error> {
3065-
self.get_validator_blocks_v4_modular(
3066-
slot,
3067-
randao_reveal,
3068-
graffiti,
3069-
SkipRandaoVerification::No,
3070-
include_payload,
3071-
builder_booster_factor,
3072-
graffiti_policy,
3073-
)
3074-
.await
3075-
}
3076-
3077-
/// `GET v4/validator/blocks/{slot}`
3078-
///
3079-
/// Returns either a bare block or the full [`BlockAndEnvelope`] (block + execution payload
3080-
/// envelope + blobs + KZG proofs) depending on the `Eth-Execution-Payload-Included` response
3081-
/// header. Note that a builder bid yields a bare block even when `include_payload=true`.
3082-
#[allow(clippy::too_many_arguments)]
3083-
pub async fn get_validator_blocks_v4_modular<E: EthSpec>(
3084-
&self,
3085-
slot: Slot,
3086-
randao_reveal: &SignatureBytes,
3087-
graffiti: Option<&Graffiti>,
3088-
skip_randao_verification: SkipRandaoVerification,
3089-
include_payload: bool,
3090-
builder_booster_factor: Option<u64>,
3091-
graffiti_policy: Option<GraffitiPolicy>,
3092-
) -> Result<(ProduceBlockV4Response<E>, ProduceBlockV4Metadata), Error> {
3093-
let mut path = self
3094-
.post_validator_blocks_v4_path(
3095-
slot,
3096-
randao_reveal,
3097-
graffiti,
3098-
skip_randao_verification,
3099-
include_payload,
3100-
graffiti_policy,
3101-
)
3102-
.await?;
3103-
3104-
if let Some(builder_booster_factor) = builder_booster_factor {
3105-
path.query_pairs_mut()
3106-
.append_pair("builder_boost_factor", &builder_booster_factor.to_string());
3107-
}
3108-
3109-
let opt_result = self
3110-
.get_response_with_response_headers(
3111-
path,
3112-
Accept::Json,
3113-
self.timeouts.get_validator_block,
3114-
|response, headers| async move {
3115-
let metadata = ProduceBlockV4Metadata::try_from(&headers)
3116-
.map_err(Error::InvalidHeaders)?;
3117-
let block_response = if metadata.execution_payload_included {
3118-
ProduceBlockV4Response::BlockAndEnvelope(
3119-
response
3120-
.json::<ForkVersionedResponse<
3121-
BlockAndEnvelope<E>,
3122-
ProduceBlockV4Metadata,
3123-
>>()
3124-
.await?
3125-
.data,
3126-
)
3127-
} else {
3128-
ProduceBlockV4Response::BlockOnly(
3129-
response
3130-
.json::<ForkVersionedResponse<
3131-
BeaconBlock<E>,
3132-
ProduceBlockV4Metadata,
3133-
>>()
3134-
.await?
3135-
.data,
3136-
)
3137-
};
3138-
Ok((block_response, metadata))
3139-
},
3140-
)
3141-
.await?;
3142-
3143-
opt_result.ok_or(Error::StatusCode(StatusCode::NOT_FOUND))
3144-
}
3145-
3146-
/// `GET v4/validator/blocks/{slot}` in ssz format
3147-
pub async fn get_validator_blocks_v4_ssz<E: EthSpec>(
3148-
&self,
3149-
slot: Slot,
3150-
randao_reveal: &SignatureBytes,
3151-
graffiti: Option<&Graffiti>,
3152-
include_payload: bool,
3153-
builder_booster_factor: Option<u64>,
3154-
graffiti_policy: Option<GraffitiPolicy>,
3155-
) -> Result<(ProduceBlockV4Response<E>, ProduceBlockV4Metadata), Error> {
3156-
self.get_validator_blocks_v4_modular_ssz::<E>(
3157-
slot,
3158-
randao_reveal,
3159-
graffiti,
3160-
SkipRandaoVerification::No,
3161-
include_payload,
3162-
builder_booster_factor,
3163-
graffiti_policy,
3164-
)
3165-
.await
3166-
}
3167-
3168-
/// `GET v4/validator/blocks/{slot}` in ssz format
3169-
///
3170-
/// See [`Self::get_validator_blocks_v4_modular`] for the response semantics.
3171-
#[allow(clippy::too_many_arguments)]
3172-
pub async fn get_validator_blocks_v4_modular_ssz<E: EthSpec>(
3173-
&self,
3174-
slot: Slot,
3175-
randao_reveal: &SignatureBytes,
3176-
graffiti: Option<&Graffiti>,
3177-
skip_randao_verification: SkipRandaoVerification,
3178-
include_payload: bool,
3179-
builder_booster_factor: Option<u64>,
3180-
graffiti_policy: Option<GraffitiPolicy>,
3181-
) -> Result<(ProduceBlockV4Response<E>, ProduceBlockV4Metadata), Error> {
3182-
let mut path = self
3183-
.post_validator_blocks_v4_path(
3184-
slot,
3185-
randao_reveal,
3186-
graffiti,
3187-
skip_randao_verification,
3188-
include_payload,
3189-
graffiti_policy,
3190-
)
3191-
.await?;
3192-
3193-
if let Some(builder_booster_factor) = builder_booster_factor {
3194-
path.query_pairs_mut()
3195-
.append_pair("builder_boost_factor", &builder_booster_factor.to_string());
3196-
}
3197-
3198-
let opt_response = self
3199-
.get_response_with_response_headers(
3200-
path,
3201-
Accept::Ssz,
3202-
self.timeouts.get_validator_block,
3203-
|response, headers| async move {
3204-
let metadata = ProduceBlockV4Metadata::try_from(&headers)
3205-
.map_err(Error::InvalidHeaders)?;
3206-
let response_bytes = response.bytes().await?;
3207-
let block_response = if metadata.execution_payload_included {
3208-
ProduceBlockV4Response::BlockAndEnvelope(
3209-
BlockAndEnvelope::from_ssz_bytes_for_fork(
3210-
&response_bytes,
3211-
metadata.consensus_version,
3212-
)
3213-
.map_err(Error::InvalidSsz)?,
3214-
)
3215-
} else {
3216-
ProduceBlockV4Response::BlockOnly(
3217-
BeaconBlock::from_ssz_bytes_for_fork(
3218-
&response_bytes,
3219-
metadata.consensus_version,
3220-
)
3221-
.map_err(Error::InvalidSsz)?,
3222-
)
3223-
};
3224-
3225-
Ok((block_response, metadata))
3226-
},
3227-
)
3228-
.await?;
3229-
3230-
opt_response.ok_or(Error::StatusCode(StatusCode::NOT_FOUND))
3231-
}
3232-
32333051
/// `GET v1/validator/execution_payload_envelopes/{slot}/{beacon_block_root}`
32343052
pub async fn get_validator_execution_payload_envelopes<E: EthSpec>(
32353053
&self,

0 commit comments

Comments
 (0)