diff --git a/apis/builder/beacon_block.yaml b/apis/builder/beacon_block.yaml new file mode 100644 index 00000000..1a86500b --- /dev/null +++ b/apis/builder/beacon_block.yaml @@ -0,0 +1,50 @@ +post: + operationId: "submitSignedBeaconBlock" + summary: Submit a signed beacon block with the execution payload bid. + description: | + Submits a `SignedBeaconBlock` to the builder, binding the proposer to the block. + + A success response (200) indicates that the signed beacon block was + valid. If the signed beacon block was invalid, then the builder + must return an error response (400) with a description of the validation + failure. + tags: + - Builder + parameters: + - in: header + schema: + $ref: "../../builder-oapi.yaml#/components/schemas/ConsensusVersion" + required: false + name: Eth-Consensus-Version + description: "The active consensus version to which the block being submitted belongs. Required if request is SSZ encoded." + requestBody: + description: A `SignedBeaconBlock`. + required: true + content: + application/json: + schema: + type: object + required: [data] + properties: + data: + $ref: "../../beacon-apis/types/gloas/block.yaml#/Gloas/SignedBeaconBlock" + description: "The signed beacon block." + application/octet-stream: + schema: + description: "SSZ serialized `SignedBeaconBlock` bytes. Use content type header to indicate that SSZ data is contained in the request body." + responses: + "202": + description: Success response. + "400": + description: Error response. + content: + application/json: + schema: + $ref: "../../builder-oapi.yaml#/components/schemas/ErrorMessage" + example: + code: 400 + message: "Invalid signed beacon block: missing signature" + "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 new file mode 100644 index 00000000..40261319 --- /dev/null +++ b/apis/builder/execution_payload_bid.yaml @@ -0,0 +1,188 @@ +post: + operationId: "getExecutionPayloadBid" + summary: Get an execution payload bid. + description: | + 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 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. + - The `X-Eth-Max-Trusted-Bid` header carrying the proposer's + `max_execution_payment` (decimal `uint64`, in Gwei) for this request. + - Optionally, a `SignedRequestAuth` in the request body that + authenticates the request. The body MAY be encoded as JSON + (`Content-Type: application/json`) or SSZ + (`Content-Type: application/octet-stream`). + + The `X-Eth-Max-Trusted-Bid` header is required. If it is missing or + malformed, the builder MUST NOT serve a bid for the proposer and MUST + return a 400 response. + + The `SignedRequestAuth` body is optional. If it is present but malformed + or fails signature verification, the builder MAY return a 400 response. + If it is absent, the builder MAY still serve a bid, but builders MAY + use the presence and validity of the `SignedRequestAuth` to apply + per-validator policy (e.g. rate-limiting, prioritization, or refusing + unauthenticated requests). + + 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. + + This API is applicable from Gloas fork onwards. + tags: + - Builder + parameters: + - name: slot + in: path + required: true + description: The slot for which the block should be proposed. + schema: + $ref: "../../builder-oapi.yaml#/components/schemas/Uint64" + - name: parent_hash + in: path + required: true + description: Hash of execution layer block the proposer will build on. + schema: + $ref: "../../builder-oapi.yaml#/components/schemas/Root" + - name: parent_root + in: path + required: true + description: Root of the beacon block the proposer will build on. + schema: + $ref: "../../builder-oapi.yaml#/components/schemas/Root" + - name: proposer_index + in: path + required: true + description: Index of the proposer. + schema: + $ref: "../../builder-oapi.yaml#/components/schemas/Uint64" + - name: Date-Milliseconds + in: header + required: false + 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. + schema: + type: integer + format: int64 + example: 1710338135000 + - name: X-Timeout-Ms + in: header + required: false + description: | + Optional header containing the proposer's timeout for the request in milliseconds. + Builders should use this header to adjust the amount of time by which they delay the + requests to maximise block rewards. Otherwise, requests will timeout and the proposer + will not receive the header in time. + schema: + type: integer + format: int64 + example: 10000 + - name: Eth-Consensus-Version + in: header + required: false + description: | + The active consensus version to which the `SignedRequestAuth` in the + request body belongs. Required if the request body is SSZ encoded. + schema: + $ref: "../../builder-oapi.yaml#/components/schemas/ConsensusVersion" + - name: X-Eth-Max-Trusted-Bid + in: header + required: true + description: | + Decimal `uint64` (in Gwei) carrying the proposer's `max_execution_payment` + for this request. `bid.execution_payment` MUST NOT exceed this value. + A value of `0` indicates that the proposer does not accept any + trusted payments from this builder. A value of `2**64 - 1` + (`MAX_TRUSTED_BID`) indicates that the proposer accepts any trusted + payment amount. If the header is missing or malformed, the builder + MUST return a 400 response. + schema: + type: integer + format: uint64 + example: 1000000000 + requestBody: + description: | + Optional `SignedRequestAuth` authenticating the request. If provided, + the builder MAY verify the BLS signature against the validator pubkey + resolved from the `proposer_index` path parameter, and check that + `builder_pubkey` matches its own identity and that `slot` matches the + requested slot. If absent, the builder MAY still serve a bid subject + to its own policy. + required: false + content: + application/json: + schema: + $ref: "../../types/gloas/request_auth.yaml#/Gloas/SignedRequestAuth" + application/octet-stream: + schema: + description: "SSZ serialized `SignedRequestAuth` bytes. Use Content-Type header to indicate that SSZ data is contained in the request body. The `Eth-Consensus-Version` header MUST also be set." + responses: + "200": + description: Success response. + headers: + Eth-Consensus-Version: + $ref: "../../builder-oapi.yaml#/components/headers/Eth-Consensus-Version" + required: false + content: + application/json: + schema: + title: GetExecutionPayloadBidResponse + type: object + required: [version, data] + properties: + version: + type: string + enum: [ gloas ] + example: "gloas" + data: + $ref: "../../beacon-apis/types/gloas/execution_payload_bid.yaml#/Gloas/SignedExecutionPayloadBid" + application/octet-stream: + schema: + description: "SSZ serialized `SignedExecutionPayloadBid` bytes. Use Accept header to choose this response type" + "204": + description: No bid is available. + "400": + description: Error response. + content: + application/json: + schema: + $ref: "../../builder-oapi.yaml#/components/schemas/ErrorMessage" + examples: + InvalidHash: + value: + code: 400 + message: "Unknown hash: missing parent hash" + InvalidAuth: + value: + code: 400 + message: "Invalid SignedRequestAuth: signature verification failed" + MissingMaxTrustedBid: + value: + code: 400 + message: "Missing X-Eth-Max-Trusted-Bid header" + "401": + description: Authentication required. + content: + application/json: + schema: + $ref: "../../builder-oapi.yaml#/components/schemas/ErrorMessage" + examples: + MissingAuth: + value: + code: 401 + message: "Missing SignedRequestAuth: this builder requires authenticated requests" + "406": + $ref: "../../builder-oapi.yaml#/components/responses/NotAcceptable" + "415": + $ref: "../../builder-oapi.yaml#/components/responses/UnsupportedMediaType" + "500": + $ref: "../../builder-oapi.yaml#/components/responses/InternalError" diff --git a/beacon-apis b/beacon-apis index 339eea96..8da01b0f 160000 --- a/beacon-apis +++ b/beacon-apis @@ -1 +1 @@ -Subproject commit 339eea96b41c787dad47765fc781303fb40aa886 +Subproject commit 8da01b0febff59ec2ffdf40f0a2f0d8217d4b364 diff --git a/builder-oapi.yaml b/builder-oapi.yaml index a9c05f06..bfcb6d9f 100644 --- a/builder-oapi.yaml +++ b/builder-oapi.yaml @@ -53,6 +53,10 @@ paths: $ref: "./apis/builder/validators.yaml" /eth/v1/builder/header/{slot}/{parent_hash}/{pubkey}: $ref: "./apis/builder/header.yaml" + /eth/v1/builder/execution_payload_bid/{slot}/{parent_hash}/{parent_root}/{proposer_index}: + $ref: "./apis/builder/execution_payload_bid.yaml" + /eth/v1/builder/beacon_block: + $ref: "./apis/builder/beacon_block.yaml" /eth/v1/builder/blinded_blocks: $ref: "./apis/builder/blinded_blocks.yaml" /eth/v2/builder/blinded_blocks: @@ -72,7 +76,7 @@ components: $ref: "./beacon-apis/types/http.yaml#/ErrorMessage" ConsensusVersion: $ref: "./beacon-apis/beacon-node-oapi.yaml#/components/schemas/ConsensusVersion" - enum: [bellatrix, capella, deneb, electra, fulu] + enum: [bellatrix, capella, deneb, electra, fulu, gloas] example: "bellatrix" Bellatrix.ExecutionPayload: $ref: "./beacon-apis/types/bellatrix/execution_payload.yaml#/Bellatrix/ExecutionPayload" @@ -102,7 +106,11 @@ components: $ref: "./types/fulu/blobs_bundle.yaml#/Fulu/BlobsBundle" Fulu.ExecutionPayloadAndBlobsBundle: $ref: "./types/fulu/execution_payload_and_blobs_bundle.yaml#/Fulu/ExecutionPayloadAndBlobsBundle" - + Gloas.RequestAuth: + $ref: "./types/gloas/request_auth.yaml#/Gloas/RequestAuth" + Gloas.SignedRequestAuth: + $ref: "./types/gloas/request_auth.yaml#/Gloas/SignedRequestAuth" + responses: InternalError: $ref: "./types/http.yaml#/InternalError" @@ -151,3 +159,5 @@ components: $ref: "./examples/fulu/signed_blinded_beacon_block.json" Fulu.SignedBuilderBid: $ref: "./examples/fulu/signed_builder_bid.json" + Gloas.SignedRequestAuth: + $ref: "./examples/gloas/signed_request_auth.json" \ No newline at end of file diff --git a/examples/gloas/signed_request_auth.json b/examples/gloas/signed_request_auth.json new file mode 100644 index 00000000..eb5bfe95 --- /dev/null +++ b/examples/gloas/signed_request_auth.json @@ -0,0 +1,9 @@ +{ + "value": { + "message": { + "builder_pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a", + "slot": "1" + }, + "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" + } +} diff --git a/specs/gloas/bid_scoring.md b/specs/gloas/bid_scoring.md new file mode 100644 index 00000000..ba488ae2 --- /dev/null +++ b/specs/gloas/bid_scoring.md @@ -0,0 +1,117 @@ +# Gloas - Bid Scoring + +## Overview + +This document specifies how a validator selects the best execution payload bid +from among bids received over the p2p network, bids received via the offchain +builder API, and a locally built block. + +Bids from the two sources are scored differently because offchain bids carry an +`execution_payment` component that is conditionally trusted up to the +`max_execution_payment` the validator expressed for that request. + +`min_bid` from `GlobalPreferences` applies equally to both p2p and offchain +bids. There is no separate threshold per source. + +## Scoring + +### P2P Bids + +P2P bids are scored solely by `bid.value`, the on-chain collateral commitment. +`bid.execution_payment` is ignored for p2p bids because there is no per-request +`max_execution_payment` negotiation over gossip. + +A p2p bid is eligible only if `bid.value > min_bid`. + +```python +def select_best_p2p_bid( + bids: List[SignedExecutionPayloadBid], + min_bid: uint64, +) -> Optional[SignedExecutionPayloadBid]: + eligible = [b for b in bids if b.message.value > min_bid] + if not eligible: + return None + return max(eligible, key=lambda b: b.message.value) +``` + +### Offchain (Builder API) Bids + +For bids received via the offchain builder API, the total bid score accounts for +both the on-chain collateral commitment and the trusted execution layer payment, +capped at the `max_execution_payment` the validator advertised for that request: + +``` +bid_score = bid.value + min(bid.execution_payment, max_execution_payment) +``` + +A bid is eligible only if `bid_score > min_bid`, the same threshold applied to +p2p bids. + +```python +def score_offchain_bid( + bid: ExecutionPayloadBid, + max_execution_payment: uint64, +) -> uint64: + return bid.value + min(bid.execution_payment, max_execution_payment) + +def select_best_offchain_bid( + bids: List[Tuple[SignedExecutionPayloadBid, uint64]], + min_bid: uint64, +) -> Optional[SignedExecutionPayloadBid]: + """ + `bids` is a list of (signed_bid, max_execution_payment) pairs, where + max_execution_payment is the value sent in the X-Eth-Max-Trusted-Bid header of + the corresponding getExecutionPayloadBid request. + """ + eligible = [ + (b, score_offchain_bid(b.message, max_execution_payment)) + for b, max_execution_payment in bids + if score_offchain_bid(b.message, max_execution_payment) > min_bid + ] + if not eligible: + return None + return max(eligible, key=lambda pair: pair[1])[0] +``` + +### Selecting the Best Bid + +Once the best p2p bid and best offchain bid are identified, the validator +compares them against the locally built block using `local_block_boost`. + +`local_block_boost` is a multiplier (in basis points, where `10000 = 100%`) +applied to the local block value before comparison. A value of `10000` means no +boost; a value of `11000` means the local block must be beaten by at least 10% +for an external bid to be preferred. + +```python +def select_best_bid( + local_block_value: uint64, + best_p2p_bid: Optional[SignedExecutionPayloadBid], + best_offchain_bid: Optional[SignedExecutionPayloadBid], + max_execution_payment_for_offchain: uint64, + global_preferences: GlobalPreferences, +) -> Optional[SignedExecutionPayloadBid]: + boosted_local = local_block_value * global_preferences.local_block_boost // 10000 + + best_external: Optional[SignedExecutionPayloadBid] = None + best_external_score: uint64 = 0 + + if best_p2p_bid is not None: + p2p_score = best_p2p_bid.message.value + if p2p_score > best_external_score: + best_external = best_p2p_bid + best_external_score = p2p_score + + if best_offchain_bid is not None: + offchain_score = score_offchain_bid(best_offchain_bid.message, max_execution_payment_for_offchain) + if offchain_score > best_external_score: + best_external = best_offchain_bid + best_external_score = offchain_score + + if best_external is not None and best_external_score > boosted_local: + return best_external + + return None # fall back to local block +``` + +Returning `None` indicates the validator should propose its locally built block. diff --git a/specs/gloas/builder.md b/specs/gloas/builder.md new file mode 100644 index 00000000..6ec95c8a --- /dev/null +++ b/specs/gloas/builder.md @@ -0,0 +1,171 @@ + + + + +- [Gloas - Builder Specification](#gloas---builder-specification) + - [Introduction](#introduction) + - [Constants](#constants) + - [Bidding](#bidding) + - [Per-request Validator Inputs](#per-request-validator-inputs) + - [`max_execution_payment`](#max_execution_payment) + - [Proposer Preferences (Deprecation of Validator Registrations)](#proposer-preferences-deprecation-of-validator-registrations) + - [Constructing a `SignedExecutionPayloadBid`](#constructing-a-signedexecutionpayloadbid) + - [Constructing a `SignedExecutionPayloadEnvelope`](#constructing-a-signedexecutionpayloadenvelope) + + + +# Gloas - Builder Specification + +## Introduction + +This document documents the builder behaviour with the Builder-API post ePBS. It +describes how builders consume per-request inputs from validators and construct +[`SignedExecutionPayloadBid`][signed-execution-payload-bid] and +[`SignedExecutionPayloadEnvelope`][signed-execution-payload-envelope] objects. + +## Constants + +| Name | Value | +| ----------------- | ----------- | +| `MAX_TRUSTED_BID` | `2**64 - 1` | + +## Bidding + +In Gloas, Execution payloads are built for a specific `slot`, `parent_hash`, +`validator_index` along with the `parent_root` tuple corresponding to a unique +beacon block serving as the parent. + +This is because in Gloas with [EIP-7732], the execution payload and beacon +blocks are decoupled. The `parent_hash` could refer to a beacon block which is +an ancestor of the parent beacon block corresponding to the current beacon block +for which we are building the execution payload. + +We update `is_eligible_for_bid` below. *Note*: `hash_tree_root` is defined in +the [Gloas consensus specs][gloas-consensus-specs]. + +```python +def is_eligible_for_bid( + state: BeaconState, + proposer_preferences: Dict[ValidatorIndex, ProposerPreferences], + slot: Slot, + parent_hash: Hash32, + # [New in Gloas] + parent_root: Root, + # [New in Gloas] + validator_index: ValidatorIndex, +): + # Verify slot + assert slot == state.slot + + assert validator_index in state.validators.keys() + + # Verify that proposer preferences have been received via the gossip topic + assert validator_index in proposer_preferences.keys() + + # Verify parent hash. The proposer could build on the FULL parent block or on the EMPTY parent block based on + # their view of the chain. + # [Modified in Gloas:EIP7732] + assert ( + parent_hash == state.latest_execution_payload_bid.block_hash + or parent_hash == state.latest_block_hash + ) + + # Verify parent root + # [Modified in Gloas:EIP7732] + assert parent_root == hash_tree_root(state.latest_block_header) +``` + +## Per-request Validator Inputs + +Validators communicate per-request inputs to a builder on each +[`getExecutionPayloadBid`][get-execution-payload-bid-api] call: + +- The `X-Eth-Max-Trusted-Bid` header carrying a decimal `uint64` (in Gwei) with + the proposer's `max_execution_payment` for this request. This header is + **required**. +- Optionally, a [`SignedRequestAuth`][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. + +If the `X-Eth-Max-Trusted-Bid` header is missing or malformed, the builder MUST +NOT serve a bid for the proposer (return a 400 response). + +If the request body is present, builders MAY verify the `SignedRequestAuth` +signature against the validator pubkey resolved from the `proposer_index` path +parameter, and check that `builder_pubkey` matches their own identity and that +`slot` matches the requested slot. If verification fails, the builder MAY return +a 400 response. + +If the request body is absent, the builder MAY still serve a bid. + +### `max_execution_payment` + +`max_execution_payment` is the maximum value (in Gwei) that a proposer is willing to +accept as a trusted execution layer payment from this builder for this request. +A value of `0` indicates that the proposer does not accept any trusted payments +from the builder, requiring all payments to use the on-chain trustless payments +mechanism. A value of `MAX_TRUSTED_BID` indicates that the proposer will accept +any trusted 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 sent in the clear in the `X-Eth-Max-Trusted-Bid` header and +is **not** covered by the `RequestAuth` signature. + +## Proposer Preferences (Deprecation of Validator Registrations) + +*Note*: `ValidatorRegistrationV1` is **deprecated** in favor of +[`ProposerPreferences`][proposer-preferences] from the consensus specs. + +Builders SHOULD subscribe to the +[`proposer_preferences`][proposer-preferences-topic] gossip topic to learn about +a validator's general preferences. Validators broadcast these messages at the +beginning of each epoch for their proposal slots in the next epoch. + +## Constructing a `SignedExecutionPayloadBid` + +The specification for a block builder to construct a +[`SignedExecutionPayloadBid`][signed-execution-payload-bid] is documented in the +[Gloas consensus specs][gloas-builder-specs]. + +`bid.fee_recipient` MUST be set to the fee recipient from the proposer's +[`ProposerPreferences`][proposer-preferences], regardless of whether the builder +pays via `bid.execution_payment` or `bid.value`. + +If the builder intends to pay the proposer via their staked collateral, they +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` received in the `X-Eth-Max-Trusted-Bid` header of the +corresponding [`getExecutionPayloadBid`][get-execution-payload-bid-api] request. + +*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 +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. + +## Constructing a `SignedExecutionPayloadEnvelope` + +If the builder's [`SignedExecutionPayloadBid`][signed-execution-payload-bid] has +been accepted by the proposer and it has been included in its +`SignedBeaconBlock`, then the builder has to construct a +[`SignedExecutionPayloadEnvelope`][signed-execution-payload-envelope] +corresponding to the [`SignedExecutionPayloadBid`][signed-execution-payload-bid] +and it has to broadcast via the `execution_payload_envelope` gossip topic. + +The specification for a block builder to construct a +[`SignedExecutionPayloadEnvelope`][signed-execution-payload-envelope] is +documented in the [Gloas consensus specs][gloas-builder-specs]. + +[eip-7732]: https://eips.ethereum.org/EIPS/eip-7732 +[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 +[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#signedrequestauth diff --git a/specs/gloas/validator.md b/specs/gloas/validator.md new file mode 100644 index 00000000..70ef6b19 --- /dev/null +++ b/specs/gloas/validator.md @@ -0,0 +1,268 @@ + + + + +- [Gloas - Honest Validator](#gloas---honest-validator) + - [Introduction](#introduction) + - [Containers](#containers) + - [New Containers](#new-containers) + - [`RequestAuth`](#requestauth) + - [`SignedRequestAuth`](#signedrequestauth) + - [Bid Request](#bid-request) + - [Constructing the `RequestAuth`](#constructing-the-requestauth) + - [`max_execution_payment`](#max_execution_payment) + - [Proposer Preferences](#proposer-preferences) + - [Validating a `SignedExecutionPayloadBid`](#validating-a-signedexecutionpayloadbid) + - [Block proposal](#block-proposal) + - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) + - [Receiving ExecutionPayloadBid](#receiving-executionpayloadbid) + + + +# Gloas - Honest Validator + +## Introduction + +This document explains how a beacon-chain validator can participate in the +external block building market with the Builder-API post ePBS. + +Validators request a [`SignedExecutionPayloadBid`][signed-execution-payload-bid] +from the external builder network to put it in their `SignedBeaconBlock`. The +external builder network broadcasts the +[`SignedExecutionPayloadEnvelope`][signed-execution-payload-envelope] +corresponding to the included bid to the PTC committee. + +## Containers + +### New Containers + +#### `RequestAuth` + +`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 RequestAuth(Container): + builder_pubkey: BLSPubkey + slot: Slot +``` + +#### `SignedRequestAuth` + +```python +class SignedRequestAuth(Container): + message: RequestAuth + signature: BLSSignature +``` + +### `BuilderConfig` + +`BuilderConfig` specifies the connection details and per-builder preferences for +a single whitelisted builder. This includes: + +- `url`: The URL of the builder where execution payload bids can be fetched. +- `builder_pubkey`: The advertised BLS public key of the builder. Configured + alongside the URL, it forms the builder's off-chain identity and is used to + bind request authentication to a specific builder, preventing cross-builder + replay attacks. +- `max_execution_payment`: The maximum value (in Gwei) the proposer is willing to + accept as a trusted execution layer payment from this builder. A value of `0` + means the proposer does not accept any trusted payments from this builder, + requiring all payments to use the on-chain trustless mechanism. A value of + `MAX_TRUSTED_BID` means the proposer accepts any trusted payment amount. + Proposers should adjust this based on their level of trust in the builder. +```python +class BuilderConfig(Container): + url: ByteList[MAX_URL_BYTES] + builder_pubkey: BLSPubkey + max_execution_payment: uint64 +``` + +### `GlobalPreferences` + +The `GlobalPreferences` container contains validator preferences across all +builders. This includes: + +- `min_bid`: The minimum bid value (in Gwei) from any builder for the proposer + to consider using a builder bid. If no builder bid meets this threshold, the + proposer falls back to the locally built block. A value of `0` means no + minimum. +- `local_block_boost`: A multiplier factor (in basis points, where 10000 = 100%) + applied to the locally built block value when comparing against bids from + builders. This gives priority to the local block in bid selection. + +```python +class GlobalPreferences(Container): + min_bid: uint64 + local_block_boost: uint64 +``` + +### `BuilderWhitelist` + +```python +class BuilderWhitelist(Container): + builders: List[BuilderConfig, MAX_WHITELISTED_BUILDERS] + global_preferences: GlobalPreferences +``` + +## Bid Request + +When calling [`getExecutionPayloadBid`][get-execution-payload-bid-api], the +validator MUST send the `X-Eth-Max-Trusted-Bid` header carrying a decimal +`uint64` (in Gwei) expressing the per-builder `max_execution_payment` configured in +the [`BuilderConfig`](#builderconfig) for this builder. If the header is +missing, the builder will not serve a bid for the proposer. + +The validator MAY additionally 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. + +### Constructing the `RequestAuth` + +If the validator chooses to authenticate its request, it constructs a +`RequestAuth` with the following fields: + +- `builder_pubkey`: The BLS public key of the builder the request is intended + for. +- `slot`: The slot for which the bid is being requested. + +The builder resolves the validator's public key from the `proposer_index` path +parameter of the [`getExecutionPayloadBid`][get-execution-payload-bid-api] +request, so it does not need to be carried inside `RequestAuth`. + +The validator then constructs the `SignedRequestAuth` by signing the +`RequestAuth`, and sends it in the body of the +[`getExecutionPayloadBid`][get-execution-payload-bid-api] request. 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 + +*Note*: Validator registrations (`ValidatorRegistrationV1`) are **deprecated** +in favor of [`ProposerPreferences`][proposer-preferences] from the consensus +specs. + +General validator preferences are now communicated via the +[`proposer_preferences`][proposer-preferences-topic] gossip topic defined in the +[Gloas consensus specs][gloas-consensus-specs]. At the beginning of each epoch, +validators broadcast [`SignedProposerPreferences`][proposer-preferences] +messages for their proposal slots in the next epoch. + +Builders SHOULD subscribe to this gossip topic to learn about proposer +preferences for upcoming slots. + +## Validating a `SignedExecutionPayloadBid` + +When the proposer receives a +[`SignedExecutionPayloadBid`][signed-execution-payload-bid] from a builder, it +can validate the bid using `validate_bid`. It can discard the bid if the +conditions are not satisfied. + +*Note*: `hash_tree_root`, `get_randao_mix`, and `get_current_epoch` are defined +in the [Gloas consensus specs][gloas-consensus-specs]. The predicates +[`is_active_builder`][is-active-builder], +[`can_builder_cover_bid`][can-builder-cover-bid], and +[`verify_execution_payload_bid_signature`][verify-execution-payload-bid-signature] +are also defined in the consensus specs. + +```python +def validate_bid( + state: BeaconState, + proposer_preferences: ProposerPreferences, + max_execution_payment: uint64, + signed_bid: SignedExecutionPayloadBid, + fee_recipient: ExecutionAddress, +) -> bool: + bid = signed_bid.message + + assert is_active_builder(state, bid.builder_index) + assert bid.slot == state.slot + assert bid.fee_recipient == fee_recipient + # Bid can choose to extend on FULL or EMPTY. + assert ( + bid.parent_block_hash == state.latest_execution_payload_bid.block_hash + or bid.parent_block_hash == state.latest_block_hash + ) + assert bid.parent_block_root == hash_tree_root(state.latest_block_header) + assert bid.prev_randao == get_randao_mix(state, get_current_epoch(state)) + assert bid.gas_limit <= proposer_preferences.gas_limit + + assert bid.execution_payment <= max_execution_payment + + if bid.value > 0: + assert can_builder_cover_bid(state, bid.builder_index, bid.value) + + return verify_execution_payload_bid_signature(state, signed_bid) +``` + +`max_execution_payment` is the value the validator sent in the `X-Eth-Max-Trusted-Bid` +header of the corresponding +[`getExecutionPayloadBid`][get-execution-payload-bid-api] request. Validators +MUST validate each bid against the `max_execution_payment` they sent for that request. + +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 +builder pays the validator via execution layer payments, we require that the +bid's fee recipient matches the validators expected fee recipient and not the +builder's fee recipient. + +## Block proposal + +### Constructing the `BeaconBlockBody` + +#### Receiving ExecutionPayloadBid + +To obtain execution payloads for a given `slot`, a block proposer building a +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 MUST include the `X-Eth-Max-Trusted-Bid` header on the request; + otherwise the builder will not serve a bid. The validator MAY additionally + send a `SignedRequestAuth` 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. +4. The upstream block building software constructs the corresponding + [`SignedExecutionPayloadEnvelope`][signed-execution-payload-envelope] and + broadcasts it to the PTC committee. + +## Connecting with upstream block building + +### Builder Config + +The `BuilderConfig` specifies how the client can maintain builder +configurations. It is manually maintained by an operator and +contains the information on how to call a builder and preferences for the +specific builder. It is left up to the client on how the config is passed and +parsed. + +The `BuilderWhitelist` includes the per-builder configs along with the global +preferences. + +See [`BuilderConfig`](#builderconfig) and [`GlobalPreferences`](#globalpreferences) +for field descriptions. + +Aspects such as deadline enforcement and bid selection strategy are left up to +the client implementation. + +[builder-preferences]: ./builder.md#builderpreferences +[can-builder-cover-bid]: https://github.com/ethereum/consensus-specs/blob/master/specs/gloas/beacon-chain.md#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 +[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-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 diff --git a/types/gloas/request_auth.yaml b/types/gloas/request_auth.yaml new file mode 100644 index 00000000..b8742949 --- /dev/null +++ b/types/gloas/request_auth.yaml @@ -0,0 +1,19 @@ +Gloas: + RequestAuth: + type: object + required: [builder_pubkey, slot] + properties: + builder_pubkey: + $ref: "../../beacon-apis/types/primitive.yaml#/Pubkey" + description: "The BLS public key of the builder the request is intended for." + slot: + $ref: "../../beacon-apis/types/primitive.yaml#/Uint64" + description: "The slot for which the bid is being requested." + SignedRequestAuth: + type: object + required: [message, signature] + properties: + message: + $ref: "#/Gloas/RequestAuth" + signature: + $ref: "../../beacon-apis/types/primitive.yaml#/Signature" \ No newline at end of file diff --git a/wordlist.txt b/wordlist.txt index 970e86ca..353ab834 100644 --- a/wordlist.txt +++ b/wordlist.txt @@ -28,6 +28,12 @@ validator's vc wei EIP +ePBS Fulu fulu -submitBlindedBlockV \ No newline at end of file +Gloas +gloas +Gwei +PTC +submitBlindedBlockV +ValidatorRegistrationsV \ No newline at end of file