Skip to content

Commit

Permalink
Merge pull request #949 from input-output-hk/jpraynaud/892-add-certif…
Browse files Browse the repository at this point in the history
…icate-list-route

Add Certificate list route to aggregator REST API
  • Loading branch information
jpraynaud committed May 31, 2023
2 parents 1fb7dee + 597b73b commit dadb34c
Show file tree
Hide file tree
Showing 11 changed files with 542 additions and 19 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion mithril-aggregator/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "mithril-aggregator"
version = "0.3.23"
version = "0.3.24"
description = "A Mithril Aggregator server"
authors = { workspace = true }
edition = { workspace = true }
Expand Down
26 changes: 26 additions & 0 deletions mithril-aggregator/src/certifier_service.rs
Expand Up @@ -110,6 +110,12 @@ pub trait CertifierService: Sync + Send {
&self,
signed_entity_type: &SignedEntityType,
) -> StdResult<Option<Certificate>>;

/// Returns a certificate from its hash.
async fn get_certificate_by_hash(&self, hash: &str) -> StdResult<Option<Certificate>>;

/// Returns the list of the latest created certificates.
async fn get_latest_certificates(&self, last_n: usize) -> StdResult<Vec<Certificate>>;
}

/// Mithril CertifierService implementation
Expand Down Expand Up @@ -351,6 +357,16 @@ impl CertifierService for MithrilCertifierService {

Ok(Some(certificate))
}

async fn get_certificate_by_hash(&self, hash: &str) -> StdResult<Option<Certificate>> {
self.certificate_repository.get_certificate(hash).await
}

async fn get_latest_certificates(&self, last_n: usize) -> StdResult<Vec<Certificate>> {
self.certificate_repository
.get_latest_certificates(last_n)
.await
}
}

#[cfg(test)]
Expand Down Expand Up @@ -632,6 +648,16 @@ mod tests {
.unwrap()
.unwrap();
assert!(open_message.is_certified);

let certificate_retrieved = certifier_service
.get_certificate_by_hash(&certificate_created.hash)
.await
.unwrap()
.unwrap();
assert_eq!(certificate_created, certificate_retrieved);

let latest_certificates = certifier_service.get_latest_certificates(10).await.unwrap();
assert!(!latest_certificates.is_empty());
}

#[tokio::test]
Expand Down
32 changes: 32 additions & 0 deletions mithril-aggregator/src/database/provider/certificate.rs
Expand Up @@ -480,6 +480,15 @@ impl CertificateRepository {
Ok(cursor.next().map(|v| v.into()))
}

/// Return the latest certificates.
pub async fn get_latest_certificates(&self, last_n: usize) -> StdResult<Vec<Certificate>> {
let lock = self.connection.lock().await;
let provider = CertificateRecordProvider::new(&lock);
let cursor = provider.get_all()?;

Ok(cursor.take(last_n).map(|v| v.into()).collect())
}

/// Return the first certificate signed per epoch as the reference
/// certificate for this Epoch. This will be the parent certificate for all
/// other certificates issued within this Epoch.
Expand Down Expand Up @@ -928,6 +937,29 @@ mod tests {
assert_eq!(expected_hash, certificate.hash);
}

#[tokio::test]
async fn repository_get_latest_certificates() {
let (certificates, _) = setup_certificate_chain(5, 2);
let mut deps = DependenciesBuilder::new(Configuration::new_sample());
let connection = deps.get_sqlite_connection().await.unwrap();
{
let lock = connection.lock().await;
let provider = InsertCertificateRecordProvider::new(&lock);

for certificate in certificates.iter().rev() {
provider.persist(certificate.to_owned().into()).unwrap();
}
}

let repository = CertificateRepository::new(connection);
let latest_certificates = repository
.get_latest_certificates(certificates.len())
.await
.unwrap();

assert_eq!(certificates, latest_certificates);
}

async fn insert_certificate_records(
connection: Arc<Mutex<Connection>>,
records: Vec<CertificateRecord>,
Expand Down
106 changes: 101 additions & 5 deletions mithril-aggregator/src/http_server/routes/certificate_routes.rs
Expand Up @@ -7,6 +7,7 @@ pub fn routes(
dependency_manager: Arc<DependencyManager>,
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
certificate_pending(dependency_manager.clone())
.or(certificate_certificates(dependency_manager.clone()))
.or(certificate_certificate_hash(dependency_manager))
}

Expand All @@ -22,25 +23,38 @@ fn certificate_pending(
.and_then(handlers::certificate_pending)
}

/// GET /certificates
fn certificate_certificates(
dependency_manager: Arc<DependencyManager>,
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
warp::path!("certificates")
.and(warp::get())
.and(middlewares::with_certifier_service(dependency_manager))
.and_then(handlers::certificate_certificates)
}

/// GET /certificate/{certificate_hash}
fn certificate_certificate_hash(
dependency_manager: Arc<DependencyManager>,
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
warp::path!("certificate" / String)
.and(warp::get())
.and(middlewares::with_certificate_store(dependency_manager))
.and(middlewares::with_certifier_service(dependency_manager))
.and_then(handlers::certificate_certificate_hash)
}

mod handlers {
use crate::certifier_service::CertifierService;
use crate::http_server::routes::reply;
use crate::message_adapters::ToCertificateMessageAdapter;
use crate::{CertificatePendingStore, CertificateStore, ToCertificatePendingMessageAdapter};
use crate::message_adapters::{ToCertificateListMessageAdapter, ToCertificateMessageAdapter};
use crate::{CertificatePendingStore, ToCertificatePendingMessageAdapter};
use slog_scope::{debug, warn};
use std::convert::Infallible;
use std::sync::Arc;
use warp::http::StatusCode;

pub const LIST_MAX_ITEMS: usize = 20;

/// Certificate Pending
pub async fn certificate_pending(
certificate_pending_store: Arc<CertificatePendingStore>,
Expand All @@ -60,17 +74,41 @@ mod handlers {
}
}

/// List all Certificates
pub async fn certificate_certificates(
certifier_service: Arc<dyn CertifierService>,
) -> Result<impl warp::Reply, Infallible> {
debug!("⇄ HTTP SERVER: certificate_certificates",);

match certifier_service
.get_latest_certificates(LIST_MAX_ITEMS)
.await
{
Ok(certificates) => Ok(reply::json(
&ToCertificateListMessageAdapter::adapt(certificates),
StatusCode::OK,
)),
Err(err) => {
warn!("certificate_certificates::error"; "error" => ?err);
Ok(reply::internal_server_error(err.to_string()))
}
}
}

/// Certificate by certificate hash
pub async fn certificate_certificate_hash(
certificate_hash: String,
certificate_store: Arc<CertificateStore>,
certifier_service: Arc<dyn CertifierService>,
) -> Result<impl warp::Reply, Infallible> {
debug!(
"⇄ HTTP SERVER: certificate_certificate_hash/{}",
certificate_hash
);

match certificate_store.get_from_hash(&certificate_hash).await {
match certifier_service
.get_certificate_by_hash(&certificate_hash)
.await
{
Ok(Some(certificate)) => Ok(reply::json(
&ToCertificateMessageAdapter::adapt(certificate),
StatusCode::OK,
Expand Down Expand Up @@ -178,6 +216,64 @@ mod tests {
);
}

#[tokio::test]
async fn test_certificate_certificates_get_ok() {
let dependency_manager = initialize_dependencies().await;
dependency_manager
.certificate_store
.save(fake_data::genesis_certificate(
"{certificate_hash}".to_string(),
))
.await
.expect("certificate store save should have succeeded");

let method = Method::GET.as_str();
let path = "/certificates";

let response = request()
.method(method)
.path(&format!("/{SERVER_BASE_PATH}{path}"))
.reply(&setup_router(Arc::new(dependency_manager)))
.await;

APISpec::verify_conformity(
APISpec::get_all_spec_files(),
method,
path,
"application/json",
&Null,
&response,
);
}

#[tokio::test]
async fn test_certificate_certificates_get_ko() {
let mut dependency_manager = initialize_dependencies().await;
let certificate_store = CertificateStore::new(Box::new(FailStoreAdapter::<
String,
entities::Certificate,
>::new()));
dependency_manager.certificate_store = Arc::new(certificate_store);

let method = Method::GET.as_str();
let path = "/certificates";

let response = request()
.method(method)
.path(&format!("/{SERVER_BASE_PATH}{path}"))
.reply(&setup_router(Arc::new(dependency_manager)))
.await;

APISpec::verify_conformity(
APISpec::get_all_spec_files(),
method,
path,
"application/json",
&Null,
&response,
);
}

#[tokio::test]
async fn test_certificate_certificate_hash_get_ok() {
let dependency_manager = initialize_dependencies().await;
Expand Down
11 changes: 2 additions & 9 deletions mithril-aggregator/src/http_server/routes/middlewares.rs
Expand Up @@ -3,21 +3,14 @@ use crate::event_store::{EventMessage, TransmitterService};
use crate::signed_entity_service::SignedEntityService;
use crate::ticker_service::TickerService;
use crate::{
dependency::MultiSignerWrapper, CertificatePendingStore, CertificateStore, Configuration,
DependencyManager, ProtocolParametersStore, SignerRegisterer,
dependency::MultiSignerWrapper, CertificatePendingStore, Configuration, DependencyManager,
ProtocolParametersStore, SignerRegisterer,
};
use mithril_common::BeaconProvider;
use std::convert::Infallible;
use std::sync::Arc;
use warp::Filter;

/// With certificate store middleware
pub fn with_certificate_store(
dependency_manager: Arc<DependencyManager>,
) -> impl Filter<Extract = (Arc<CertificateStore>,), Error = Infallible> + Clone {
warp::any().map(move || dependency_manager.certificate_store.clone())
}

/// With certificate pending store
pub(crate) fn with_certificate_pending_store(
dependency_manager: Arc<DependencyManager>,
Expand Down
2 changes: 2 additions & 0 deletions mithril-aggregator/src/message_adapters/mod.rs
@@ -1,5 +1,6 @@
mod from_register_signature;
mod from_register_signer;
mod to_certificate_list_message;
mod to_certificate_message;
mod to_certificate_pending_message;
mod to_epoch_settings_message;
Expand All @@ -10,6 +11,7 @@ mod to_snapshot_message;

pub use from_register_signature::FromRegisterSingleSignatureAdapter;
pub use from_register_signer::FromRegisterSignerAdapter;
pub use to_certificate_list_message::ToCertificateListMessageAdapter;
pub use to_certificate_message::ToCertificateMessageAdapter;
pub use to_certificate_pending_message::ToCertificatePendingMessageAdapter;
pub use to_epoch_settings_message::ToEpochSettingsMessageAdapter;
Expand Down
@@ -0,0 +1,63 @@
use mithril_common::entities::Certificate;
use mithril_common::messages::{
CertificateListItemMessage, CertificateListItemMessageMetadata, CertificateListMessage,
};

/// Adapter to convert a list of [Certificate] to [CertificateListMessage] instances
pub struct ToCertificateListMessageAdapter;

impl ToCertificateListMessageAdapter {
/// Method to trigger the conversion
pub fn adapt(certificates: Vec<Certificate>) -> CertificateListMessage {
certificates
.into_iter()
.map(|certificate| CertificateListItemMessage {
hash: certificate.hash,
previous_hash: certificate.previous_hash,
beacon: certificate.beacon,
metadata: CertificateListItemMessageMetadata {
protocol_version: certificate.metadata.protocol_version,
protocol_parameters: certificate.metadata.protocol_parameters,
initiated_at: certificate.metadata.initiated_at,
sealed_at: certificate.metadata.sealed_at,
total_signers: certificate.metadata.signers.len(),
},
protocol_message: certificate.protocol_message,
signed_message: certificate.signed_message,
aggregate_verification_key: certificate.aggregate_verification_key,
})
.collect()
}
}

#[cfg(test)]
mod tests {
use mithril_common::test_utils::fake_data;

use super::*;

#[test]
fn adapt_ok() {
let certificate = fake_data::certificate("hash123".to_string());

let certificate_list_message =
ToCertificateListMessageAdapter::adapt(vec![certificate.clone()]);
let certificate_list_message_expected = vec![CertificateListItemMessage {
hash: certificate.hash,
previous_hash: certificate.previous_hash,
beacon: certificate.beacon,
metadata: CertificateListItemMessageMetadata {
protocol_version: certificate.metadata.protocol_version,
protocol_parameters: certificate.metadata.protocol_parameters,
initiated_at: certificate.metadata.initiated_at,
sealed_at: certificate.metadata.sealed_at,
total_signers: certificate.metadata.signers.len(),
},
protocol_message: certificate.protocol_message,
signed_message: certificate.signed_message,
aggregate_verification_key: certificate.aggregate_verification_key,
}];

assert_eq!(certificate_list_message_expected, certificate_list_message);
}
}

0 comments on commit dadb34c

Please sign in to comment.