diff --git a/crates/rollup-boost/src/cli.rs b/crates/rollup-boost/src/cli.rs index 3eb4d3d6..15313b54 100644 --- a/crates/rollup-boost/src/cli.rs +++ b/crates/rollup-boost/src/cli.rs @@ -118,6 +118,7 @@ impl RollupBoostArgs { l2_auth_jwt, l2_client_args.l2_timeout, PayloadSource::L2, + l2_client_args.retry_config(), )?; let builder_args = self.builder; @@ -134,6 +135,7 @@ impl RollupBoostArgs { builder_auth_jwt, builder_args.builder_timeout, PayloadSource::Builder, + builder_args.retry_config(), )?; let (probe_layer, probes) = ProbeLayer::new(); diff --git a/crates/rollup-boost/src/client/rpc.rs b/crates/rollup-boost/src/client/rpc.rs index 6633e643..41877b82 100644 --- a/crates/rollup-boost/src/client/rpc.rs +++ b/crates/rollup-boost/src/client/rpc.rs @@ -6,7 +6,7 @@ use crate::version::{CARGO_PKG_VERSION, VERGEN_GIT_SHA}; use alloy_primitives::{B256, Bytes}; use alloy_rpc_types_engine::{ ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, JwtError, JwtSecret, PayloadId, - PayloadStatus, + PayloadStatus, PayloadStatusEnum, }; use alloy_rpc_types_eth::{Block, BlockNumberOrTag}; use clap::{Parser, arg}; @@ -109,6 +109,17 @@ pub struct RpcClient { auth_rpc: Uri, /// The source of the payload payload_source: PayloadSource, + /// Retry configuration for forkChoiceUpdated + fcu_retry_config: Option, +} + +/// Configuration for retry strategy +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RetryConfig { + /// Maximum number of retry attempts + pub max_attempts: u32, + /// Delay between retries in milliseconds + pub delay_ms: u64, } impl RpcClient { @@ -118,6 +129,7 @@ impl RpcClient { auth_rpc_jwt_secret: JwtSecret, timeout: u64, payload_source: PayloadSource, + fcu_retry_config: Option, ) -> Result { let version = format!("{CARGO_PKG_VERSION}-{VERGEN_GIT_SHA}"); let mut headers = HeaderMap::new(); @@ -134,6 +146,7 @@ impl RpcClient { auth_client, auth_rpc, payload_source, + fcu_retry_config, }) } @@ -155,11 +168,35 @@ impl RpcClient { payload_attributes: Option, ) -> ClientResult { info!("Sending fork_choice_updated_v3 to {}", self.payload_source); - let res = self - .auth_client - .fork_choice_updated_v3(fork_choice_state, payload_attributes.clone()) - .await - .set_code()?; + + // Retry FCU if the response is SYNCING + let res = if let Some(retry_config) = &self.fcu_retry_config { + // Initialize a dummy result because we can't break out of a + // for-loop with a value + let mut res = + ForkchoiceUpdated::new(PayloadStatus::from_status(PayloadStatusEnum::Syncing)); + for _ in 0..retry_config.max_attempts { + res = self + .auth_client + .fork_choice_updated_v3(fork_choice_state, payload_attributes.clone()) + .await + .set_code()?; + + if !res.is_syncing() { + break; + } + + tokio::time::sleep(Duration::from_millis(retry_config.delay_ms)).await; + // Just return the response even if it's SYNCING for now, we're + // just trying to reduce the likelihood of it with these retries + } + res + } else { + self.auth_client + .fork_choice_updated_v3(fork_choice_state, payload_attributes.clone()) + .await + .set_code()? + }; if let Some(payload_id) = res.payload_id { tracing::Span::current().record("payload_id", payload_id.to_string()); @@ -398,6 +435,26 @@ macro_rules! define_rpc_args { /// Timeout for http calls in milliseconds #[arg(long, env, default_value_t = 1000)] pub [<$prefix _timeout>]: u64, + + /// Optional retry max attempts for forkChoiceUpdate + #[arg(long, env)] + [<$prefix _fcu_retry_max_attempts>]: Option, + + /// Optional retry delay ms for forkChoiceUpdate + #[arg(long, env)] + [<$prefix _fcu_retry_delay_ms>]: Option, + } + + impl $name { + pub fn retry_config(&self) -> Option { + match (self.[<$prefix _fcu_retry_max_attempts>], self.[<$prefix _fcu_retry_delay_ms>]) { + (Some(max_attempts), Some(delay_ms)) => Some(RetryConfig { + max_attempts, + delay_ms, + }), + _ => None, + } + } } } )* @@ -456,8 +513,8 @@ pub mod tests { async fn valid_jwt() { let port = get_available_port(); let secret = JwtSecret::from_hex(SECRET).unwrap(); - let auth_rpc = Uri::from_str(&format!("http://{}:{}", AUTH_ADDR, port)).unwrap(); - let client = RpcClient::new(auth_rpc, secret, 1000, PayloadSource::L2).unwrap(); + let auth_rpc = Uri::from_str(&format!("http://{AUTH_ADDR}:{port}")).unwrap(); + let client = RpcClient::new(auth_rpc, secret, 1000, PayloadSource::L2, None).unwrap(); let response = send_request(client.auth_client, port).await; assert!(response.is_ok()); assert_eq!(response.unwrap(), "You are the dark lord"); diff --git a/crates/rollup-boost/src/flashblocks/service.rs b/crates/rollup-boost/src/flashblocks/service.rs index 2e7f02bd..7219aee0 100644 --- a/crates/rollup-boost/src/flashblocks/service.rs +++ b/crates/rollup-boost/src/flashblocks/service.rs @@ -410,6 +410,7 @@ mod tests { jwt_secret, 2000, PayloadSource::Builder, + None, )?; let service = @@ -439,6 +440,7 @@ mod tests { jwt_secret, 2000, PayloadSource::Builder, + None, )?; let service = diff --git a/crates/rollup-boost/src/health.rs b/crates/rollup-boost/src/health.rs index fed2e27c..0af8f450 100644 --- a/crates/rollup-boost/src/health.rs +++ b/crates/rollup-boost/src/health.rs @@ -273,6 +273,7 @@ mod tests { JwtSecret::random(), 100, PayloadSource::Builder, + None, )?); let health_handle = HealthHandle { @@ -304,6 +305,7 @@ mod tests { JwtSecret::random(), 100, PayloadSource::Builder, + None, )?); let health_handle = HealthHandle { @@ -336,6 +338,7 @@ mod tests { JwtSecret::random(), 100, PayloadSource::Builder, + None, )?); let health_handle = HealthHandle { @@ -368,6 +371,7 @@ mod tests { JwtSecret::random(), 100, PayloadSource::Builder, + None, )?); let health_handle = HealthHandle { @@ -393,6 +397,7 @@ mod tests { JwtSecret::random(), 100, PayloadSource::Builder, + None, )?); let health_handle = HealthHandle { diff --git a/crates/rollup-boost/src/server.rs b/crates/rollup-boost/src/server.rs index d6f315a2..7c1b2e08 100644 --- a/crates/rollup-boost/src/server.rs +++ b/crates/rollup-boost/src/server.rs @@ -652,8 +652,14 @@ pub mod tests { let (builder_server, builder_server_addr) = spawn_server(builder_mock.clone()).await; let l2_auth_rpc = Uri::from_str(&format!("http://{l2_server_addr}")).unwrap(); - let l2_client = - RpcClient::new(l2_auth_rpc.clone(), jwt_secret, 2000, PayloadSource::L2).unwrap(); + let l2_client = RpcClient::new( + l2_auth_rpc.clone(), + jwt_secret, + 2000, + PayloadSource::L2, + None, + ) + .unwrap(); let builder_auth_rpc = Uri::from_str(&format!("http://{builder_server_addr}")).unwrap(); let builder_client = Arc::new( @@ -662,6 +668,7 @@ pub mod tests { jwt_secret, 2000, PayloadSource::Builder, + None, ) .unwrap(), );