diff --git a/apis/builder/beacon_blocks.yaml b/apis/builder/beacon_blocks.yaml index 6bbaede..d4b2baf 100644 --- a/apis/builder/beacon_blocks.yaml +++ b/apis/builder/beacon_blocks.yaml @@ -9,8 +9,9 @@ post: must return an error response (400) with a description of the validation failure. - After receiving 202, the proposer takes no further action on the block; the - builder is responsible for publishing the execution payload envelope. + After the builder returns 202, the proposer takes no further action on the + block; the builder is responsible for publishing the execution payload + envelope. This API is applicable from Gloas fork onwards. tags: diff --git a/apis/builder/builder_preferences.yaml b/apis/builder/builder_preferences.yaml index 2d603bb..98bd29a 100644 --- a/apis/builder/builder_preferences.yaml +++ b/apis/builder/builder_preferences.yaml @@ -2,19 +2,25 @@ post: operationId: "submitBuilderPreferences" summary: Submit builder preferences for a proposer. description: | - Submits a proposer's `BuilderPreferencesRequestV1` to the builder, including + Submits a proposer's `BuilderPreferencesRequest` to the builder, including the `max_execution_payment` that the proposer is willing to accept from this - builder, authenticated via a `SignedRequestAuthV1`. + builder, authenticated via a `SignedRequestAuth`. Validators MAY call this endpoint in the epoch prior to the epoch in which they will be proposing, as determined from `state.proposer_lookahead`, so that builders have the preferences before the bid request arrives. - The builder MUST verify the BLS signature in `auth` against - `validator_pubkey`, and check that `auth.message.data` - matches its own URL. If the signature check fails, the builder MUST - return a 401 response. If the URL or slot check fails, the builder MUST - return a 400 response. + The body MAY be encoded as JSON (`Content-Type: application/json`) or SSZ + (`Content-Type: application/octet-stream`); `BuilderPreferencesRequest` + is fork-versioned, so the `Eth-Consensus-Version` header is required. + + The builder MUST verify the BLS signature in `auth` against the + `proposer_pubkey` path parameter, and check that `auth.message.data` matches + the value it agreed with the proposer. `auth.message.slot` is the proposal slot the + preferences apply to; the builder MUST reject preferences whose slot has + already passed. If the signature check fails, the builder MUST return a 401 + response. If the data or slot check fails, the builder MUST return a 400 + response. A success response (202) indicates that the preferences were accepted. If the preferences are invalid, then the builder MUST return an error response @@ -24,31 +30,31 @@ post: tags: - Builder parameters: - - name: validator_pubkey + - name: proposer_pubkey in: path required: true - description: "The BLS public key of the validator expressing these preferences." + description: BLS public key of the proposer. schema: $ref: "../../beacon-apis/types/primitive.yaml#/Pubkey" - - name: Eth-Consensus-Version - in: header - required: true - description: "The active consensus version to which the request body belongs. Required if the request body is SSZ encoded." + - in: header schema: $ref: "../../builder-oapi.yaml#/components/schemas/ConsensusVersion" + required: true + name: Eth-Consensus-Version + description: "The active consensus version to which the submitted preferences belong." requestBody: - description: A `BuilderPreferencesRequestV1` containing the proposer's preferences and a `SignedRequestAuthV1` for authentication. + description: A `BuilderPreferencesRequest` containing the proposer's preferences and a `SignedRequestAuth` for authentication. required: true content: application/json: schema: - $ref: "../../types/gloas/builder_preferences.yaml#/Gloas/BuilderPreferencesRequestV1" + $ref: "../../types/gloas/builder_preferences.yaml#/Gloas/BuilderPreferencesRequest" examples: - BuilderPreferencesRequestV1: - $ref: "../../builder-oapi.yaml#/components/examples/Gloas.BuilderPreferencesRequestV1" + BuilderPreferencesRequest: + $ref: "../../builder-oapi.yaml#/components/examples/Gloas.BuilderPreferencesRequest" application/octet-stream: schema: - description: "SSZ serialized `BuilderPreferencesRequestV1` bytes. Use content type header to indicate that SSZ data is contained in the request body." + description: "SSZ serialized `BuilderPreferencesRequest` bytes. Use content type header to indicate that SSZ data is contained in the request body." responses: "202": description: Success response. @@ -62,25 +68,27 @@ post: SlotMismatch: value: code: 400 - message: "Invalid SignedRequestAuthV1: auth.message.slot does not match the requested slot" - WrongBuilder: + message: "Invalid SignedRequestAuth: auth.message.slot has already passed" + DataMismatch: value: code: 400 - message: "auth.message.data does not match this builder's URL" + message: "Invalid SignedRequestAuth: auth.message.data does not match the value agreed with this builder" InvalidPreferences: value: code: 400 message: "Invalid builder preferences: max_execution_payment malformed" "401": - description: Authentication required. + description: Authentication failed. content: application/json: schema: $ref: "../../builder-oapi.yaml#/components/schemas/ErrorMessage" examples: - SignatureVerificationFailed: + InvalidAuth: value: code: 401 - message: "Invalid SignedRequestAuthV1: signature verification failed" + message: "Invalid SignedRequestAuth: signature verification failed" + "415": + $ref: "../../builder-oapi.yaml#/components/responses/UnsupportedMediaType" "500": $ref: "../../builder-oapi.yaml#/components/responses/InternalError" diff --git a/apis/builder/execution_payload_bid.yaml b/apis/builder/execution_payload_bid.yaml index 6aa12cf..214a849 100644 --- a/apis/builder/execution_payload_bid.yaml +++ b/apis/builder/execution_payload_bid.yaml @@ -5,32 +5,35 @@ post: Requests a builder node to produce a valid execution payload bid, which can be integrated into a beacon block and signed. - The proposer sends a POST request to the builder with the following information: + The proposer's beacon node sends a POST request to the builder with the following information: - The slot for which the block should be proposed. - The hash of the execution layer block the proposer will build on. - The root of the beacon block the proposer will build on. - - The index of the proposer. - - Optionally, a `SignedRequestAuthV1` in the request body that - authenticates the request. The body MAY be encoded as JSON or SSZ. + - The public key of the proposer. + - A `SignedRequestAuth` in the request body that authenticates the + request. The body MAY be encoded as JSON or SSZ. + - A `Date-Milliseconds` header with the send time and an `X-Timeout-Ms` + header with the proposer's timeout for the request. The proposer's `max_execution_payment` is communicated exclusively via the - `submitBuilderPreferences` endpoint. If no `BuilderPreferencesV1` have been - submitted for the proposer, the builder MUST treat `max_execution_payment` as `0` - and MUST NOT include an execution layer payment in the bid. + `submitBuilderPreferences` endpoint. Any bid the builder serves MUST honor + the `max_execution_payment` cap from stored preferences. Without stored + preferences the builder MAY serve a bid with any `execution_payment`; the + proposer's locally configured per-builder limits are the backstop: the + proposer discards any bid that exceeds them. - The `SignedRequestAuthV1` body is optional. If it is present but malformed - or fails signature verification, the builder MAY return a 401 response. - If it is absent, the builder MAY still serve a bid, but builders MAY - use the presence and validity of the `SignedRequestAuthV1` to apply - per-validator policy (e.g. rate-limiting, prioritization, or refusing - unauthenticated requests). + The `SignedRequestAuth` body is required. If the body is missing or + malformed, the request is invalid and the builder MUST return a 400 + response. If its `auth.message.data` or `auth.message.slot` does not match, + the builder MUST return a 400 response; if its signature fails to verify, + the builder MUST return a 401 response. Builders MAY use the authenticated + identity to apply per-validator policy (e.g. rate-limiting). The builder responds with a 200 response containing an execution payload bid if it can provide one. - If the builder is unable to produce a valid execution payload bid, then - the builder MUST return a 204 response. If the request is invalid, then the - builder MUST return an error response (400) with a description of the - validation failure. + A builder that does not serve a bid MUST return a 204 response. If the + request is invalid, then the builder MUST return an error response (400) + with a description of the validation failure. This API is applicable from Gloas fork onwards. tags: @@ -62,48 +65,48 @@ post: $ref: "../../beacon-apis/types/primitive.yaml#/Pubkey" - name: Date-Milliseconds in: header - required: false + required: true description: | - Optional header containing a Unix timestamp in milliseconds representing - the point-in-time the request was sent. This header can be used to measure - latency. + Unix timestamp in milliseconds at which the request was sent. The + builder compares it with its own receive time to estimate the transit + delay from proposer to builder. schema: type: integer format: int64 example: 1710338135000 - name: X-Timeout-Ms in: header - required: false + required: true description: | - Optional header containing the proposer's timeout for the request in milliseconds. + The proposer's timeout for the request in milliseconds, measured from + `Date-Milliseconds`: the builder MUST respond by `Date-Milliseconds` + plus `X-Timeout-Ms`, and the proposer discards later responses. schema: type: integer format: int64 example: 10000 - - name: Eth-Consensus-Version - in: header - required: true - description: | - The active consensus version to which the `SignedRequestAuthV1` in the - request body belongs. Required if the request body is SSZ encoded. + - in: header schema: $ref: "../../builder-oapi.yaml#/components/schemas/ConsensusVersion" + required: true + name: Eth-Consensus-Version + description: "The active consensus version to which the request body belongs." requestBody: description: | - Optional `SignedRequestAuthV1` authenticating the request. If provided, - the builder MAY verify the BLS signature against the validator pubkey - resolved from the `proposer_pubkey` path parameter, and check that - `data` matches its own URL and that `slot` matches the - requested slot. If absent, the builder MAY still serve a bid subject - to its own policy. - required: false + A `SignedRequestAuth` authenticating the request. The builder MUST + verify the BLS signature against the validator pubkey resolved from the + `proposer_pubkey` path parameter, and MUST check that `auth.message.data` + matches the value it agreed with the proposer and that + `auth.message.slot` matches the `slot` path parameter (the proposal + slot). + required: true content: application/json: schema: - $ref: "../../types/gloas/request_auth.yaml#/Gloas/SignedRequestAuthV1" + $ref: "../../types/gloas/request_auth.yaml#/Gloas/SignedRequestAuth" application/octet-stream: schema: - description: "SSZ serialized `SignedRequestAuthV1` bytes. Use Content-Type header to indicate that SSZ data is contained in the request body." + description: "SSZ serialized `SignedRequestAuth` bytes. Use Content-Type header to indicate that SSZ data is contained in the request body." responses: "200": description: Success response. @@ -143,26 +146,30 @@ post: SlotMismatch: value: code: 400 - message: "Invalid SignedRequestAuthV1: auth.message.slot does not match the requested slot" - WrongBuilder: + message: "Invalid SignedRequestAuth: auth.message.slot does not match the proposal slot in the request path" + DataMismatch: value: code: 400 - message: "Invalid SignedRequestAuthV1: auth.message.data does not match this builder's URL" + message: "Invalid SignedRequestAuth: auth.message.data does not match the value agreed with this builder" + MissingBody: + value: + code: 400 + message: "Invalid request: SignedRequestAuth body is required" + MissingHeader: + value: + code: 400 + message: "Invalid request: Date-Milliseconds and X-Timeout-Ms headers are required" "401": - description: Authentication required. + description: Authentication failed. content: application/json: schema: $ref: "../../builder-oapi.yaml#/components/schemas/ErrorMessage" examples: - MissingAuth: - value: - code: 401 - message: "Missing SignedRequestAuthV1: this builder requires authenticated requests" InvalidAuth: value: code: 401 - message: "Invalid SignedRequestAuthV1: signature verification failed" + message: "Invalid SignedRequestAuth: signature verification failed" "406": $ref: "../../builder-oapi.yaml#/components/responses/NotAcceptable" "415": diff --git a/builder-oapi.yaml b/builder-oapi.yaml index 3e25135..62232e6 100644 --- a/builder-oapi.yaml +++ b/builder-oapi.yaml @@ -61,7 +61,7 @@ paths: $ref: "./apis/builder/blinded_blocks.yaml" /eth/v2/builder/blinded_blocks: $ref: "./apis/builder/blinded_blocks_v2.yaml" - /eth/v1/builder/builder_preferences/{validator_pubkey}: + /eth/v1/builder/builder_preferences/{proposer_pubkey}: $ref: "./apis/builder/builder_preferences.yaml" /eth/v1/builder/status: $ref: "./apis/builder/status.yaml" @@ -108,14 +108,14 @@ components: $ref: "./types/fulu/blobs_bundle.yaml#/Fulu/BlobsBundle" Fulu.ExecutionPayloadAndBlobsBundle: $ref: "./types/fulu/execution_payload_and_blobs_bundle.yaml#/Fulu/ExecutionPayloadAndBlobsBundle" - Gloas.RequestAuthV1: - $ref: "./types/gloas/request_auth.yaml#/Gloas/RequestAuthV1" - Gloas.SignedRequestAuthV1: - $ref: "./types/gloas/request_auth.yaml#/Gloas/SignedRequestAuthV1" - Gloas.BuilderPreferencesV1: - $ref: "./types/gloas/builder_preferences.yaml#/Gloas/BuilderPreferencesV1" - Gloas.BuilderPreferencesRequestV1: - $ref: "./types/gloas/builder_preferences.yaml#/Gloas/BuilderPreferencesRequestV1" + Gloas.RequestAuth: + $ref: "./types/gloas/request_auth.yaml#/Gloas/RequestAuth" + Gloas.SignedRequestAuth: + $ref: "./types/gloas/request_auth.yaml#/Gloas/SignedRequestAuth" + Gloas.BuilderPreferences: + $ref: "./types/gloas/builder_preferences.yaml#/Gloas/BuilderPreferences" + Gloas.BuilderPreferencesRequest: + $ref: "./types/gloas/builder_preferences.yaml#/Gloas/BuilderPreferencesRequest" responses: InternalError: @@ -165,9 +165,9 @@ components: $ref: "./examples/fulu/signed_blinded_beacon_block.json" Fulu.SignedBuilderBid: $ref: "./examples/fulu/signed_builder_bid.json" - Gloas.SignedRequestAuthV1: + Gloas.SignedRequestAuth: $ref: "./examples/gloas/signed_request_auth.json" - Gloas.BuilderPreferencesV1: + Gloas.BuilderPreferences: $ref: "./examples/gloas/builder_preferences.json" - Gloas.BuilderPreferencesRequestV1: + Gloas.BuilderPreferencesRequest: $ref: "./examples/gloas/builder_preferences_request.json" \ No newline at end of file diff --git a/examples/gloas/builder_preferences_request.json b/examples/gloas/builder_preferences_request.json index 4fd016b..fb12b59 100644 --- a/examples/gloas/builder_preferences_request.json +++ b/examples/gloas/builder_preferences_request.json @@ -5,7 +5,7 @@ }, "auth": { "message": { - "data": "0x68747470733a2f2f6275696c6465722e6578616d706c652e636f6d", + "data": "0x1234567890abcdef", "slot": "1" }, "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" diff --git a/examples/gloas/signed_request_auth.json b/examples/gloas/signed_request_auth.json index 5eab95d..bdadfc0 100644 --- a/examples/gloas/signed_request_auth.json +++ b/examples/gloas/signed_request_auth.json @@ -1,7 +1,7 @@ { "value": { "message": { - "data": "0x68747470733a2f2f6275696c6465722e6578616d706c652e636f6d", + "data": "0x1234567890abcdef", "slot": "1" }, "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" diff --git a/specs/gloas/builder.md b/specs/gloas/builder.md index 21a5caf..1b39e75 100644 --- a/specs/gloas/builder.md +++ b/specs/gloas/builder.md @@ -11,6 +11,7 @@ - [Per-request Validator Inputs](#per-request-validator-inputs) - [Proposer Preferences (Deprecation of Validator Registrations)](#proposer-preferences-deprecation-of-validator-registrations) - [Constructing a `SignedExecutionPayloadBid`](#constructing-a-signedexecutionpayloadbid) + - [Signing](#signing) - [Constructing a `SignedExecutionPayloadEnvelope`](#constructing-a-signedexecutionpayloadenvelope) @@ -94,24 +95,29 @@ Validators MAY communicate their per-builder preferences ahead of the bid request by calling the [`submitBuilderPreferences`][submit-builder-preferences-api] API in the epoch prior to the epoch in which they will be proposing, as determined from -`state.proposer_lookahead`. The builder receives a `BuilderPreferencesRequestV1` object +`state.proposer_lookahead`. The proposer is identified by the `proposer_pubkey` +path parameter, and the builder receives a `BuilderPreferencesRequest` object containing: -- `validator_pubkey`: The BLS public key of the validator submitting these - preferences, passed as a path parameter. -- `preferences`: A `BuilderPreferencesV1` with: +- `preferences`: A `BuilderPreferences` with: - `max_execution_payment`: The maximum execution layer payment the proposer will accept from this builder (in Gwei). -- `auth`: A `SignedRequestAuthV1` authenticating the request. The builder MUST - check that `auth.message.data` matches its own URL and MUST verify the - BLS signature against the `validator_pubkey` path parameter. If either check - fails, the builder MUST return a 400 response. - -The builder SHOULD store the preferences for each proposer and apply the -`max_execution_payment` constraint when constructing bids. If no preferences -have been submitted for a proposer, the builder MUST treat the proposer's -`max_execution_payment` as `0`. The builder can also choose to not serve the -bid. +- `auth`: A `SignedRequestAuth` authenticating the request. `auth.message.slot` + is the proposal slot the preferences apply to. The builder MUST verify the BLS + signature against the `proposer_pubkey` path parameter and MUST check that + `auth.message.data` matches the value it agreed with the proposer, so that an + unauthenticated or replayed request cannot skew a proposer's preferences away + from the value the proposer chose. If the signature fails to verify, the + builder MUST return a 401 response; if the `auth.message.data` check fails, + the builder MUST return a 400 response. The builder MUST also reject, with a + 400 response, preferences whose `auth.message.slot` has already passed, so + that a replayed request cannot roll preferences back to a stale value. + +The builder SHOULD store the preferences per proposer per `auth.message.slot` +and MUST honor the `max_execution_payment` cap in any bid it serves for that +slot. Without preferences stored for the requested slot it MAY serve a bid with +any `execution_payment`. The proposer's locally configured per-builder limits are +the backstop: the proposer discards any bid that exceeds them. ### `max_execution_payment` @@ -129,33 +135,37 @@ reputation. Validators communicate per-request inputs to a builder on each [`getExecutionPayloadBid`][get-execution-payload-bid-api] call: -- Optionally, a [`SignedRequestAuthV1`][signed-request-auth] in the request body - used to authenticate the requesting validator. The body MAY be encoded as JSON - (`Content-Type: application/json`) or SSZ - (`Content-Type: application/octet-stream`); when SSZ is used, the - `Eth-Consensus-Version` header MUST also be set. +- A [`SignedRequestAuth`][signed-request-auth] in the request body + authenticating the requesting validator. The body is required and MAY be + encoded as JSON (`Content-Type: application/json`) or SSZ + (`Content-Type: application/octet-stream`); `RequestAuth` is fork-versioned, + so the `Eth-Consensus-Version` header is required. +- A required `Date-Milliseconds` header with the Unix timestamp in milliseconds + at which the request was sent, and a required `X-Timeout-Ms` header with the + proposer's timeout for the request, measured from `Date-Milliseconds`. The + timestamp lets the builder estimate the transit delay from proposer to + builder; the builder MUST respond by `Date-Milliseconds` plus `X-Timeout-Ms`, + and the proposer discards later responses. + +The `Eth-Consensus-Version` header is required on every request that carries a +body, naming the fork of the body's type. The builder MUST return a 400 response +when the header is absent or names a fork it does not recognize. The proposer's `max_execution_payment` is communicated exclusively via the -[`submitBuilderPreferences`][submit-builder-preferences-api] endpoint. If no -`BuilderPreferencesV1` have been submitted for the proposer, the builder MUST -treat `max_execution_payment` as `0` or can choose to not serve the bid. - -If the request body is present, builders MAY verify the `SignedRequestAuthV1` -signature against the `proposer_pubkey` path parameter, and check that -`data` matches their own URL and that `slot` matches the requested slot. -If verification fails, the builder MAY return a 401 response. - -```python -def verify_request_auth_signature( - signed_request_auth: SignedRequestAuthV1, - pubkey: BLSPubkey, -) -> bool: - domain = compute_domain(DOMAIN_REQUEST_AUTH) - signing_root = compute_signing_root(signed_request_auth.message, domain) - return bls.Verify(pubkey, signing_root, signed_request_auth.signature) -``` - -If the request body is absent, the builder MAY still serve a bid. +[`submitBuilderPreferences`][submit-builder-preferences-api] endpoint. A bid +MUST honor the `max_execution_payment` cap from stored preferences; without them +the builder MAY serve a bid with any `execution_payment`. + +Builders MUST verify the `SignedRequestAuth` signature against the +`proposer_pubkey` path parameter, and MUST check that `auth.message.data` +matches the value they agreed with the proposer and that `auth.message.slot` +matches the proposal `slot` path parameter (see +[Constructing the `RequestAuth`][signed-request-auth]). The signature is +verified with [`verify_request_auth_signature`](#signing). If the signature +fails to verify, the builder MUST return a 401 response; if the +`auth.message.data` or `auth.message.slot` check fails, the builder MUST return +a 400 response. A missing or malformed body is an invalid request and the +builder MUST return a 400 response. ## Proposer Preferences (Deprecation of Validator Registrations) @@ -182,9 +192,9 @@ MUST set `bid.value` to the amount they are committing to pay. If the builder intends to pay the proposer via an execution layer payment, they MUST set `bid.execution_payment`. This value MUST NOT exceed the -`max_execution_payment` from the proposer's stored `BuilderPreferencesV1`. If no -`BuilderPreferencesV1` have been submitted, the builder MUST NOT include an -execution layer payment (i.e. MUST set `bid.execution_payment` to `0`). +`max_execution_payment` from the proposer's stored `BuilderPreferences`. Without +stored preferences the builder MAY set any `bid.execution_payment`; the proposer +discards any bid that exceeds its locally configured limits. *Note*: `bid.value` and `bid.execution_payment` are not mutually exclusive. A builder MAY set both fields on a single bid; in that case the builder is @@ -192,6 +202,42 @@ committing to pay the proposer the sum of the two. `bid.value` is deducted from the builder's staked collateral on-chain even when `bid.execution_payment` is also set. +### Signing + +All signature operations follow the [standard BLS operations][bls] interface +defined in `consensus-specs`. + +The [`SignedRequestAuth`][signed-request-auth] is an out-of-protocol Builder API +message, specific to this API and analogous to the now-deprecated +`ValidatorRegistrationV1`. It is signed and verified under +`DOMAIN_REQUEST_AUTH`. This domain MUST NOT be confused with +`DOMAIN_BEACON_BUILDER`, which is used for in-protocol builder messages defined +by the consensus specs. + +Signing and verification compute the signing root with `compute_signing_root` +over the `RequestAuth` message, as shown below. A beacon node that forwards a +`SignedRequestAuth` MUST pass its `message` and `signature` through unchanged, +so a builder verifies exactly what the validator signed. + +```python +def get_request_auth_signature( + request_auth: RequestAuth, + privkey: int, +) -> BLSSignature: + domain = compute_domain(DOMAIN_REQUEST_AUTH) + signing_root = compute_signing_root(request_auth, domain) + return bls.Sign(privkey, signing_root) + + +def verify_request_auth_signature( + signed_request_auth: SignedRequestAuth, + pubkey: BLSPubkey, +) -> bool: + domain = compute_domain(DOMAIN_REQUEST_AUTH) + signing_root = compute_signing_root(signed_request_auth.message, domain) + return bls.Verify(pubkey, signing_root, signed_request_auth.signature) +``` + ## Constructing a `SignedExecutionPayloadEnvelope` If the builder's [`SignedExecutionPayloadBid`][signed-execution-payload-bid] has @@ -205,12 +251,13 @@ The specification for a block builder to construct a [`SignedExecutionPayloadEnvelope`][signed-execution-payload-envelope] is documented in the [Gloas consensus specs][gloas-builder-specs]. +[bls]: https://github.com/ethereum/consensus-specs/blob/master/specs/phase0/beacon-chain.md#bls-signatures [get-execution-payload-bid-api]: ./../../apis/builder/execution_payload_bid.yaml [gloas-builder-specs]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/builder.md [gloas-consensus-specs]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas -[proposer-preferences]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md -[proposer-preferences-topic]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md +[proposer-preferences]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md#new-proposerpreferences +[proposer-preferences-topic]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md#new-proposer_preferences [signed-execution-payload-bid]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#signedexecutionpayloadbid [signed-execution-payload-envelope]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#signedexecutionpayloadenvelope -[signed-request-auth]: ./validator.md#signedrequestauthv1 +[signed-request-auth]: ./validator.md#signedrequestauth [submit-builder-preferences-api]: ./../../apis/builder/builder_preferences.yaml diff --git a/specs/gloas/validator.md b/specs/gloas/validator.md index db29e60..2b70fb1 100644 --- a/specs/gloas/validator.md +++ b/specs/gloas/validator.md @@ -6,14 +6,14 @@ - [Introduction](#introduction) - [Containers](#containers) - [New Containers](#new-containers) - - [`RequestAuthV1`](#requestauthv1) - - [`SignedRequestAuthV1`](#signedrequestauthv1) - - [`BuilderPreferencesV1`](#builderpreferencesv1) - - [`BuilderPreferencesRequestV1`](#builderpreferencesrequestv1) + - [`RequestAuth`](#requestauth) + - [`SignedRequestAuth`](#signedrequestauth) + - [`BuilderPreferences`](#builderpreferences) + - [`BuilderPreferencesRequest`](#builderpreferencesrequest) - [Submitting Builder Preferences](#submitting-builder-preferences) - [`max_execution_payment`](#max_execution_payment) - [Bid Request](#bid-request) - - [Constructing the `RequestAuthV1`](#constructing-the-requestauthv1) + - [Constructing the `RequestAuth`](#constructing-the-requestauth) - [Proposer Preferences](#proposer-preferences) - [Validating a `SignedExecutionPayloadBid`](#validating-a-signedexecutionpayloadbid) - [Block proposal](#block-proposal) @@ -39,73 +39,76 @@ corresponding to the included bid to the PTC committee. ### New Containers -#### `RequestAuthV1` +#### `RequestAuth` -`RequestAuthV1` is used to authenticate requests to a builder. This is useful so +`RequestAuth` is used to authenticate requests to a builder. This is useful so that other builders do not DDOS or run replay attacks on the builder. ```python -class RequestAuthV1(Container): +class RequestAuth(Container): data: ByteList[MAX_DATA_SIZE] slot: Slot ``` -#### `SignedRequestAuthV1` +#### `SignedRequestAuth` ```python -class SignedRequestAuthV1(Container): - message: RequestAuthV1 +class SignedRequestAuth(Container): + message: RequestAuth signature: BLSSignature ``` -#### `BuilderPreferencesV1` +#### `BuilderPreferences` -`BuilderPreferencesV1` communicates a proposer's per-builder preferences to a +`BuilderPreferences` communicates a proposer's per-builder preferences to a specific builder ahead of the bid request. ```python -class BuilderPreferencesV1(Container): +class BuilderPreferences(Container): max_execution_payment: Gwei ``` -#### `BuilderPreferencesRequestV1` +#### `BuilderPreferencesRequest` ```python -class BuilderPreferencesRequestV1(Container): - preferences: BuilderPreferencesV1 - auth: SignedRequestAuthV1 +class BuilderPreferencesRequest(Container): + auth: SignedRequestAuth + preferences: BuilderPreferences ``` ## Submitting Builder Preferences The validator MAY submit its -[`BuilderPreferencesRequestV1`](#builderpreferencesrequestv1) to each builder -via the [`submitBuilderPreferences`][submit-builder-preferences-api] API call in -the epoch prior to the epoch in which they will be proposing, as determined from +[`BuilderPreferencesRequest`](#builderpreferencesrequest) to each builder via +the [`submitBuilderPreferences`][submit-builder-preferences-api] API call in the +epoch prior to the epoch in which they will be proposing, as determined from `state.proposer_lookahead`. This ensures builders have the preferences before the bid request arrives. -The validator constructs a `BuilderPreferencesV1` with: +The validator constructs a `BuilderPreferences` with: - `max_execution_payment`: The maximum execution layer payment the proposer will accept from this builder. See [`max_execution_payment`](#max_execution_payment). -The validator's BLS public key is passed as the `validator_pubkey` path -parameter in the [`submitBuilderPreferences`][submit-builder-preferences-api] -API call. - -The validator then constructs a `BuilderPreferencesRequestV1` with the -`BuilderPreferencesV1` as `preferences` and a `SignedRequestAuthV1` as `auth`. -The `SignedRequestAuthV1` is constructed as described in -[Constructing the `RequestAuthV1`](#constructing-the-requestauthv1); its -`auth.message.data` identifies the intended builder. The builder MUST -verify the `auth` signature against the `validator_pubkey` path parameter and -MUST reject the request with a 400 response if `auth.message.data` does -not match its own URL. - -If no preferences have been submitted, the builder MUST treat the proposer's -`max_execution_payment` as `0`. +The validator then constructs a `BuilderPreferencesRequest` with the +`BuilderPreferences` as `preferences` and a `SignedRequestAuth` as `auth`, and +submits it to the [`submitBuilderPreferences`][submit-builder-preferences-api] +endpoint for its `proposer_pubkey`. The `SignedRequestAuth` is constructed as +described in [Constructing the `RequestAuth`](#constructing-the-requestauth); +its `auth.message.data` is the authentication data the builder expects and its +`auth.message.slot` is the proposal slot the preferences apply to. The builder +MUST verify the `auth` signature against the `proposer_pubkey` path parameter, +returning a 401 response if it fails to verify, and MUST reject the request with +a 400 response if `auth.message.data` does not match the value it agreed with +the proposer. The builder MUST also reject, with a 400 response, a request whose +`auth.message.slot` has already passed, so that a replay cannot roll preferences +back to a stale value. + +A builder MUST honor the `max_execution_payment` cap in any bid it serves for a +slot it has stored preferences for; without them it MAY serve a bid with any +`execution_payment`. The proposer's locally configured per-builder limits are +the backstop: the proposer discards any bid that exceeds them. ### `max_execution_payment` @@ -118,35 +121,40 @@ payment amount from the builder. Proposers may adjust this parameter based on their level of trust in the builder's reliability and reputation. `max_execution_payment` is communicated exclusively via the -[`submitBuilderPreferences`][submit-builder-preferences-api] endpoint. If no -`BuilderPreferencesV1` have been submitted to a builder, that builder MUST NOT -include an execution layer payment in its bid. +[`submitBuilderPreferences`][submit-builder-preferences-api] endpoint. ## Bid Request When calling [`getExecutionPayloadBid`][get-execution-payload-bid-api], the -validator MAY send a [`SignedRequestAuthV1`](#signedrequestauthv1) as the -request body to authenticate the request. The body MAY be encoded as JSON +validator MUST send a [`SignedRequestAuth`](#signedrequestauth) as the request +body to authenticate the request. The body MAY be encoded as JSON (`Content-Type: application/json`) or SSZ -(`Content-Type: application/octet-stream`); when SSZ is used, the validator MUST -also send the `Eth-Consensus-Version` header. If the body is omitted, the -builder MAY still serve a bid. +(`Content-Type: application/octet-stream`); `RequestAuth` is fork-versioned, so +the `Eth-Consensus-Version` header is required. Proposer duties are known an +epoch in advance, so the validator can sign the `SignedRequestAuth` ahead of +time, off the proposal hot path. -### Constructing the `RequestAuthV1` +### Constructing the `RequestAuth` -If the validator chooses to authenticate its request, it constructs a -`RequestAuthV1` with the following fields: +The validator constructs a `RequestAuth` with the following fields: -- `data`: MUST be set to the URL of the builder the request is intended for. -- `slot`: The slot for which the request is being sent. +- `data`: opaque authentication data agreed with the builder out of band whose + meaning is left to the two parties. It is not tied to an endpoint, so one + `SignedRequestAuth` can authenticate the proposer for both + `getExecutionPayloadBid` and `submitBuilderPreferences`. When no value has + been agreed out of band, the validator SHOULD use the UTF-8 bytes of the + builder's own advertised URL, exactly as advertised. A zero-length `data` is + invalid. +- `slot`: The proposal slot this request is authorized for, not the slot at + which the request is signed or sent. The proposer's public key is already carried as a path parameter in the relevant -API request, so it does not need to be carried inside `RequestAuthV1`. +API request, so it does not need to be carried inside `RequestAuth`. -The validator then constructs the `SignedRequestAuthV1` by signing the -`RequestAuthV1`. The signature lets builders authenticate the requesting -validator and discard requests from other parties (e.g. DDOS or replay attempts -from competing builders). +The validator then constructs the `SignedRequestAuth` by signing the +`RequestAuth`. The signature lets builders authenticate the requesting validator +and discard requests from other parties (e.g. DDOS or replay attempts from +competing builders). ## Proposer Preferences @@ -207,11 +215,11 @@ def validate_bid( return verify_execution_payload_bid_signature(state, signed_bid) ``` -`max_execution_payment` is the value from the `BuilderPreferencesV1` the -validator submitted to this builder via -[`submitBuilderPreferences`][submit-builder-preferences-api]. Validators MUST -validate each bid against the `max_execution_payment` they submitted for that -builder. +`max_execution_payment` is the limit the validator has locally configured for +this builder, the same value it submits via +[`submitBuilderPreferences`][submit-builder-preferences-api] when it submits +preferences. Validators MUST validate each bid against that limit, whether or +not preferences were submitted. Note that the fee recipient specified in `bid.fee_recipient` does not necessarily correspond to the fee recipient of the execution payload. Even if a @@ -231,28 +239,29 @@ block on top of a beacon `state` must take the following actions: 1. Call upstream builder software to get a [`SignedExecutionPayloadBid`][signed-execution-payload-bid] using the [`getExecutionPayloadBid`][get-execution-payload-bid-api] API call. The - validator MAY send a `SignedRequestAuthV1` in the request body to - authenticate the request. + validator signs a `SignedRequestAuth` and the beacon node sends it unchanged + in the request body to authenticate the request. 2. Assemble a `SignedBeaconBlock` according to the process outlined in the [Gloas validator specs][gloas-validator-specs] but with the best [`SignedExecutionPayloadBid`][signed-execution-payload-bid] from the prior step. -3. The proposer returns the `SignedBeaconBlock` back to the upstream block - building software via [`submitSignedBeaconBlock`][submit-signed-beacon-block] - API call. +3. The validator publishes the `SignedBeaconBlock` to its beacon node, which + returns it to the upstream block building software via + [`submitSignedBeaconBlock`][submit-signed-beacon-block] API call. The + validator does not call builders directly. 4. The upstream block building software constructs the corresponding [`SignedExecutionPayloadEnvelope`][signed-execution-payload-envelope] and broadcasts it to the PTC committee. -[can-builder-cover-bid]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#can_builder_cover_bid +[can-builder-cover-bid]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#new-can_builder_cover_bid [get-execution-payload-bid-api]: ./../../apis/builder/execution_payload_bid.yaml [gloas-consensus-specs]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas [gloas-validator-specs]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/validator.md#block-proposal -[is-active-builder]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#is_active_builder -[proposer-preferences]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md -[proposer-preferences-topic]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md +[is-active-builder]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#new-is_active_builder +[proposer-preferences]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md#new-proposerpreferences +[proposer-preferences-topic]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/p2p-interface.md#new-proposer_preferences [signed-execution-payload-bid]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#signedexecutionpayloadbid [signed-execution-payload-envelope]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#signedexecutionpayloadenvelope [submit-builder-preferences-api]: ./../../apis/builder/builder_preferences.yaml -[submit-signed-beacon-block]: ./../../apis/builder/beacon_block.yaml -[verify-execution-payload-bid-signature]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#verify_execution_payload_bid_signature +[submit-signed-beacon-block]: ./../../apis/builder/beacon_blocks.yaml +[verify-execution-payload-bid-signature]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#new-verify_execution_payload_bid_signature diff --git a/types/gloas/builder_preferences.yaml b/types/gloas/builder_preferences.yaml index 01b045e..83ee65a 100644 --- a/types/gloas/builder_preferences.yaml +++ b/types/gloas/builder_preferences.yaml @@ -1,18 +1,18 @@ Gloas: - BuilderPreferencesV1: + BuilderPreferences: type: object description: "Per-builder preferences that a proposer can express." required: [max_execution_payment] properties: max_execution_payment: $ref: "../../beacon-apis/types/primitive.yaml#/Uint64" - description: "Indicates the maximum amount that a proposer is willing to accept as an execution payment from the builder." - BuilderPreferencesRequestV1: + description: "The maximum amount, in Gwei, that a proposer is willing to accept as an execution payment from the builder." + BuilderPreferencesRequest: type: object description: "A builder preferences submission containing the proposer's preferences and a signed request authentication." - required: [preferences, auth] + required: [auth, preferences] properties: - preferences: - $ref: "#/Gloas/BuilderPreferencesV1" auth: - $ref: "./request_auth.yaml#/Gloas/SignedRequestAuthV1" + $ref: "./request_auth.yaml#/Gloas/SignedRequestAuth" + preferences: + $ref: "#/Gloas/BuilderPreferences" diff --git a/types/gloas/request_auth.yaml b/types/gloas/request_auth.yaml index 8255ffb..c46b4e8 100644 --- a/types/gloas/request_auth.yaml +++ b/types/gloas/request_auth.yaml @@ -1,22 +1,30 @@ Gloas: - RequestAuthV1: + RequestAuth: type: object required: [data, slot] properties: data: type: string format: hex - description: "Opaque authentication data, up to `MAX_DATA_SIZE` bytes. Typically set to the URL of the builder the request is intended for." - example: "0x68747470733a2f2f6275696c6465722e6578616d706c652e636f6d" - pattern: "^0x[a-fA-F0-9]{0,8192}$" + description: >- + Opaque authentication data unique to the builder, agreed upon out of + band. The meaning of the up to `MAX_DATA_SIZE` (4096) bytes is left + to the two parties; the builder checks the exact bytes when it + verifies. When no value has been agreed out of band, implementations + SHOULD default to the UTF-8 bytes of the builder's own advertised + URL, exactly as advertised, so proposers with no prior relationship + can construct an identical `data` deterministically. A zero-length + `data` is invalid. + example: "0x1234567890abcdef" + pattern: "^0x(?:[a-fA-F0-9]{2}){1,4096}$" slot: $ref: "../../beacon-apis/types/primitive.yaml#/Uint64" - description: "The slot for which the bid is being requested." - SignedRequestAuthV1: + description: "The proposal slot this request is authorized for." + SignedRequestAuth: type: object required: [message, signature] properties: message: - $ref: "#/Gloas/RequestAuthV1" + $ref: "#/Gloas/RequestAuth" signature: - $ref: "../../beacon-apis/types/primitive.yaml#/Signature" \ No newline at end of file + $ref: "../../beacon-apis/types/primitive.yaml#/Signature" diff --git a/wordlist.txt b/wordlist.txt index a7d4815..629239b 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -35,8 +35,9 @@ Gloas gloas Gwei PTC -BuilderPreferencesV -RequestAuthV -SignedRequestAuthV +UTF +BuilderPreferences +RequestAuth +SignedRequestAuth submitBlindedBlockV ValidatorRegistrationsV \ No newline at end of file