@@ -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 ) ]
3738pub 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
5353impl < 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