Skip to content

Commit b855ab3

Browse files
committed
feat(aggregator): add RemoteCertificateRetriever to AggregatorHTTPClient
1 parent 6a4c9c0 commit b855ab3

File tree

1 file changed

+308
-4
lines changed

1 file changed

+308
-4
lines changed

mithril-aggregator/src/services/aggregator_client.rs

Lines changed: 308 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use anyhow::{Context, anyhow};
22
use async_trait::async_trait;
3-
use mithril_common::messages::TryFromMessageAdapter;
43
use reqwest::header::{self, HeaderValue};
54
use reqwest::{self, Client, Proxy, RequestBuilder, Response, StatusCode, Url};
65

@@ -12,14 +11,16 @@ use thiserror::Error;
1211
use 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

2021
use crate::entities::LeaderAggregatorEpochSettings;
2122
use crate::message_adapters::FromEpochSettingsAdapter;
22-
use crate::services::LeaderAggregatorClient;
23+
use crate::services::{LeaderAggregatorClient, RemoteCertificateRetriever};
2324

2425
const 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)]
279361
pub(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

Comments
 (0)