@@ -3,7 +3,7 @@ use std::{fmt::Debug, time::Duration};
33use alloy:: {
44 eips:: { BlockId , BlockNumberOrTag } ,
55 network:: { Ethereum , Network } ,
6- primitives:: BlockHash ,
6+ primitives:: { BlockHash , BlockNumber } ,
77 providers:: { Provider , RootProvider } ,
88 pubsub:: Subscription ,
99 rpc:: types:: { Filter , Log } ,
@@ -90,7 +90,7 @@ impl<N: Network> RobustProvider<N> {
9090 /// # Errors
9191 ///
9292 /// See [retry errors](#retry-errors).
93- pub async fn get_block_number ( & self ) -> Result < u64 , Error > {
93+ pub async fn get_block_number ( & self ) -> Result < BlockNumber , Error > {
9494 info ! ( "eth_getBlockNumber called" ) ;
9595 let result = self
9696 . retry_with_total_timeout (
@@ -115,7 +115,7 @@ impl<N: Network> RobustProvider<N> {
115115 /// # Errors
116116 ///
117117 /// See [retry errors](#retry-errors).
118- pub async fn get_block_number_by_id ( & self , block_id : BlockId ) -> Result < u64 , Error > {
118+ pub async fn get_block_number_by_id ( & self , block_id : BlockId ) -> Result < BlockNumber , Error > {
119119 info ! ( "get_block_number_by_id called" ) ;
120120 let result = self
121121 . retry_with_total_timeout (
@@ -326,10 +326,30 @@ mod tests {
326326 consensus:: BlockHeader ,
327327 providers:: { ProviderBuilder , WsConnect , ext:: AnvilApi } ,
328328 } ;
329- use alloy_node_bindings:: Anvil ;
329+ use alloy_node_bindings:: { Anvil , AnvilInstance } ;
330330 use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
331331 use tokio:: time:: sleep;
332332
333+ async fn setup_anvil ( ) -> anyhow:: Result < ( AnvilInstance , RobustProvider , impl Provider ) > {
334+ let anvil = Anvil :: new ( ) . try_spawn ( ) ?;
335+ let alloy_provider = ProviderBuilder :: new ( ) . connect_http ( anvil. endpoint_url ( ) ) ;
336+
337+ let robust = RobustProviderBuilder :: new ( alloy_provider. clone ( ) )
338+ . max_timeout ( Duration :: from_secs ( 5 ) )
339+ . build ( )
340+ . await ?;
341+
342+ Ok ( ( anvil, robust, alloy_provider) )
343+ }
344+
345+ async fn setup_anvil_with_blocks (
346+ num_blocks : u64 ,
347+ ) -> anyhow:: Result < ( AnvilInstance , RobustProvider , impl Provider ) > {
348+ let ( anvil, robust, alloy_provider) = setup_anvil ( ) . await ?;
349+ alloy_provider. anvil_mine ( Some ( num_blocks) , None ) . await ?;
350+ Ok ( ( anvil, robust, alloy_provider) )
351+ }
352+
333353 fn test_provider ( timeout : u64 , max_retries : usize , min_delay : u64 ) -> RobustProvider {
334354 RobustProvider {
335355 providers : vec ! [ RootProvider :: new_http( "http://localhost:8545" . parse( ) . unwrap( ) ) ] ,
@@ -535,4 +555,221 @@ mod tests {
535555
536556 Ok ( ( ) )
537557 }
558+
559+ #[ tokio:: test]
560+ async fn test_get_block_by_number_succeeds ( ) -> anyhow:: Result < ( ) > {
561+ let ( _anvil, robust, alloy_provider) = setup_anvil_with_blocks ( 100 ) . await ?;
562+
563+ let tags = [
564+ BlockNumberOrTag :: Number ( 50 ) ,
565+ BlockNumberOrTag :: Latest ,
566+ BlockNumberOrTag :: Earliest ,
567+ BlockNumberOrTag :: Safe ,
568+ BlockNumberOrTag :: Finalized ,
569+ ] ;
570+
571+ for tag in tags {
572+ let robust_block = robust. get_block_by_number ( tag) . await ?;
573+ let alloy_block =
574+ alloy_provider. get_block_by_number ( tag) . await ?. expect ( "block should exist" ) ;
575+
576+ assert_eq ! ( robust_block. header. number, alloy_block. header. number) ;
577+ assert_eq ! ( robust_block. header. hash, alloy_block. header. hash) ;
578+ }
579+
580+ Ok ( ( ) )
581+ }
582+
583+ #[ tokio:: test]
584+ async fn test_get_block_by_number_future_block_fails ( ) -> anyhow:: Result < ( ) > {
585+ let ( _anvil, robust, _alloy_provider) = setup_anvil ( ) . await ?;
586+
587+ let future_block = 999_999 ;
588+ let result = robust. get_block_by_number ( BlockNumberOrTag :: Number ( future_block) ) . await ;
589+
590+ assert ! ( matches!( result, Err ( Error :: BlockNotFound ( _) ) ) ) ;
591+
592+ Ok ( ( ) )
593+ }
594+
595+ #[ tokio:: test]
596+ async fn test_get_block_succeeds ( ) -> anyhow:: Result < ( ) > {
597+ let ( _anvil, robust, alloy_provider) = setup_anvil_with_blocks ( 100 ) . await ?;
598+
599+ let block_ids = [
600+ BlockId :: number ( 50 ) ,
601+ BlockId :: latest ( ) ,
602+ BlockId :: earliest ( ) ,
603+ BlockId :: safe ( ) ,
604+ BlockId :: finalized ( ) ,
605+ ] ;
606+
607+ for block_id in block_ids {
608+ let robust_block = robust. get_block ( block_id) . await ?;
609+ let alloy_block =
610+ alloy_provider. get_block ( block_id) . await ?. expect ( "block should exist" ) ;
611+
612+ assert_eq ! ( robust_block. header. number, alloy_block. header. number) ;
613+ assert_eq ! ( robust_block. header. hash, alloy_block. header. hash) ;
614+ }
615+
616+ // test block hash
617+ let block = alloy_provider
618+ . get_block_by_number ( BlockNumberOrTag :: Number ( 50 ) )
619+ . await ?
620+ . expect ( "block should exist" ) ;
621+ let block_hash = block. header . hash ;
622+ let block_id = BlockId :: hash ( block_hash) ;
623+ let robust_block = robust. get_block ( block_id) . await ?;
624+ assert_eq ! ( robust_block. header. hash, block_hash) ;
625+ assert_eq ! ( robust_block. header. number, 50 ) ;
626+
627+ Ok ( ( ) )
628+ }
629+
630+ #[ tokio:: test]
631+ async fn test_get_block_fails ( ) -> anyhow:: Result < ( ) > {
632+ let ( _anvil, robust, _alloy_provider) = setup_anvil ( ) . await ?;
633+
634+ // Future block number
635+ let result = robust. get_block ( BlockId :: number ( 999_999 ) ) . await ;
636+ assert ! ( matches!( result, Err ( Error :: BlockNotFound ( _) ) ) ) ;
637+
638+ // Non-existent hash
639+ let result = robust. get_block ( BlockId :: hash ( BlockHash :: ZERO ) ) . await ;
640+ assert ! ( matches!( result, Err ( Error :: BlockNotFound ( _) ) ) ) ;
641+
642+ Ok ( ( ) )
643+ }
644+
645+ #[ tokio:: test]
646+ async fn test_get_block_number_succeeds ( ) -> anyhow:: Result < ( ) > {
647+ let ( _anvil, robust, alloy_provider) = setup_anvil_with_blocks ( 100 ) . await ?;
648+
649+ let robust_block_num = robust. get_block_number ( ) . await ?;
650+ let alloy_block_num = alloy_provider. get_block_number ( ) . await ?;
651+ assert_eq ! ( robust_block_num, alloy_block_num) ;
652+ assert_eq ! ( robust_block_num, 100 ) ;
653+
654+ alloy_provider. anvil_mine ( Some ( 10 ) , None ) . await ?;
655+ let new_block = robust. get_block_number ( ) . await ?;
656+ assert_eq ! ( new_block, 110 ) ;
657+
658+ Ok ( ( ) )
659+ }
660+
661+ #[ tokio:: test]
662+ async fn test_get_block_number_by_id_succeeds ( ) -> anyhow:: Result < ( ) > {
663+ let ( _anvil, robust, alloy_provider) = setup_anvil_with_blocks ( 100 ) . await ?;
664+
665+ let block_num = robust. get_block_number_by_id ( BlockId :: number ( 50 ) ) . await ?;
666+ assert_eq ! ( block_num, 50 ) ;
667+
668+ let block = alloy_provider
669+ . get_block_by_number ( BlockNumberOrTag :: Number ( 50 ) )
670+ . await ?
671+ . expect ( "block should exist" ) ;
672+ let block_num = robust. get_block_number_by_id ( BlockId :: hash ( block. header . hash ) ) . await ?;
673+ assert_eq ! ( block_num, 50 ) ;
674+
675+ let block_num = robust. get_block_number_by_id ( BlockId :: latest ( ) ) . await ?;
676+ assert_eq ! ( block_num, 100 ) ;
677+
678+ let block_num = robust. get_block_number_by_id ( BlockId :: earliest ( ) ) . await ?;
679+ assert_eq ! ( block_num, 0 ) ;
680+
681+ // Returns block number even if it doesnt 'exist' on chain
682+ let block_num = robust. get_block_number_by_id ( BlockId :: number ( 999_999 ) ) . await ?;
683+ let alloy_block_num = alloy_provider
684+ . get_block_number_by_id ( BlockId :: number ( 999_999 ) )
685+ . await ?
686+ . expect ( "Should return block num" ) ;
687+ assert_eq ! ( alloy_block_num, block_num) ;
688+ assert_eq ! ( block_num, 999_999 ) ;
689+
690+ Ok ( ( ) )
691+ }
692+
693+ #[ tokio:: test]
694+ async fn test_get_block_number_by_id_fails ( ) -> anyhow:: Result < ( ) > {
695+ let ( _anvil, robust, _alloy_provider) = setup_anvil ( ) . await ?;
696+
697+ let result = robust. get_block_number_by_id ( BlockId :: hash ( BlockHash :: ZERO ) ) . await ;
698+ assert ! ( matches!( result, Err ( Error :: BlockNotFound ( _) ) ) ) ;
699+
700+ Ok ( ( ) )
701+ }
702+
703+ #[ tokio:: test]
704+ async fn test_get_latest_confirmed_succeeds ( ) -> anyhow:: Result < ( ) > {
705+ let ( _anvil, robust, _alloy_provider) = setup_anvil_with_blocks ( 100 ) . await ?;
706+
707+ // With confirmations
708+ let confirmed_block = robust. get_latest_confirmed ( 10 ) . await ?;
709+ assert_eq ! ( confirmed_block, 90 ) ;
710+
711+ // Zero confirmations returns latest
712+ let confirmed_block = robust. get_latest_confirmed ( 0 ) . await ?;
713+ assert_eq ! ( confirmed_block, 100 ) ;
714+
715+ // Single confirmation
716+ let confirmed_block = robust. get_latest_confirmed ( 1 ) . await ?;
717+ assert_eq ! ( confirmed_block, 99 ) ;
718+
719+ // confirmations = latest - 1
720+ let confirmed_block = robust. get_latest_confirmed ( 99 ) . await ?;
721+ assert_eq ! ( confirmed_block, 1 ) ;
722+
723+ // confirmations = latest (should return 0)
724+ let confirmed_block = robust. get_latest_confirmed ( 100 ) . await ?;
725+ assert_eq ! ( confirmed_block, 0 ) ;
726+
727+ // confirmations = latest + 1 (saturates at zero)
728+ let confirmed_block = robust. get_latest_confirmed ( 101 ) . await ?;
729+ assert_eq ! ( confirmed_block, 0 ) ;
730+
731+ // Saturates at zero when confirmations > latest
732+ let confirmed_block = robust. get_latest_confirmed ( 200 ) . await ?;
733+ assert_eq ! ( confirmed_block, 0 ) ;
734+
735+ Ok ( ( ) )
736+ }
737+
738+ #[ tokio:: test]
739+ async fn test_get_block_by_hash_succeeds ( ) -> anyhow:: Result < ( ) > {
740+ let ( _anvil, robust, alloy_provider) = setup_anvil_with_blocks ( 100 ) . await ?;
741+
742+ let block = alloy_provider
743+ . get_block_by_number ( BlockNumberOrTag :: Number ( 50 ) )
744+ . await ?
745+ . expect ( "block should exist" ) ;
746+ let block_hash = block. header . hash ;
747+
748+ let robust_block = robust. get_block_by_hash ( block_hash) . await ?;
749+ let alloy_block =
750+ alloy_provider. get_block_by_hash ( block_hash) . await ?. expect ( "block should exist" ) ;
751+ assert_eq ! ( robust_block. header. hash, alloy_block. header. hash) ;
752+ assert_eq ! ( robust_block. header. number, alloy_block. header. number) ;
753+
754+ let genesis = alloy_provider
755+ . get_block_by_number ( BlockNumberOrTag :: Earliest )
756+ . await ?
757+ . expect ( "genesis should exist" ) ;
758+ let genesis_hash = genesis. header . hash ;
759+ let robust_block = robust. get_block_by_hash ( genesis_hash) . await ?;
760+ assert_eq ! ( robust_block. header. number, 0 ) ;
761+ assert_eq ! ( robust_block. header. hash, genesis_hash) ;
762+
763+ Ok ( ( ) )
764+ }
765+
766+ #[ tokio:: test]
767+ async fn test_get_block_by_hash_fails ( ) -> anyhow:: Result < ( ) > {
768+ let ( _anvil, robust, _alloy_provider) = setup_anvil ( ) . await ?;
769+
770+ let result = robust. get_block_by_hash ( BlockHash :: ZERO ) . await ;
771+ assert ! ( matches!( result, Err ( Error :: BlockNotFound ( _) ) ) ) ;
772+
773+ Ok ( ( ) )
774+ }
538775}
0 commit comments