Skip to content

Commit f3958af

Browse files
committed
feat: use providers vec to store all primary and fallback providers
1 parent a616703 commit f3958af

File tree

1 file changed

+40
-38
lines changed

1 file changed

+40
-38
lines changed

src/robust_provider.rs

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ impl From<RpcError<TransportErrorKind>> for Error {
3333
///
3434
/// This wrapper around Alloy providers automatically handles retries,
3535
/// timeouts, and error logging for RPC calls.
36+
/// The first provider in the vector is treated as the primary provider.
3637
#[derive(Clone)]
3738
pub struct RobustProvider<N: Network> {
38-
provider: RootProvider<N>,
39+
providers: Vec<RootProvider<N>>,
3940
max_timeout: Duration,
4041
max_retries: usize,
4142
retry_interval: Duration,
42-
fallback_providers: Vec<RootProvider<N>>,
4343
}
4444

4545
// RPC retry and timeout settings
@@ -52,14 +52,14 @@ pub const DEFAULT_RETRY_INTERVAL: Duration = Duration::from_secs(1);
5252

5353
impl<N: Network> RobustProvider<N> {
5454
/// Create a new `RobustProvider` with default settings.
55+
/// The provided provider is treated as the primary provider.
5556
#[must_use]
5657
pub fn new(provider: impl Provider<N>) -> Self {
5758
Self {
58-
provider: provider.root().to_owned(),
59+
providers: vec![provider.root().to_owned()],
5960
max_timeout: DEFAULT_MAX_TIMEOUT,
6061
max_retries: DEFAULT_MAX_RETRIES,
6162
retry_interval: DEFAULT_RETRY_INTERVAL,
62-
fallback_providers: Vec::new(),
6363
}
6464
}
6565

@@ -81,18 +81,23 @@ impl<N: Network> RobustProvider<N> {
8181
self
8282
}
8383

84-
/// Get a reference to the primary provider
84+
/// Get a reference to the primary provider (the first provider in the list)
85+
///
86+
/// # Panics
87+
///
88+
/// If there are no providers set (this should never happen)
8589
#[must_use]
8690
pub fn root(&self) -> &RootProvider<N> {
87-
&self.provider
91+
// Safe to unwrap because we always have at least one provider
92+
self.providers.first().expect("providers vector should never be empty")
8893
}
8994

9095
/// Add a fallback provider to the list.
9196
///
92-
/// Fallback providers are used when the primary provider times out.
97+
/// Fallback providers are used when the primary provider times out or fails.
9398
#[must_use]
9499
pub fn fallback(mut self, provider: RootProvider<N>) -> Self {
95-
self.fallback_providers.push(provider);
100+
self.providers.push(provider);
96101
self
97102
}
98103

@@ -189,7 +194,7 @@ impl<N: Network> RobustProvider<N> {
189194
pub async fn subscribe_blocks(&self) -> Result<Subscription<N::HeaderResponse>, Error> {
190195
info!("eth_subscribe called");
191196
// We need this otherwise error is not clear
192-
self.provider.client().expect_pubsub_frontend();
197+
self.root().client().expect_pubsub_frontend();
193198
let result = self
194199
.retry_with_total_timeout(
195200
move |provider| async move { provider.subscribe_blocks().await },
@@ -221,43 +226,41 @@ impl<N: Network> RobustProvider<N> {
221226
F: Fn(RootProvider<N>) -> Fut,
222227
Fut: Future<Output = Result<T, RpcError<TransportErrorKind>>>,
223228
{
224-
// Try primary provider first
225-
let result = self.try_provider_with_timeout(&self.provider, &operation).await;
226-
227-
if let Ok(value) = result {
228-
return Ok(value);
229-
}
230-
231-
if self.fallback_providers.is_empty() {
232-
return result;
233-
}
234-
235-
info!("Primary provider failed, trying fallback provider(s)");
236-
237-
// Try each fallback provider
238-
for (idx, fallback_provider) in self.fallback_providers.iter().enumerate() {
239-
info!(
240-
"Attempting fallback provider {} out of {}",
241-
idx + 1,
242-
self.fallback_providers.len()
243-
);
229+
let mut last_error = None;
230+
231+
// Try each provider in sequence (first one is primary)
232+
for (idx, provider) in self.providers.iter().enumerate() {
233+
if idx == 0 {
234+
info!("Attempting primary provider");
235+
} else {
236+
info!("Attempting fallback provider {} out of {}", idx, self.providers.len() - 1);
237+
}
244238

245-
let fallback_result =
246-
self.try_provider_with_timeout(fallback_provider, &operation).await;
239+
let result = self.try_provider_with_timeout(provider, &operation).await;
247240

248-
match fallback_result {
241+
match result {
249242
Ok(value) => {
250-
info!(provider_num = idx + 1, "Fallback provider succeeded");
243+
if idx > 0 {
244+
info!(provider_num = idx, "Fallback provider succeeded");
245+
}
251246
return Ok(value);
252247
}
253248
Err(e) => {
254-
error!(provider_num = idx + 1, err = %e, "Fallback provider failed with error");
249+
last_error = Some(e);
250+
if idx == 0 {
251+
if self.providers.len() > 1 {
252+
info!("Primary provider failed, trying fallback provider(s)");
253+
}
254+
} else {
255+
error!(provider_num = idx, err = %last_error.as_ref().unwrap(), "Fallback provider failed with error");
256+
}
255257
}
256258
}
257259
}
258260

259-
error!("All fallback providers failed or timed out");
260-
Err(Error::Timeout)
261+
error!("All providers failed or timed out");
262+
// Return the last error encountered
263+
Err(last_error.unwrap_or(Error::Timeout))
261264
}
262265

263266
/// Try executing an operation with a specific provider with retry and timeout.
@@ -299,11 +302,10 @@ mod tests {
299302
retry_interval: u64,
300303
) -> RobustProvider<Ethereum> {
301304
RobustProvider {
302-
provider: RootProvider::new_http("http://localhost:8545".parse().unwrap()),
305+
providers: vec![RootProvider::new_http("http://localhost:8545".parse().unwrap())],
303306
max_timeout: Duration::from_millis(timeout),
304307
max_retries,
305308
retry_interval: Duration::from_millis(retry_interval),
306-
fallback_providers: Vec::new(),
307309
}
308310
}
309311

0 commit comments

Comments
 (0)