11use anyhow:: { Context , anyhow} ;
22use async_trait:: async_trait;
3- use mithril_common:: messages:: TryFromMessageAdapter ;
43use reqwest:: header:: { self , HeaderValue } ;
54use reqwest:: { self , Client , Proxy , RequestBuilder , Response , StatusCode , Url } ;
65
@@ -12,14 +11,16 @@ use thiserror::Error;
1211use mithril_common:: {
1312 MITHRIL_AGGREGATOR_VERSION_HEADER , MITHRIL_API_VERSION_HEADER , StdError , StdResult ,
1413 api_version:: APIVersionProvider ,
15- entities:: { ClientError , ServerError } ,
14+ entities:: { Certificate , ClientError , ServerError } ,
1615 logging:: LoggerExtensions ,
17- messages:: EpochSettingsMessage ,
16+ messages:: {
17+ CertificateListMessage , CertificateMessage , EpochSettingsMessage , TryFromMessageAdapter ,
18+ } ,
1819} ;
1920
2021use crate :: entities:: LeaderAggregatorEpochSettings ;
2122use crate :: message_adapters:: FromEpochSettingsAdapter ;
22- use crate :: services:: LeaderAggregatorClient ;
23+ use crate :: services:: { LeaderAggregatorClient , RemoteCertificateRetriever } ;
2324
2425const JSON_CONTENT_TYPE : HeaderValue = HeaderValue :: from_static ( "application/json" ) ;
2526
@@ -265,6 +266,64 @@ impl AggregatorHTTPClient {
265266 Err ( err) => Err ( AggregatorClientError :: RemoteServerUnreachable ( anyhow ! ( err) ) ) ,
266267 }
267268 }
269+
270+ async fn latest_certificates_list (
271+ & self ,
272+ ) -> Result < CertificateListMessage , AggregatorClientError > {
273+ debug ! ( self . logger, "Retrieve latest certificates list" ) ;
274+ let url = self . join_aggregator_endpoint ( "certificates" ) ?;
275+ let response = self
276+ . prepare_request_builder ( self . prepare_http_client ( ) ?. get ( url) )
277+ . send ( )
278+ . await ;
279+
280+ match response {
281+ Ok ( response) => match response. status ( ) {
282+ StatusCode :: OK => {
283+ self . warn_if_api_version_mismatch ( & response) ;
284+ match response. json :: < CertificateListMessage > ( ) . await {
285+ Ok ( message) => Ok ( message) ,
286+ Err ( err) => Err ( AggregatorClientError :: JsonParseFailed ( anyhow ! ( err) ) ) ,
287+ }
288+ }
289+ _ => Err ( AggregatorClientError :: from_response ( response) . await ) ,
290+ } ,
291+ Err ( err) => Err ( AggregatorClientError :: RemoteServerUnreachable ( anyhow ! ( err) ) ) ,
292+ }
293+ }
294+
295+ async fn certificates_details (
296+ & self ,
297+ certificate_hash : & str ,
298+ ) -> Result < Option < CertificateMessage > , AggregatorClientError > {
299+ debug ! ( self . logger, "Retrieve certificate details" ; "certificate_hash" => %certificate_hash) ;
300+ let url = self . join_aggregator_endpoint ( & format ! ( "certificate/{certificate_hash}" ) ) ?;
301+ let response = self
302+ . prepare_request_builder ( self . prepare_http_client ( ) ?. get ( url) )
303+ . send ( )
304+ . await ;
305+
306+ match response {
307+ Ok ( response) => match response. status ( ) {
308+ StatusCode :: OK => {
309+ self . warn_if_api_version_mismatch ( & response) ;
310+ match response. json :: < CertificateMessage > ( ) . await {
311+ Ok ( message) => Ok ( Some ( message) ) ,
312+ Err ( err) => Err ( AggregatorClientError :: JsonParseFailed ( anyhow ! ( err) ) ) ,
313+ }
314+ }
315+ StatusCode :: NOT_FOUND => Ok ( None ) ,
316+ _ => Err ( AggregatorClientError :: from_response ( response) . await ) ,
317+ } ,
318+ Err ( err) => Err ( AggregatorClientError :: RemoteServerUnreachable ( anyhow ! ( err) ) ) ,
319+ }
320+ }
321+
322+ async fn latest_genesis_certificate (
323+ & self ,
324+ ) -> Result < Option < CertificateMessage > , AggregatorClientError > {
325+ self . certificates_details ( "genesis" ) . await
326+ }
268327}
269328
270329#[ async_trait]
@@ -275,6 +334,29 @@ impl LeaderAggregatorClient for AggregatorHTTPClient {
275334 }
276335}
277336
337+ #[ async_trait]
338+ impl RemoteCertificateRetriever for AggregatorHTTPClient {
339+ async fn get_latest_certificate_details ( & self ) -> StdResult < Option < Certificate > > {
340+ let latest_certificates_list = self . latest_certificates_list ( ) . await ?;
341+
342+ match latest_certificates_list. first ( ) {
343+ None => Ok ( None ) ,
344+ Some ( latest_certificate_list_item) => {
345+ let latest_certificate_message =
346+ self . certificates_details ( & latest_certificate_list_item. hash ) . await ?;
347+ latest_certificate_message. map ( TryInto :: try_into) . transpose ( )
348+ }
349+ }
350+ }
351+
352+ async fn get_genesis_certificate_details ( & self ) -> StdResult < Option < Certificate > > {
353+ match self . latest_genesis_certificate ( ) . await ? {
354+ Some ( message) => Ok ( Some ( message. try_into ( ) ?) ) ,
355+ None => Ok ( None ) ,
356+ }
357+ }
358+ }
359+
278360#[ cfg( test) ]
279361pub ( crate ) mod dumb {
280362 use tokio:: sync:: RwLock ;
@@ -316,6 +398,7 @@ mod tests {
316398 use serde_json:: json;
317399
318400 use mithril_common:: api_version:: DummyApiVersionDiscriminantSource ;
401+ use mithril_common:: messages:: CertificateListItemMessage ;
319402
320403 use crate :: test_tools:: TestLogger ;
321404
@@ -421,6 +504,178 @@ mod tests {
421504 ) ;
422505 }
423506
507+ #[ tokio:: test]
508+ async fn test_latest_certificates_list_ok_200 ( ) {
509+ let ( server, client) = setup_server_and_client ( ) ;
510+ let expected_list = vec ! [
511+ CertificateListItemMessage :: dummy( ) ,
512+ CertificateListItemMessage :: dummy( ) ,
513+ ] ;
514+ let _server_mock = server. mock ( |when, then| {
515+ when. path ( "/certificates" ) ;
516+ then. status ( 200 ) . body ( json ! ( expected_list) . to_string ( ) ) ;
517+ } ) ;
518+
519+ let fetched_list = client. latest_certificates_list ( ) . await . unwrap ( ) ;
520+
521+ assert_eq ! ( expected_list, fetched_list) ;
522+ }
523+
524+ #[ tokio:: test]
525+ async fn test_latest_certificates_list_ko_500 ( ) {
526+ let ( server, client) = setup_server_and_client ( ) ;
527+ let _server_mock = server. mock ( |when, then| {
528+ when. path ( "/certificates" ) ;
529+ then. status ( 500 ) . body ( "an error occurred" ) ;
530+ } ) ;
531+
532+ match client. latest_certificates_list ( ) . await . unwrap_err ( ) {
533+ AggregatorClientError :: RemoteServerTechnical ( _) => ( ) ,
534+ e => panic ! ( "Expected Aggregator::RemoteServerTechnical error, got '{e:?}'." ) ,
535+ } ;
536+ }
537+
538+ #[ tokio:: test]
539+ async fn test_latest_certificates_list_timeout ( ) {
540+ let ( server, mut client) = setup_server_and_client ( ) ;
541+ client. timeout_duration = Some ( Duration :: from_millis ( 10 ) ) ;
542+ let _server_mock = server. mock ( |when, then| {
543+ when. path ( "/certificates" ) ;
544+ then. delay ( Duration :: from_millis ( 100 ) ) ;
545+ } ) ;
546+
547+ let error = client
548+ . latest_certificates_list ( )
549+ . await
550+ . expect_err ( "retrieve_epoch_settings should fail" ) ;
551+
552+ assert ! (
553+ matches!( error, AggregatorClientError :: RemoteServerUnreachable ( _) ) ,
554+ "unexpected error type: {error:?}"
555+ ) ;
556+ }
557+
558+ #[ tokio:: test]
559+ async fn test_certificates_details_ok_200 ( ) {
560+ let ( server, client) = setup_server_and_client ( ) ;
561+ let expected_message = CertificateMessage :: dummy ( ) ;
562+ let _server_mock = server. mock ( |when, then| {
563+ when. path ( format ! ( "/certificate/{}" , expected_message. hash) ) ;
564+ then. status ( 200 ) . body ( json ! ( expected_message) . to_string ( ) ) ;
565+ } ) ;
566+
567+ let fetched_message = client. certificates_details ( & expected_message. hash ) . await . unwrap ( ) ;
568+
569+ assert_eq ! ( Some ( expected_message) , fetched_message) ;
570+ }
571+
572+ #[ tokio:: test]
573+ async fn test_certificates_details_ok_404 ( ) {
574+ let ( server, client) = setup_server_and_client ( ) ;
575+ let _server_mock = server. mock ( |when, then| {
576+ when. path ( "/certificate/not-found" ) ;
577+ then. status ( 404 ) ;
578+ } ) ;
579+
580+ let fetched_message = client. latest_genesis_certificate ( ) . await . unwrap ( ) ;
581+
582+ assert_eq ! ( None , fetched_message) ;
583+ }
584+
585+ #[ tokio:: test]
586+ async fn test_certificates_details_ko_500 ( ) {
587+ let ( server, client) = setup_server_and_client ( ) ;
588+ let _server_mock = server. mock ( |when, then| {
589+ when. path ( "/certificate/whatever" ) ;
590+ then. status ( 500 ) . body ( "an error occurred" ) ;
591+ } ) ;
592+
593+ match client. certificates_details ( "whatever" ) . await . unwrap_err ( ) {
594+ AggregatorClientError :: RemoteServerTechnical ( _) => ( ) ,
595+ e => panic ! ( "Expected Aggregator::RemoteServerTechnical error, got '{e:?}'." ) ,
596+ } ;
597+ }
598+
599+ #[ tokio:: test]
600+ async fn test_certificates_details_timeout ( ) {
601+ let ( server, mut client) = setup_server_and_client ( ) ;
602+ client. timeout_duration = Some ( Duration :: from_millis ( 10 ) ) ;
603+ let _server_mock = server. mock ( |when, then| {
604+ when. path ( "/certificate/whatever" ) ;
605+ then. delay ( Duration :: from_millis ( 100 ) ) ;
606+ } ) ;
607+
608+ let error = client
609+ . certificates_details ( "whatever" )
610+ . await
611+ . expect_err ( "retrieve_epoch_settings should fail" ) ;
612+
613+ assert ! (
614+ matches!( error, AggregatorClientError :: RemoteServerUnreachable ( _) ) ,
615+ "unexpected error type: {error:?}"
616+ ) ;
617+ }
618+
619+ #[ tokio:: test]
620+ async fn test_latest_genesis_ok_200 ( ) {
621+ let ( server, client) = setup_server_and_client ( ) ;
622+ let genesis_message = CertificateMessage :: dummy ( ) ;
623+ let _server_mock = server. mock ( |when, then| {
624+ when. path ( "/certificate/genesis" ) ;
625+ then. status ( 200 ) . body ( json ! ( genesis_message) . to_string ( ) ) ;
626+ } ) ;
627+
628+ let fetched = client. latest_genesis_certificate ( ) . await . unwrap ( ) ;
629+
630+ assert_eq ! ( Some ( genesis_message) , fetched) ;
631+ }
632+
633+ #[ tokio:: test]
634+ async fn test_latest_genesis_ok_404 ( ) {
635+ let ( server, client) = setup_server_and_client ( ) ;
636+ let _server_mock = server. mock ( |when, then| {
637+ when. path ( "/certificate/genesis" ) ;
638+ then. status ( 404 ) ;
639+ } ) ;
640+
641+ let fetched = client. latest_genesis_certificate ( ) . await . unwrap ( ) ;
642+
643+ assert_eq ! ( None , fetched) ;
644+ }
645+
646+ #[ tokio:: test]
647+ async fn test_latest_genesis_ko_500 ( ) {
648+ let ( server, client) = setup_server_and_client ( ) ;
649+ let _server_mock = server. mock ( |when, then| {
650+ when. path ( "/certificate/genesis" ) ;
651+ then. status ( 500 ) . body ( "an error occurred" ) ;
652+ } ) ;
653+
654+ let error = client. latest_genesis_certificate ( ) . await . unwrap_err ( ) ;
655+
656+ assert ! (
657+ matches!( error, AggregatorClientError :: RemoteServerTechnical ( _) ) ,
658+ "Expected Aggregator::RemoteServerTechnical error, got {error:?}"
659+ ) ;
660+ }
661+
662+ #[ tokio:: test]
663+ async fn test_latest_genesis_timeout ( ) {
664+ let ( server, mut client) = setup_server_and_client ( ) ;
665+ client. timeout_duration = Some ( Duration :: from_millis ( 10 ) ) ;
666+ let _server_mock = server. mock ( |when, then| {
667+ when. path ( "/certificate/genesis" ) ;
668+ then. delay ( Duration :: from_millis ( 100 ) ) ;
669+ } ) ;
670+
671+ let error = client. latest_genesis_certificate ( ) . await . unwrap_err ( ) ;
672+
673+ assert ! (
674+ matches!( error, AggregatorClientError :: RemoteServerUnreachable ( _) ) ,
675+ "unexpected error type: {error:?}"
676+ ) ;
677+ }
678+
424679 #[ tokio:: test]
425680 async fn test_4xx_errors_are_handled_as_remote_server_logical ( ) {
426681 let response = build_text_response ( StatusCode :: BAD_REQUEST , "error text" ) ;
@@ -724,4 +979,53 @@ mod tests {
724979 ) ;
725980 }
726981 }
982+
983+ mod remote_certificate_retriever {
984+ use mithril_common:: test_utils:: fake_data;
985+
986+ use super :: * ;
987+
988+ #[ tokio:: test]
989+ async fn test_get_latest_certificate_details ( ) {
990+ let ( server, client) = setup_server_and_client ( ) ;
991+ let expected_certificate = fake_data:: certificate ( "expected" ) ;
992+ let latest_message: CertificateMessage =
993+ expected_certificate. clone ( ) . try_into ( ) . unwrap ( ) ;
994+ let latest_certificates = vec ! [
995+ CertificateListItemMessage {
996+ hash: expected_certificate. hash. clone( ) ,
997+ ..CertificateListItemMessage :: dummy( )
998+ } ,
999+ CertificateListItemMessage :: dummy( ) ,
1000+ CertificateListItemMessage :: dummy( ) ,
1001+ ] ;
1002+ let _server_mock = server. mock ( |when, then| {
1003+ when. path ( "/certificates" ) ;
1004+ then. status ( 200 ) . body ( json ! ( latest_certificates) . to_string ( ) ) ;
1005+ } ) ;
1006+ let _server_mock = server. mock ( |when, then| {
1007+ when. path ( format ! ( "/certificate/{}" , latest_message. hash) ) ;
1008+ then. status ( 200 ) . body ( json ! ( latest_message) . to_string ( ) ) ;
1009+ } ) ;
1010+
1011+ let fetched_certificate = client. get_latest_certificate_details ( ) . await . unwrap ( ) ;
1012+
1013+ assert_eq ! ( Some ( expected_certificate) , fetched_certificate) ;
1014+ }
1015+
1016+ #[ tokio:: test]
1017+ async fn test_get_latest_genesis_certificate ( ) {
1018+ let ( server, client) = setup_server_and_client ( ) ;
1019+ let genesis_message = CertificateMessage :: dummy ( ) ;
1020+ let expected_genesis: Certificate = genesis_message. clone ( ) . try_into ( ) . unwrap ( ) ;
1021+ let _server_mock = server. mock ( |when, then| {
1022+ when. path ( "/certificate/genesis" ) ;
1023+ then. status ( 200 ) . body ( json ! ( genesis_message) . to_string ( ) ) ;
1024+ } ) ;
1025+
1026+ let fetched = client. get_genesis_certificate_details ( ) . await . unwrap ( ) ;
1027+
1028+ assert_eq ! ( Some ( expected_genesis) , fetched) ;
1029+ }
1030+ }
7271031}
0 commit comments