Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions crates/common/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ pub const PATH_GET_HEADER: &str = "/header/{slot}/{parent_hash}/{pubkey}";
pub const PATH_HEADER_STREAM: &str = "/header_stream/{slot}/{parent_hash}/{pubkey}";
pub const PATH_GET_PAYLOAD: &str = "/blinded_blocks";

// Gloas (ePBS) builder-API additions, per https://github.com/ethereum/builder-specs/pull/165.
// Not yet wired to the auctioneer -- see docs/gloas-support-plan.md.
// Gloas (ePBS) builder-API additions, per
// https://github.com/ethereum/builder-specs/blob/main/specs/gloas/builder.md.
// TODO(gloas): not yet wired to the auctioneer; see gattaca-com/helix#489.
pub const PATH_GET_EXECUTION_PAYLOAD_BID: &str =
"/execution_payload_bid/{slot}/{parent_hash}/{parent_root}/{proposer_pubkey}";
pub const PATH_SUBMIT_BUILDER_PREFERENCES: &str = "/builder_preferences/{proposer_pubkey}";
Expand Down
4 changes: 2 additions & 2 deletions crates/common/src/chain_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ pub struct ChainInfo {
pub clock: SlotClock,
pub genesis_time_in_secs: u64,
pub builder_domain: B256,
/// Domain for verifying Gloas builder-API `SignedRequestAuth` signatures. Not a consensus
/// domain; see `ChainSpec::get_request_auth_domain`.
/// Domain for verifying Gloas builder-API `SignedBuilderRequestAuth` signatures. Not a
/// consensus domain; see `ChainSpec::get_request_auth_domain`.
pub request_auth_domain: B256,
}

Expand Down
6 changes: 3 additions & 3 deletions crates/relay/src/api/proposer/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,12 @@ pub enum ProposerApiError {
SszDecodeError(DecodeError),

// Gloas (ePBS) builder-API additions, per
// https://github.com/ethereum/builder-specs/pull/165. Not yet wired to the auctioneer.
#[error("invalid SignedRequestAuth: signature verification failed")]
// https://github.com/ethereum/builder-specs/blob/main/specs/gloas/builder.md.
#[error("invalid SignedBuilderRequestAuth: signature verification failed")]
InvalidRequestAuthSignature,

#[error(
"invalid SignedRequestAuth: auth.message.slot ({auth_slot}) does not match the request slot ({request_slot})"
"invalid SignedBuilderRequestAuth: auth.message.slot ({auth_slot}) does not match the request slot ({request_slot})"
)]
RequestAuthSlotMismatch { auth_slot: u64, request_slot: u64 },

Expand Down
22 changes: 10 additions & 12 deletions crates/relay/src/api/proposer/get_execution_payload_bid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use helix_common::{
decoder::Encoding,
utils::extract_request_id,
};
use helix_types::{ForkName, SignedRequestAuth};
use helix_types::{ForkName, SignedBuilderRequestAuth};
use hyper::StatusCode;
use ssz::Decode;
use tracing::info;
Expand All @@ -19,11 +19,8 @@ use crate::api::{Api, proposer::error::ProposerApiError};

impl<A: Api> ProposerApi<A> {
/// Serves a `SignedExecutionPayloadBid` for the given slot/parent_hash/parent_root to a
/// Gloas (ePBS) proposer.
///
/// Not yet wired to the auctioneer: this only validates the request per
/// <https://github.com/ethereum/builder-specs/pull/165> and always answers with the
/// spec's "no bid available" response.
/// Gloas (ePBS) proposer, per
/// <https://github.com/ethereum/builder-specs/blob/main/specs/gloas/builder.md#per-request-validator-inputs>.
#[tracing::instrument(skip_all, err(level = tracing::Level::TRACE), fields(id =% extract_request_id(&headers), slot = params.slot))]
pub async fn get_execution_payload_bid(
Extension(proposer_api): Extension<Arc<ProposerApi<A>>>,
Expand All @@ -42,10 +39,11 @@ impl<A: Api> ProposerApi<A> {
return Err(ProposerApiError::MissingTimingHeaders);
}

let signed_request_auth: SignedRequestAuth = match Encoding::from_content_type(&headers) {
Encoding::Json => serde_json::from_slice(&body)?,
Encoding::Ssz => SignedRequestAuth::from_ssz_bytes(&body)?,
};
let signed_request_auth: SignedBuilderRequestAuth =
match Encoding::from_content_type(&headers) {
Encoding::Json => serde_json::from_slice(&body)?,
Encoding::Ssz => SignedBuilderRequestAuth::from_ssz_bytes(&body)?,
};

if signed_request_auth.message.slot != params.slot {
return Err(ProposerApiError::RequestAuthSlotMismatch {
Expand All @@ -67,8 +65,8 @@ impl<A: Api> ProposerApi<A> {
);

// TODO(gloas): fetch/build the SignedExecutionPayloadBid from the auctioneer, honoring
// any stored max_execution_payment preference. Not wired in yet -- always reports "no
// bid available", which is a valid response per spec.
// any stored max_execution_payment preference. Until then, "no bid available" is a
// valid response per spec.
Ok(StatusCode::NO_CONTENT)
}
}
11 changes: 4 additions & 7 deletions crates/relay/src/api/proposer/submit_builder_preferences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,8 @@ use super::{ProposerApi, get_payload::fork_name_from_header};
use crate::api::{Api, proposer::error::ProposerApiError};

impl<A: Api> ProposerApi<A> {
/// Accepts a proposer's Gloas (ePBS) `BuilderPreferencesRequest`.
///
/// Not yet wired in: this only validates the request per
/// <https://github.com/ethereum/builder-specs/pull/165>; preferences are not yet stored
/// or enforced when serving bids.
/// Accepts a proposer's Gloas (ePBS) `BuilderPreferencesRequest`, per
/// <https://github.com/ethereum/builder-specs/blob/main/specs/gloas/builder.md#builder-preferences>.
#[tracing::instrument(skip_all, err(level = tracing::Level::TRACE), fields(id =% extract_request_id(&headers)))]
pub async fn submit_builder_preferences(
Extension(proposer_api): Extension<Arc<ProposerApi<A>>>,
Expand All @@ -44,11 +41,11 @@ impl<A: Api> ProposerApi<A> {
proposer_pubkey = ?params.proposer_pubkey,
slot = request.auth.message.slot,
max_execution_payment = request.preferences.max_execution_payment,
"validated submitBuilderPreferences request (not yet persisted -- storage not wired in)"
"validated submitBuilderPreferences request (not yet persisted)"
);

// TODO(gloas): reject stale slots, store preferences per proposer per slot, and honor
// max_execution_payment when serving bids. Not wired in yet.
// max_execution_payment when serving bids.
Ok(StatusCode::ACCEPTED)
}
}
27 changes: 13 additions & 14 deletions crates/types/src/request_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
//! `getExecutionPayloadBid` and `submitBuilderPreferences` requests, and to carry a
//! proposer's per-builder payment preferences.
//!
//! See <https://github.com/ethereum/builder-specs/pull/165>. Not yet part of any merged
//! spec, so field shapes may still change.
//! See <https://github.com/ethereum/builder-specs/blob/main/specs/gloas/validator.md#new-containers>.

use alloy_primitives::B256;
use lh_types::SignedRoot;
Expand All @@ -23,25 +22,25 @@ crate::ssz_bytes_wrapper! {
/// Authenticates a `getExecutionPayloadBid` or `submitBuilderPreferences` request. Signed
/// under `DOMAIN_REQUEST_AUTH`, distinct from the in-protocol `DOMAIN_BEACON_BUILDER`.
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode, TreeHash)]
pub struct RequestAuth {
pub struct BuilderRequestAuth {
/// Opaque authentication data agreed with the builder out of band.
pub data: RequestAuthData,
/// The proposal slot this request is authorized for.
#[serde(with = "serde_utils::quoted_u64")]
pub slot: u64,
}

impl SignedRoot for RequestAuth {}
impl SignedRoot for BuilderRequestAuth {}

#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode)]
pub struct SignedRequestAuth {
pub message: RequestAuth,
pub struct SignedBuilderRequestAuth {
pub message: BuilderRequestAuth,
pub signature: BlsSignatureBytes,
}

impl SignedRequestAuth {
impl SignedBuilderRequestAuth {
/// `pubkey` is resolved from the `proposer_pubkey` path parameter, not carried inside
/// `RequestAuth` itself. `domain` is `ChainInfo::request_auth_domain`.
/// `BuilderRequestAuth` itself. `domain` is `ChainInfo::request_auth_domain`.
pub fn verify_signature(
&self,
pubkey: &BlsPublicKeyBytes,
Expand Down Expand Up @@ -72,7 +71,7 @@ pub struct BuilderPreferences {
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone, Encode, Decode)]
pub struct BuilderPreferencesRequest {
pub preferences: BuilderPreferences,
pub auth: SignedRequestAuth,
pub auth: SignedBuilderRequestAuth,
}

#[cfg(test)]
Expand All @@ -82,8 +81,8 @@ mod tests {
use super::*;
use crate::BlsKeypair;

fn sample_request_auth() -> RequestAuth {
RequestAuth { data: RequestAuthData(vec![1, 2, 3, 4].into()), slot: 123 }
fn sample_request_auth() -> BuilderRequestAuth {
BuilderRequestAuth { data: RequestAuthData(vec![1, 2, 3, 4].into()), slot: 123 }
}

#[test]
Expand All @@ -97,14 +96,14 @@ mod tests {
fn request_auth_ssz_round_trip() {
let auth = sample_request_auth();
let bytes = auth.as_ssz_bytes();
assert_eq!(auth, RequestAuth::from_ssz_bytes(&bytes).unwrap());
assert_eq!(auth, BuilderRequestAuth::from_ssz_bytes(&bytes).unwrap());
}

#[test]
fn builder_preferences_request_json_round_trip() {
let request = BuilderPreferencesRequest {
preferences: BuilderPreferences { max_execution_payment: 42 },
auth: SignedRequestAuth {
auth: SignedBuilderRequestAuth {
message: sample_request_auth(),
signature: BlsSignatureBytes::default(),
},
Expand All @@ -121,7 +120,7 @@ mod tests {
let root = message.signing_root(domain);
let signature = keypair.sk.sign(root);

let signed = SignedRequestAuth { message, signature: signature.serialize().into() };
let signed = SignedBuilderRequestAuth { message, signature: signature.serialize().into() };
let pubkey: BlsPublicKeyBytes = keypair.pk.serialize().into();

signed.verify_signature(&pubkey, domain).expect("valid signature should verify");
Expand Down
Loading