Skip to content

Commit

Permalink
Merge pull request #4 from ralexstokes/add-validations
Browse files Browse the repository at this point in the history
Refactor around a Builder API trait
  • Loading branch information
ralexstokes authored May 14, 2022
2 parents a019dc3 + cc3f53d commit 1cd28b7
Show file tree
Hide file tree
Showing 10 changed files with 303 additions and 317 deletions.
12 changes: 7 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ tokio = { version = "1.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
futures = "0.3.21"
async-trait = "0.1.53"

axum = "0.5.4"
url = { version = "2.2.2", default-features = false }
Expand Down
31 changes: 31 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::types::{BidRequest, SignedBuilderBid};
use async_trait::async_trait;
use beacon_api_client::ApiError;
use ethereum_consensus::bellatrix::mainnet::{ExecutionPayload, SignedBlindedBeaconBlock};
use ethereum_consensus::builder::SignedValidatorRegistration;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
#[error("{0}")]
Api(#[from] ApiError),
#[error("internal server error")]
Internal(String),
#[error("{0}")]
Custom(String),
}

#[async_trait]
pub trait Builder {
async fn register_validator(
&self,
registration: &SignedValidatorRegistration,
) -> Result<(), Error>;

async fn fetch_best_bid(&self, bid_request: &BidRequest) -> Result<SignedBuilderBid, Error>;

async fn open_bid(
&self,
signed_block: &SignedBlindedBeaconBlock,
) -> Result<ExecutionPayload, Error>;
}
138 changes: 35 additions & 103 deletions src/builder_api_server.rs
Original file line number Diff line number Diff line change
@@ -1,174 +1,106 @@
use crate::relay_mux::{Error as RelayMuxError, RelayMux};
use crate::builder::{Builder, Error};
use crate::types::{
BidRequest, ExecutionPayload, SignedBlindedBeaconBlock, SignedBuilderBid,
SignedValidatorRegistration,
};
use axum::routing::{get, post};
use axum::{
extract::{Extension, Json, Path},
http::StatusCode,
response::{IntoResponse, Response},
routing::{get, post},
Router,
};
use beacon_api_client::{ApiError, ConsensusVersion, Error as BeaconApiError, VersionedValue};
use beacon_api_client::{ApiError, ConsensusVersion, VersionedValue};
use std::net::{Ipv4Addr, SocketAddr};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
#[error("unknown parent hash in proposal request")]
UnknownHash,
#[error("unknown validator with pubkey in proposal request")]
UnknownValidator,
#[error("unknown fee recipient for proposer given in proposal request")]
UnknownFeeRecipient,
#[error("block does not match the provided header")]
UnknownBlock,
#[error("invalid signature")]
InvalidSignature,
#[error("invalid timestamp")]
InvalidTimestamp,
#[error("issue with relay mux: {0}")]
Relay(#[from] RelayMuxError),
#[error("internal server error")]
Internal,
}

impl IntoResponse for Error {
fn into_response(self) -> Response {
let message = self.to_string();
let code = match self {
Self::UnknownHash => StatusCode::BAD_REQUEST,
Self::UnknownValidator => StatusCode::BAD_REQUEST,
Self::UnknownFeeRecipient => StatusCode::BAD_REQUEST,
Self::UnknownBlock => StatusCode::BAD_REQUEST,
Self::InvalidSignature => StatusCode::BAD_REQUEST,
Self::InvalidTimestamp => StatusCode::BAD_REQUEST,
Self::Relay(err) => match err {
RelayMuxError::Relay(BeaconApiError::Api(ApiError { code, .. })) => {
StatusCode::from_u16(code).expect("constructed safely")
}
_ => StatusCode::BAD_REQUEST,
},
Self::Internal => StatusCode::INTERNAL_SERVER_ERROR,
Self::Internal(..) => StatusCode::INTERNAL_SERVER_ERROR,
_ => StatusCode::BAD_REQUEST,
};
(
code,
Json(ApiError {
code: code.as_u16(),
message,
}),
)
.into_response()
(code, Json(ApiError { code, message })).into_response()
}
}

async fn validate_registration(_registration: &SignedValidatorRegistration) -> Result<(), Error> {
// TODO validations
Ok(())
}

async fn validate_bid_request(_bid_request: &BidRequest) -> Result<(), Error> {
// TODO validations
Ok(())
}

async fn validate_bid(_bid: &SignedBuilderBid) -> Result<(), Error> {
// TODO validations
Ok(())
}

async fn validate_signed_block(_signed_block: &SignedBlindedBeaconBlock) -> Result<(), Error> {
// TODO validations
Ok(())
}

async fn validate_execution_payload(_execution_payload: &ExecutionPayload) -> Result<(), Error> {
// TODO validations
Ok(())
}

async fn handle_status_check() -> impl IntoResponse {
tracing::debug!("status check");
StatusCode::OK
}

async fn handle_validator_registration(
async fn handle_validator_registration<B: Builder>(
Json(registration): Json<SignedValidatorRegistration>,
Extension(relay_mux): Extension<RelayMux>,
Extension(builder): Extension<B>,
) -> Result<(), Error> {
tracing::debug!("processing registration {registration:?}");

validate_registration(&registration).await?;

relay_mux.register_validator(&registration).await?;

Ok(())
builder
.register_validator(&registration)
.await
.map_err(From::from)
}

async fn handle_fetch_bid(
async fn handle_fetch_bid<B: Builder>(
Path(bid_request): Path<BidRequest>,
Extension(relay_mux): Extension<RelayMux>,
Extension(builder): Extension<B>,
) -> Result<Json<VersionedValue<SignedBuilderBid>>, Error> {
tracing::debug!("fetching best bid for block for request {bid_request:?}");

validate_bid_request(&bid_request).await?;

let bid = relay_mux.fetch_best_bid(&bid_request).await?;

validate_bid(&bid).await?;
let signed_bid = builder.fetch_best_bid(&bid_request).await?;

Ok(Json(VersionedValue {
version: ConsensusVersion::Bellatrix,
data: bid,
data: signed_bid,
}))
}

async fn handle_accept_bid(
async fn handle_open_bid<B: Builder>(
Json(block): Json<SignedBlindedBeaconBlock>,
Extension(relay_mux): Extension<RelayMux>,
Extension(builder): Extension<B>,
) -> Result<Json<VersionedValue<ExecutionPayload>>, Error> {
tracing::debug!("accepting bid for block {block:?}");

validate_signed_block(&block).await?;
tracing::debug!("opening bid for block {block:?}");

let payload = relay_mux.accept_bid(&block).await?;

validate_execution_payload(&payload).await?;
let payload = builder.open_bid(&block).await?;

Ok(Json(VersionedValue {
version: ConsensusVersion::Bellatrix,
data: payload,
}))
}

pub struct Server {
pub struct Server<B: Builder> {
host: Ipv4Addr,
port: u16,
builder: B,
}

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

pub async fn run(&self, relay_mux: RelayMux) {
pub async fn run(&self) {
let router = Router::new()
.route("/eth/v1/builder/status", get(handle_status_check))
.route(
"/eth/v1/builder/validators",
post(handle_validator_registration),
post(handle_validator_registration::<B>),
)
.route(
"/eth/v1/builder/header/:slot/:parent_hash/:public_key",
get(handle_fetch_bid),
get(handle_fetch_bid::<B>),
)
.route("/eth/v1/builder/blinded_blocks", post(handle_accept_bid))
.layer(Extension(relay_mux));
.route("/eth/v1/builder/blinded_blocks", post(handle_open_bid::<B>))
.layer(Extension(self.builder.clone()));
let addr = SocketAddr::from((self.host, self.port));
let server = axum::Server::bind(&addr).serve(router.into_make_service());

tracing::info!("listening at {addr}...");
tracing::info!("relay server listening at {addr}...");
if let Err(err) = server.await {
tracing::error!("error while listening for incoming: {err}")
}
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod builder_api_server;
pub mod builder;
pub mod builder_api_server;
mod relay;
mod relay_mux;
mod serde;
Expand Down
47 changes: 31 additions & 16 deletions src/relay.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
use crate::builder::{Builder, Error as BuilderError};
use crate::types::{
BidRequest, ExecutionPayload, SignedBlindedBeaconBlock, SignedBuilderBid,
SignedValidatorRegistration,
};
use beacon_api_client::{Client as BeaconApiClient, Error, VersionedValue};
use async_trait::async_trait;
use beacon_api_client::{api_error_or_ok, Client as BeaconApiClient, VersionedValue};

pub type Error = beacon_api_client::Error;

impl From<Error> for BuilderError {
fn from(err: Error) -> BuilderError {
match err {
Error::Api(err) => err.into(),
err => BuilderError::Internal(err.to_string()),
}
}
}

pub struct Relay {
api: BeaconApiClient,
Expand All @@ -14,27 +27,29 @@ impl Relay {
}

pub async fn check_status(&self) -> Result<(), Error> {
let _ = self
.api
.http_get("/eth/v1/builder/status")
.await?
.error_for_status()?;
Ok(())
let response = self.api.http_get("/eth/v1/builder/status").await?;
api_error_or_ok(response).await
}
}

pub async fn register_validator(
#[async_trait]
impl Builder for Relay {
async fn register_validator(
&self,
registration: &SignedValidatorRegistration,
) -> Result<(), Error> {
let _ = self
) -> Result<(), BuilderError> {
let response = self
.api
.http_post("/eth/v1/builder/validators", registration)
.await?
.error_for_status()?;
Ok(())
.await?;
let result = api_error_or_ok(response).await?;
Ok(result)
}

pub async fn fetch_bid(&self, bid_request: &BidRequest) -> Result<SignedBuilderBid, Error> {
async fn fetch_best_bid(
&self,
bid_request: &BidRequest,
) -> Result<SignedBuilderBid, BuilderError> {
let target = format!(
"/eth/v1/builder/header/{}/{}/{}",
bid_request.slot, bid_request.parent_hash, bid_request.public_key
Expand All @@ -43,10 +58,10 @@ impl Relay {
Ok(response.data)
}

pub async fn accept_bid(
async fn open_bid(
&self,
signed_block: &SignedBlindedBeaconBlock,
) -> Result<ExecutionPayload, Error> {
) -> Result<ExecutionPayload, BuilderError> {
let response: VersionedValue<ExecutionPayload> = self
.api
.post("/eth/v1/builder/blinded_blocks", signed_block)
Expand Down
Loading

0 comments on commit 1cd28b7

Please sign in to comment.