From 7c368e2c128f94e9dc7d250f1876c9b1c8899dcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:26:16 -0300 Subject: [PATCH 1/2] fix(checkpoint-sync): retry failed attempts within startup budget Checkpoint sync failed fast on any connection or timeout error: with a single checkpoint URL, one 15s miss to a peer still booting crashed the whole process (Hive reported "client did not start: terminated unexpectedly"). Wrap the anchor fetch in a bounded retry that reattempts a failed fetch a fixed number of times with a fixed backoff, staying within Hive's client-startup budget. Per-attempt connect/read timeouts and the internal AnchorPairingMismatch retry are unchanged. --- bin/ethlambda/src/checkpoint_sync.rs | 36 ++++++++++++++++++++++++++++ bin/ethlambda/src/main.rs | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/bin/ethlambda/src/checkpoint_sync.rs b/bin/ethlambda/src/checkpoint_sync.rs index 9bed6372..970fe88a 100644 --- a/bin/ethlambda/src/checkpoint_sync.rs +++ b/bin/ethlambda/src/checkpoint_sync.rs @@ -27,6 +27,14 @@ const MAX_ANCHOR_FETCH_ATTEMPTS: u32 = 3; /// Delay between anchor fetch attempts. const ANCHOR_FETCH_RETRY_DELAY: Duration = Duration::from_secs(1); +/// Fixed backoff between checkpoint-sync attempts. +const CHECKPOINT_RETRY_BACKOFF: Duration = Duration::from_secs(5); + +/// Maximum checkpoint-sync attempts before the process gives up, giving a +/// slow-to-boot peer time to become reachable. Attempts stay within Hive's +/// client-startup budget when each one fails fast. +const MAX_CHECKPOINT_ATTEMPTS: u32 = 5; + #[derive(Debug, thiserror::Error)] pub enum CheckpointSyncError { #[error("HTTP request failed: {0}")] @@ -346,6 +354,34 @@ pub async fn fetch_anchor_block_and_state( } } +/// Fetch the finalized anchor, retrying any failure (e.g. the peer not yet +/// reachable) for up to MAX_CHECKPOINT_ATTEMPTS attempts with a fixed backoff +/// between them before giving up. +pub async fn fetch_anchor_with_retry( + checkpoint_urls: &[String], + genesis_time: u64, + validators: &[Validator], +) -> Result<(State, SignedBlock), CheckpointSyncError> { + let mut attempt: u32 = 1; + loop { + match fetch_anchor_block_and_state(checkpoint_urls, genesis_time, validators).await { + Ok(pair) => return Ok(pair), + Err(err) if attempt < MAX_CHECKPOINT_ATTEMPTS => { + warn!( + attempt, + max_attempts = MAX_CHECKPOINT_ATTEMPTS, + %err, + backoff_secs = CHECKPOINT_RETRY_BACKOFF.as_secs(), + "Checkpoint sync attempt failed; retrying" + ); + tokio::time::sleep(CHECKPOINT_RETRY_BACKOFF).await; + attempt += 1; + } + Err(err) => return Err(err), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/bin/ethlambda/src/main.rs b/bin/ethlambda/src/main.rs index b4d2828d..33e242e9 100644 --- a/bin/ethlambda/src/main.rs +++ b/bin/ethlambda/src/main.rs @@ -707,7 +707,7 @@ async fn fetch_initial_state( info!(?checkpoint_urls, "Starting checkpoint sync"); - let (state, signed_block) = checkpoint_sync::fetch_anchor_block_and_state( + let (state, signed_block) = checkpoint_sync::fetch_anchor_with_retry( checkpoint_urls, genesis.genesis_time, &validators, From d5bf94ede189947aff28ab69c768c5e068dbe4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:56:18 -0300 Subject: [PATCH 2/2] chore: simplify log --- bin/ethlambda/src/checkpoint_sync.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/bin/ethlambda/src/checkpoint_sync.rs b/bin/ethlambda/src/checkpoint_sync.rs index 970fe88a..ff466ec1 100644 --- a/bin/ethlambda/src/checkpoint_sync.rs +++ b/bin/ethlambda/src/checkpoint_sync.rs @@ -367,13 +367,7 @@ pub async fn fetch_anchor_with_retry( match fetch_anchor_block_and_state(checkpoint_urls, genesis_time, validators).await { Ok(pair) => return Ok(pair), Err(err) if attempt < MAX_CHECKPOINT_ATTEMPTS => { - warn!( - attempt, - max_attempts = MAX_CHECKPOINT_ATTEMPTS, - %err, - backoff_secs = CHECKPOINT_RETRY_BACKOFF.as_secs(), - "Checkpoint sync attempt failed; retrying" - ); + warn!(attempt, %err, "Checkpoint sync attempt failed; retrying"); tokio::time::sleep(CHECKPOINT_RETRY_BACKOFF).await; attempt += 1; }