Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 2 additions & 14 deletions crates/app/src/sse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,12 +392,12 @@ impl SseListenerActor {
/// failure.
async fn fetch_config(client: &EthBeaconNodeApiClient) -> Result<(DateTime<Utc>, Duration, u64)> {
let genesis_time = (|| client.fetch_genesis_time())
.retry(fast_backoff())
.retry(pluto_core::expbackoff::fast())
.notify(|err, _| tracing::error!(err = ?err, "Failure fetching genesis time"))
.await?;

let (slot_duration, slots_per_epoch) = (|| client.fetch_slots_config())
.retry(fast_backoff())
.retry(pluto_core::expbackoff::fast())
.notify(|err, _| tracing::error!(err = ?err, "Failure fetching slots config"))
.await?;

Expand Down Expand Up @@ -503,18 +503,6 @@ async fn stream_once(
}
}

// TODO: Extract these backoff configurations into a shared module.

/// Backoff used while waiting for the beacon node configuration.
fn fast_backoff() -> ExponentialBuilder {
ExponentialBuilder::default()
.with_min_delay(Duration::from_millis(100))
.with_max_delay(Duration::from_secs(5))
.with_factor(1.6)
.without_max_times()
.with_jitter()
}

/// Backoff used between SSE reconnection attempts.
fn reconnect_backoff() -> ExponentialBuilder {
ExponentialBuilder::default()
Expand Down
41 changes: 41 additions & 0 deletions crates/core/src/expbackoff.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//! Exponential backoff builders mirroring Charon's `app/expbackoff` package.
//!
//! Charon defines two shared configurations (`app/expbackoff/expbackoff.go`):
//! `FastConfig` (100ms base, 5s max) for quick retries, and `DefaultConfig`
//! (1s base, 120s max) for slower loops. Both use a 1.6 multiplier and 0.2
//! jitter and retry until the surrounding context is cancelled.
//!
//! Note on jitter: Charon applies a `±20%` multiplicative jitter, whereas
//! `backon`'s [`ExponentialBuilder::with_jitter`] adds a randomized delay in
//! `(0, base)`. This is an approximation, but it matches every existing
//! Pluto backoff call site, so consolidating here introduces no behavioral
//! change.

use std::time::Duration;

use backon::ExponentialBuilder;

/// Backoff matching Charon's `expbackoff.FastConfig`: base=100ms, max=5s,
/// multiplier=1.6, jitter≈0.2. Retries until the surrounding cancellation
/// stops it (`without_max_times`), mirroring Go's "back off until the context
/// is cancelled" loops.
pub fn fast() -> ExponentialBuilder {
ExponentialBuilder::default()
.with_min_delay(Duration::from_millis(100))
.with_max_delay(Duration::from_secs(5))
.with_factor(1.6)
.without_max_times()
.with_jitter()
}

/// Backoff matching Charon's `expbackoff.DefaultConfig`: base=1s, max=120s,
/// multiplier=1.6, jitter≈0.2. Retries until the surrounding cancellation
/// stops it (`without_max_times`).
pub fn default() -> ExponentialBuilder {
ExponentialBuilder::default()
.with_min_delay(Duration::from_secs(1))
.with_max_delay(Duration::from_secs(120))
.with_factor(1.6)
.without_max_times()
.with_jitter()
}
3 changes: 3 additions & 0 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ pub mod clock;
/// future.
pub mod gater;

/// Exponential backoff builders mirroring Charon's `app/expbackoff`.
pub mod expbackoff;

/// parsigdb
pub mod parsigdb;

Expand Down
42 changes: 5 additions & 37 deletions crates/core/src/scheduler.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use std::{
collections::{HashMap, hash_map::Entry},
time::Duration,
};
use std::collections::{HashMap, hash_map::Entry};

use backon::{BackoffBuilder, Retryable};
use tokio::sync;
Expand Down Expand Up @@ -719,39 +716,10 @@ async fn resolve_active_validators(
Ok(validators)
}

// TODO: Duplicated from `crates/p2p/src/bootnode.rs`
fn fast_backoff() -> backon::ExponentialBuilder {
/// Backoff configuration constants matching Go's expbackoff.FastConfig.
const FAST_BASE_DELAY: Duration = Duration::from_millis(100);
const FAST_MAX_DELAY: Duration = Duration::from_secs(5);
const FAST_MULTIPLIER: f32 = 1.6;

backon::ExponentialBuilder::default()
.with_min_delay(FAST_BASE_DELAY)
.with_max_delay(FAST_MAX_DELAY)
.with_factor(FAST_MULTIPLIER)
.without_max_times()
.with_jitter()
}

fn default_backoff() -> backon::ExponentialBuilder {
/// Backoff configuration constants matching Go's expbackoff.DefaultConfig.
const DEFAULT_BASE_DELAY: Duration = Duration::from_secs(1);
const DEFAULT_MAX_DELAY: Duration = Duration::from_secs(120);
const DEFAULT_MULTIPLIER: f32 = 1.6;

backon::ExponentialBuilder::default()
.with_min_delay(DEFAULT_BASE_DELAY)
.with_max_delay(DEFAULT_MAX_DELAY)
.with_factor(DEFAULT_MULTIPLIER)
.without_max_times()
.with_jitter()
}

/// Blocks until the beacon chain has started.
async fn wait_chain_start(client: &pluto_eth2api::BeaconNodeClient) -> Result<()> {
let fetch = || client.api().fetch_genesis_time();
let backoff = fast_backoff();
let backoff = crate::expbackoff::fast();
let genesis_time = fetch
.retry(backoff)
.notify(|err, _| tracing::error!(err = ?err, "Failure getting genesis"))
Expand All @@ -777,9 +745,9 @@ async fn wait_beacon_sync(client: &pluto_eth2api::BeaconNodeClient) -> Result<()
.api()
.get_syncing_status(pluto_eth2api::GetSyncingStatusRequest {})
};
let fetch_backoff = fast_backoff();
let fetch_backoff = crate::expbackoff::fast();

let mut is_syncing_backoff = default_backoff().build();
let mut is_syncing_backoff = crate::expbackoff::default().build();

loop {
let response: pluto_eth2api::GetSyncingStatusResponse = fetch
Expand Down Expand Up @@ -1036,7 +1004,7 @@ async fn fetch_sync_committee_duties(

#[cfg(test)]
mod tests {
use std::collections::HashSet;
use std::{collections::HashSet, time::Duration};

use pluto_eth2api::{
BeaconNodeClient, GetStateValidatorsResponseResponse,
Expand Down
16 changes: 4 additions & 12 deletions crates/p2p/src/bootnode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::time::Duration;

use backon::{ExponentialBuilder, Retryable};
use backon::Retryable;
use libp2p::Multiaddr;
use pluto_eth2util::enr::Record;
use tokio_util::sync::CancellationToken;
Expand All @@ -12,11 +12,6 @@ use crate::peer::{
AddrInfo, MutablePeer, Peer, PeerError, addr_infos_from_p2p_addrs, peer_id_from_key,
};

/// Backoff configuration constants matching Go's expbackoff.FastConfig.
const FAST_BASE_DELAY: Duration = Duration::from_millis(100);
const FAST_MAX_DELAY: Duration = Duration::from_secs(5);
const FAST_MULTIPLIER: f32 = 1.6;

/// Polling interval for relay address updates.
const RELAY_POLL_INTERVAL: Duration = Duration::from_secs(120); // 2 minutes

Expand Down Expand Up @@ -235,12 +230,9 @@ async fn query_relay_addrs(
return Err(BootnodeError::InvalidRelayUrl);
}

// Retry with exponential backoff
let backoff = ExponentialBuilder::default()
.with_min_delay(FAST_BASE_DELAY)
.with_max_delay(FAST_MAX_DELAY)
.with_factor(FAST_MULTIPLIER)
.with_jitter();
// Retry with exponential backoff until the cancel token fires, matching
// Go's `queryRelayAddrs` ("It retries until the context is cancelled").
let backoff = pluto_core::expbackoff::fast();

let fetch = || async {
if cancel.is_cancelled() {
Expand Down
Loading