fix(checkpoint-sync): retry on timeout#537
Conversation
🤖 Codex Code Review
No consensus-state-transition, fork-choice, attestation, XMSS, or SSZ logic changed in this diff; the risk here is startup/liveness rather than consensus correctness. I couldn’t run the Rust tests in this environment because Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Greptile SummaryThis PR adds bounded retries for transient checkpoint-sync failures during startup. The main changes are:
Confidence Score: 4/5The checkpoint retry path can exceed its startup deadline and delay deterministic HTTP failures.
bin/ethlambda/src/checkpoint_sync.rs
|
| Filename | Overview |
|---|---|
| bin/ethlambda/src/checkpoint_sync.rs | Adds error classification and retry logic, but active requests are not bounded by the total deadline and permanent HTTP failures are retried. |
| bin/ethlambda/src/main.rs | Switches initial checkpoint loading to the new retry wrapper. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Fetch initial state] --> B[Fetch checkpoint anchor]
B --> C{Result}
C -->|Success| D[Return state and block]
C -->|Non-transient error| E[Return error]
C -->|HTTP error| F{Backoff fits before deadline?}
F -->|No| E
F -->|Yes| G[Sleep with exponential backoff]
G --> B
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
bin/ethlambda/src/checkpoint_sync.rs:384
**In-Flight Fetch Escapes Deadline**
The deadline only checks whether the backoff fits; it does not limit the fetch started after that sleep. A request that starts near the deadline can run past 90 seconds, and a peer that keeps sending data within each inactivity timeout can extend it much longer, so startup can still exceed the harness budget.
### Issue 2 of 2
bin/ethlambda/src/checkpoint_sync.rs:90-91
**Permanent HTTP Errors Are Retried**
`error_for_status()` represents responses such as 401, 403, and 404 as `reqwest::Error`, so this treats invalid credentials or a wrong endpoint as transient. With a single URL, startup repeats the same deterministic failure with backoff until the budget is consumed instead of returning it immediately.
Reviews (1): Last reviewed commit: "fix(checkpoint-sync): retry transient fa..." | Re-trigger Greptile
🤖 Kimi Code ReviewThe retry logic is well-structured with proper deadline tracking and exponential backoff capping, but there's a critical bug in the error classification: Bug: Incomplete transient error detection ( The // Current (incorrect)
fn is_transient(&self) -> bool {
matches!(self, CheckpointSyncError::Http(_))
}
// Should be:
fn is_transient(&self) -> bool {
matches!(self,
CheckpointSyncError::Reqwest(_) |
CheckpointSyncError::Timeout
)
}If Consideration: Syncing peers (
Minor: Deadline edge case ( The condition Positives:
Recommendation: Fix the Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
🤖 Claude Code ReviewReview:
|
b76acab to
1cd57e3
Compare
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.
1cd57e3 to
7c368e2
Compare
Problem
In Hive interop runs, ethlambda intermittently fails to start with the harness reporting
client did not start: terminated unexpectedly. On the dashboard this surfaces as flaky reqresp failures against whichever scenario's fresh client-boot happened to coincide with the peer being slow (a different scenario, or none, fails each run).The container log shows the real cause:
Root cause
Checkpoint sync fails fast on any connection/timeout error. Hive supplies a single checkpoint URL, and
try_checkpoint_urlonly retries theAnchorPairingMismatchcase; any connection/timeout/HTTP error returns immediately. So one 15s miss to a peer that is still booting propagates up throughfetch_initial_state(main.rs?) and exits the whole process, well inside the harness's client-startup budget where a retry would have succeeded.Fix
Wrap the anchor fetch in a bounded retry (
fetch_anchor_with_retry) that reattempts a failed fetch for up toMAX_CHECKPOINT_ATTEMPTSattempts with a fixedCHECKPOINT_RETRY_BACKOFFdelay between them. When each attempt fails fast at the connect timeout, the attempts stay within the harness's client-startup budget.The per-attempt 15s connect/read timeouts and the internal
AnchorPairingMismatchretry are intentionally left unchanged.Testing
cargo build -p ethlambda,cargo clippy -p ethlambda -- -D warnings,cargo fmt -p ethlambda -- --check— clean.cargo test -p ethlambda checkpoint_sync— existing checkpoint-sync unit tests pass (22).