From 28d09c1134718b9b3c9a39ff9a6195f1ba1446ce Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Thu, 9 Jul 2026 15:35:25 -0700 Subject: [PATCH 1/6] Deprecate prior and add /validator/config endpoint --- apis/fee_recipient.yaml | 12 ++- apis/gas_limit.yaml | 9 ++ apis/graffiti.yaml | 9 ++ apis/validator_config.yaml | 201 ++++++++++++++++++++++++++++++++++++ keymanager-oapi.yaml | 21 +++- types/validator_config.yaml | 153 +++++++++++++++++++++++++++ wordlist.txt | 3 + 7 files changed, 403 insertions(+), 5 deletions(-) create mode 100644 apis/validator_config.yaml create mode 100644 types/validator_config.yaml diff --git a/apis/fee_recipient.yaml b/apis/fee_recipient.yaml index 30a9bd4..41334dc 100644 --- a/apis/fee_recipient.yaml +++ b/apis/fee_recipient.yaml @@ -1,7 +1,10 @@ get: operationId: listFeeRecipient + deprecated: true summary: List Fee Recipient. description: | + **DEPRECATED**: superseded by the Validator Config endpoint (`/eth/v1/validator/config`), which manages the fee recipient together with the rest of a validator's block-production preferences as one atomic per-key document. This endpoint will be removed in a future release. + List the validator public key to eth address mapping for fee recipient feature on a specific public key. The validator public key will return with the default fee recipient address if a specific one was not found. @@ -41,8 +44,11 @@ get: post: operationId: setFeeRecipient + deprecated: true summary: Set Fee Recipient. description: | + **DEPRECATED**: superseded by the Validator Config endpoint (`/eth/v1/validator/config`), which manages the fee recipient together with the rest of a validator's block-production preferences as one atomic per-key document. This endpoint will be removed in a future release. + Sets the validator client fee recipient mapping which will then update the beacon node. Existing mappings for the same validator public key will be overwritten. Specific Public keys not mapped will continue to use the default address for fee recipient in accordance to the startup of the validator client and beacon node. @@ -85,8 +91,12 @@ post: $ref: "../keymanager-oapi.yaml#/components/responses/InternalError" delete: operationId: deleteFeeRecipient + deprecated: true summary: Delete Configured Fee Recipient - description: Delete a configured fee recipient mapping for the specified public key. + description: | + **DEPRECATED**: superseded by the Validator Config endpoint (`/eth/v1/validator/config`), which manages the fee recipient together with the rest of a validator's block-production preferences as one atomic per-key document. This endpoint will be removed in a future release. + + Delete a configured fee recipient mapping for the specified public key. security: - bearerAuth: [ ] tags: diff --git a/apis/gas_limit.yaml b/apis/gas_limit.yaml index b8bd339..5229ebf 100644 --- a/apis/gas_limit.yaml +++ b/apis/gas_limit.yaml @@ -1,7 +1,10 @@ get: operationId: getGasLimit + deprecated: true summary: Get Gas Limit. description: | + **DEPRECATED**: superseded by the Validator Config endpoint (`/eth/v1/validator/config`), which manages the gas limit together with the rest of a validator's block-production preferences as one atomic per-key document. This endpoint will be removed in a future release. + Get the execution gas limit for an individual validator. This gas limit is the one used by the validator when proposing blocks via an external builder. If no limit has been set explicitly for a key then the process-wide default will be returned. @@ -44,8 +47,11 @@ get: post: operationId: setGasLimit + deprecated: true summary: Set Gas Limit. description: | + **DEPRECATED**: superseded by the Validator Config endpoint (`/eth/v1/validator/config`), which manages the gas limit together with the rest of a validator's block-production preferences as one atomic per-key document. This endpoint will be removed in a future release. + Set the gas limit for an individual validator. This limit will be propagated to the beacon node for use on future block proposals. The beacon node is responsible for informing external block builders of the change. @@ -88,8 +94,11 @@ post: $ref: "../keymanager-oapi.yaml#/components/responses/InternalError" delete: operationId: deleteGasLimit + deprecated: true summary: Delete Configured Gas Limit description: | + **DEPRECATED**: superseded by the Validator Config endpoint (`/eth/v1/validator/config`), which manages the gas limit together with the rest of a validator's block-production preferences as one atomic per-key document. This endpoint will be removed in a future release. + Delete a configured gas limit for the specified public key. The server may return a 400 status code if no external builder is configured. diff --git a/apis/graffiti.yaml b/apis/graffiti.yaml index 3c453c2..3c49283 100644 --- a/apis/graffiti.yaml +++ b/apis/graffiti.yaml @@ -1,7 +1,10 @@ get: operationId: getGraffiti + deprecated: true summary: Get Graffiti description: | + **DEPRECATED**: superseded by the Validator Config endpoint (`/eth/v1/validator/config`), which manages the graffiti together with the rest of a validator's block-production preferences as one atomic per-key document. This endpoint will be removed in a future release. + Get the graffiti for an individual validator. If no graffiti is set explicitly, returns the process-wide default. security: @@ -45,8 +48,11 @@ get: post: operationId: setGraffiti + deprecated: true summary: Set Graffiti description: | + **DEPRECATED**: superseded by the Validator Config endpoint (`/eth/v1/validator/config`), which manages the graffiti together with the rest of a validator's block-production preferences as one atomic per-key document. This endpoint will be removed in a future release. + Set the graffiti for an individual validator. security: - bearerAuth: [] @@ -85,8 +91,11 @@ post: delete: operationId: deleteGraffiti + deprecated: true summary: Delete Configured Graffiti description: | + **DEPRECATED**: superseded by the Validator Config endpoint (`/eth/v1/validator/config`), which manages the graffiti together with the rest of a validator's block-production preferences as one atomic per-key document. This endpoint will be removed in a future release. + Delete the configured graffiti for the specified public key. security: - bearerAuth: [] diff --git a/apis/validator_config.yaml b/apis/validator_config.yaml new file mode 100644 index 0000000..3d07855 --- /dev/null +++ b/apis/validator_config.yaml @@ -0,0 +1,201 @@ +get: + operationId: getValidatorConfig + summary: Get Validator Config. + description: | + Returns the configured block-production preferences of the requested keys (or of all keys known + to the server when `pubkeys` is not given), together with the configured `default_config`. + + The returned per-key documents are the CONFIGURED fragments: fields that were never explicitly + set for a key are absent, not resolved. This lets management tooling distinguish an explicit + per-key value from an inherited default. Clients can compute the effective preferences of a key + locally by resolving absent fields against `default_config` in the normative order defined on + the `ValidatorConfig` schema. + security: + - bearerAuth: [] + tags: + - Validator Config + parameters: + - in: query + name: pubkeys + required: false + description: Restrict the response to the given public keys. Unknown keys are omitted from the response. + style: form + explode: false + schema: + type: array + items: + $ref: "../keymanager-oapi.yaml#/components/schemas/Pubkey" + responses: + "200": + description: Success response + content: + application/json: + schema: + title: GetValidatorConfigResponse + type: object + required: [data] + properties: + data: + type: object + required: [configs] + properties: + default_config: + allOf: + - $ref: "../keymanager-oapi.yaml#/components/schemas/ValidatorConfig" + - description: | + The configured defaults that per-key documents fall back to. Read-only + through this API. Absent when the server has no configured defaults. + configs: + type: object + description: | + Mapping of validator public key (hex encoded with 0x prefix) to its configured + `ValidatorConfig` fragment. Keys with no per-key configuration map to an empty + object. + additionalProperties: + $ref: "../keymanager-oapi.yaml#/components/schemas/ValidatorConfig" + example: + data: + default_config: + fee_recipient: "0x8943545177806ED17B9F23F0a21ee5948eCaa776" + gas_limit: "30000000" + graffiti: "example graffiti" + builder: + enabled: true + builders: + - url: "https://builder-a.example.com" + - url: "https://builder-b.example.com" + max_execution_payment: "1000000000" + configs: + "0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a": + fee_recipient: "0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3" + gas_limit: "45000000" + builder: + enabled: true + builders: + - url: "https://builder-a.example.com" + pubkey: "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a" + proxy: "http://side-car:9001" + max_execution_payment: "250000000" + min_bid: "10000000" + builder_boost_factor: "100" + max_execution_payment: "500000000" + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c": + builder: + enabled: false + "400": + $ref: "../keymanager-oapi.yaml#/components/responses/BadRequest" + "401": + $ref: "../keymanager-oapi.yaml#/components/responses/Unauthorized" + "403": + $ref: "../keymanager-oapi.yaml#/components/responses/Forbidden" + "500": + $ref: "../keymanager-oapi.yaml#/components/responses/InternalError" + +post: + operationId: setValidatorConfig + summary: Set Validator Config. + description: | + Sets the block-production preferences of one or more keys. For each key in `request.configs`, + the submitted document REPLACES the key's entire configured fragment: the server MUST NOT merge + it with previously configured values. A field absent from the submitted document is unset and + inherits from `default_config` per the normative resolution order. Submitting an empty document + (`{}`) therefore clears a key's configuration entirely, returning it to pure default behavior; + there is no separate delete operation. + + Keys NOT present in `request.configs` are left untouched: only submitted keys are affected, so + a large key set may be split across multiple requests. Servers SHOULD accept request bodies + large enough to cover their full key set in one request (thousand-key batches are expected). + + Atomicity is per key, NOT per batch: each key's document takes effect fully or not at all, but + the batch is not transactional across keys. Keys that validate successfully are applied even + when other keys in the same request fail, and each key's outcome is reported in the per-key + response status. + + Servers MUST validate the syntax of every submitted field (types, formats, patterns) and MUST + reject unrecognized fields. An unrecognized or invalid field inside a key's document fails that + key with a per-key `error` status; an unrecognized field in the request envelope fails the + request with a `400` response. In particular, `default_config` cannot be written through this + API. + security: + - bearerAuth: [] + tags: + - Validator Config + requestBody: + required: true + content: + application/json: + schema: + title: SetValidatorConfigRequest + type: object + required: [configs] + additionalProperties: false + properties: + configs: + type: object + description: | + Mapping of validator public key (hex encoded with 0x prefix) to the full + `ValidatorConfig` document to set for that key. + additionalProperties: + $ref: "../keymanager-oapi.yaml#/components/schemas/ValidatorConfig" + example: + configs: + "0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a": + fee_recipient: "0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3" + builder: + enabled: true + builders: + - url: "https://builder-a.example.com" + max_execution_payment: "250000000" + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c": + builder: + enabled: false + "0xb0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7": {} + responses: + "200": + description: Success response + content: + application/json: + schema: + title: SetValidatorConfigResponse + type: object + required: [data] + properties: + data: + type: object + description: Status of each key in `request.configs`, keyed by the same public keys. + additionalProperties: + type: object + required: [status] + properties: + status: + type: string + description: | + - set: the document was validated and applied atomically + - not_found: the key is not known to the server + - error: the document failed validation or could not be applied; no change + was made for this key + enum: + - set + - not_found + - error + example: set + message: + type: string + description: error message if status == error + example: + data: + "0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a": + status: set + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c": + status: error + message: "builder.builders[0].url is not a valid URL" + "0xb0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7": + status: set + "400": + $ref: "../keymanager-oapi.yaml#/components/responses/BadRequest" + "401": + $ref: "../keymanager-oapi.yaml#/components/responses/Unauthorized" + "403": + $ref: "../keymanager-oapi.yaml#/components/responses/Forbidden" + "500": + $ref: "../keymanager-oapi.yaml#/components/responses/InternalError" diff --git a/keymanager-oapi.yaml b/keymanager-oapi.yaml index a5422ab..93d6dde 100644 --- a/keymanager-oapi.yaml +++ b/keymanager-oapi.yaml @@ -18,7 +18,7 @@ info: of at least 256 bits. If such a parameter is not given, the client SHOULD generate such a token and write it to a file, to be reused across multiple restarts of the binary. If such a parameter is given, but the file or token cannot be read, the client SHOULD treat this as an error: either abort the startup, or show the error and continue without exposing the key manager routes. - version: "Dev" + version: "2.0.0-dev" contact: name: Ethereum Github url: https://github.com/ethereum/keymanager-APIs/issues @@ -34,12 +34,17 @@ servers: default: "http://localhost/" tags: + - name: Validator Config + description: | + Management of a validator's full block-production preferences (fee recipient, gas limit, + graffiti, builders) as one atomic per-key document. Supersedes the Fee Recipient, Gas Limit + and Graffiti endpoints. - name: Fee Recipient - description: Set of endpoints for management of fee recipient. + description: Set of endpoints for management of fee recipient. Deprecated, superseded by Validator Config. - name: Gas Limit - description: Set of endpoints for management of gas limits. + description: Set of endpoints for management of gas limits. Deprecated, superseded by Validator Config. - name: Graffiti - description: Set of endpoints for management of graffiti. + description: Set of endpoints for management of graffiti. Deprecated, superseded by Validator Config. - name: Local Key Manager description: Set of endpoints for key management of local keys. - name: Remote Key Manager @@ -52,6 +57,8 @@ paths: $ref: './apis/local_keystores.yaml' /eth/v1/remotekeys: $ref: './apis/remote_keystores.yaml' + /eth/v1/validator/config: + $ref: './apis/validator_config.yaml' /eth/v1/validator/{pubkey}/feerecipient: $ref: './apis/fee_recipient.yaml' /eth/v1/validator/{pubkey}/gas_limit: @@ -83,6 +90,12 @@ components: $ref: './types/graffiti.yaml' Uint64: $ref: './types/uint.yaml#/Uint64' + ValidatorConfig: + $ref: './types/validator_config.yaml#/ValidatorConfig' + BuilderConfig: + $ref: './types/validator_config.yaml#/BuilderConfig' + BuilderEntry: + $ref: './types/validator_config.yaml#/BuilderEntry' SignerDefinition: $ref: './types/signer_definition.yaml' ImportRemoteSignerDefinition: diff --git a/types/validator_config.yaml b/types/validator_config.yaml new file mode 100644 index 0000000..6dd06a8 --- /dev/null +++ b/types/validator_config.yaml @@ -0,0 +1,153 @@ +ValidatorConfig: + type: object + additionalProperties: false + description: | + The full set of block-production preferences for a single validator public key, managed as one + atomic document. Every field is optional: an absent field inherits its value from the + `default_config`, and where the default is also absent, from the client's built-in behavior. + + Field resolution order (normative). For key-level fields (`fee_recipient`, `gas_limit`, + `graffiti`, and the fields of `builder` itself): the key's document, then `default_config`, + then the client default. For per-builder fields (`max_execution_payment`, `min_bid`, + `builder_boost_factor`, `proxy`): the `BuilderEntry`, then the enclosing `BuilderConfig` + fallback of the same name, then the same two levels within `default_config`, then the client + default. + properties: + fee_recipient: + $ref: "../keymanager-oapi.yaml#/components/schemas/EthAddress" + gas_limit: + allOf: + - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" + - description: | + The gas limit the validator targets for its blocks. Communicated to builders and used + for local block production and gas limit voting. + graffiti: + $ref: "../keymanager-oapi.yaml#/components/schemas/Graffiti" + builder: + $ref: "#/BuilderConfig" + +BuilderConfig: + type: object + additionalProperties: false + description: | + Preferences for sourcing blocks from builders. `enabled` toggles the builder path for the key + without discarding the rest of the configuration. When `enabled` is `true` and `builders` is + absent or empty, the key uses the `default_config` builder list. + + The optional fields of this object are per-key fallbacks: a `BuilderEntry` omitting a field of + the same name resolves to the value configured here. + properties: + enabled: + type: boolean + description: | + Whether blocks may be sourced from builders for this key. Like every other field, absent + means inherit: from `default_config.builder.enabled`, then the client default. + example: true + builders: + type: array + description: The builders this key requests bids from. + items: + $ref: "#/BuilderEntry" + max_execution_payment: + allOf: + - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" + - description: | + Fallback maximum trusted execution payment, in Gwei, accepted from builders whose entry + does not set its own. Bids promising a trusted payment above this value are rejected. + example: "1000000000" + min_bid: + allOf: + - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" + - description: | + Fallback minimum bid, in Gwei, accepted from builders whose entry does not set its own. + Bids with a value lower than this amount are rejected. + example: "10000000" + builder_boost_factor: + allOf: + - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" + - description: | + Fallback percentage multiplier applied to the bid value of builders whose entry does not + set its own, when choosing between a builder bid and a payload from the paired execution + node. See `BuilderEntry.builder_boost_factor` for the normative selection rule and the + reserved values (`0`, `100`, `2**64 - 1`). + example: "100" + proxy: + type: string + format: uri + description: | + Fallback proxy for builders whose entry does not set its own. Overrides where builder HTTP + requests are sent; the builder `url` remains the signed identity. See `BuilderEntry.proxy`. + example: "http://side-car:9001" + +BuilderEntry: + type: object + additionalProperties: false + description: | + One builder this key may source blocks from. `url` is the signed identity: request + authentications are signed over it, regardless of where the request is transported. + required: [url] + properties: + url: + type: string + format: uri + description: | + The builder's canonical URL, uniquely identifying it. Request authentications are signed + over this URL, so it MUST be the builder's own endpoint, not an intermediary's. + example: "https://builder.example.com" + pubkey: + allOf: + - $ref: "../keymanager-oapi.yaml#/components/schemas/Pubkey" + - description: | + The builder's BLS public key. When set, binds trusted execution payments to this + builder's on-chain key: bids not signed by it MUST NOT be accepted. _48-bytes, hex + encoded with 0x prefix, case insensitive._ + proxy: + type: string + format: uri + description: | + Overrides where this builder's HTTP requests are sent. The `url` remains the signed identity: + the proxy extracts the intended builder from the signed request authentication and forwards + the request, and cannot re-target it without invalidating the signature. + example: "http://side-car:9001" + max_execution_payment: + allOf: + - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" + - description: | + The maximum trusted execution payment, in Gwei, accepted from this builder. Bids + promising a trusted payment above this value MUST NOT be accepted. The remainder of a + builder's payment is expected in the trustless (in-protocol) form. + example: "250000000" + min_bid: + allOf: + - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" + - description: | + The minimum bid, in Gwei, that the validator will accept from this builder. Bids with a + value lower than this amount MUST NOT be accepted. + example: "10000000" + builder_boost_factor: + allOf: + - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" + - description: | + Percentage multiplier to apply to this builder's bid value when choosing between a + builder bid and payload from the paired execution node. This value is only relevant + if the beacon node has a viable `ExecutionPayloadBid` available from this builder and + receives a valid response from the paired execution node. When these preconditions + are met, the block producer MUST act as follows: + + * if `exec_node_payload_value >= builder_boost_factor * (builder_payload_value // 100)` + for the highest builder bid known (each bid boosted by the factor applicable to the + builder it originates from), then commit to the local execution node payload. + * otherwise, commit to the builder bid. + + The following values encode common preferences and MUST be supported: + + * `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 payload pays + more. + * `builder_boost_factor=2**64 - 1`: prefer this builder's bid unless an error or + health check makes it unviable. + + Implementations should use saturating arithmetic or another technique to ensure that + large values of the `builder_boost_factor` do not trigger overflows or errors. + example: "100" diff --git a/wordlist.txt b/wordlist.txt index 2156f88..e088f3d 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -19,3 +19,6 @@ VPN BLS oapi Vero +Gwei +atomicity +uri From c86e1bbbc8482a29bf0f262cda7082b255213cef Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Thu, 9 Jul 2026 16:41:10 -0700 Subject: [PATCH 2/6] rename gas_limit to target_gas_limit --- apis/validator_config.yaml | 4 ++-- types/validator_config.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apis/validator_config.yaml b/apis/validator_config.yaml index 3d07855..0a7153a 100644 --- a/apis/validator_config.yaml +++ b/apis/validator_config.yaml @@ -57,7 +57,7 @@ get: data: default_config: fee_recipient: "0x8943545177806ED17B9F23F0a21ee5948eCaa776" - gas_limit: "30000000" + target_gas_limit: "30000000" graffiti: "example graffiti" builder: enabled: true @@ -68,7 +68,7 @@ get: configs: "0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a": fee_recipient: "0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3" - gas_limit: "45000000" + target_gas_limit: "45000000" builder: enabled: true builders: diff --git a/types/validator_config.yaml b/types/validator_config.yaml index 6dd06a8..52201e1 100644 --- a/types/validator_config.yaml +++ b/types/validator_config.yaml @@ -6,7 +6,7 @@ ValidatorConfig: atomic document. Every field is optional: an absent field inherits its value from the `default_config`, and where the default is also absent, from the client's built-in behavior. - Field resolution order (normative). For key-level fields (`fee_recipient`, `gas_limit`, + Field resolution order (normative). For key-level fields (`fee_recipient`, `target_gas_limit`, `graffiti`, and the fields of `builder` itself): the key's document, then `default_config`, then the client default. For per-builder fields (`max_execution_payment`, `min_bid`, `builder_boost_factor`, `proxy`): the `BuilderEntry`, then the enclosing `BuilderConfig` @@ -15,7 +15,7 @@ ValidatorConfig: properties: fee_recipient: $ref: "../keymanager-oapi.yaml#/components/schemas/EthAddress" - gas_limit: + target_gas_limit: allOf: - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" - description: | From 9f66e70effaf6225a5006d7bbf7edb10590ffafa Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Wed, 15 Jul 2026 14:56:21 -0700 Subject: [PATCH 3/6] decouple auth_data from the builder url --- apis/validator_config.yaml | 1 + types/validator_config.yaml | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/apis/validator_config.yaml b/apis/validator_config.yaml index 0a7153a..b4cfb5d 100644 --- a/apis/validator_config.yaml +++ b/apis/validator_config.yaml @@ -73,6 +73,7 @@ get: enabled: true builders: - url: "https://builder-a.example.com" + auth_data: "0x1234567890abcdef" pubkey: "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a" proxy: "http://side-car:9001" max_execution_payment: "250000000" diff --git a/types/validator_config.yaml b/types/validator_config.yaml index 52201e1..5c857f8 100644 --- a/types/validator_config.yaml +++ b/types/validator_config.yaml @@ -75,25 +75,32 @@ BuilderConfig: type: string format: uri description: | - Fallback proxy for builders whose entry does not set its own. Overrides where builder HTTP - requests are sent; the builder `url` remains the signed identity. See `BuilderEntry.proxy`. + Fallback proxy for builders whose entry does not set its own. See `BuilderEntry.proxy`. example: "http://side-car:9001" BuilderEntry: type: object additionalProperties: false description: | - One builder this key may source blocks from. `url` is the signed identity: request - authentications are signed over it, regardless of where the request is transported. + One builder this key may source blocks from. required: [url] properties: url: type: string format: uri description: | - The builder's canonical URL, uniquely identifying it. Request authentications are signed - over this URL, so it MUST be the builder's own endpoint, not an intermediary's. + The builder's URL, uniquely identifying it. Requests are sent here unless `proxy` is set. example: "https://builder.example.com" + auth_data: + type: string + pattern: "^0x[a-fA-F0-9]{0,8192}$" + description: | + Opaque data used to authenticate this validator's requests to the builder. Agreed with the + builder out of band and placed verbatim in the `RequestAuthV1.data` the validator signs. The + builder checks the signature and the exact bytes. Its meaning is left to the two parties (for + example, a shared secret). If unset, no authentication is sent to this builder. At most + `MAX_DATA_SIZE` (4096) bytes. + example: "0x1234567890abcdef" pubkey: allOf: - $ref: "../keymanager-oapi.yaml#/components/schemas/Pubkey" @@ -105,9 +112,8 @@ BuilderEntry: type: string format: uri description: | - Overrides where this builder's HTTP requests are sent. The `url` remains the signed identity: - the proxy extracts the intended builder from the signed request authentication and forwards - the request, and cannot re-target it without invalidating the signature. + Optional proxy that routes this builder's requests. When set, requests are sent here instead + of directly to `url`. example: "http://side-car:9001" max_execution_payment: allOf: From 8052f4740afcabb2036154a8e4a76bd9db714fe5 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Mon, 20 Jul 2026 12:07:05 -0700 Subject: [PATCH 4/6] address review feedback: - keep pubkey as the config key (since no index exists for not-yet-active keys) - pre-provisioning: a config may be set for an unmanaged key - accept unrecognized fields to match the rest of the keymanager API - disambiguate per-builder field resolution --- apis/validator_config.yaml | 23 +++++++++++++++-------- types/validator_config.yaml | 24 +++++++++++++++--------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/apis/validator_config.yaml b/apis/validator_config.yaml index b4cfb5d..e4700ed 100644 --- a/apis/validator_config.yaml +++ b/apis/validator_config.yaml @@ -107,15 +107,19 @@ post: a large key set may be split across multiple requests. Servers SHOULD accept request bodies large enough to cover their full key set in one request (thousand-key batches are expected). + A configuration may be set for a key the server does not yet manage. The server SHOULD store it + (pre-provisioning) so it takes effect when the key is later added; a server that does not + pre-provision returns `not_found`. + Atomicity is per key, NOT per batch: each key's document takes effect fully or not at all, but the batch is not transactional across keys. Keys that validate successfully are applied even when other keys in the same request fail, and each key's outcome is reported in the per-key response status. - Servers MUST validate the syntax of every submitted field (types, formats, patterns) and MUST - reject unrecognized fields. An unrecognized or invalid field inside a key's document fails that - key with a per-key `error` status; an unrecognized field in the request envelope fails the - request with a `400` response. In particular, `default_config` cannot be written through this + Servers MUST validate the syntax of every submitted field (types, formats, patterns). An invalid + value for a recognized field inside a key's document fails that key with a per-key `error` status. + Unrecognized fields are ignored for forward compatibility, consistent with the rest of the + keymanager API. In particular, `default_config` is read-only and cannot be written through this API. security: - bearerAuth: [] @@ -129,13 +133,14 @@ post: title: SetValidatorConfigRequest type: object required: [configs] - additionalProperties: false properties: configs: type: object description: | Mapping of validator public key (hex encoded with 0x prefix) to the full - `ValidatorConfig` document to set for that key. + `ValidatorConfig` document to set for that key. Keyed by public key rather than + validator index so configurations can be pre-provisioned for keys that are not yet + active additionalProperties: $ref: "../keymanager-oapi.yaml#/components/schemas/ValidatorConfig" example: @@ -171,8 +176,10 @@ post: status: type: string description: | - - set: the document was validated and applied atomically - - not_found: the key is not known to the server + - set: the document was validated and stored (pre-provisioned if the key is + not yet managed, taking effect when it is added) + - not_found: the server neither manages this key nor supports + pre-provisioning, so nothing was stored - error: the document failed validation or could not be applied; no change was made for this key enum: diff --git a/types/validator_config.yaml b/types/validator_config.yaml index 5c857f8..07b6c9e 100644 --- a/types/validator_config.yaml +++ b/types/validator_config.yaml @@ -1,17 +1,25 @@ ValidatorConfig: type: object - additionalProperties: false description: | The full set of block-production preferences for a single validator public key, managed as one atomic document. Every field is optional: an absent field inherits its value from the `default_config`, and where the default is also absent, from the client's built-in behavior. - Field resolution order (normative). For key-level fields (`fee_recipient`, `target_gas_limit`, - `graffiti`, and the fields of `builder` itself): the key's document, then `default_config`, - then the client default. For per-builder fields (`max_execution_payment`, `min_bid`, - `builder_boost_factor`, `proxy`): the `BuilderEntry`, then the enclosing `BuilderConfig` - fallback of the same name, then the same two levels within `default_config`, then the client - default. + Field resolution order (normative). The validator client resolves each field to its effective + value by walking this order, and management tooling computes the same effective value from the + fragments returned by GET: + + - Key-level fields (`fee_recipient`, `target_gas_limit`, `graffiti`, and the fields of `builder` + itself): key document → `default_config` → client default. + - Per-builder fields (`max_execution_payment`, `min_bid`, `builder_boost_factor`, `proxy`): + `BuilderEntry` → enclosing `BuilderConfig` → `default_config.builder` → client default. + + Builder entries are not matched between a key and `default_config`. + + Example. If `default_config.builder` is `{ enabled: false, builders: [X], max_execution_payment: + 1G }` and a key sets only `builder: { enabled: true }`, the key's effective builder config is + `{ enabled: true, builders: [X], max_execution_payment: 1G }`: only `enabled` is overridden, and + `builders` and `max_execution_payment` fall through to `default_config`. properties: fee_recipient: $ref: "../keymanager-oapi.yaml#/components/schemas/EthAddress" @@ -28,7 +36,6 @@ ValidatorConfig: BuilderConfig: type: object - additionalProperties: false description: | Preferences for sourcing blocks from builders. `enabled` toggles the builder path for the key without discarding the rest of the configuration. When `enabled` is `true` and `builders` is @@ -80,7 +87,6 @@ BuilderConfig: BuilderEntry: type: object - additionalProperties: false description: | One builder this key may source blocks from. required: [url] From 25d3ea99aee20c49321751f1ee26a76e0e99fd8c Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Wed, 22 Jul 2026 13:03:55 -0700 Subject: [PATCH 5/6] require fully resolved builder entries and drop proxy routing - BuilderEntry: all fields required except pubkey (optional verification pin); auth_data is required and opaque, compared but never parsed - entries are identified by their (url, auth_data) pair: url as the exact string, auth_data as its decoded bytes; a duplicate pair rejects the key's entire document, never silently dropped - config documents are all-or-nothing: a key has a complete document or none, {} clears it, and default_config is a read-only view of client defaults; per-field inheritance and per-builder fallbacks are removed - proxy fields removed; multiple entries may share a url, one request per entry - nothing-agreed convention: auth_data SHOULD be the UTF-8 bytes of the builder's advertised URL, hex encoded --- .gitignore | 1 + apis/validator_config.yaml | 100 +++++++++++++++++++++++---------- types/validator_config.yaml | 107 +++++++++++++----------------------- wordlist.txt | 2 + 4 files changed, 111 insertions(+), 99 deletions(-) diff --git a/.gitignore b/.gitignore index dfb6730..ffd91a0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .DS_Store deploy node_modules +dictionary.dic diff --git a/apis/validator_config.yaml b/apis/validator_config.yaml index e4700ed..1459012 100644 --- a/apis/validator_config.yaml +++ b/apis/validator_config.yaml @@ -2,14 +2,14 @@ get: operationId: getValidatorConfig summary: Get Validator Config. description: | - Returns the configured block-production preferences of the requested keys (or of all keys known - to the server when `pubkeys` is not given), together with the configured `default_config`. + Returns the block-production preferences of the requested keys, or of all keys known to the + server when `pubkeys` is not given. - The returned per-key documents are the CONFIGURED fragments: fields that were never explicitly - set for a key are absent, not resolved. This lets management tooling distinguish an explicit - per-key value from an inherited default. Clients can compute the effective preferences of a key - locally by resolving absent fields against `default_config` in the normative order defined on - the `ValidatorConfig` schema. + Each configured key returns its complete document, exactly as it was submitted. A key the + server knows but has no document for returns an empty object; its effective preferences are + the read-only `default_config` included in the response, taken whole (documents are + all-or-nothing, so nothing is merged per field). Keys the server does not know are omitted + from the response entirely. security: - bearerAuth: [] tags: @@ -18,7 +18,9 @@ get: - in: query name: pubkeys required: false - description: Restrict the response to the given public keys. Unknown keys are omitted from the response. + description: | + Restrict the response to the given public keys. Keys the server does not know are omitted + from the response. When not given, the response covers all keys known to the server. style: form explode: false schema: @@ -43,13 +45,15 @@ get: allOf: - $ref: "../keymanager-oapi.yaml#/components/schemas/ValidatorConfig" - description: | - The configured defaults that per-key documents fall back to. Read-only - through this API. Absent when the server has no configured defaults. + The defaults the client applies to keys with no configured document. + Read-only through this API. When present, `default_config` is itself a + complete document. When absent, keys without a document follow the + client's built-in defaults. configs: type: object description: | Mapping of validator public key (hex encoded with 0x prefix) to its configured - `ValidatorConfig` fragment. Keys with no per-key configuration map to an empty + `ValidatorConfig` document. Keys with no per-key configuration map to an empty object. additionalProperties: $ref: "../keymanager-oapi.yaml#/components/schemas/ValidatorConfig" @@ -63,26 +67,41 @@ get: enabled: true builders: - url: "https://builder-a.example.com" + auth_data: "0x68747470733a2f2f6275696c6465722d612e6578616d706c652e636f6d" + max_execution_payment: "0" + min_bid: "0" + builder_boost_factor: "100" - url: "https://builder-b.example.com" - max_execution_payment: "1000000000" + auth_data: "0x68747470733a2f2f6275696c6465722d622e6578616d706c652e636f6d" + max_execution_payment: "0" + min_bid: "0" + builder_boost_factor: "100" configs: "0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a": fee_recipient: "0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3" target_gas_limit: "45000000" + graffiti: "example graffiti" builder: enabled: true builders: - url: "https://builder-a.example.com" auth_data: "0x1234567890abcdef" pubkey: "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a" - proxy: "http://side-car:9001" max_execution_payment: "250000000" min_bid: "10000000" builder_boost_factor: "100" - max_execution_payment: "500000000" + - url: "https://builder-a.example.com" + auth_data: "0xdeadbeef" + max_execution_payment: "0" + min_bid: "0" + builder_boost_factor: "100" "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c": + fee_recipient: "0x8943545177806ED17B9F23F0a21ee5948eCaa776" + target_gas_limit: "30000000" + graffiti: "" builder: enabled: false + "0xb0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7": {} "400": $ref: "../keymanager-oapi.yaml#/components/responses/BadRequest" "401": @@ -96,12 +115,12 @@ post: operationId: setValidatorConfig summary: Set Validator Config. description: | - Sets the block-production preferences of one or more keys. For each key in `request.configs`, - the submitted document REPLACES the key's entire configured fragment: the server MUST NOT merge - it with previously configured values. A field absent from the submitted document is unset and - inherits from `default_config` per the normative resolution order. Submitting an empty document - (`{}`) therefore clears a key's configuration entirely, returning it to pure default behavior; - there is no separate delete operation. + Sets the block-production preferences of one or more keys. Each submitted document replaces + the key's entire configuration; the server MUST NOT merge it with previously stored values. + Submit either a complete document (`fee_recipient`, `target_gas_limit`, `graffiti`, and a + fully resolved `builder`) or an empty one (`{}`), which clears the key so it follows the + client's defaults again (there is no separate delete operation). Anything in between fails + that key with a per-key `error` status. Keys NOT present in `request.configs` are left untouched: only submitted keys are affected, so a large key set may be split across multiple requests. Servers SHOULD accept request bodies @@ -116,8 +135,11 @@ post: when other keys in the same request fail, and each key's outcome is reported in the per-key response status. - Servers MUST validate the syntax of every submitted field (types, formats, patterns). An invalid - value for a recognized field inside a key's document fails that key with a per-key `error` status. + Servers MUST validate the syntax of every submitted field (types, formats, patterns). A + `builders` list containing two entries with the same (`url`, `auth_data`) pair, compared as + the decoded `auth_data` bytes, is invalid; the server MUST NOT silently drop the duplicate. + Any such failure rejects that key's entire document with a per-key `error` status: no part of + it is applied. Unrecognized fields are ignored for forward compatibility, consistent with the rest of the keymanager API. In particular, `default_config` is read-only and cannot be written through this API. @@ -138,23 +160,42 @@ post: type: object description: | Mapping of validator public key (hex encoded with 0x prefix) to the full - `ValidatorConfig` document to set for that key. Keyed by public key rather than - validator index so configurations can be pre-provisioned for keys that are not yet - active + `ValidatorConfig` document to set for that key, or `{}` to clear it. Keyed by + public key rather than validator index so configurations can be pre-provisioned + for keys that are not yet active. additionalProperties: $ref: "../keymanager-oapi.yaml#/components/schemas/ValidatorConfig" example: configs: "0xa057816155ad77931185101128655c0191bd0214c201ca48ed887f6c4c6adf334070efcd75140eada5ac83a92506dd7a": fee_recipient: "0x50155530FCE8a85ec7055A5F8b2bE214B3DaeFd3" + target_gas_limit: "45000000" + graffiti: "example graffiti" builder: enabled: true builders: - url: "https://builder-a.example.com" + auth_data: "0x68747470733a2f2f6275696c6465722d612e6578616d706c652e636f6d" max_execution_payment: "250000000" + min_bid: "10000000" + builder_boost_factor: "100" "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c": + fee_recipient: "0x8943545177806ED17B9F23F0a21ee5948eCaa776" + target_gas_limit: "30000000" + graffiti: "" builder: - enabled: false + enabled: true + builders: + - url: "https://builder-b.example.com" + auth_data: "0xdeadbeef" + max_execution_payment: "0" + min_bid: "0" + builder_boost_factor: "100" + - url: "https://builder-b.example.com" + auth_data: "0xDEADBEEF" + max_execution_payment: "0" + min_bid: "0" + builder_boost_factor: "100" "0xb0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7": {} responses: "200": @@ -176,8 +217,9 @@ post: status: type: string description: | - - set: the document was validated and stored (pre-provisioned if the key is - not yet managed, taking effect when it is added) + - set: the document was validated and applied. A complete document is + stored (pre-provisioned if the key is not yet managed, taking effect + when it is added); `{}` clears the key's configuration. - not_found: the server neither manages this key nor supports pre-provisioning, so nothing was stored - error: the document failed validation or could not be applied; no change @@ -196,7 +238,7 @@ post: status: set "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c": status: error - message: "builder.builders[0].url is not a valid URL" + message: "builder.builders[1] duplicates the (url, auth_data) pair of builder.builders[0]" "0xb0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7": status: set "400": diff --git a/types/validator_config.yaml b/types/validator_config.yaml index 07b6c9e..72c6987 100644 --- a/types/validator_config.yaml +++ b/types/validator_config.yaml @@ -1,25 +1,13 @@ ValidatorConfig: type: object description: | - The full set of block-production preferences for a single validator public key, managed as one - atomic document. Every field is optional: an absent field inherits its value from the - `default_config`, and where the default is also absent, from the client's built-in behavior. - - Field resolution order (normative). The validator client resolves each field to its effective - value by walking this order, and management tooling computes the same effective value from the - fragments returned by GET: - - - Key-level fields (`fee_recipient`, `target_gas_limit`, `graffiti`, and the fields of `builder` - itself): key document → `default_config` → client default. - - Per-builder fields (`max_execution_payment`, `min_bid`, `builder_boost_factor`, `proxy`): - `BuilderEntry` → enclosing `BuilderConfig` → `default_config.builder` → client default. - - Builder entries are not matched between a key and `default_config`. - - Example. If `default_config.builder` is `{ enabled: false, builders: [X], max_execution_payment: - 1G }` and a key sets only `builder: { enabled: true }`, the key's effective builder config is - `{ enabled: true, builders: [X], max_execution_payment: 1G }`: only `enabled` is overridden, and - `builders` and `max_execution_payment` fall through to `default_config`. + All block-production preferences for one validator key, managed as a single document. A + document is all-or-nothing: it contains every field (`fee_recipient`, `target_gas_limit`, + `graffiti`, and `builder`, with each builder entry fully resolved), and the validator client + uses it exactly as submitted. A key without a document follows the client's defaults + (surfaced read-only as `default_config` on GET). Nothing is inherited or merged per field: + for a configured key the defaults play no role, so its document applies in full even where it + differs from them (for example, `builder.enabled: true` while the default is `false`). properties: fee_recipient: $ref: "../keymanager-oapi.yaml#/components/schemas/EthAddress" @@ -37,76 +25,62 @@ ValidatorConfig: BuilderConfig: type: object description: | - Preferences for sourcing blocks from builders. `enabled` toggles the builder path for the key - without discarding the rest of the configuration. When `enabled` is `true` and `builders` is - absent or empty, the key uses the `default_config` builder list. + Preferences for sourcing blocks from builders. `enabled` switches the builder path on or off + for the key; a document may carry a `builders` list while `enabled` is `false`. `enabled` is + required; `builders` may be absent or empty, in which case no builders are solicited. - The optional fields of this object are per-key fallbacks: a `BuilderEntry` omitting a field of - the same name resolves to the value configured here. + Builder entries are submitted fully resolved: this API is called programmatically, and the + caller sets every preference an entry carries on the entry itself. There are no per-builder + fallback fields; every entry field except `pubkey` is required. + required: [enabled] properties: enabled: type: boolean description: | - Whether blocks may be sourced from builders for this key. Like every other field, absent - means inherit: from `default_config.builder.enabled`, then the client default. + Whether blocks may be sourced from builders for this key. example: true builders: type: array - description: The builders this key requests bids from. + description: | + The builders this key requests bids from. + + Entries are identified by their (`url`, `auth_data`) pair: `url` compared as the exact + string, `auth_data` as its decoded bytes (hex case does not distinguish entries). One + list MUST NOT contain two entries with the same `url` and the same `auth_data`. Multiple + entries MAY share a `url`, each with different `auth_data`. Requests are made per entry, + so entries sharing a `url` result in one request to that `url` for each such entry. items: $ref: "#/BuilderEntry" - max_execution_payment: - allOf: - - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" - - description: | - Fallback maximum trusted execution payment, in Gwei, accepted from builders whose entry - does not set its own. Bids promising a trusted payment above this value are rejected. - example: "1000000000" - min_bid: - allOf: - - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" - - description: | - Fallback minimum bid, in Gwei, accepted from builders whose entry does not set its own. - Bids with a value lower than this amount are rejected. - example: "10000000" - builder_boost_factor: - allOf: - - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" - - description: | - Fallback percentage multiplier applied to the bid value of builders whose entry does not - set its own, when choosing between a builder bid and a payload from the paired execution - node. See `BuilderEntry.builder_boost_factor` for the normative selection rule and the - reserved values (`0`, `100`, `2**64 - 1`). - example: "100" - proxy: - type: string - format: uri - description: | - Fallback proxy for builders whose entry does not set its own. See `BuilderEntry.proxy`. - example: "http://side-car:9001" BuilderEntry: type: object description: | One builder this key may source blocks from. - required: [url] + required: [url, auth_data, max_execution_payment, min_bid, builder_boost_factor] properties: url: type: string format: uri description: | - The builder's URL, uniquely identifying it. Requests are sent here unless `proxy` is set. + The URL this entry's requests are sent to. Multiple entries MAY share a `url`; entries are + identified by their (`url`, `auth_data`) pair (see `builders`). example: "https://builder.example.com" auth_data: type: string - pattern: "^0x[a-fA-F0-9]{0,8192}$" + pattern: "^0x(?:[a-fA-F0-9]{2}){0,4096}$" description: | Opaque data used to authenticate this validator's requests to the builder. Agreed with the builder out of band and placed verbatim in the `RequestAuthV1.data` the validator signs. The - builder checks the signature and the exact bytes. Its meaning is left to the two parties (for - example, a shared secret). If unset, no authentication is sent to this builder. At most - `MAX_DATA_SIZE` (4096) bytes. - example: "0x1234567890abcdef" + builder checks the signature and the exact bytes. Its meaning is left to the two parties + (for example, a shared secret). At most `MAX_DATA_SIZE` (4096) bytes. + + 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. + example: "0x68747470733a2f2f6275696c6465722e6578616d706c652e636f6d" pubkey: allOf: - $ref: "../keymanager-oapi.yaml#/components/schemas/Pubkey" @@ -114,13 +88,6 @@ BuilderEntry: The builder's BLS public key. When set, binds trusted execution payments to this builder's on-chain key: bids not signed by it MUST NOT be accepted. _48-bytes, hex encoded with 0x prefix, case insensitive._ - proxy: - type: string - format: uri - description: | - Optional proxy that routes this builder's requests. When set, requests are sent here instead - of directly to `url`. - example: "http://side-car:9001" max_execution_payment: allOf: - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" diff --git a/wordlist.txt b/wordlist.txt index e088f3d..1c591b3 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -22,3 +22,5 @@ Vero Gwei atomicity uri +RequestAuthV +UTF From f4315e3a8e6f1f7aa9fa80b53e3d505330168c72 Mon Sep 17 00:00:00 2001 From: Jason Vranek Date: Wed, 22 Jul 2026 17:19:51 -0700 Subject: [PATCH 6/6] clarify bid evaluation and fix the builder pubkey description - min_bid floors the bid's total payment (value plus execution_payment); builder_payload_value is defined as that total and builder_boost_factor applies only after the max_execution_payment and min_bid checks, matching beacon-APIs wording - bump the builder pubkey description above the allOf so renderers show it instead of the generic validator pubkey text --- types/validator_config.yaml | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/types/validator_config.yaml b/types/validator_config.yaml index 72c6987..1b43caf 100644 --- a/types/validator_config.yaml +++ b/types/validator_config.yaml @@ -82,12 +82,12 @@ BuilderEntry: is purely a convention about what tooling writes into the field. example: "0x68747470733a2f2f6275696c6465722e6578616d706c652e636f6d" pubkey: + description: | + The builder's BLS public key. When set, binds trusted execution payments to this + builder's on-chain key: bids not signed by it MUST NOT be accepted. _48-bytes, hex + encoded with 0x prefix, case insensitive._ allOf: - $ref: "../keymanager-oapi.yaml#/components/schemas/Pubkey" - - description: | - The builder's BLS public key. When set, binds trusted execution payments to this - builder's on-chain key: bids not signed by it MUST NOT be accepted. _48-bytes, hex - encoded with 0x prefix, case insensitive._ max_execution_payment: allOf: - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" @@ -100,18 +100,22 @@ BuilderEntry: allOf: - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" - description: | - The minimum bid, in Gwei, that the validator will accept from this builder. Bids with a - value lower than this amount MUST NOT be accepted. + The minimum total payment, in Gwei, that the validator will accept from this builder. + A bid whose `value` plus `execution_payment` is below this amount MUST NOT be + accepted. example: "10000000" builder_boost_factor: allOf: - $ref: "../keymanager-oapi.yaml#/components/schemas/Uint64" - description: | - Percentage multiplier to apply to this builder's bid value when choosing between a - builder bid and payload from the paired execution node. This value is only relevant - if the beacon node has a viable `ExecutionPayloadBid` available from this builder and - receives a valid response from the paired execution node. When these preconditions - are met, the block producer MUST act as follows: + Percentage multiplier to apply to this builder's bid when choosing between a builder + bid and payload from the paired execution node. `builder_payload_value` is the bid's + total payment to the proposer, its `value` plus its `execution_payment`, and the + factor applies only to bids that passed the `max_execution_payment` and `min_bid` + checks. This value is only relevant if the beacon node has a viable + `ExecutionPayloadBid` available from this builder and receives a valid response from + the paired execution node. When these preconditions are met, the block producer MUST + act as follows: * if `exec_node_payload_value >= builder_boost_factor * (builder_payload_value // 100)` for the highest builder bid known (each bid boosted by the factor applicable to the