Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 2 additions & 2 deletions crates/cli/src/docker_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use cb_common::{
SIGNER_DIR_SECRETS_ENV, SIGNER_ENDPOINT_ENV, SIGNER_KEYS_ENV, SIGNER_MODULE_NAME,
SIGNER_PORT_DEFAULT, SIGNER_URL_ENV,
},
pbs::{BUILDER_API_PATH, GET_STATUS_PATH},
pbs::{BUILDER_V1_API_PATH, GET_STATUS_PATH},
signer::{ProxyStore, SignerLoader},
types::ModuleId,
utils::random_jwt_secret,
Expand Down Expand Up @@ -308,7 +308,7 @@ pub async fn handle_docker_init(config_path: PathBuf, output_dir: PathBuf) -> Re
healthcheck: Some(Healthcheck {
test: Some(HealthcheckTest::Single(format!(
"curl -f http://localhost:{}{}{}",
cb_config.pbs.pbs_config.port, BUILDER_API_PATH, GET_STATUS_PATH
cb_config.pbs.pbs_config.port, BUILDER_V1_API_PATH, GET_STATUS_PATH
))),
interval: Some("30s".into()),
timeout: Some("5s".into()),
Expand Down
30 changes: 30 additions & 0 deletions crates/common/src/pbs/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use std::fmt::{Display, Formatter, Result};

use serde::{Deserialize, Serialize};

use crate::pbs::{BUILDER_V1_API_PATH, BUILDER_V2_API_PATH};

// Version of the builder API for various routes
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum BuilderApiVersion {
V1 = 1,
V2,
}
impl BuilderApiVersion {
pub const fn path(&self) -> &'static str {
match self {
BuilderApiVersion::V1 => BUILDER_V1_API_PATH,
BuilderApiVersion::V2 => BUILDER_V2_API_PATH,
}
}
}
impl Display for BuilderApiVersion {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let s = match self {
BuilderApiVersion::V1 => "v1",
BuilderApiVersion::V2 => "v2",
};
write!(f, "{}", s)
}
}
3 changes: 2 additions & 1 deletion crates/common/src/pbs/constants.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::constants::COMMIT_BOOST_VERSION;

pub const BUILDER_API_PATH: &str = "/eth/v1/builder";
pub const BUILDER_V1_API_PATH: &str = "/eth/v1/builder";
pub const BUILDER_V2_API_PATH: &str = "/eth/v2/builder";

pub const GET_HEADER_PATH: &str = "/header/{slot}/{parent_hash}/{pubkey}";
pub const GET_STATUS_PATH: &str = "/status";
Expand Down
7 changes: 4 additions & 3 deletions crates/common/src/pbs/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use super::{
};
use crate::{
config::{load_optional_env_var, BUILDER_URLS_ENV, HTTP_TIMEOUT_SECONDS_DEFAULT},
pbs::BUILDER_EVENTS_PATH,
pbs::{BuilderApiVersion, BUILDER_EVENTS_PATH},
};

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand All @@ -29,8 +29,9 @@ pub enum BuilderEvent {
GetHeaderResponse(Box<Option<GetHeaderResponse>>),
GetStatusEvent,
GetStatusResponse,
SubmitBlockRequest(Box<SignedBlindedBeaconBlock>),
SubmitBlockResponse(Box<SubmitBlindedBlockResponse>),
SubmitBlockRequest(Box<SignedBlindedBeaconBlock>, BuilderApiVersion),
SubmitBlockResponseV1(Box<SubmitBlindedBlockResponse>),
SubmitBlockResponseV2,
MissedPayload {
/// Hash for the block for which no payload was received
block_hash: B256,
Expand Down
2 changes: 2 additions & 0 deletions crates/common/src/pbs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod builder;
mod constants;
pub mod error;
mod event;
mod relay;
mod types;

pub use builder::*;
pub use constants::*;
pub use event::*;
pub use relay::*;
Expand Down
25 changes: 16 additions & 9 deletions crates/common/src/pbs/relay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ use serde::{Deserialize, Serialize};
use url::Url;

use super::{
constants::{BUILDER_API_PATH, GET_STATUS_PATH, REGISTER_VALIDATOR_PATH, SUBMIT_BLOCK_PATH},
constants::{GET_STATUS_PATH, REGISTER_VALIDATOR_PATH, SUBMIT_BLOCK_PATH},
error::PbsError,
HEADER_VERSION_KEY, HEADER_VERSION_VALUE,
};
use crate::{config::RelayConfig, DEFAULT_REQUEST_TIMEOUT};
use crate::{config::RelayConfig, pbs::BuilderApiVersion, DEFAULT_REQUEST_TIMEOUT};

/// A parsed entry of the relay url in the format: scheme://pubkey@host
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -101,8 +101,12 @@ impl RelayClient {

Ok(url)
}
pub fn builder_api_url(&self, path: &str) -> Result<Url, PbsError> {
self.get_url(&format!("{BUILDER_API_PATH}{path}"))
pub fn builder_api_url(
&self,
path: &str,
api_version: BuilderApiVersion,
) -> Result<Url, PbsError> {
self.get_url(&format!("{}{path}", api_version.path()))
}

pub fn get_header_url(
Expand All @@ -111,19 +115,22 @@ impl RelayClient {
parent_hash: B256,
validator_pubkey: BlsPublicKey,
) -> Result<Url, PbsError> {
self.builder_api_url(&format!("/header/{slot}/{parent_hash}/{validator_pubkey}"))
self.builder_api_url(
&format!("/header/{slot}/{parent_hash}/{validator_pubkey}"),
BuilderApiVersion::V1,
)
}

pub fn get_status_url(&self) -> Result<Url, PbsError> {
self.builder_api_url(GET_STATUS_PATH)
self.builder_api_url(GET_STATUS_PATH, BuilderApiVersion::V1)
}

pub fn register_validator_url(&self) -> Result<Url, PbsError> {
self.builder_api_url(REGISTER_VALIDATOR_PATH)
self.builder_api_url(REGISTER_VALIDATOR_PATH, BuilderApiVersion::V1)
}

pub fn submit_block_url(&self) -> Result<Url, PbsError> {
self.builder_api_url(SUBMIT_BLOCK_PATH)
pub fn submit_block_url(&self, api_version: BuilderApiVersion) -> Result<Url, PbsError> {
self.builder_api_url(SUBMIT_BLOCK_PATH, api_version)
}
}

Expand Down
6 changes: 3 additions & 3 deletions crates/common/src/pbs/types/beacon_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ mod tests {
// this is from mev-boost test data
fn test_signed_blinded_block_fb_electra() {
let data = include_str!("testdata/signed-blinded-beacon-block-electra.json");
let block = test_encode_decode::<SignedBlindedBeaconBlock>(&data);
let block = test_encode_decode::<SignedBlindedBeaconBlock>(data);
assert!(matches!(block.message, BlindedBeaconBlock::Electra(_)));
}

Expand Down Expand Up @@ -166,7 +166,7 @@ mod tests {
// this is dummy data generated with https://github.com/attestantio/go-eth2-client
fn test_signed_blinded_block_ssz() {
let data_json = include_str!("testdata/signed-blinded-beacon-block-electra-2.json");
let block_json = test_encode_decode::<SignedBlindedBeaconBlock>(&data_json);
let block_json = test_encode_decode::<SignedBlindedBeaconBlock>(data_json);
assert!(matches!(block_json.message, BlindedBeaconBlock::Electra(_)));

let data_ssz = include_bytes!("testdata/signed-blinded-beacon-block-electra-2.ssz");
Expand All @@ -181,7 +181,7 @@ mod tests {
// this is dummy data generated with https://github.com/attestantio/go-builder-client
fn test_execution_payload_block_ssz() {
let data_json = include_str!("testdata/execution-payload-electra.json");
let block_json = test_encode_decode::<PayloadAndBlobsElectra>(&data_json);
let block_json = test_encode_decode::<PayloadAndBlobsElectra>(data_json);

let data_ssz = include_bytes!("testdata/execution-payload-electra.ssz");
let data_ssz = alloy::primitives::hex::decode(data_ssz).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion crates/common/src/pbs/types/execution_payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ mod tests {
"excess_blob_gas": "95158272"
}"#;

let parsed = test_encode_decode::<ExecutionPayloadHeader<ElectraSpec>>(&data);
let parsed = test_encode_decode::<ExecutionPayloadHeader<ElectraSpec>>(data);

assert_eq!(
parsed.parent_hash,
Expand Down
4 changes: 2 additions & 2 deletions crates/common/src/pbs/types/get_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ mod tests {
}
}"#;

let parsed = test_encode_decode::<GetHeaderResponse>(&data);
let parsed = test_encode_decode::<GetHeaderResponse>(data);
let VersionedResponse::Electra(parsed) = parsed;

assert_eq!(parsed.message.value, U256::from(1));
Expand All @@ -187,7 +187,7 @@ mod tests {
let data_json = include_str!("testdata/get-header-response.json");
let block_json = test_encode_decode::<
SignedExecutionPayloadHeader<ExecutionPayloadHeaderMessageElectra>,
>(&data_json);
>(data_json);

let data_ssz = include_bytes!("testdata/get-header-response.ssz");
let data_ssz = alloy::primitives::hex::decode(data_ssz).unwrap();
Expand Down
11 changes: 7 additions & 4 deletions crates/pbs/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use alloy::rpc::types::beacon::relay::ValidatorRegistration;
use async_trait::async_trait;
use axum::{http::HeaderMap, Router};
use cb_common::pbs::{
GetHeaderParams, GetHeaderResponse, SignedBlindedBeaconBlock, SubmitBlindedBlockResponse,
BuilderApiVersion, GetHeaderParams, GetHeaderResponse, SignedBlindedBeaconBlock,
SubmitBlindedBlockResponse,
};

use crate::{
Expand Down Expand Up @@ -31,13 +32,15 @@ pub trait BuilderApi<S: BuilderApiState>: 'static {
mev_boost::get_status(req_headers, state).await
}

/// https://ethereum.github.io/builder-specs/#/Builder/submitBlindedBlock
/// https://ethereum.github.io/builder-specs/#/Builder/submitBlindedBlock and
/// https://ethereum.github.io/builder-specs/#/Builder/submitBlindedBlockV2
async fn submit_block(
signed_blinded_block: SignedBlindedBeaconBlock,
req_headers: HeaderMap,
state: PbsState<S>,
) -> eyre::Result<SubmitBlindedBlockResponse> {
mev_boost::submit_block(signed_blinded_block, req_headers, state).await
api_version: &BuilderApiVersion,
) -> eyre::Result<Option<SubmitBlindedBlockResponse>> {
mev_boost::submit_block(signed_blinded_block, req_headers, state, api_version).await
}

/// https://ethereum.github.io/builder-specs/#/Builder/registerValidator
Expand Down
27 changes: 19 additions & 8 deletions crates/pbs/src/mev_boost/submit_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use axum::http::{HeaderMap, HeaderValue};
use cb_common::{
pbs::{
error::{PbsError, ValidationError},
BlindedBeaconBlock, BlindedBeaconBlockElectra, PayloadAndBlobsElectra, RelayClient,
SignedBlindedBeaconBlock, SubmitBlindedBlockResponse, VersionedResponse,
BlindedBeaconBlock, BlindedBeaconBlockElectra, BuilderApiVersion, PayloadAndBlobsElectra,
RelayClient, SignedBlindedBeaconBlock, SubmitBlindedBlockResponse, VersionedResponse,
HEADER_START_TIME_UNIX_MS,
},
utils::{get_user_agent_with_version, read_chunked_body_with_max, utcnow_ms},
Expand All @@ -23,12 +23,15 @@ use crate::{
state::{BuilderApiState, PbsState},
};

/// Implements https://ethereum.github.io/builder-specs/#/Builder/submitBlindedBlock
/// Implements https://ethereum.github.io/builder-specs/#/Builder/submitBlindedBlock and
/// https://ethereum.github.io/builder-specs/#/Builder/submitBlindedBlockV2. Use `api_version` to
/// distinguish between the two.
pub async fn submit_block<S: BuilderApiState>(
signed_blinded_block: SignedBlindedBeaconBlock,
req_headers: HeaderMap,
state: PbsState<S>,
) -> eyre::Result<SubmitBlindedBlockResponse> {
api_version: &BuilderApiVersion,
) -> eyre::Result<Option<SubmitBlindedBlockResponse>> {
// prepare headers
let mut send_headers = HeaderMap::new();
send_headers.insert(HEADER_START_TIME_UNIX_MS, HeaderValue::from(utcnow_ms()));
Expand All @@ -42,6 +45,7 @@ pub async fn submit_block<S: BuilderApiState>(
relay,
send_headers.clone(),
state.pbs_config().timeout_get_payload_ms,
api_version,
)));
}

Expand All @@ -59,8 +63,9 @@ async fn submit_block_with_timeout(
relay: &RelayClient,
headers: HeaderMap,
timeout_ms: u64,
) -> Result<SubmitBlindedBlockResponse, PbsError> {
let url = relay.submit_block_url()?;
api_version: &BuilderApiVersion,
) -> Result<Option<SubmitBlindedBlockResponse>, PbsError> {
let url = relay.submit_block_url(api_version.clone())?;
let mut remaining_timeout_ms = timeout_ms;
let mut retry = 0;
let mut backoff = Duration::from_millis(250);
Expand All @@ -74,6 +79,7 @@ async fn submit_block_with_timeout(
headers.clone(),
remaining_timeout_ms,
retry,
api_version,
)
.await
{
Expand Down Expand Up @@ -107,7 +113,8 @@ async fn send_submit_block(
headers: HeaderMap,
timeout_ms: u64,
retry: u32,
) -> Result<SubmitBlindedBlockResponse, PbsError> {
api_version: &BuilderApiVersion,
) -> Result<Option<SubmitBlindedBlockResponse>, PbsError> {
let start_request = Instant::now();
let res = match relay
.client
Expand Down Expand Up @@ -151,6 +158,10 @@ async fn send_submit_block(
warn!(relay_id = relay.id.as_ref(), retry, %err, "failed to get payload (this might be ok if other relays have it)");
return Err(err);
};
if api_version != &BuilderApiVersion::V1 {
// v2 response is going to be empty, so just break here
return Ok(None);
}

let block_response = match serde_json::from_slice::<SubmitBlindedBlockResponse>(&response_bytes)
{
Expand Down Expand Up @@ -187,7 +198,7 @@ async fn send_submit_block(
) => validate_unblinded_block_electra(signed_blinded_block, block_response),
}?;

Ok(block_response)
Ok(Some(block_response))
}

fn validate_unblinded_block_electra(
Expand Down
2 changes: 1 addition & 1 deletion crates/pbs/src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ use get_header::handle_get_header;
use register_validator::handle_register_validator;
pub use router::create_app_router;
use status::handle_get_status;
use submit_block::handle_submit_block;
use submit_block::handle_submit_block_v1;
21 changes: 15 additions & 6 deletions crates/pbs/src/routes/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ use axum::{
};
use axum_extra::headers::{ContentType, HeaderMapExt, UserAgent};
use cb_common::pbs::{
BUILDER_API_PATH, GET_HEADER_PATH, GET_STATUS_PATH, REGISTER_VALIDATOR_PATH, RELOAD_PATH,
SUBMIT_BLOCK_PATH,
BUILDER_V1_API_PATH, BUILDER_V2_API_PATH, GET_HEADER_PATH, GET_STATUS_PATH,
REGISTER_VALIDATOR_PATH, RELOAD_PATH, SUBMIT_BLOCK_PATH,
};
use tracing::trace;
use uuid::Uuid;

use super::{
handle_get_header, handle_get_status, handle_register_validator, handle_submit_block,
handle_get_header, handle_get_status, handle_register_validator, handle_submit_block_v1,
reload::handle_reload,
};
use crate::{
api::BuilderApi,
routes::submit_block::handle_submit_block_v2,
state::{BuilderApiState, PbsStateGuard},
MAX_SIZE_REGISTER_VALIDATOR_REQUEST, MAX_SIZE_SUBMIT_BLOCK_RESPONSE,
};
Expand All @@ -27,7 +28,7 @@ pub fn create_app_router<S: BuilderApiState, A: BuilderApi<S>>(state: PbsStateGu
// DefaultBodyLimit is 2Mib by default, so we only increase it for a few routes
// thay may need more

let builder_routes = Router::new()
let v1_builder_routes = Router::new()
.route(GET_HEADER_PATH, get(handle_get_header::<S, A>))
.route(GET_STATUS_PATH, get(handle_get_status::<S, A>))
.route(
Expand All @@ -37,11 +38,19 @@ pub fn create_app_router<S: BuilderApiState, A: BuilderApi<S>>(state: PbsStateGu
)
.route(
SUBMIT_BLOCK_PATH,
post(handle_submit_block::<S, A>)
post(handle_submit_block_v1::<S, A>)
.route_layer(DefaultBodyLimit::max(MAX_SIZE_SUBMIT_BLOCK_RESPONSE)),
); // header is smaller than the response but err on the safe side
let v2_builder_routes = Router::new().route(
SUBMIT_BLOCK_PATH,
post(handle_submit_block_v2::<S, A>)
.route_layer(DefaultBodyLimit::max(MAX_SIZE_SUBMIT_BLOCK_RESPONSE)),
);
let v1_builder_router = Router::new().nest(BUILDER_V1_API_PATH, v1_builder_routes);
let v2_builder_router = Router::new().nest(BUILDER_V2_API_PATH, v2_builder_routes);
let reload_router = Router::new().route(RELOAD_PATH, post(handle_reload::<S, A>));
let builder_api = Router::new().nest(BUILDER_API_PATH, builder_routes).merge(reload_router);
let builder_api =
Router::new().merge(v1_builder_router).merge(v2_builder_router).merge(reload_router);

let app = if let Some(extra_routes) = A::extra_routes() {
builder_api.merge(extra_routes)
Expand Down
Loading
Loading