Skip to content

Commit

Permalink
implement client/server for relay specs
Browse files Browse the repository at this point in the history
  • Loading branch information
ralexstokes committed Jul 31, 2023
1 parent a591927 commit e738668
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 15 deletions.
4 changes: 3 additions & 1 deletion mev-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ license = "MIT OR Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
default = ["serde", "api", "engine-proxy"]
default = ["serde", "builder-api", "relay-api", "engine-proxy"]
builder-api = ["api"]
relay-api = ["api"]
api = ["tokio", "axum", "hyper", "beacon-api-client", "tracing", "serde_json"]
engine-proxy = ["serde", "api", "anvil-rpc", "reqwest", "serde_json"]

Expand Down
12 changes: 2 additions & 10 deletions mev-rs/src/blinded_block_provider/api/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ use crate::{
use axum::{
extract::{Json, Path, State},
http::StatusCode,
response::{IntoResponse, Response},
response::IntoResponse,
routing::{get, post, IntoMakeService},
Router,
};
use beacon_api_client::{ApiError, Error as ApiClientError, VersionedValue};
use beacon_api_client::{Error as ApiClientError, VersionedValue};
use hyper::server::conn::AddrIncoming;
use serde::Deserialize;
use std::net::{Ipv4Addr, SocketAddr};
Expand All @@ -22,14 +22,6 @@ use tokio::task::JoinHandle;
/// Type alias for the configured axum server
pub type BlockProviderServer = axum::Server<AddrIncoming, IntoMakeService<Router>>;

impl IntoResponse for Error {
fn into_response(self) -> Response {
let message = self.to_string();
let code = StatusCode::BAD_REQUEST;
(code, Json(ApiError::ErrorMessage { code, message })).into_response()
}
}

async fn handle_status_check() -> impl IntoResponse {
StatusCode::OK
}
Expand Down
4 changes: 2 additions & 2 deletions mev-rs/src/blinded_block_provider/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[cfg(feature = "api")]
#[cfg(feature = "builder-api")]
mod api;

#[cfg(feature = "api")]
#[cfg(feature = "builder-api")]
pub use {api::client::Client, api::server::Server};

use crate::{
Expand Down
41 changes: 41 additions & 0 deletions mev-rs/src/blinded_block_relayer/api/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use crate::{
blinded_block_relayer::BlindedBlockRelayer,
types::{ProposerSchedule, SignedBidReceipt, SignedBidSubmission},
Error,
};
use beacon_api_client::{mainnet::Client as BeaconApiClient, ApiResult, Error as ApiError};

/// A `Client` for a service implementing the Builder APIs.
/// Note that `Client` does not implement the `Builder` trait so that
/// it can provide more flexibility to callers with respect to the types
/// it accepts.
#[derive(Clone)]
pub struct Client {
api: BeaconApiClient,
}

impl Client {
pub fn new(api_client: BeaconApiClient) -> Self {
Self { api: api_client }
}
}

#[async_trait::async_trait]
impl BlindedBlockRelayer for Client {
async fn get_proposal_schedule(&self) -> Result<Vec<ProposerSchedule>, Error> {
self.api.get("/relay/v1/builder/validators").await.map_err(From::from)
}

// TODO support content types
async fn submit_bid(
&self,
signed_submission: &SignedBidSubmission,
) -> Result<SignedBidReceipt, Error> {
let response = self.api.http_post("/relay/v1/builder/blocks", signed_submission).await?;
let receipt: ApiResult<SignedBidReceipt> = response.json().await.map_err(ApiError::from)?;
match receipt {
ApiResult::Ok(receipt) => Ok(receipt),
ApiResult::Err(err) => Err(ApiError::from(err).into()),
}
}
}
2 changes: 2 additions & 0 deletions mev-rs/src/blinded_block_relayer/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod client;
pub mod server;
65 changes: 65 additions & 0 deletions mev-rs/src/blinded_block_relayer/api/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::{
blinded_block_relayer::BlindedBlockRelayer,
error::Error,
types::{ProposerSchedule, SignedBidReceipt, SignedBidSubmission},
};
use axum::{
extract::{Json, State},
routing::{get, post, IntoMakeService},
Router,
};
use hyper::server::conn::AddrIncoming;
use std::net::{Ipv4Addr, SocketAddr};
use tokio::task::JoinHandle;

/// Type alias for the configured axum server
pub type BlockRelayerServer = axum::Server<AddrIncoming, IntoMakeService<Router>>;

async fn handle_get_proposal_schedule<R: BlindedBlockRelayer>(
State(relayer): State<R>,
) -> Result<Json<Vec<ProposerSchedule>>, Error> {
tracing::info!("serving proposal schedule for current and next epoch");
Ok(Json(relayer.get_proposal_schedule().await?))
}

async fn handle_submit_bid<R: BlindedBlockRelayer>(
State(relayer): State<R>,
Json(signed_bid_submission): Json<SignedBidSubmission>,
) -> Result<Json<SignedBidReceipt>, Error> {
tracing::info!("handling bid submission");
Ok(Json(relayer.submit_bid(&signed_bid_submission).await?))
}

pub struct Server<R: BlindedBlockRelayer> {
host: Ipv4Addr,
port: u16,
relayer: R,
}

impl<R: BlindedBlockRelayer + Clone + Send + Sync + 'static> Server<R> {
pub fn new(host: Ipv4Addr, port: u16, relayer: R) -> Self {
Self { host, port, relayer }
}

/// Configures and returns the axum server
pub fn serve(&self) -> BlockRelayerServer {
let router = Router::new()
.route("/relay/v1/builder/validators", get(handle_get_proposal_schedule::<R>))
.route("/relay/v1/builder/blocks", post(handle_submit_bid::<R>))
.with_state(self.relayer.clone());
let addr = SocketAddr::from((self.host, self.port));
axum::Server::bind(&addr).serve(router.into_make_service())
}

/// Spawns the server on a new task returning the handle for it
pub fn spawn(&self) -> JoinHandle<()> {
let server = self.serve();
let address = server.local_addr();
tokio::spawn(async move {
tracing::info!("listening at {address}...");
if let Err(err) = server.await {
tracing::error!("error while listening for incoming: {err}")
}
})
}
}
22 changes: 22 additions & 0 deletions mev-rs/src/blinded_block_relayer/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#[cfg(feature = "relay-api")]
mod api;

#[cfg(feature = "relay-api")]
pub use {api::client::Client, api::server::Server};

use crate::{
error::Error,
types::{ProposerSchedule, SignedBidReceipt, SignedBidSubmission},
};
use async_trait::async_trait;

#[async_trait]
pub trait BlindedBlockRelayer {
async fn get_proposal_schedule(&self) -> Result<Vec<ProposerSchedule>, Error>;

// TODO: support cancellations?
async fn submit_bid(
&self,
signed_submission: &SignedBidSubmission,
) -> Result<SignedBidReceipt, Error>;
}
16 changes: 16 additions & 0 deletions mev-rs/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,19 @@ pub enum Error {
#[error("{0}")]
EngineApi(#[from] crate::engine_api_proxy::Error),
}

#[cfg(feature = "api")]
use axum::extract::Json;
#[cfg(feature = "api")]
use axum::http::StatusCode;
#[cfg(feature = "api")]
use axum::response::{IntoResponse, Response};

#[cfg(feature = "api")]
impl IntoResponse for Error {
fn into_response(self) -> Response {
let message = self.to_string();
let code = StatusCode::BAD_REQUEST;
(code, Json(beacon_api_client::ApiError::ErrorMessage { code, message })).into_response()
}
}
1 change: 1 addition & 0 deletions mev-rs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod blinded_block_provider;
pub mod blinded_block_relayer;
#[cfg(feature = "engine-proxy")]
pub mod engine_api_proxy;
mod error;
Expand Down
62 changes: 60 additions & 2 deletions mev-rs/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use crate::signing::{
pub use ethereum_consensus::builder::SignedValidatorRegistration;
use ethereum_consensus::{
crypto::SecretKey,
primitives::{BlsPublicKey, Hash32, Root, Slot, ValidatorIndex},
primitives::{
BlsPublicKey, BlsSignature, ExecutionAddress, Hash32, Root, Slot, ValidatorIndex,
},
state_transition::{Context, Error},
};
use ssz_rs::prelude::*;
Expand Down Expand Up @@ -218,7 +220,7 @@ impl SignedBlindedBeaconBlock {
}
}

#[derive(Debug)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "version", content = "data"))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
Expand All @@ -227,6 +229,12 @@ pub enum ExecutionPayload {
Capella(capella::ExecutionPayload),
}

impl Default for ExecutionPayload {
fn default() -> Self {
Self::Bellatrix(Default::default())
}
}

impl ExecutionPayload {
pub fn block_hash(&self) -> &Hash32 {
match self {
Expand Down Expand Up @@ -274,3 +282,53 @@ impl ExecutionPayloadHeader {
}
}
}

#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProposerSchedule {
#[serde(with = "crate::serde::as_string")]
pub slot: Slot,
#[serde(with = "crate::serde::as_string")]
pub validator_index: ValidatorIndex,
pub entry: SignedValidatorRegistration,
}

#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BidTrace {
#[serde(with = "crate::serde::as_string")]
pub slot: Slot,
pub parent_hash: Hash32,
pub block_hash: Hash32,
#[serde(rename = "builder_pubkey")]
pub builder_public_key: BlsPublicKey,
pub proposer_fee_recipient: ExecutionAddress,
#[serde(with = "crate::serde::as_string")]
pub gas_limit: u64,
#[serde(with = "crate::serde::as_string")]
pub gas_used: u64,
pub value: U256,
}

#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SignedBidSubmission {
pub message: BidTrace,
pub execution_payload: ExecutionPayload,
pub signature: BlsSignature,
}

#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BidReceipt {
#[serde(with = "crate::serde::as_string")]
pub receive_timestamp: u64,
pub bid_trace: BidTrace,
}

#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SignedBidReceipt {
pub message: BidReceipt,
pub signature: BlsSignature,
}

0 comments on commit e738668

Please sign in to comment.