From 05d11b83736bfdd2fb1ca4837f388769ca5c1e27 Mon Sep 17 00:00:00 2001 From: Joe Clapis Date: Mon, 25 Aug 2025 01:04:10 -0400 Subject: [PATCH 1/2] Added simple x-timeout-ms header support to get_header --- crates/common/src/pbs/constants.rs | 1 + crates/pbs/src/mev_boost/get_header.rs | 31 +++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/crates/common/src/pbs/constants.rs b/crates/common/src/pbs/constants.rs index d3b060d9..be8d4f8e 100644 --- a/crates/common/src/pbs/constants.rs +++ b/crates/common/src/pbs/constants.rs @@ -16,6 +16,7 @@ pub const RELOAD_PATH: &str = "/reload"; pub const HEADER_VERSION_KEY: &str = "X-CommitBoost-Version"; pub const HEADER_VERSION_VALUE: &str = COMMIT_BOOST_VERSION; pub const HEADER_START_TIME_UNIX_MS: &str = "Date-Milliseconds"; +pub const HEADER_TIMEOUT_MS: &str = "X-Timeout-Ms"; pub const BUILDER_EVENTS_PATH: &str = "/builder_events"; pub const DEFAULT_PBS_JWT_KEY: &str = "DEFAULT_PBS"; diff --git a/crates/pbs/src/mev_boost/get_header.rs b/crates/pbs/src/mev_boost/get_header.rs index 6014b513..ba6c51c0 100644 --- a/crates/pbs/src/mev_boost/get_header.rs +++ b/crates/pbs/src/mev_boost/get_header.rs @@ -14,7 +14,7 @@ use cb_common::{ pbs::{ error::{PbsError, ValidationError}, GetHeaderParams, GetHeaderResponse, RelayClient, VersionedResponse, EMPTY_TX_ROOT_HASH, - HEADER_START_TIME_UNIX_MS, + HEADER_START_TIME_UNIX_MS, HEADER_TIMEOUT_MS, }, signature::verify_signed_message, types::{BlsPublicKey, BlsSignature, Chain}, @@ -81,6 +81,31 @@ pub async fn get_header( return Ok(None); } + // Use the minimum of the time left and the user provided timeout header + let max_timeout_ms = match req_headers.get(HEADER_TIMEOUT_MS) { + Some(user_header_value) => match user_header_value.to_str() { + Ok(user_timeout_string) => match user_timeout_string.parse::() { + Ok(user_timeout) => { + if user_timeout == 0 { + warn!("user-supplied timeout header is 0, using {max_timeout_ms}ms"); + max_timeout_ms + } else { + user_timeout.min(max_timeout_ms) + } + } + Err(e) => { + warn!("cannot parse user-supplied timeout header '{user_timeout_string}', using {max_timeout_ms}ms ({e})"); + max_timeout_ms + } + }, + Err(e) => { + warn!("invalid user-supplied timeout header, using {max_timeout_ms}ms ({e})"); + max_timeout_ms + } + }, + None => max_timeout_ms, + }; + // prepare headers, except for start time which is set in `send_one_get_header` let mut send_headers = HeaderMap::new(); send_headers.insert(USER_AGENT, get_user_agent_with_version(&req_headers)?); @@ -301,6 +326,10 @@ async fn send_one_get_header( let start_request_time = utcnow_ms(); req_config.headers.insert(HEADER_START_TIME_UNIX_MS, HeaderValue::from(start_request_time)); + // The timeout header indicating how long a relay has to respond, so they can + // minimize timing games without losing the bid + req_config.headers.insert(HEADER_TIMEOUT_MS, HeaderValue::from(req_config.timeout_ms)); + let start_request = Instant::now(); let res = match relay .client From 4819aa480b7ecb3cf2af92627162cc0e2dca6bf7 Mon Sep 17 00:00:00 2001 From: Joe Clapis Date: Mon, 25 Aug 2025 13:19:09 -0400 Subject: [PATCH 2/2] Consolidated some logic --- crates/pbs/src/mev_boost/get_header.rs | 30 ++++++++------------------ 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/crates/pbs/src/mev_boost/get_header.rs b/crates/pbs/src/mev_boost/get_header.rs index ba6c51c0..2b89880b 100644 --- a/crates/pbs/src/mev_boost/get_header.rs +++ b/crates/pbs/src/mev_boost/get_header.rs @@ -82,29 +82,17 @@ pub async fn get_header( } // Use the minimum of the time left and the user provided timeout header - let max_timeout_ms = match req_headers.get(HEADER_TIMEOUT_MS) { - Some(user_header_value) => match user_header_value.to_str() { - Ok(user_timeout_string) => match user_timeout_string.parse::() { - Ok(user_timeout) => { - if user_timeout == 0 { - warn!("user-supplied timeout header is 0, using {max_timeout_ms}ms"); - max_timeout_ms - } else { - user_timeout.min(max_timeout_ms) - } - } - Err(e) => { - warn!("cannot parse user-supplied timeout header '{user_timeout_string}', using {max_timeout_ms}ms ({e})"); - max_timeout_ms - } - }, - Err(e) => { - warn!("invalid user-supplied timeout header, using {max_timeout_ms}ms ({e})"); + let max_timeout_ms = req_headers + .get(HEADER_TIMEOUT_MS) + .map(|header| match header.to_str().ok().and_then(|v| v.parse::().ok()) { + None | Some(0) => { + // Header can't be stringified, or parsed, or it's set to 0 + warn!(?header, "invalid user-supplied timeout header, using {max_timeout_ms}ms"); max_timeout_ms } - }, - None => max_timeout_ms, - }; + Some(user_timeout) => user_timeout.min(max_timeout_ms), + }) + .unwrap_or(max_timeout_ms); // prepare headers, except for start time which is set in `send_one_get_header` let mut send_headers = HeaderMap::new();