Skip to content

Commit 68a3d60

Browse files
committed
Merge branch 'main' into stable
2 parents c267c64 + bad5b08 commit 68a3d60

File tree

13 files changed

+100
-60
lines changed

13 files changed

+100
-60
lines changed

Cargo.lock

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ resolver = "2"
55
[workspace.package]
66
edition = "2024"
77
rust-version = "1.89"
8-
version = "0.9.0"
8+
version = "0.9.1"
99

1010
[workspace.dependencies]
1111
aes = "0.8"

crates/common/src/config/mux.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ async fn fetch_lido_registry_keys(
369369
}
370370

371371
async fn fetch_ssv_pubkeys(
372-
api_url: Url,
372+
mut api_url: Url,
373373
chain: Chain,
374374
node_operator_id: U256,
375375
http_timeout: Duration,
@@ -386,6 +386,15 @@ async fn fetch_ssv_pubkeys(
386386
let mut pubkeys: Vec<BlsPublicKey> = vec![];
387387
let mut page = 1;
388388

389+
// Validate the URL - this appends a trailing slash if missing as efficiently as
390+
// possible
391+
if !api_url.path().ends_with('/') {
392+
match api_url.path_segments_mut() {
393+
Ok(mut segments) => segments.push(""), // Analogous to a trailing slash
394+
Err(_) => bail!("SSV API URL is not a valid base URL"),
395+
};
396+
}
397+
389398
loop {
390399
let route = format!(
391400
"{chain_name}/validators/in_operator/{node_operator_id}?perPage={MAX_PER_PAGE}&page={page}",

crates/common/src/config/pbs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,5 +404,5 @@ pub async fn load_pbs_custom_config<T: DeserializeOwned>() -> Result<(PbsModuleC
404404

405405
/// Default URL for the SSV network API
406406
fn default_ssv_api_url() -> Url {
407-
Url::parse("https://api.ssv.network/api/v4").expect("default URL is valid")
407+
Url::parse("https://api.ssv.network/api/v4/").expect("default URL is valid")
408408
}

crates/common/src/pbs/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ pub enum PbsError {
2525

2626
#[error("URL parsing error: {0}")]
2727
UrlParsing(#[from] url::ParseError),
28+
29+
#[error("tokio join error: {0}")]
30+
TokioJoinError(#[from] tokio::task::JoinError),
2831
}
2932

3033
impl PbsError {

crates/pbs/src/api.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::sync::Arc;
2+
13
use async_trait::async_trait;
24
use axum::{Router, http::HeaderMap};
35
use cb_common::pbs::{
@@ -34,10 +36,10 @@ pub trait BuilderApi<S: BuilderApiState>: 'static {
3436
/// https://ethereum.github.io/builder-specs/#/Builder/submitBlindedBlock and
3537
/// https://ethereum.github.io/builder-specs/#/Builder/submitBlindedBlockV2
3638
async fn submit_block(
37-
signed_blinded_block: SignedBlindedBeaconBlock,
39+
signed_blinded_block: Arc<SignedBlindedBeaconBlock>,
3840
req_headers: HeaderMap,
3941
state: PbsState<S>,
40-
api_version: &BuilderApiVersion,
42+
api_version: BuilderApiVersion,
4143
) -> eyre::Result<Option<SubmitBlindedBlockResponse>> {
4244
mev_boost::submit_block(signed_blinded_block, req_headers, state, api_version).await
4345
}

crates/pbs/src/mev_boost/register_validator.rs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ use cb_common::{
77
utils::{get_user_agent_with_version, read_chunked_body_with_max, utcnow_ms},
88
};
99
use eyre::bail;
10-
use futures::future::{join_all, select_ok};
10+
use futures::{
11+
FutureExt,
12+
future::{join_all, select_ok},
13+
};
1114
use reqwest::header::{CONTENT_TYPE, USER_AGENT};
1215
use tracing::{Instrument, debug, error};
1316
use url::Url;
@@ -49,32 +52,38 @@ pub async fn register_validator<S: BuilderApiState>(
4952

5053
for (n_regs, body) in bodies {
5154
for relay in state.all_relays().iter().cloned() {
52-
handles.push(tokio::spawn(
53-
send_register_validator_with_timeout(
54-
n_regs,
55-
body.clone(),
56-
relay,
57-
send_headers.clone(),
58-
state.pbs_config().timeout_register_validator_ms,
59-
state.pbs_config().register_validator_retry_limit,
55+
handles.push(
56+
tokio::spawn(
57+
send_register_validator_with_timeout(
58+
n_regs,
59+
body.clone(),
60+
relay,
61+
send_headers.clone(),
62+
state.pbs_config().timeout_register_validator_ms,
63+
state.pbs_config().register_validator_retry_limit,
64+
)
65+
.in_current_span(),
6066
)
61-
.in_current_span(),
62-
));
67+
.map(|join_result| match join_result {
68+
Ok(res) => res,
69+
Err(err) => Err(PbsError::TokioJoinError(err)),
70+
}),
71+
);
6372
}
6473
}
6574

6675
if state.pbs_config().wait_all_registrations {
6776
// wait for all relays registrations to complete
6877
let results = join_all(handles).await;
69-
if results.into_iter().any(|res| res.is_ok_and(|res| res.is_ok())) {
78+
if results.into_iter().any(|res| res.is_ok()) {
7079
Ok(())
7180
} else {
7281
bail!("No relay passed register_validator successfully")
7382
}
7483
} else {
7584
// return once first completes, others proceed in background
76-
let result = select_ok(handles).await?;
77-
match result.0 {
85+
let result = select_ok(handles).await;
86+
match result {
7887
Ok(_) => Ok(()),
7988
Err(_) => bail!("No relay passed register_validator successfully"),
8089
}

crates/pbs/src/mev_boost/submit_block.rs

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
22
str::FromStr,
3+
sync::Arc,
34
time::{Duration, Instant},
45
};
56

@@ -14,7 +15,7 @@ use cb_common::{
1415
},
1516
utils::{get_user_agent_with_version, read_chunked_body_with_max, utcnow_ms},
1617
};
17-
use futures::future::select_ok;
18+
use futures::{FutureExt, future::select_ok};
1819
use reqwest::header::USER_AGENT;
1920
use tracing::{debug, warn};
2021
use url::Url;
@@ -31,10 +32,10 @@ use crate::{
3132
/// https://ethereum.github.io/builder-specs/#/Builder/submitBlindedBlockV2. Use `api_version` to
3233
/// distinguish between the two.
3334
pub async fn submit_block<S: BuilderApiState>(
34-
signed_blinded_block: SignedBlindedBeaconBlock,
35+
signed_blinded_block: Arc<SignedBlindedBeaconBlock>,
3536
req_headers: HeaderMap,
3637
state: PbsState<S>,
37-
api_version: &BuilderApiVersion,
38+
api_version: BuilderApiVersion,
3839
) -> eyre::Result<Option<SubmitBlindedBlockResponse>> {
3940
debug!(?req_headers, "received headers");
4041

@@ -58,17 +59,22 @@ pub async fn submit_block<S: BuilderApiState>(
5859
send_headers.insert(USER_AGENT, get_user_agent_with_version(&req_headers)?);
5960
send_headers.insert(HEADER_CONSENSUS_VERSION, consensus_version);
6061

61-
let relays = state.all_relays();
62-
let mut handles = Vec::with_capacity(relays.len());
63-
for relay in relays.iter() {
64-
handles.push(Box::pin(submit_block_with_timeout(
65-
&signed_blinded_block,
66-
relay,
67-
send_headers.clone(),
68-
state.pbs_config().timeout_get_payload_ms,
69-
api_version,
70-
fork_name,
71-
)));
62+
let mut handles = Vec::with_capacity(state.all_relays().len());
63+
for relay in state.all_relays().iter().cloned() {
64+
handles.push(
65+
tokio::spawn(submit_block_with_timeout(
66+
signed_blinded_block.clone(),
67+
relay,
68+
send_headers.clone(),
69+
state.pbs_config().timeout_get_payload_ms,
70+
api_version,
71+
fork_name,
72+
))
73+
.map(|join_result| match join_result {
74+
Ok(res) => res,
75+
Err(err) => Err(PbsError::TokioJoinError(err)),
76+
}),
77+
);
7278
}
7379

7480
let results = select_ok(handles).await;
@@ -81,14 +87,14 @@ pub async fn submit_block<S: BuilderApiState>(
8187
/// Submit blinded block to relay, retry connection errors until the
8288
/// given timeout has passed
8389
async fn submit_block_with_timeout(
84-
signed_blinded_block: &SignedBlindedBeaconBlock,
85-
relay: &RelayClient,
90+
signed_blinded_block: Arc<SignedBlindedBeaconBlock>,
91+
relay: RelayClient,
8692
headers: HeaderMap,
8793
timeout_ms: u64,
88-
api_version: &BuilderApiVersion,
94+
api_version: BuilderApiVersion,
8995
fork_name: ForkName,
9096
) -> Result<Option<SubmitBlindedBlockResponse>, PbsError> {
91-
let mut url = relay.submit_block_url(*api_version)?;
97+
let mut url = relay.submit_block_url(api_version)?;
9298
let mut remaining_timeout_ms = timeout_ms;
9399
let mut retry = 0;
94100
let mut backoff = Duration::from_millis(250);
@@ -97,12 +103,12 @@ async fn submit_block_with_timeout(
97103
let start_request = Instant::now();
98104
match send_submit_block(
99105
url.clone(),
100-
signed_blinded_block,
101-
relay,
106+
&signed_blinded_block,
107+
&relay,
102108
headers.clone(),
103109
remaining_timeout_ms,
104110
retry,
105-
api_version,
111+
&api_version,
106112
fork_name,
107113
)
108114
.await

crates/pbs/src/routes/submit_block.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::sync::Arc;
2+
13
use axum::{Json, extract::State, http::HeaderMap, response::IntoResponse};
24
use cb_common::{
35
pbs::{BuilderApiVersion, GetPayloadInfo, SignedBlindedBeaconBlock},
@@ -17,7 +19,7 @@ use crate::{
1719
pub async fn handle_submit_block_v1<S: BuilderApiState, A: BuilderApi<S>>(
1820
state: State<PbsStateGuard<S>>,
1921
req_headers: HeaderMap,
20-
signed_blinded_block: Json<SignedBlindedBeaconBlock>,
22+
Json(signed_blinded_block): Json<Arc<SignedBlindedBeaconBlock>>,
2123
) -> Result<impl IntoResponse, PbsClientError> {
2224
handle_submit_block_impl::<S, A>(
2325
state,
@@ -31,7 +33,7 @@ pub async fn handle_submit_block_v1<S: BuilderApiState, A: BuilderApi<S>>(
3133
pub async fn handle_submit_block_v2<S: BuilderApiState, A: BuilderApi<S>>(
3234
state: State<PbsStateGuard<S>>,
3335
req_headers: HeaderMap,
34-
signed_blinded_block: Json<SignedBlindedBeaconBlock>,
36+
Json(signed_blinded_block): Json<Arc<SignedBlindedBeaconBlock>>,
3537
) -> Result<impl IntoResponse, PbsClientError> {
3638
handle_submit_block_impl::<S, A>(
3739
state,
@@ -45,7 +47,7 @@ pub async fn handle_submit_block_v2<S: BuilderApiState, A: BuilderApi<S>>(
4547
async fn handle_submit_block_impl<S: BuilderApiState, A: BuilderApi<S>>(
4648
State(state): State<PbsStateGuard<S>>,
4749
req_headers: HeaderMap,
48-
Json(signed_blinded_block): Json<SignedBlindedBeaconBlock>,
50+
signed_blinded_block: Arc<SignedBlindedBeaconBlock>,
4951
api_version: BuilderApiVersion,
5052
) -> Result<impl IntoResponse, PbsClientError> {
5153
tracing::Span::current().record("slot", signed_blinded_block.slot().as_u64() as i64);
@@ -65,7 +67,7 @@ async fn handle_submit_block_impl<S: BuilderApiState, A: BuilderApi<S>>(
6567

6668
info!(ua, ms_into_slot = now.saturating_sub(slot_start_ms), "new request");
6769

68-
match A::submit_block(signed_blinded_block, req_headers, state, &api_version).await {
70+
match A::submit_block(signed_blinded_block, req_headers, state, api_version).await {
6971
Ok(res) => match res {
7072
Some(block_response) => {
7173
trace!(?block_response);

docs/docs/get_started/overview.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ git clone https://github.com/Commit-Boost/commit-boost-client
4040

4141
# Stable branch has the latest released version
4242
git checkout stable
43+
44+
# Init submodules
45+
git submodule update --init --recursive
4346
```
4447

4548
:::note

0 commit comments

Comments
 (0)