@@ -350,11 +350,6 @@ mod tests {
350350 Ok ( ( anvil, provider. root ( ) . to_owned ( ) ) )
351351 }
352352
353- async fn mine_after_delay ( provider : RootProvider , blocks : u64 , delay : Duration ) {
354- sleep ( delay) . await ;
355- provider. anvil_mine ( Some ( blocks) , None ) . await . unwrap ( ) ;
356- }
357-
358353 fn assert_backend_gone_or_timeout ( err : Error ) {
359354 match err {
360355 Error :: Timeout => { }
@@ -418,20 +413,30 @@ mod tests {
418413 } ;
419414 }
420415
421- async fn trigger_fb_and_assert_next_block (
416+ /// Waits for current provider to timeout, then mines on `next_provider` to trigger failover.
417+ async fn trigger_failover_with_delay (
422418 stream : & mut RobustSubscriptionStream < alloy:: network:: Ethereum > ,
423- fallback_provider : RootProvider ,
419+ next_provider : RootProvider ,
424420 expected_block : u64 ,
421+ extra_delay : Duration ,
425422 ) -> anyhow:: Result < ( ) > {
426423 let task = tokio:: spawn ( async move {
427- sleep ( SHORT_TIMEOUT + BUFFER_TIME ) . await ;
428- fallback_provider . anvil_mine ( Some ( 1 ) , None ) . await . unwrap ( ) ;
424+ sleep ( SHORT_TIMEOUT + extra_delay + BUFFER_TIME ) . await ;
425+ next_provider . anvil_mine ( Some ( 1 ) , None ) . await . unwrap ( ) ;
429426 } ) ;
430427 assert_next_block ! ( * stream, expected_block) ;
431428 task. await ?;
432429 Ok ( ( ) )
433430 }
434431
432+ async fn trigger_failover (
433+ stream : & mut RobustSubscriptionStream < alloy:: network:: Ethereum > ,
434+ next_provider : RootProvider ,
435+ expected_block : u64 ,
436+ ) -> anyhow:: Result < ( ) > {
437+ trigger_failover_with_delay ( stream, next_provider, expected_block, Duration :: ZERO ) . await
438+ }
439+
435440 #[ tokio:: test]
436441 async fn test_retry_with_timeout_succeeds_on_first_attempt ( ) {
437442 let provider = test_provider ( 100 , 3 , 10 ) ;
@@ -626,7 +631,7 @@ mod tests {
626631 assert_next_block ! ( stream, 2 ) ;
627632
628633 // After timeout, should failover to fallback provider
629- trigger_fb_and_assert_next_block ( & mut stream, fallback. clone ( ) , 1 ) . await ?;
634+ trigger_failover ( & mut stream, fallback. clone ( ) , 1 ) . await ?;
630635
631636 fallback. anvil_mine ( Some ( 1 ) , None ) . await ?;
632637 assert_next_block ! ( stream, 2 ) ;
@@ -649,65 +654,66 @@ mod tests {
649654 let subscription = robust. subscribe_blocks ( ) . await ?;
650655 let mut stream = subscription. into_stream ( ) ;
651656
652- // Test: Start on primary
657+ // Start on primary
653658 primary. anvil_mine ( Some ( 1 ) , None ) . await ?;
654659 assert_next_block ! ( stream, 1 ) ;
655660
656- // Trigger failover to fallback
657- trigger_fb_and_assert_next_block ( & mut stream, fallback. clone ( ) , 1 ) . await ?;
661+ // PP times out -> FP1
662+ trigger_failover ( & mut stream, fallback. clone ( ) , 1 ) . await ?;
658663
659664 fallback. anvil_mine ( Some ( 1 ) , None ) . await ?;
660665 assert_next_block ! ( stream, 2 ) ;
661666
662- // Wait for reconnect interval to trigger primary reconnection
663- sleep ( RECONNECT_INTERVAL + BUFFER_TIME ) . await ;
667+ // FP1 times out -> PP (reconnect succeeds)
668+ trigger_failover ( & mut stream , primary . clone ( ) , 2 ) . await ? ;
664669
665- // Mine on primary after reconnection window
666- let reconnect_task =
667- tokio:: spawn ( mine_after_delay ( primary. clone ( ) , 1 , Duration :: from_millis ( 50 ) ) ) ;
670+ // PP times out -> FP1 (fallback index was reset)
671+ trigger_failover ( & mut stream, fallback. clone ( ) , 3 ) . await ?;
668672
669- // Verify: Successfully reconnected to primary (block 2 on primary chain)
670- assert_next_block ! ( stream, 2 ) ;
671- reconnect_task. await ?;
672-
673- assert_stream_finished ! ( stream) ;
673+ fallback. anvil_mine ( Some ( 1 ) , None ) . await ?;
674+ assert_next_block ! ( stream, 4 ) ;
674675
675676 Ok ( ( ) )
676677 }
677678
678679 #[ tokio:: test]
679680 async fn subscription_cycles_through_multiple_fallbacks ( ) -> anyhow:: Result < ( ) > {
680- // Setup: Three providers, will cycle through all
681- let ( _anvil_1, provider_1) = spawn_ws_anvil ( ) . await ?;
682- let ( _anvil_2, provider_2) = spawn_ws_anvil ( ) . await ?;
683- let ( _anvil_3, provider_3) = spawn_ws_anvil ( ) . await ?;
684-
685- let robust = RobustProviderBuilder :: fragile ( provider_1. clone ( ) )
686- . fallback ( provider_2. clone ( ) )
687- . fallback ( provider_3. clone ( ) )
681+ let ( anvil_pp, primary) = spawn_ws_anvil ( ) . await ?;
682+ let ( _anvil_1, fb_1) = spawn_ws_anvil ( ) . await ?;
683+ let ( _anvil_2, fb_2) = spawn_ws_anvil ( ) . await ?;
684+
685+ let robust = RobustProviderBuilder :: fragile ( primary. clone ( ) )
686+ . fallback ( fb_1. clone ( ) )
687+ . fallback ( fb_2. clone ( ) )
688688 . subscription_timeout ( SHORT_TIMEOUT )
689+ . call_timeout ( SHORT_TIMEOUT )
689690 . build ( )
690691 . await ?;
691692
692693 let subscription = robust. subscribe_blocks ( ) . await ?;
693694 let mut stream = subscription. into_stream ( ) ;
694695
695- // Test: Start on primary
696- provider_1 . anvil_mine ( Some ( 1 ) , None ) . await ?;
696+ // Start on primary
697+ primary . anvil_mine ( Some ( 1 ) , None ) . await ?;
697698 assert_next_block ! ( stream, 1 ) ;
698699
699- // Failover to second provider
700- trigger_fb_and_assert_next_block ( & mut stream , provider_2 . clone ( ) , 1 ) . await ? ;
700+ // Kill primary - all future PP reconnection attempts will fail
701+ drop ( anvil_pp ) ;
701702
702- // Failover to third provider
703- trigger_fb_and_assert_next_block ( & mut stream, provider_3. clone ( ) , 1 ) . await ?;
703+ // PP times out -> FP1
704+ trigger_failover ( & mut stream, fb_1. clone ( ) , 1 ) . await ?;
705+
706+ // FP1 times out -> tries PP (fails, takes call_timeout) -> FP2
707+ trigger_failover_with_delay ( & mut stream, fb_2. clone ( ) , 1 , SHORT_TIMEOUT ) . await ?;
708+
709+ fb_2. anvil_mine ( Some ( 1 ) , None ) . await ?;
710+ assert_next_block ! ( stream, 2 ) ;
704711
705712 Ok ( ( ) )
706713 }
707714
708715 #[ tokio:: test]
709716 async fn subscription_fails_with_no_fallbacks ( ) -> anyhow:: Result < ( ) > {
710- // Setup: Single provider with no fallbacks
711717 let ( _anvil, provider) = spawn_ws_anvil ( ) . await ?;
712718
713719 let robust = RobustProviderBuilder :: fragile ( provider. clone ( ) )
@@ -718,18 +724,12 @@ mod tests {
718724 let subscription = robust. subscribe_blocks ( ) . await ?;
719725 let mut stream = subscription. into_stream ( ) ;
720726
721- // Test: Provider works initially
722727 provider. anvil_mine ( Some ( 1 ) , None ) . await ?;
723728 assert_next_block ! ( stream, 1 ) ;
724729
725- // Verify: No fallback available, should error
726- let task = tokio:: spawn ( async move {
727- sleep ( SHORT_TIMEOUT + BUFFER_TIME ) . await ;
728- provider. anvil_mine ( Some ( 1 ) , None ) . await . unwrap ( ) ;
729- } ) ;
730+ // No fallback available - should error after timeout
731+ sleep ( SHORT_TIMEOUT + BUFFER_TIME ) . await ;
730732 let err = stream. next ( ) . await . unwrap ( ) . unwrap_err ( ) ;
731- task. await ?;
732-
733733 assert_backend_gone_or_timeout ( err) ;
734734
735735 Ok ( ( ) )
0 commit comments