From d814c029bb841c311c0cb03d9b8cb2f957df8c74 Mon Sep 17 00:00:00 2001 From: Rahul Barman Date: Mon, 29 Jun 2026 14:46:37 +0530 Subject: [PATCH] Fix checkpoint sync 401 with trailing-slash path-embedded API keys (#9548) Closes #9545 `BeaconNodeHttpClient::eth_path` clones the configured server URL and pushes path segments onto it. When the base URL ends with a slash (such as a QuickNode-style endpoint that embeds the API key in the path, `https:////`), the URL already carries an empty trailing segment, so pushing `eth` yields a double slash: ``` https://///eth/v2/debug/beacon/states/finalized ``` The provider responds to the `//` form with a `307` redirect to a normalized path that drops the key segment, so the redirected request arrives unauthenticated and checkpoint sync fails with `Error loading checkpoint state from remote: StatusCode(401)`. This PR calls `pop_if_empty()` before pushing segments, the standard `url`-crate idiom for trailing-slash-tolerant joining, so a trailing slash no longer changes the resulting path. The fix is applied in `eth_path` (the shared prefix builder that every `/eth/vX` endpoint derives from) and in `post_lighthouse_liveness`, the only other builder that extends the base URL directly. Co-Authored-By: rahulbarman --- common/eth2/src/lib.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 46f553542a..24bb873992 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -208,6 +208,9 @@ impl BeaconNodeHttpClient { path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? + // Drop a trailing empty segment so a base URL with a trailing slash + // (e.g. `https://host//`) does not produce a double slash. + .pop_if_empty() .push("eth") .push(&version.to_string()); @@ -3232,6 +3235,7 @@ impl BeaconNodeHttpClient { path.path_segments_mut() .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .pop_if_empty() .push("lighthouse") .push("liveness"); @@ -3508,3 +3512,37 @@ impl BeaconNodeHttpClient { .await } } + +#[cfg(test)] +mod tests { + use super::*; + use std::time::Duration; + + fn eth_v2_prefix(base: &str) -> String { + let server = SensitiveUrl::parse(base).expect("valid base url"); + let client = BeaconNodeHttpClient::new(server, Timeouts::set_all(Duration::from_secs(1))); + client.eth_path(V2).unwrap().to_string() + } + + // A trailing slash on the base URL must not change the resulting endpoint + // path. With a path-embedded API key this previously produced a `//eth` + // double slash that providers redirect to a key-less, unauthenticated path. + // See https://github.com/sigp/lighthouse/issues/9545. + #[test] + fn eth_path_is_trailing_slash_tolerant() { + // The reported case: a path-embedded API key with a trailing slash. + assert_eq!( + eth_v2_prefix("https://example.com/secret-key/"), + "https://example.com/secret-key/eth/v2" + ); + assert_eq!( + eth_v2_prefix("https://example.com/secret-key/"), + eth_v2_prefix("https://example.com/secret-key") + ); + // Regression guard: a bare host with a trailing slash. + assert_eq!( + eth_v2_prefix("http://localhost:5052/"), + eth_v2_prefix("http://localhost:5052") + ); + } +}