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..2b89880b 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,19 @@ 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 = 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 + } + 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(); send_headers.insert(USER_AGENT, get_user_agent_with_version(&req_headers)?); @@ -301,6 +314,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