diff --git a/Cargo.lock b/Cargo.lock index 7608992d3b4..704c3d47e0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2063,7 +2063,7 @@ dependencies = [ [[package]] name = "mithril-aggregator" -version = "0.2.53" +version = "0.3.0" dependencies = [ "async-trait", "chrono", @@ -2126,7 +2126,7 @@ dependencies = [ [[package]] name = "mithril-common" -version = "0.2.40" +version = "0.2.41" dependencies = [ "async-trait", "bech32", @@ -2193,7 +2193,7 @@ dependencies = [ [[package]] name = "mithril-signer" -version = "0.2.33" +version = "0.2.34" dependencies = [ "async-trait", "clap 4.2.2", diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml index bc92370169c..a1c17d54890 100644 --- a/mithril-aggregator/Cargo.toml +++ b/mithril-aggregator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-aggregator" -version = "0.2.53" +version = "0.3.0" description = "A Mithril Aggregator server" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-aggregator/src/certificate_creator.rs b/mithril-aggregator/src/certificate_creator.rs deleted file mode 100644 index acc3fbb6c6c..00000000000 --- a/mithril-aggregator/src/certificate_creator.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::runtime::WorkingCertificate; -use chrono::Utc; -use mithril_common::{ - crypto_helper::{key_encode_hex, ProtocolMultiSignature, PROTOCOL_VERSION}, - entities::{self, PartyId}, -}; -use thiserror::Error; - -/// Error type for multi signer service. -#[derive(Error, Debug)] -pub enum CertificateCreationError { - /// Codec error. - #[error("codec error: '{0}'")] - Codec(String), -} - -/// Define a way to create a [Certificate][mithril_common::entities::Certificate] -pub trait CertificateCreator { - /// Create a [Certificate][mithril_common::entities::Certificate] - fn create_certificate( - working: &WorkingCertificate, - signatures_party_ids: &[PartyId], - multi_signature: ProtocolMultiSignature, - ) -> Result; -} - -/// Implementation of a [CertificateCreator] -pub struct MithrilCertificateCreator {} - -impl CertificateCreator for MithrilCertificateCreator { - /// Creates a certificate from a multi signature - fn create_certificate( - working_certificate: &WorkingCertificate, - signatures_party_ids: &[PartyId], - multi_signature: ProtocolMultiSignature, - ) -> Result { - let protocol_version = PROTOCOL_VERSION.to_string(); - let initiated_at = format!("{:?}", working_certificate.initiated_at); - let sealed_at = format!("{:?}", Utc::now()); - let signers = working_certificate - .signers - .iter() - .filter(|signer| signatures_party_ids.contains(&signer.party_id)) - .cloned() - .collect::>(); - let metadata = entities::CertificateMetadata::new( - protocol_version, - working_certificate.protocol_parameters.clone(), - initiated_at, - sealed_at, - signers, - ); - let multi_signature = - key_encode_hex(multi_signature).map_err(CertificateCreationError::Codec)?; - let genesis_signature = "".to_string(); - - Ok(entities::Certificate::new( - working_certificate.previous_hash.clone(), - working_certificate.beacon.clone(), - metadata, - working_certificate.message.clone(), - working_certificate.aggregate_verification_key.clone(), - multi_signature, - genesis_signature, - )) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - certificate_creator::MithrilCertificateCreator, runtime::WorkingCertificate, - CertificateCreator, - }; - use chrono::{DateTime, Utc}; - use mithril_common::{ - crypto_helper::{key_decode_hex, tests_setup::setup_certificate_chain}, - entities::PartyId, - }; - use std::str::FromStr; - - #[test] - fn test() { - let (certificates, _) = setup_certificate_chain(3, 1); - let expected = &certificates[1]; - let working_certicate = WorkingCertificate { - beacon: expected.beacon.clone(), - protocol_parameters: expected.metadata.protocol_parameters.clone(), - signers: expected.metadata.signers.clone(), - message: expected.protocol_message.clone(), - aggregate_verification_key: expected.aggregate_verification_key.clone(), - initiated_at: DateTime::::from_str(&expected.metadata.initiated_at).unwrap(), - previous_hash: expected.previous_hash.clone(), - }; - let party_ids: Vec = expected - .metadata - .signers - .iter() - .map(|s| s.party_id.clone()) - .collect(); - - let mut certificate = MithrilCertificateCreator::create_certificate( - &working_certicate, - &party_ids, - key_decode_hex(&expected.multi_signature.clone()).unwrap(), - ) - .expect("certificate creation should not fail"); - // Note: We can't sync the 'sealed_at' property with the expected cert before hand since it's - // computed by create_certificate itself, so we need to do that now : - certificate.metadata.sealed_at = expected.metadata.sealed_at.clone(); - certificate.hash = certificate.compute_hash(); - - assert_eq!(expected, &certificate); - } -} diff --git a/mithril-aggregator/src/certifier_service.rs b/mithril-aggregator/src/certifier_service.rs new file mode 100644 index 00000000000..e6f3a668a5e --- /dev/null +++ b/mithril-aggregator/src/certifier_service.rs @@ -0,0 +1,679 @@ +//! ## Certifier Service +//! +//! This service is responsible of [OpenMessage] cycle of life. It creates open +//! messages and turn them into [CertificateRecord]. To do so, it registers +//! single signatures and deal with the multi_signer for aggregate signature +//! creation. +use std::sync::Arc; + +use async_trait::async_trait; +use chrono::Utc; +use mithril_common::certificate_chain::CertificateVerifier; +use mithril_common::crypto_helper::{key_encode_hex, ProtocolGenesisVerifier, PROTOCOL_VERSION}; +use mithril_common::entities::{ + Certificate, CertificateMetadata, Epoch, ProtocolMessage, SignedEntityType, SingleSignatures, +}; +use mithril_common::StdResult; +use slog::Logger; +use slog_scope::{debug, error, info, warn}; +use thiserror::Error; +use tokio::sync::RwLock; + +use crate::database::provider::{ + CertificateRepository, OpenMessage, OpenMessageRepository, OpenMessageWithSingleSignatures, + SingleSignatureRepository, +}; +use crate::MultiSigner; + +#[cfg(test)] +use mockall::automock; + +/// Errors dedicated to the CertifierService. +#[derive(Debug, Error)] +pub enum CertifierServiceError { + /// OpenMessage not found. + #[error("The open message was not found for beacon {0:?}.")] + NotFound(SignedEntityType), + + /// The open message is already certified, no more single signatures may be + /// attached to it nor be certified again. + #[error("Open message for beacon {0:?} already certified.")] + AlreadyCertified(SignedEntityType), + + /// The given beacon is older than the current open message for this type. + #[error("Given beacon {0:?} is older than the current open message beacon.")] + BeaconTooOld(SignedEntityType), + + /// The given OpenMessage already exists + #[error("An open message already exist for this beacon {0:?}, cannot create another one.")] + OpenMessageAlreadyExists(SignedEntityType), + + /// No parent certificate could be found, this certifier cannot create genesis certificates. + #[error( + "No parent certificate could be found, this certifier cannot create genesis certificates." + )] + NoParentCertificateFound, + + /// Codec error. + #[error("codec error: '{0}'")] + Codec(String), +} + +/// ## CertifierService +/// +/// This service manages the open message and their beacon transitions. It can +/// ultimately transform open messages into certificates. +#[cfg_attr(test, automock)] +#[async_trait] +pub trait CertifierService: Sync + Send { + /// Inform the certifier I have detected a new epoch, it may clear its state + /// and prepare the new signature round. If the given Epoch is equal or less + /// than the previous informed Epoch, nothing is done. + async fn inform_epoch(&self, epoch: Epoch) -> StdResult<()>; + + /// Add a new single signature for the open message at the given beacon. If + /// the open message does not exist or the open message has been certified + /// since then, an error is returned. + async fn register_single_signature( + &self, + signed_entity_type: &SignedEntityType, + signature: &SingleSignatures, + ) -> StdResult<()>; + + /// Create an open message at the given beacon. If the open message does not + /// exist or exists at an older beacon, the older open messages are cleared + /// along with their associated single signatures and the new open message + /// is created. If the message already exists, an error is returned. + async fn create_open_message( + &self, + signed_entity_type: &SignedEntityType, + protocol_message: &ProtocolMessage, + ) -> StdResult; + + /// Return the open message at the given Beacon. If the message does not + /// exist, None is returned. + async fn get_open_message( + &self, + signed_entity_type: &SignedEntityType, + ) -> StdResult>; + + /// Create a certificate if possible. If the pointed open message does + /// not exist or has been already certified, an error is raised. If a multi + /// signature is created then the flag `is_certified` of the open + /// message is set to true. The Certificate is created. + /// If the stake quorum of the single signatures is + /// not reached for the multisignature to be created, the certificate is not + /// created and None is returned. If the certificate can be created, the + /// list of the registered signers for the given epoch is used. + async fn create_certificate( + &self, + signed_entity_type: &SignedEntityType, + ) -> StdResult>; +} + +/// Mithril CertifierService implementation +pub struct MithrilCertifierService { + open_message_repository: Arc, + single_signature_repository: Arc, + certificate_repository: Arc, + certificate_verifier: Arc, + genesis_verifier: Arc, + multi_signer: Arc>, + current_epoch: Arc>, + _logger: Logger, +} + +impl MithrilCertifierService { + /// instantiate the service + #[allow(clippy::too_many_arguments)] + pub fn new( + open_message_repository: Arc, + single_signature_repository: Arc, + certificate_repository: Arc, + certificate_verifier: Arc, + genesis_verifier: Arc, + multi_signer: Arc>, + current_epoch: Epoch, + logger: Logger, + ) -> Self { + Self { + open_message_repository, + single_signature_repository, + certificate_repository, + multi_signer, + certificate_verifier, + genesis_verifier, + current_epoch: Arc::new(RwLock::new(current_epoch)), + _logger: logger, + } + } +} + +#[async_trait] +impl CertifierService for MithrilCertifierService { + async fn inform_epoch(&self, epoch: Epoch) -> StdResult<()> { + debug!("CertifierService::inform_epoch(epoch: {epoch:?})"); + let mut current_epoch = self.current_epoch.write().await; + + if epoch <= *current_epoch { + debug!("CertifierService::inform_epoch: given epoch ({epoch:?}) is older than current epoch ({}), ignoring", *current_epoch); + + return Ok(()); + } + let nb = self + .open_message_repository + .clean_epoch(*current_epoch) + .await?; + info!("MithrilCertifierService: Got a new Epoch: {epoch:?}. Cleaned {nb} open messages along with their single signatures."); + *current_epoch = epoch; + + Ok(()) + } + + async fn register_single_signature( + &self, + signed_entity_type: &SignedEntityType, + signature: &SingleSignatures, + ) -> StdResult<()> { + debug!("CertifierService::register_single_signature(signed_entity_type: {signed_entity_type:?}, single_signatures: {signature:?}"); + let open_message = self + .get_open_message(signed_entity_type) + .await? + .ok_or_else(|| { + warn!("CertifierService::register_single_signature: OpenMessage not found for type {signed_entity_type:?}."); + CertifierServiceError::NotFound(signed_entity_type.clone()) + })?; + + if open_message.is_certified { + warn!("CertifierService::register_single_signature: open message {signed_entity_type:?} is already certified, cannot register single signature."); + + return Err(CertifierServiceError::AlreadyCertified(signed_entity_type.clone()).into()); + } + + let multi_signer = self.multi_signer.read().await; + multi_signer + .verify_single_signature(&open_message.protocol_message, signature) + .await?; + + let single_signature = self + .single_signature_repository + .create_single_signature(signature, &open_message.into()) + .await?; + info!("CertifierService::register_single_signature: created pool '{}' single signature for {signed_entity_type:?}.", single_signature.signer_id); + debug!("CertifierService::register_single_signature: created single signature for open message ID='{}'.", single_signature.open_message_id); + + Ok(()) + } + + async fn create_open_message( + &self, + signed_entity_type: &SignedEntityType, + protocol_message: &ProtocolMessage, + ) -> StdResult { + debug!("CertifierService::create_open_message(signed_entity_type: {signed_entity_type:?}, protocol_message: {protocol_message:?})"); + let current_epoch = self.current_epoch.read().await; + + let open_message = self + .open_message_repository + .create_open_message(*current_epoch, signed_entity_type, protocol_message) + .await?; + info!("CertifierService::create_open_message: created open message for {signed_entity_type:?}"); + debug!( + "CertifierService::create_open_message: created open message ID='{}'", + open_message.open_message_id + ); + + Ok(open_message) + } + + async fn get_open_message( + &self, + signed_entity_type: &SignedEntityType, + ) -> StdResult> { + debug!("CertifierService::get_open_message(signed_entity_type: {signed_entity_type:?})"); + + self.open_message_repository + .get_open_message_with_single_signatures(signed_entity_type) + .await + } + + async fn create_certificate( + &self, + signed_entity_type: &SignedEntityType, + ) -> StdResult> { + debug!("CertifierService::create_certificate(signed_entity_type: {signed_entity_type:?})"); + let open_message = self + .get_open_message(signed_entity_type) + .await? + .ok_or_else(|| { + warn!("CertifierService::create_certificate: OpenMessage not found for type {signed_entity_type:?}."); + CertifierServiceError::NotFound(signed_entity_type.clone()) + })?; + + if open_message.is_certified { + warn!("CertifierService::create_certificate: open message {signed_entity_type:?} is already certified, cannot create certificate."); + + return Err(CertifierServiceError::AlreadyCertified(signed_entity_type.clone()).into()); + } + + let multi_signer = self.multi_signer.write().await; + let signature = multi_signer.create_multi_signature(&open_message).await?; + + if signature.is_some() { + info!("CertifierService::create_certificate: multi-signature created for open message {signed_entity_type:?}"); + } else { + debug!("CertifierService::create_certificate: No multi-signature could be created for open message {signed_entity_type:?}"); + + return Ok(None); + } + let signature = signature.unwrap(); + let signer_ids = open_message.get_signers_id(); + let signers = multi_signer + .get_signers_with_stake() + .await? + .into_iter() + .filter(|signer| signer_ids.contains(&signer.party_id)) + .collect::>(); + + let protocol_version = PROTOCOL_VERSION.to_string(); + let initiated_at = format!("{:?}", open_message.created_at); + let sealed_at = format!("{:?}", Utc::now()); + let metadata = CertificateMetadata::new( + protocol_version, + // TODO remove this multi_signer call ↓ + multi_signer + .get_protocol_parameters() + .await? + .unwrap() + .into(), + initiated_at, + sealed_at, + signers, + ); + let multi_signature = key_encode_hex(signature).map_err(CertifierServiceError::Codec)?; + let parent_certificate_hash = self + .certificate_repository + .get_master_certificate_for_epoch(open_message.epoch) + .await? + .map(|cert| cert.hash) + .ok_or_else(|| Box::new(CertifierServiceError::NoParentCertificateFound))?; + + let certificate = Certificate::new( + parent_certificate_hash, + // TODO: remove this multi_signer call ↓ + multi_signer.get_current_beacon().await.unwrap(), + metadata, + open_message.protocol_message.clone(), + multi_signer + .compute_stake_distribution_aggregate_verification_key() + .await? + .unwrap(), + multi_signature, + "".to_string(), + ); + + self.certificate_verifier + .verify_certificate( + &certificate, + self.certificate_repository.clone(), + &self.genesis_verifier, + ) + .await?; + + let certificate = self + .certificate_repository + .create_certificate(certificate) + .await?; + + let mut open_message_certified: OpenMessage = open_message.into(); + open_message_certified.is_certified = true; + self.open_message_repository + .update_open_message(&open_message_certified) + .await?; + + Ok(Some(certificate)) + } +} + +#[cfg(test)] +mod tests { + + use crate::{ + dependency_injection::DependenciesBuilder, multi_signer::MockMultiSigner, Configuration, + }; + use mithril_common::{ + entities::{Beacon, ProtocolMessagePartKey}, + test_utils::{MithrilFixture, MithrilFixtureBuilder}, + }; + + use super::*; + + async fn setup_certifier_service( + fixture: &MithrilFixture, + epochs_with_signers: &[Epoch], + ) -> MithrilCertifierService { + let configuration = Configuration::new_sample(); + let mut dependency_builder = DependenciesBuilder::new(configuration); + let connection = dependency_builder.get_sqlite_connection().await.unwrap(); + + let dependency_manager = dependency_builder + .build_dependency_container() + .await + .unwrap(); + dependency_manager + .init_state_from_fixture(fixture, epochs_with_signers) + .await; + + let open_message_repository = Arc::new(OpenMessageRepository::new(connection.clone())); + let single_signature_repository = + Arc::new(SingleSignatureRepository::new(connection.clone())); + let certificate_repository = Arc::new(CertificateRepository::new(connection)); + let certificate_verifier = dependency_builder.get_certificate_verifier().await.unwrap(); + let genesis_verifier = dependency_builder.get_genesis_verifier().await.unwrap(); + let multi_signer = dependency_builder.get_multi_signer().await.unwrap(); + let logger = dependency_builder.get_logger().await.unwrap(); + + MithrilCertifierService::new( + open_message_repository, + single_signature_repository, + certificate_repository, + certificate_verifier, + genesis_verifier, + multi_signer, + Epoch(0), + logger, + ) + } + + #[tokio::test] + async fn should_not_clean_epoch_when_inform_same_epoch() { + let beacon = Beacon::new("devnet".to_string(), 1, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epoch = beacon.epoch; + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let mut certifier_service = setup_certifier_service(&fixture, &epochs_with_signers).await; + certifier_service.current_epoch = Arc::new(RwLock::new(epoch)); + certifier_service + .create_open_message(&signed_entity_type, &protocol_message) + .await + .unwrap(); + certifier_service.inform_epoch(epoch).await.unwrap(); + let open_message = certifier_service + .get_open_message(&signed_entity_type) + .await + .unwrap(); + assert!(open_message.is_some()); + } + + #[tokio::test] + async fn should_clean_epoch_when_inform_new_epoch() { + let beacon = Beacon::new("devnet".to_string(), 1, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epoch = beacon.epoch; + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let mut certifier_service = setup_certifier_service(&fixture, &epochs_with_signers).await; + certifier_service.current_epoch = Arc::new(RwLock::new(epoch)); + certifier_service + .create_open_message(&signed_entity_type, &protocol_message) + .await + .unwrap(); + certifier_service.inform_epoch(epoch + 1).await.unwrap(); + let open_message = certifier_service + .get_open_message(&signed_entity_type) + .await + .unwrap(); + assert!(open_message.is_none()); + } + + #[tokio::test] + async fn should_register_valid_single_signature() { + let beacon = Beacon::new("devnet".to_string(), 3, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epoch = beacon.epoch; + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); + let mut certifier_service = setup_certifier_service(&fixture, &epochs_with_signers).await; + certifier_service.current_epoch = Arc::new(RwLock::new(epoch)); + certifier_service + .multi_signer + .write() + .await + .update_current_beacon(beacon.clone()) + .await + .unwrap(); + + certifier_service + .create_open_message(&signed_entity_type, &protocol_message) + .await + .unwrap(); + + let mut signatures = Vec::new(); + for signer_fixture in fixture.signers_fixture() { + if let Some(signature) = signer_fixture.sign(&protocol_message) { + signatures.push(signature); + } + } + certifier_service + .register_single_signature(&signed_entity_type, &signatures[0]) + .await + .unwrap(); + let open_message = certifier_service + .get_open_message(&signed_entity_type) + .await + .unwrap() + .unwrap(); + assert!(!open_message.single_signatures.is_empty()); + } + + #[tokio::test] + async fn should_not_register_invalid_single_signature() { + let beacon = Beacon::new("devnet".to_string(), 3, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let mut protocol_message = ProtocolMessage::new(); + let epoch = beacon.epoch; + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); + let mut certifier_service = setup_certifier_service(&fixture, &epochs_with_signers).await; + certifier_service.current_epoch = Arc::new(RwLock::new(epoch)); + certifier_service + .multi_signer + .write() + .await + .update_current_beacon(beacon.clone()) + .await + .unwrap(); + + certifier_service + .create_open_message(&signed_entity_type, &protocol_message) + .await + .unwrap(); + + protocol_message.set_message_part( + ProtocolMessagePartKey::SnapshotDigest, + "snapshot-digest-123".to_string(), + ); + + let mut signatures = Vec::new(); + for signer_fixture in fixture.signers_fixture() { + if let Some(signature) = signer_fixture.sign(&protocol_message) { + signatures.push(signature); + } + } + certifier_service + .register_single_signature(&signed_entity_type, &signatures[0]) + .await + .expect_err("register_single_signature should fail"); + } + + #[tokio::test] + async fn should_not_register_single_signature_for_certified_open_message() { + let beacon = Beacon::new("devnet".to_string(), 3, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epoch = beacon.epoch; + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); + let mut certifier_service = setup_certifier_service(&fixture, &epochs_with_signers).await; + certifier_service.current_epoch = Arc::new(RwLock::new(epoch)); + let mut open_message = certifier_service + .create_open_message(&signed_entity_type, &protocol_message) + .await + .unwrap(); + open_message.is_certified = true; + certifier_service + .open_message_repository + .update_open_message(&open_message) + .await + .unwrap(); + + let mut signatures = Vec::new(); + for signer_fixture in fixture.signers_fixture() { + if let Some(signature) = signer_fixture.sign(&protocol_message) { + signatures.push(signature); + } + } + certifier_service + .register_single_signature(&signed_entity_type, &signatures[0]) + .await + .expect_err("register_single_signature should fail"); + } + + #[tokio::test] + async fn should_create_certificate_when_multi_signature_produced() { + let beacon = Beacon::new("devnet".to_string(), 3, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epoch = beacon.epoch; + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(3).build(); + let mut certifier_service = setup_certifier_service(&fixture, &epochs_with_signers).await; + certifier_service.current_epoch = Arc::new(RwLock::new(epoch)); + certifier_service + .multi_signer + .write() + .await + .update_current_beacon(beacon.clone()) + .await + .unwrap(); + + certifier_service + .create_open_message(&signed_entity_type, &protocol_message) + .await + .unwrap(); + + let genesis_beacon = Beacon { + epoch: beacon.epoch - 1, + ..beacon.clone() + }; + let genesis_certificate = fixture.create_genesis_certificate(&genesis_beacon); + certifier_service + .certificate_repository + .create_certificate(genesis_certificate) + .await + .unwrap(); + + let mut signatures = Vec::new(); + for signer_fixture in fixture.signers_fixture() { + if let Some(signature) = signer_fixture.sign(&protocol_message) { + signatures.push(signature); + } + } + for signature in signatures { + certifier_service + .register_single_signature(&signed_entity_type, &signature) + .await + .expect("register_single_signature should not fail"); + } + + let create_certificate_result = certifier_service + .create_certificate(&signed_entity_type) + .await + .unwrap(); + assert!(create_certificate_result.is_some()); + + let certificate_created = create_certificate_result.unwrap(); + certifier_service + .certificate_verifier + .verify_certificate( + &certificate_created, + certifier_service.certificate_repository.clone(), + &certifier_service.genesis_verifier, + ) + .await + .unwrap(); + + let open_message = certifier_service + .get_open_message(&signed_entity_type) + .await + .unwrap() + .unwrap(); + assert!(open_message.is_certified); + } + + #[tokio::test] + async fn should_not_create_certificate_for_open_message_not_created() { + let beacon = Beacon::new("devnet".to_string(), 1, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let epoch = beacon.epoch; + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let mut certifier_service = setup_certifier_service(&fixture, &epochs_with_signers).await; + certifier_service.current_epoch = Arc::new(RwLock::new(epoch)); + certifier_service + .create_certificate(&signed_entity_type) + .await + .expect_err("create_certificate should fail"); + } + + #[tokio::test] + async fn should_not_create_certificate_for_open_message_already_certified() { + let beacon = Beacon::new("devnet".to_string(), 1, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epoch = beacon.epoch; + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let mut certifier_service = setup_certifier_service(&fixture, &epochs_with_signers).await; + certifier_service.current_epoch = Arc::new(RwLock::new(epoch)); + certifier_service + .open_message_repository + .create_open_message(epoch, &signed_entity_type, &protocol_message) + .await + .unwrap(); + certifier_service + .create_certificate(&signed_entity_type) + .await + .expect_err("create_certificate should fail"); + } + + #[tokio::test] + async fn should_not_create_certificate_when_no_multi_signature_produced() { + let mut mock_multi_signer = MockMultiSigner::new(); + mock_multi_signer + .expect_create_multi_signature() + .return_once(move |_| Ok(None)); + let beacon = Beacon::new("devnet".to_string(), 1, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epoch = beacon.epoch; + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let mut certifier_service = setup_certifier_service(&fixture, &epochs_with_signers).await; + certifier_service.current_epoch = Arc::new(RwLock::new(epoch)); + certifier_service.multi_signer = Arc::new(RwLock::new(mock_multi_signer)); + certifier_service + .create_open_message(&signed_entity_type, &protocol_message) + .await + .unwrap(); + let create_certificate_result = certifier_service + .create_certificate(&signed_entity_type) + .await + .unwrap(); + assert!(create_certificate_result.is_none()); + } +} diff --git a/mithril-aggregator/src/configuration.rs b/mithril-aggregator/src/configuration.rs index 328c036dfbb..57c9dd8afe4 100644 --- a/mithril-aggregator/src/configuration.rs +++ b/mithril-aggregator/src/configuration.rs @@ -146,7 +146,7 @@ impl Configuration { run_interval: 5000, db_directory: PathBuf::new(), snapshot_directory: PathBuf::new(), - data_stores_directory: PathBuf::new(), + data_stores_directory: PathBuf::from(":memory:"), genesis_verification_key: key_encode_hex(genesis_verification_key).unwrap(), reset_digests_cache: false, disable_digests_cache: false, diff --git a/mithril-aggregator/src/database/migration.rs b/mithril-aggregator/src/database/migration.rs index a93cfa11f9e..f35b18b4b95 100644 --- a/mithril-aggregator/src/database/migration.rs +++ b/mithril-aggregator/src/database/migration.rs @@ -279,6 +279,17 @@ create table single_signature ( foreign key (open_message_id) references open_message(open_message_id) on delete cascade foreign key (signer_id, registration_epoch_setting_id) references signer_registration(signer_id, epoch_setting_id) ); +"#, + ), + // Migration 10 + // Alter `open_message` table and drop `single_signature_legacy` table + SqlMigration::new( + 10, + r#" +drop table single_signature_legacy; +alter table open_message drop column message; +alter table open_message add column protocol_message json not null; +alter table open_message add column is_certified bool not null default false; "#, ), ] diff --git a/mithril-aggregator/src/database/provider/certificate.rs b/mithril-aggregator/src/database/provider/certificate.rs index 447f251c390..a1c0a985b66 100644 --- a/mithril-aggregator/src/database/provider/certificate.rs +++ b/mithril-aggregator/src/database/provider/certificate.rs @@ -5,6 +5,7 @@ use sqlite::{Connection, Value}; use async_trait::async_trait; use mithril_common::{ + certificate_chain::{CertificateRetriever, CertificateRetrieverError}, entities::{ Beacon, Certificate, CertificateMetadata, Epoch, HexEncodedAgregateVerificationKey, HexEncodedKey, ProtocolMessage, ProtocolParameters, ProtocolVersion, SignerWithStake, @@ -14,6 +15,7 @@ use mithril_common::{ WhereCondition, }, store::adapter::{AdapterError, StoreAdapter}, + StdResult, }; use mithril_common::StdError; @@ -23,13 +25,13 @@ use tokio::sync::Mutex; #[derive(Debug, PartialEq, Clone)] pub struct CertificateRecord { /// Certificate id. - certificate_id: String, + pub certificate_id: String, /// Parent Certificate id. - parent_certificate_id: Option, + pub parent_certificate_id: Option, /// Message that is signed. - message: String, + pub message: String, /// Signature of the certificate. /// Note: multi-signature if parent certificate id is set, genesis signature otherwise. @@ -40,28 +42,28 @@ pub struct CertificateRecord { pub aggregate_verification_key: HexEncodedAgregateVerificationKey, /// Epoch of creation of the certificate. - epoch: Epoch, + pub epoch: Epoch, /// Beacon used to produce the signed message - beacon: Beacon, + pub beacon: Beacon, /// Protocol Version (semver) - protocol_version: ProtocolVersion, + pub protocol_version: ProtocolVersion, /// Protocol parameters. - protocol_parameters: ProtocolParameters, + pub protocol_parameters: ProtocolParameters, /// Structured message that is used to create the signed message - protocol_message: ProtocolMessage, + pub protocol_message: ProtocolMessage, /// The list of the active signers with their stakes and verification keys - signers: Vec, + pub signers: Vec, /// Date and time when the certificate was initiated - initiated_at: String, + pub initiated_at: String, /// Date and time when the certificate was sealed - sealed_at: String, + pub sealed_at: String, } impl From for CertificateRecord { @@ -259,11 +261,11 @@ impl<'client> CertificateRecordProvider<'client> { fn condition_by_certificate_id( &self, - certificate_id: String, + certificate_id: &str, ) -> Result { Ok(WhereCondition::new( "certificate_id = ?*", - vec![Value::String(certificate_id)], + vec![Value::String(certificate_id.to_owned())], )) } @@ -279,7 +281,7 @@ impl<'client> CertificateRecordProvider<'client> { /// Get CertificateRecords for a given certificate id. pub fn get_by_certificate_id( &self, - certificate_id: String, + certificate_id: &str, ) -> Result, StdError> { let filters = self.condition_by_certificate_id(certificate_id)?; let certificate_record = self.find(filters)?; @@ -329,29 +331,29 @@ impl<'conn> InsertCertificateRecordProvider<'conn> { Self { connection } } - fn get_insert_condition(&self, certificate_record: CertificateRecord) -> WhereCondition { + fn get_insert_condition(&self, certificate_record: &CertificateRecord) -> WhereCondition { WhereCondition::new( "(certificate_id, parent_certificate_id, message, signature, aggregate_verification_key, epoch, beacon, protocol_version, protocol_parameters, protocol_message, signers, initiated_at, sealed_at) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)", vec![ - Value::String(certificate_record.certificate_id), - if let Some(parent_certificate_id) = certificate_record.parent_certificate_id{ + Value::String(certificate_record.certificate_id.to_owned()), + if let Some(parent_certificate_id) = certificate_record.parent_certificate_id.to_owned() { Value::String(parent_certificate_id) }else{ Value::Null }, - Value::String(certificate_record.message), - Value::String(certificate_record.signature), - Value::String(certificate_record.aggregate_verification_key), + Value::String(certificate_record.message.to_owned()), + Value::String(certificate_record.signature.to_owned()), + Value::String(certificate_record.aggregate_verification_key.to_owned()), Value::Integer(i64::try_from(certificate_record.epoch.0).unwrap()), Value::String(serde_json::to_string(&certificate_record.beacon).unwrap()), - Value::String(certificate_record.protocol_version), + Value::String(certificate_record.protocol_version.to_owned()), Value::String( serde_json::to_string(&certificate_record.protocol_parameters).unwrap(), ), Value::String(serde_json::to_string(&certificate_record.protocol_message).unwrap()), Value::String(serde_json::to_string(&certificate_record.signers).unwrap()), - Value::String(certificate_record.initiated_at), - Value::String(certificate_record.sealed_at), + Value::String(certificate_record.initiated_at.to_owned()), + Value::String(certificate_record.sealed_at.to_owned()), ], ) } @@ -360,7 +362,7 @@ impl<'conn> InsertCertificateRecordProvider<'conn> { &self, certificate_record: CertificateRecord, ) -> Result { - let filters = self.get_insert_condition(certificate_record.clone()); + let filters = self.get_insert_condition(&certificate_record); let entity = self.find(filters)?.next().unwrap_or_else(|| { panic!( @@ -389,6 +391,120 @@ impl<'conn> Provider<'conn> for InsertCertificateRecordProvider<'conn> { } } +struct MasterCertificateProvider<'conn> { + connection: &'conn Connection, +} + +impl<'conn> MasterCertificateProvider<'conn> { + pub fn new(connection: &'conn Connection) -> Self { + Self { connection } + } + + pub fn get_master_certificate_condition(&self, epoch: Epoch) -> WhereCondition { + let condition = + WhereCondition::new("certificate.parent_certificate_id is null", Vec::new()).or_where( + WhereCondition::new("parent_certificate.epoch != certificate.epoch", Vec::new()), + ); + + let epoch_i64: i64 = epoch.0.try_into().unwrap(); + WhereCondition::new( + "certificate.epoch between ?* and ?*", + vec![Value::Integer(epoch_i64 - 1), Value::Integer(epoch_i64)], + ) + .and_where(condition) + } +} + +impl<'conn> Provider<'conn> for MasterCertificateProvider<'conn> { + type Entity = CertificateRecord; + + fn get_connection(&'conn self) -> &'conn Connection { + self.connection + } + + fn get_definition(&self, condition: &str) -> String { + // it is important to alias the fields with the same name as the table + // since the table cannot be aliased in a RETURNING statement in SQLite. + let projection = Self::Entity::get_projection().expand(SourceAlias::new(&[ + ("{:certificate:}", "certificate"), + ("{:parent_certificate:}", "parent_certificate"), + ])); + + format!( + r#" +select {projection} +from certificate + left join certificate as parent_certificate + on certificate.parent_certificate_id = parent_certificate.certificate_id +where {condition}"# + ) + } +} + +/// Database frontend API for Certificate queries. +pub struct CertificateRepository { + connection: Arc>, +} + +impl CertificateRepository { + /// Instanciate a new repository + pub fn new(connection: Arc>) -> Self { + Self { connection } + } + + /// Return the certificate corresponding to the given hash if any. + pub async fn get_certificate(&self, hash: &str) -> StdResult> { + let lock = self.connection.lock().await; + let provider = CertificateRecordProvider::new(&lock); + let mut cursor = provider.get_by_certificate_id(hash)?; + + Ok(cursor.next().map(|v| v.into())) + } + + /// 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. + pub async fn get_master_certificate_for_epoch( + &self, + epoch: Epoch, + ) -> StdResult> { + let lock = self.connection.lock().await; + let provider = MasterCertificateProvider::new(&lock); + let mut cursor = provider.find(provider.get_master_certificate_condition(epoch))?; + + Ok(cursor.next().map(|c| c.into())) + } + + /// Create a new certificate in the database. + pub async fn create_certificate(&self, certificate: Certificate) -> StdResult { + let lock = self.connection.lock().await; + let provider = InsertCertificateRecordProvider::new(&lock); + let mut cursor = provider.find(provider.get_insert_condition(&certificate.into()))?; + + let new_certificate = cursor + .next() + .ok_or_else(|| panic!("Insert certificate query should always return a record.")) + .unwrap(); + + Ok(new_certificate.into()) + } +} + +#[async_trait] +impl CertificateRetriever for CertificateRepository { + async fn get_certificate_details( + &self, + certificate_hash: &str, + ) -> Result { + self.get_certificate(certificate_hash) + .await + .map_err(|e| CertificateRetrieverError::General(e.to_string()))? + .ok_or(CertificateRetrieverError::General( + "certificate does not exist".to_string(), + )) + } +} + /// Service to deal with certificate (read & write). pub struct CertificateStoreAdapter { connection: Arc>, @@ -424,7 +540,7 @@ impl StoreAdapter for CertificateStoreAdapter { let connection = &*self.connection.lock().await; let provider = CertificateRecordProvider::new(connection); let mut cursor = provider - .get_by_certificate_id(key.to_string()) + .get_by_certificate_id(key) .map_err(|e| AdapterError::GeneralError(format!("{e}")))?; let certificate = cursor .next() @@ -466,7 +582,10 @@ impl StoreAdapter for CertificateStoreAdapter { #[cfg(test)] mod tests { - use crate::database::migration::get_migrations; + use crate::{ + database::migration::get_migrations, dependency_injection::DependenciesBuilder, + Configuration, + }; use mithril_common::crypto_helper::tests_setup::setup_certificate_chain; use super::*; @@ -488,7 +607,7 @@ mod tests { // tested on its own above. let update_provider = InsertCertificateRecordProvider::new(connection); let (sql_values, _) = update_provider - .get_insert_condition(certificates.first().unwrap().to_owned().into()) + .get_insert_condition(&(certificates.first().unwrap().to_owned().into())) .expand(); format!("insert into certificate {sql_values}") }; @@ -605,9 +724,7 @@ mod tests { fn get_certificate_record_by_certificate_id() { let connection = Connection::open(":memory:").unwrap(); let provider = CertificateRecordProvider::new(&connection); - let condition = provider - .condition_by_certificate_id("cert-123".to_string()) - .unwrap(); + let condition = provider.condition_by_certificate_id("cert-123").unwrap(); let (filter, values) = condition.expand(); assert_eq!("certificate_id = ?1".to_string(), filter); @@ -620,7 +737,7 @@ mod tests { let certificate_record: CertificateRecord = certificates.first().unwrap().to_owned().into(); let connection = Connection::open(":memory:").unwrap(); let provider = InsertCertificateRecordProvider::new(&connection); - let condition = provider.get_insert_condition(certificate_record.clone()); + let condition = provider.get_insert_condition(&certificate_record); let (values, params) = condition.expand(); assert_eq!( @@ -747,4 +864,96 @@ mod tests { .collect::>() ) } + + #[tokio::test] + async fn master_certificate_condition() { + let connection = Connection::open(":memory:").unwrap(); + let provider = MasterCertificateProvider::new(&connection); + let condition = provider.get_master_certificate_condition(Epoch(10)); + let (condition_str, parameters) = condition.expand(); + + assert_eq!("certificate.epoch between ?1 and ?2 and (certificate.parent_certificate_id is null or parent_certificate.epoch != certificate.epoch)".to_string(), condition_str); + assert_eq!(vec![Value::Integer(9), Value::Integer(10)], parameters); + } + + #[tokio::test] + async fn repository_get_certificate() { + let (certificates, _) = setup_certificate_chain(5, 2); + let expected_hash = certificates[0].hash.clone(); + 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 certificate = repository.get_certificate("whatever").await.unwrap(); + assert!(certificate.is_none()); + + let certificate = repository + .get_certificate(&expected_hash) + .await + .unwrap() + .expect("The certificate exist and should be returned."); + + assert_eq!(expected_hash, certificate.hash); + } + + #[tokio::test] + async fn get_master_certificate_for_epoch() { + let (certificates, _) = setup_certificate_chain(3, 1); + let expected_certificate_id = &certificates[2].hash; + let epoch = &certificates[2].beacon.epoch; + 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 certificate = repository + .get_master_certificate_for_epoch(*epoch) + .await + .unwrap() + .expect("This should return a certificate."); + + assert_eq!(expected_certificate_id.to_string(), certificate.hash); + } + + #[tokio::test] + async fn save_certificate() { + let (certificates, _) = setup_certificate_chain(5, 3); + let mut deps = DependenciesBuilder::new(Configuration::new_sample()); + let connection = deps.get_sqlite_connection().await.unwrap(); + let repository = CertificateRepository::new(connection); + let certificate = repository + .create_certificate(certificates[4].clone()) + .await + .unwrap(); + + assert_eq!(certificates[4].hash, certificate.hash); + { + let connection = deps.get_sqlite_connection().await.unwrap(); + let lock = connection.lock().await; + let provider = CertificateRecordProvider::new(&lock); + let mut cursor = provider + .get_by_certificate_id(&certificates[4].hash) + .unwrap(); + let cert = cursor + .next() + .expect("There should be a certificate in the database with this hash ID."); + + assert_eq!(certificates[4].hash, cert.certificate_id); + } + } } diff --git a/mithril-aggregator/src/database/provider/mod.rs b/mithril-aggregator/src/database/provider/mod.rs index 26c6e50d060..271a37f2634 100644 --- a/mithril-aggregator/src/database/provider/mod.rs +++ b/mithril-aggregator/src/database/provider/mod.rs @@ -7,6 +7,8 @@ mod signer; mod signer_registration; mod single_signature; mod stake_pool; +#[cfg(test)] +mod test_helper; pub use certificate::*; pub use epoch_setting::*; @@ -16,3 +18,5 @@ pub use signer::*; pub use signer_registration::*; pub use single_signature::*; pub use stake_pool::*; +#[cfg(test)] +pub use test_helper::*; diff --git a/mithril-aggregator/src/database/provider/open_message.rs b/mithril-aggregator/src/database/provider/open_message.rs index 02b949e88fa..ee5cf10642b 100644 --- a/mithril-aggregator/src/database/provider/open_message.rs +++ b/mithril-aggregator/src/database/provider/open_message.rs @@ -1,5 +1,6 @@ use mithril_common::StdError; +use mithril_common::entities::{PartyId, ProtocolMessage, SingleSignatures}; use mithril_common::{ entities::{Epoch, SignedEntityType}, sqlite::{HydrationError, Projection, SqLiteEntity, WhereCondition}, @@ -23,21 +24,57 @@ type StdResult = Result; /// single signature for this message from which a multi signature will be /// generated if possible. #[allow(dead_code)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct OpenMessage { /// OpenMessage unique identifier - open_message_id: Uuid, + pub open_message_id: Uuid, /// Epoch - epoch: Epoch, + pub epoch: Epoch, /// Type of message - signed_entity_type: SignedEntityType, + pub signed_entity_type: SignedEntityType, - /// Message content - message: String, + /// Message used by the Mithril Protocol + pub protocol_message: ProtocolMessage, + + /// Has this open message been converted into a certificate? + pub is_certified: bool, /// Message creation datetime, it is set by the database. - created_at: NaiveDateTime, + pub created_at: NaiveDateTime, +} + +impl OpenMessage { + #[cfg(test)] + /// Create a dumb OpenMessage instance mainly for test purposes + pub fn dummy() -> Self { + let beacon = mithril_common::test_utils::fake_data::beacon(); + let epoch = beacon.epoch; + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon); + + Self { + open_message_id: Uuid::parse_str("193d1442-e89b-43cf-9519-04d8db9a12ff").unwrap(), + epoch, + signed_entity_type, + protocol_message: ProtocolMessage::new(), + is_certified: false, + created_at: chrono::Local::now().naive_local(), + } + } +} + +impl From for OpenMessage { + fn from(value: OpenMessageWithSingleSignatures) -> Self { + Self { + open_message_id: value.open_message_id, + epoch: value.epoch, + signed_entity_type: value.signed_entity_type, + protocol_message: value.protocol_message, + is_certified: value.is_certified, + created_at: value.created_at, + } + } } impl SqLiteEntity for OpenMessage { @@ -51,19 +88,32 @@ impl SqLiteEntity for OpenMessage { "Invalid UUID in open_message.open_message_id: '{open_message_id}'. Error: {e}" )) })?; - let message = row.get::(4); + let protocol_message = row.get::(4); + let protocol_message = serde_json::from_str(&protocol_message).map_err(|e| { + HydrationError::InvalidData(format!( + "Invalid protocol message JSON representation '{protocol_message}'. Error: {e}" + )) + })?; let epoch_setting_id = row.get::(1); let epoch_val = u64::try_from(epoch_setting_id) .map_err(|e| panic!("Integer field open_message.epoch_setting_id (value={epoch_setting_id}) is incompatible with u64 Epoch representation. Error = {e}"))?; - let beacon_str = row.get::(2); + // TODO: We need to check first that the cell can be read as a string first + // (e.g. when beacon json is '{"network": "dev", "epoch": 1, "immutable_file_number": 2}'). + // If it fails, we fallback on readign the cell as an integer (e.g. when beacon json is '5'). + // Maybe there is a better way of doing this. + let beacon_str = row + .try_get::(2) + .unwrap_or_else(|_| (row.get::(2)).to_string()); + let signed_entity_type_id = usize::try_from(row.get::(3)).map_err(|e| { panic!( "Integer field open_message.signed_entity_type_id cannot be turned into usize: {e}" ) })?; let signed_entity_type = SignedEntityType::hydrate(signed_entity_type_id, &beacon_str)?; - let datetime = &row.get::(5); + let is_certified = row.get::(5) != 0; + let datetime = &row.get::(6); let created_at = NaiveDateTime::parse_from_str(datetime, "%Y-%m-%d %H:%M:%S").map_err(|e| { HydrationError::InvalidData(format!( @@ -75,7 +125,8 @@ impl SqLiteEntity for OpenMessage { open_message_id, epoch: Epoch(epoch_val), signed_entity_type, - message, + protocol_message, + is_certified, created_at, }; @@ -100,7 +151,12 @@ impl SqLiteEntity for OpenMessage { "{:open_message:}.signed_entity_type_id", "int", ), - ("message", "{:open_message:}.message", "text"), + ( + "protocol_message", + "{:open_message:}.protocol_message", + "text", + ), + ("is_certified", "{:open_message:}.is_certified", "bool"), ("created_at", "{:open_message:}.created_at", "text"), ]) } @@ -146,7 +202,10 @@ impl<'client> Provider<'client> for OpenMessageProvider<'client> { type Entity = OpenMessage; fn get_definition(&self, condition: &str) -> String { - let aliases = SourceAlias::new(&[("{:open_message:}", "open_message")]); + let aliases = SourceAlias::new(&[ + ("{:open_message:}", "open_message"), + ("{:single_signature:}", "single_signature"), + ]); let projection = Self::Entity::get_projection().expand(aliases); format!("select {projection} from open_message where {condition} order by created_at desc") @@ -169,16 +228,16 @@ impl<'client> InsertOpenMessageProvider<'client> { &self, epoch: Epoch, signed_entity_type: &SignedEntityType, - message: &str, + protocol_message: &ProtocolMessage, ) -> StdResult { - let expression = "(open_message_id, epoch_setting_id, beacon, signed_entity_type_id, message) values (?*, ?*, ?*, ?*, ?*)"; + let expression = "(open_message_id, epoch_setting_id, beacon, signed_entity_type_id, protocol_message) values (?*, ?*, ?*, ?*, ?*)"; let beacon_str = signed_entity_type.get_json_beacon()?; let parameters = vec![ Value::String(Uuid::new_v4().to_string()), Value::Integer(epoch.0 as i64), Value::String(beacon_str), Value::Integer(signed_entity_type.index() as i64), - Value::String(message.to_string()), + Value::String(serde_json::to_string(protocol_message)?), ]; Ok(WhereCondition::new(expression, parameters)) @@ -200,6 +259,45 @@ impl<'client> Provider<'client> for InsertOpenMessageProvider<'client> { } } +struct UpdateOpenMessageProvider<'client> { + connection: &'client Connection, +} +impl<'client> UpdateOpenMessageProvider<'client> { + pub fn new(connection: &'client Connection) -> Self { + Self { connection } + } + + fn get_update_condition(&self, open_message: &OpenMessage) -> StdResult { + let expression = "(open_message_id, epoch_setting_id, beacon, signed_entity_type_id, protocol_message, is_certified) values (?*, ?*, ?*, ?*, ?*, ?*)"; + let beacon_str = open_message.signed_entity_type.get_json_beacon()?; + let parameters = vec![ + Value::String(open_message.open_message_id.to_string()), + Value::Integer(open_message.epoch.0 as i64), + Value::String(beacon_str), + Value::Integer(open_message.signed_entity_type.index() as i64), + Value::String(serde_json::to_string(&open_message.protocol_message)?), + Value::Integer(open_message.is_certified as i64), + ]; + + Ok(WhereCondition::new(expression, parameters)) + } +} + +impl<'client> Provider<'client> for UpdateOpenMessageProvider<'client> { + type Entity = OpenMessage; + + fn get_connection(&'client self) -> &'client Connection { + self.connection + } + + fn get_definition(&self, condition: &str) -> String { + let aliases = SourceAlias::new(&[("{:open_message:}", "open_message")]); + let projection = Self::Entity::get_projection().expand(aliases); + + format!("replace into open_message {condition} returning {projection}") + } +} + struct DeleteOpenMessageProvider<'client> { connection: &'client Connection, } @@ -211,7 +309,7 @@ impl<'client> DeleteOpenMessageProvider<'client> { fn get_epoch_condition(&self, epoch: Epoch) -> WhereCondition { WhereCondition::new( - "epoch_setting_id = ?*", + "epoch_setting_id <= ?*", vec![Value::Integer(epoch.0 as i64)], ) } @@ -232,6 +330,143 @@ impl<'client> Provider<'client> for DeleteOpenMessageProvider<'client> { } } +/// Open Message with associated single signatures if any. +#[derive(Debug, Clone)] +pub struct OpenMessageWithSingleSignatures { + /// OpenMessage unique identifier + pub open_message_id: Uuid, + + /// Epoch + pub epoch: Epoch, + + /// Type of message + pub signed_entity_type: SignedEntityType, + + /// Message used by the Mithril Protocol + pub protocol_message: ProtocolMessage, + + /// Has this message been converted into a Certificate? + pub is_certified: bool, + + /// associated single signatures + pub single_signatures: Vec, + + /// Message creation datetime, it is set by the database. + pub created_at: NaiveDateTime, +} + +impl OpenMessageWithSingleSignatures { + /// Gather all signers party_id for this open message + pub fn get_signers_id(&self) -> Vec { + self.single_signatures + .iter() + .map(|sig| sig.party_id.to_owned()) + .collect() + } +} + +impl SqLiteEntity for OpenMessageWithSingleSignatures { + fn hydrate(row: Row) -> Result + where + Self: Sized, + { + let single_signatures = &row.get::(7); + let single_signatures: Vec = serde_json::from_str(single_signatures) + .map_err(|e| { + HydrationError::InvalidData(format!( + "Could not parse single signatures JSON: '{single_signatures}'. Error: {e}" + )) + })?; + + let open_message = OpenMessage::hydrate(row)?; + + let open_message = Self { + open_message_id: open_message.open_message_id, + epoch: open_message.epoch, + signed_entity_type: open_message.signed_entity_type, + protocol_message: open_message.protocol_message, + is_certified: open_message.is_certified, + single_signatures, + created_at: open_message.created_at, + }; + + Ok(open_message) + } + + fn get_projection() -> Projection { + Projection::from(&[ + ( + "open_message_id", + "{:open_message:}.open_message_id", + "text", + ), + ( + "epoch_setting_id", + "{:open_message:}.epoch_setting_id", + "int", + ), + ("beacon", "{:open_message:}.beacon", "text"), + ( + "signed_entity_type_id", + "{:open_message:}.signed_entity_type_id", + "int", + ), + ("protocol_message", "{:open_message:}.protocol_message", "text"), + ("is_certified", "{:open_message:}.is_certified", "bool"), + ("created_at", "{:open_message:}.created_at", "text"), + ("single_signatures", "case when {:single_signature:}.signer_id is null then json('[]') else json_group_array(json_object('party_id', {:single_signature:}.signer_id, 'signature', {:single_signature:}.signature, 'indexes', json({:single_signature:}.lottery_indexes))) end", "text") + ]) + } +} + +struct OpenMessageWithSingleSignaturesProvider<'client> { + connection: &'client Connection, +} + +impl<'client> OpenMessageWithSingleSignaturesProvider<'client> { + pub fn new(connection: &'client Connection) -> Self { + Self { connection } + } + + fn get_signed_entity_type_condition( + &self, + signed_entity_type: &SignedEntityType, + ) -> WhereCondition { + WhereCondition::new( + "signed_entity_type_id = ?*", + vec![Value::Integer(signed_entity_type.index() as i64)], + ) + } +} + +impl<'client> Provider<'client> for OpenMessageWithSingleSignaturesProvider<'client> { + type Entity = OpenMessageWithSingleSignatures; + + fn get_definition(&self, condition: &str) -> String { + let aliases = SourceAlias::new(&[ + ("{:open_message:}", "open_message"), + ("{:single_signature:}", "single_signature"), + ]); + let projection = Self::Entity::get_projection().expand(aliases); + + format!( + r#" +select {projection} +from open_message + left outer join single_signature + on open_message.open_message_id = single_signature.open_message_id +where {condition} +group by open_message.open_message_id +order by open_message.created_at desc, open_message.rowid desc +"# + ) + } + + fn get_connection(&'client self) -> &'client Connection { + self.connection + } +} + /// ## Open message repository /// /// This is a business oriented layer to perform actions on the database through @@ -249,13 +484,12 @@ impl OpenMessageRepository { /// Return the latest [OpenMessage] for the given Epoch and [SignedEntityType]. pub async fn get_open_message( &self, - epoch: Epoch, signed_entity_type: &SignedEntityType, ) -> StdResult> { let lock = self.connection.lock().await; let provider = OpenMessageProvider::new(&lock); let filters = provider - .get_epoch_condition(epoch) + .get_epoch_condition(signed_entity_type.get_epoch()) .and_where(provider.get_signed_entity_type_condition(signed_entity_type)); let mut messages = provider.find(filters)?; @@ -267,11 +501,11 @@ impl OpenMessageRepository { &self, epoch: Epoch, signed_entity_type: &SignedEntityType, - message: &str, + protocol_message: &ProtocolMessage, ) -> StdResult { let lock = self.connection.lock().await; let provider = InsertOpenMessageProvider::new(&lock); - let filters = provider.get_insert_condition(epoch, signed_entity_type, message)?; + let filters = provider.get_insert_condition(epoch, signed_entity_type, protocol_message)?; let mut cursor = provider.find(filters)?; cursor @@ -279,6 +513,18 @@ impl OpenMessageRepository { .ok_or_else(|| panic!("Inserting an open_message should not return nothing.")) } + /// Updates an [OpenMessage] in the database. + pub async fn update_open_message(&self, open_message: &OpenMessage) -> StdResult { + let lock = self.connection.lock().await; + let provider = UpdateOpenMessageProvider::new(&lock); + let filters = provider.get_update_condition(open_message)?; + let mut cursor = provider.find(filters)?; + + cursor + .next() + .ok_or_else(|| panic!("Updating an open_message should not return nothing.")) + } + /// Remove all the [OpenMessage] for the given Epoch in the database. /// It returns the number of messages removed. pub async fn clean_epoch(&self, epoch: Epoch) -> StdResult { @@ -289,6 +535,19 @@ impl OpenMessageRepository { Ok(cursor.count()) } + + /// Return an open message with its associated single signatures if any. + pub async fn get_open_message_with_single_signatures( + &self, + signed_entity_type: &SignedEntityType, + ) -> StdResult> { + let lock = self.connection.lock().await; + let provider = OpenMessageWithSingleSignaturesProvider::new(&lock); + let filters = provider.get_signed_entity_type_condition(signed_entity_type); + let mut messages = provider.find(filters)?; + + Ok(messages.next()) + } } #[cfg(test)] @@ -297,15 +556,33 @@ mod tests { use crate::{dependency_injection::DependenciesBuilder, Configuration}; + use crate::database::provider::test_helper::{ + setup_single_signature_db, setup_single_signature_records, + }; + use super::*; + #[test] + fn open_message_with_single_signature_projection() { + let projection = OpenMessageWithSingleSignatures::get_projection(); + let aliases = SourceAlias::new(&[ + ("{:open_message:}", "open_message"), + ("{:single_signature:}", "single_signature"), + ]); + + assert_eq!( + "open_message.open_message_id as open_message_id, open_message.epoch_setting_id as epoch_setting_id, open_message.beacon as beacon, open_message.signed_entity_type_id as signed_entity_type_id, open_message.protocol_message as protocol_message, open_message.is_certified as is_certified, open_message.created_at as created_at, case when single_signature.signer_id is null then json('[]') else json_group_array(json_object('party_id', single_signature.signer_id, 'signature', single_signature.signature, 'indexes', json(single_signature.lottery_indexes))) end as single_signatures".to_string(), + projection.expand(aliases) + ) + } + #[test] fn open_message_projection() { let projection = OpenMessage::get_projection(); let aliases = SourceAlias::new(&[("{:open_message:}", "open_message")]); assert_eq!( - "open_message.open_message_id as open_message_id, open_message.epoch_setting_id as epoch_setting_id, open_message.beacon as beacon, open_message.signed_entity_type_id as signed_entity_type_id, open_message.message as message, open_message.created_at as created_at".to_string(), + "open_message.open_message_id as open_message_id, open_message.epoch_setting_id as epoch_setting_id, open_message.beacon as beacon, open_message.signed_entity_type_id as signed_entity_type_id, open_message.protocol_message as protocol_message, open_message.is_certified as is_certified, open_message.created_at as created_at".to_string(), projection.expand(aliases) ) } @@ -363,19 +640,50 @@ mod tests { .get_insert_condition( epoch, &SignedEntityType::CardanoImmutableFilesFull(Beacon::default()), - "This is a message", + &ProtocolMessage::new(), ) .unwrap() .expand(); - assert_eq!("(open_message_id, epoch_setting_id, beacon, signed_entity_type_id, message) values (?1, ?2, ?3, ?4, ?5)".to_string(), expr); + assert_eq!("(open_message_id, epoch_setting_id, beacon, signed_entity_type_id, protocol_message) values (?1, ?2, ?3, ?4, ?5)".to_string(), expr); assert_eq!(Value::Integer(12), params[1]); assert_eq!( Value::String(r#"{"network":"","epoch":0,"immutable_file_number":0}"#.to_string()), params[2] ); assert_eq!(Value::Integer(2), params[3]); - assert_eq!(Value::String("This is a message".to_string()), params[4]); + assert!(!params[4].as_string().unwrap().is_empty()); + } + + #[test] + fn update_provider_condition() { + let connection = Connection::open(":memory:").unwrap(); + let provider = UpdateOpenMessageProvider::new(&connection); + let open_message = OpenMessage { + open_message_id: Uuid::new_v4(), + epoch: Epoch(12), + signed_entity_type: SignedEntityType::dummy(), + protocol_message: ProtocolMessage::new(), + is_certified: true, + created_at: NaiveDateTime::default(), + }; + let (expr, params) = provider + .get_update_condition(&open_message) + .unwrap() + .expand(); + + assert_eq!("(open_message_id, epoch_setting_id, beacon, signed_entity_type_id, protocol_message, is_certified) values (?1, ?2, ?3, ?4, ?5, ?6)".to_string(), expr); + assert_eq!( + vec![ + Value::String(open_message.open_message_id.to_string()), + Value::Integer(open_message.epoch.0 as i64), + Value::String(open_message.signed_entity_type.get_json_beacon().unwrap()), + Value::Integer(open_message.signed_entity_type.index() as i64), + Value::String(serde_json::to_string(&open_message.protocol_message).unwrap()), + Value::Integer(open_message.is_certified as i64), + ], + params + ); } #[test] @@ -384,7 +692,7 @@ mod tests { let provider = DeleteOpenMessageProvider::new(&connection); let (expr, params) = provider.get_epoch_condition(Epoch(12)).expand(); - assert_eq!("epoch_setting_id = ?1".to_string(), expr); + assert_eq!("epoch_setting_id <= ?1".to_string(), expr); assert_eq!(vec![Value::Integer(12)], params,); } @@ -394,12 +702,41 @@ mod tests { let connection = builder.get_sqlite_connection().await.unwrap(); { let lock = connection.lock().await; - lock.execute(r#"insert into epoch_setting(epoch_setting_id, protocol_parameters) values (1, '{"k": 100, "m": 5, "phi": 0.65 }');"#).unwrap(); + lock.execute(r#"insert into epoch_setting(epoch_setting_id, protocol_parameters) values (1, '{"k": 100, "m": 5, "phi": 0.65 }'), (2, '{"k": 100, "m": 5, "phi": 0.65 }');"#).unwrap(); } connection } + #[tokio::test] + async fn repository_get_open_message() { + let connection = get_connection().await; + let repository = OpenMessageRepository::new(connection.clone()); + let beacon = Beacon::new("devnet".to_string(), 1, 1); + + let signed_entity_type = SignedEntityType::MithrilStakeDistribution(beacon.epoch); + repository + .create_open_message(beacon.epoch, &signed_entity_type, &ProtocolMessage::new()) + .await + .unwrap(); + let open_message_result = repository + .get_open_message(&signed_entity_type) + .await + .unwrap(); + assert!(open_message_result.is_some()); + + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + repository + .create_open_message(beacon.epoch, &signed_entity_type, &ProtocolMessage::new()) + .await + .unwrap(); + let open_message_result = repository + .get_open_message(&signed_entity_type) + .await + .unwrap(); + assert!(open_message_result.is_some()); + } + #[tokio::test] async fn repository_create_open_message() { let connection = get_connection().await; @@ -409,13 +746,12 @@ mod tests { .create_open_message( epoch, &SignedEntityType::CardanoImmutableFilesFull(Beacon::default()), - "this is a message", + &ProtocolMessage::new(), ) .await .unwrap(); assert_eq!(Epoch(1), open_message.epoch); - assert_eq!("this is a message".to_string(), open_message.message); assert_eq!( SignedEntityType::CardanoImmutableFilesFull(Beacon::default()), open_message.signed_entity_type @@ -439,19 +775,43 @@ mod tests { }) }; - assert_eq!(open_message.message, message.message); + assert_eq!(open_message.protocol_message, message.protocol_message); assert_eq!(open_message.epoch, message.epoch); } + #[tokio::test] + async fn repository_update_open_message() { + let connection = get_connection().await; + let repository = OpenMessageRepository::new(connection.clone()); + let epoch = Epoch(1); + let open_message = repository + .create_open_message( + epoch, + &SignedEntityType::CardanoImmutableFilesFull(Beacon::default()), + &ProtocolMessage::new(), + ) + .await + .unwrap(); + + let mut open_message_updated = open_message; + open_message_updated.is_certified = true; + let open_message_saved = repository + .update_open_message(&open_message_updated) + .await + .unwrap(); + + assert_eq!(open_message_updated, open_message_saved); + } + #[tokio::test] async fn repository_clean_open_message() { let connection = get_connection().await; let repository = OpenMessageRepository::new(connection.clone()); let _ = repository .create_open_message( - Epoch(1), + Epoch(2), &SignedEntityType::CardanoImmutableFilesFull(Beacon::default()), - "this is a message", + &ProtocolMessage::new(), ) .await .unwrap(); @@ -459,12 +819,61 @@ mod tests { .create_open_message( Epoch(1), &SignedEntityType::CardanoImmutableFilesFull(Beacon::default()), - "this is a stake distribution", + &ProtocolMessage::new(), ) .await .unwrap(); - let count = repository.clean_epoch(Epoch(1)).await.unwrap(); + let count = repository.clean_epoch(Epoch(2)).await.unwrap(); assert_eq!(2, count); } + + #[tokio::test] + async fn repository_get_open_message_with_single_signatures_when_signatures_exist() { + let single_signature_records = setup_single_signature_records(1, 1, 4); + + let connection = Connection::open(":memory:").unwrap(); + setup_single_signature_db(&connection, single_signature_records.clone()).unwrap(); + let repository = OpenMessageRepository::new(Arc::new(Mutex::new(connection))); + + let mut open_message = OpenMessage::dummy(); + open_message.open_message_id = single_signature_records[0].open_message_id; + repository.update_open_message(&open_message).await.unwrap(); + + let open_message_with_single_signatures = repository + .get_open_message_with_single_signatures(&open_message.signed_entity_type) + .await + .unwrap() + .unwrap(); + assert_eq!( + 4, + open_message_with_single_signatures.single_signatures.len() + ) + } + + #[tokio::test] + async fn repository_get_open_message_with_single_signatures_when_signatures_not_exist() { + let connection = Connection::open(":memory:").unwrap(); + setup_single_signature_db(&connection, Vec::new()).unwrap(); + let repository = OpenMessageRepository::new(Arc::new(Mutex::new(connection))); + + let open_message = OpenMessage::dummy(); + repository + .create_open_message( + open_message.epoch, + &open_message.signed_entity_type, + &open_message.protocol_message, + ) + .await + .unwrap(); + + let open_message_with_single_signatures = repository + .get_open_message_with_single_signatures(&open_message.signed_entity_type) + .await + .unwrap() + .unwrap(); + assert!(open_message_with_single_signatures + .single_signatures + .is_empty()) + } } diff --git a/mithril-aggregator/src/database/provider/single_signature.rs b/mithril-aggregator/src/database/provider/single_signature.rs index b96043b76d7..1a62b85ca5c 100644 --- a/mithril-aggregator/src/database/provider/single_signature.rs +++ b/mithril-aggregator/src/database/provider/single_signature.rs @@ -14,18 +14,21 @@ use mithril_common::{ use mithril_common::StdError; use tokio::sync::Mutex; +use uuid::Uuid; + +use super::OpenMessage; /// SingleSignature record is the representation of a stored single_signature. #[derive(Debug, PartialEq, Clone)] pub struct SingleSignatureRecord { /// Open message id. - open_message_id: String, + pub open_message_id: Uuid, /// Signer id. - signer_id: String, + pub signer_id: String, /// Registration epoch setting id - registration_epoch_setting_id: Epoch, + pub registration_epoch_setting_id: Epoch, /// Lottery indexes pub lottery_indexes: Vec, @@ -34,21 +37,21 @@ pub struct SingleSignatureRecord { pub signature: HexEncodedSingleSignature, /// Date and time when the single_signature was created - created_at: String, + pub created_at: String, } impl SingleSignatureRecord { fn from_single_signatures( - other: SingleSignatures, - open_message_id: String, + other: &SingleSignatures, + open_message_id: &Uuid, registration_epoch_setting_id: Epoch, ) -> Self { SingleSignatureRecord { - open_message_id, - signer_id: other.party_id, + open_message_id: open_message_id.to_owned(), + signer_id: other.party_id.to_owned(), registration_epoch_setting_id, - lottery_indexes: other.won_indexes, - signature: other.signature, + lottery_indexes: other.won_indexes.to_owned(), + signature: other.signature.to_owned(), created_at: format!("{:?}", Utc::now()), } } @@ -70,6 +73,11 @@ impl SqLiteEntity for SingleSignatureRecord { Self: Sized, { let open_message_id = row.get::(0); + let open_message_id = Uuid::parse_str(&open_message_id).map_err(|e| { + HydrationError::InvalidData(format!( + "Invalid UUID in single_signature.open_message_id: '{open_message_id}'. Error: {e}" + )) + })?; let signer_id = row.get::(1); let registration_epoch_setting_id_int = row.get::(2); let lottery_indexes_str = row.get::(3); @@ -136,11 +144,11 @@ impl<'client> SingleSignatureRecordProvider<'client> { fn condition_by_open_message_id( &self, - open_message_id: String, + open_message_id: &Uuid, ) -> Result { Ok(WhereCondition::new( "open_message_id = ?*", - vec![Value::String(open_message_id)], + vec![Value::String(open_message_id.to_string())], )) } @@ -166,7 +174,7 @@ impl<'client> SingleSignatureRecordProvider<'client> { /// Get SingleSignatureRecords for a given Open Message id. pub fn get_by_open_message_id( &self, - open_message_id: String, + open_message_id: &Uuid, ) -> Result, StdError> { let filters = self.condition_by_open_message_id(open_message_id)?; let single_signature_record = self.find(filters)?; @@ -208,21 +216,21 @@ impl<'conn> UpdateSingleSignatureRecordProvider<'conn> { Self { connection } } - fn get_update_condition( + pub(crate) fn get_update_condition( &self, - single_signature_record: SingleSignatureRecord, + single_signature_record: &SingleSignatureRecord, ) -> WhereCondition { WhereCondition::new( "(open_message_id, signer_id, registration_epoch_setting_id, lottery_indexes, signature, created_at) values (?*, ?*, ?*, ?*, ?*, ?*)", vec![ - Value::String(single_signature_record.open_message_id), - Value::String(single_signature_record.signer_id), + Value::String(single_signature_record.open_message_id.to_string()), + Value::String(single_signature_record.signer_id.to_owned()), Value::Integer( i64::try_from(single_signature_record.registration_epoch_setting_id.0).unwrap(), ), Value::String(serde_json::to_string(&single_signature_record.lottery_indexes).unwrap()), - Value::String(single_signature_record.signature), - Value::String(single_signature_record.created_at), + Value::String(single_signature_record.signature.to_owned()), + Value::String(single_signature_record.created_at.to_owned()), ], ) } @@ -231,7 +239,7 @@ impl<'conn> UpdateSingleSignatureRecordProvider<'conn> { &self, single_signature_record: SingleSignatureRecord, ) -> Result { - let filters = self.get_update_condition(single_signature_record.clone()); + let filters = self.get_update_condition(&single_signature_record); let entity = self.find(filters)?.next().unwrap_or_else(|| { panic!( @@ -263,118 +271,51 @@ impl<'conn> Provider<'conn> for UpdateSingleSignatureRecordProvider<'conn> { } /// Service to deal with single_signature (read & write). -pub struct SingleSignatureStoreAdapter { +pub struct SingleSignatureRepository { connection: Arc>, } -impl SingleSignatureStoreAdapter { +impl SingleSignatureRepository { /// Create a new SingleSignatureStoreAdapter service pub fn new(connection: Arc>) -> Self { Self { connection } } + + /// Create a new Single Signature in database + pub async fn create_single_signature( + &self, + single_signature: &SingleSignatures, + open_message: &OpenMessage, + ) -> Result { + let connection = self.connection.lock().await; + let single_signature = SingleSignatureRecord::from_single_signatures( + single_signature, + &open_message.open_message_id, + open_message.epoch.offset_to_signer_retrieval_epoch()?, + ); + let provider = UpdateSingleSignatureRecordProvider::new(&connection); + + provider.persist(single_signature) + } } #[cfg(test)] mod tests { use mithril_common::test_utils::fake_data; - use crate::database::migration::get_migrations; + use crate::database::provider::{setup_single_signature_db, setup_single_signature_records}; use super::*; - fn test_single_signature_records( - total_epoch: u64, - total_open_message: u64, - total_signer: u64, - ) -> Vec { - let mut single_signature_records = Vec::new(); - for epoch in 1..=total_epoch { - for open_message_idx in 1..=total_open_message { - for signer_idx in 1..=total_signer { - let open_message_id = open_message_idx * epoch; - let single_signature_id = epoch - + (epoch + 1) * open_message_idx - + (epoch + 1) * (open_message_idx + 1) * signer_idx; - single_signature_records.push(SingleSignatureRecord { - open_message_id: format!("open-msg-{open_message_id}"), - signer_id: format!("signer-{signer_idx}"), - registration_epoch_setting_id: Epoch(epoch), - lottery_indexes: (1..=single_signature_id).collect(), - signature: format!("signature-{single_signature_id}"), - created_at: format!("created-at-{single_signature_id}"), - }); - } - } - } - single_signature_records - } - - pub fn setup_single_signature_db( - connection: &Connection, - single_signature_records: Vec, - ) -> Result<(), StdError> { - for migration in get_migrations() { - connection.execute(&migration.alterations)?; - } - - if single_signature_records.is_empty() { - return Ok(()); - } - - let query = { - // leverage the expanded parameter from this provider which is unit - // tested on its own above. - let update_provider = UpdateSingleSignatureRecordProvider::new(connection); - let (sql_values, _) = update_provider - .get_update_condition(single_signature_records.first().unwrap().to_owned()) - .expand(); - format!("insert into single_signature {sql_values}") - }; - - for single_signature_record in single_signature_records { - let mut statement = connection.prepare(&query)?; - - statement - .bind(1, single_signature_record.open_message_id.as_str()) - .unwrap(); - statement - .bind(2, single_signature_record.signer_id.as_str()) - .unwrap(); - statement - .bind( - 3, - single_signature_record.registration_epoch_setting_id.0 as i64, - ) - .unwrap(); - statement - .bind( - 4, - serde_json::to_string(&single_signature_record.lottery_indexes) - .unwrap() - .as_str(), - ) - .unwrap(); - statement - .bind(5, single_signature_record.signature.as_str()) - .unwrap(); - statement - .bind(6, single_signature_record.created_at.as_str()) - .unwrap(); - statement.next().unwrap(); - } - - Ok(()) - } - #[test] fn test_convert_single_signatures() { let single_signature = fake_data::single_signatures(vec![1, 3, 4, 6, 7, 9]); - let open_message_id = "msg-123".to_string(); + let open_message_id = Uuid::parse_str("193d1442-e89b-43cf-9519-04d8db9a12ff").unwrap(); let single_signature_expected = single_signature.clone(); let single_signature_record = SingleSignatureRecord::from_single_signatures( - single_signature, - open_message_id, + &single_signature, + &open_message_id, Epoch(1), ); let single_signature = single_signature_record.into(); @@ -397,13 +338,17 @@ mod tests { fn get_single_signature_record_by_epoch() { let connection = Connection::open(":memory:").unwrap(); let provider = SingleSignatureRecordProvider::new(&connection); + let open_message_id_test = Uuid::parse_str("193d1442-e89b-43cf-9519-04d8db9a12ff").unwrap(); let condition = provider - .condition_by_open_message_id("open-msg-123".to_string()) + .condition_by_open_message_id(&open_message_id_test) .unwrap(); let (filter, values) = condition.expand(); assert_eq!("open_message_id = ?1".to_string(), filter); - assert_eq!(vec![Value::String("open-msg-123".to_string())], values); + assert_eq!( + vec![Value::String(open_message_id_test.to_string())], + values + ); } #[test] @@ -436,13 +381,13 @@ mod tests { fn update_single_signature_record() { let single_signature = fake_data::single_signatures(vec![1, 3, 4, 6, 7, 9]); let single_signature_record = SingleSignatureRecord::from_single_signatures( - single_signature, - "open-msg-123".to_string(), + &single_signature, + &Uuid::parse_str("193d1442-e89b-43cf-9519-04d8db9a12ff").unwrap(), Epoch(1), ); let connection = Connection::open(":memory:").unwrap(); let provider = UpdateSingleSignatureRecordProvider::new(&connection); - let condition = provider.get_update_condition(single_signature_record.clone()); + let condition = provider.get_update_condition(&single_signature_record); let (values, params) = condition.expand(); assert_eq!( @@ -451,7 +396,7 @@ mod tests { ); assert_eq!( vec![ - Value::String(single_signature_record.open_message_id), + Value::String(single_signature_record.open_message_id.to_string()), Value::String(single_signature_record.signer_id), Value::Integer(single_signature_record.registration_epoch_setting_id.0 as i64), Value::String( @@ -466,16 +411,16 @@ mod tests { #[tokio::test] async fn test_get_single_signature_records() { - let single_signature_records = test_single_signature_records(2, 3, 4); + let single_signature_records = setup_single_signature_records(2, 3, 4); let connection = Connection::open(":memory:").unwrap(); - setup_single_signature_db(&connection, single_signature_records).unwrap(); + setup_single_signature_db(&connection, single_signature_records.clone()).unwrap(); let provider = SingleSignatureRecordProvider::new(&connection); - let open_message_id_test = "open-msg-1".to_string(); + let open_message_id_test = single_signature_records[0].open_message_id.to_owned(); let single_signature_records: Vec = provider - .get_by_open_message_id(open_message_id_test.clone()) + .get_by_open_message_id(&open_message_id_test) .unwrap() .collect(); let expected_single_signature_records: Vec = @@ -492,9 +437,13 @@ mod tests { assert!(!single_signature_records.is_empty()); assert_eq!(expected_single_signature_records, single_signature_records); - let open_message_id_test = "open-msg-2".to_string(); + let open_message_id_test = single_signature_records + .last() + .unwrap() + .open_message_id + .to_owned(); let single_signature_records: Vec = provider - .get_by_open_message_id(open_message_id_test.clone()) + .get_by_open_message_id(&open_message_id_test) .unwrap() .collect(); let expected_single_signature_records: Vec = @@ -511,9 +460,9 @@ mod tests { assert!(!single_signature_records.is_empty()); assert_eq!(expected_single_signature_records, single_signature_records); - let open_message_id_test = "open-msg-123".to_string(); + let open_message_id_test = Uuid::parse_str("193d1442-e89b-43cf-9519-04d8db9a12ff").unwrap(); let single_signature_records: Vec = provider - .get_by_open_message_id(open_message_id_test) + .get_by_open_message_id(&open_message_id_test) .unwrap() .collect(); assert!(single_signature_records.is_empty()); @@ -527,7 +476,7 @@ mod tests { #[test] fn test_update_single_signature_record() { - let single_signature_records = test_single_signature_records(2, 3, 4); + let single_signature_records = setup_single_signature_records(2, 3, 4); let single_signature_records_copy = single_signature_records.clone(); let connection = Connection::open(":memory:").unwrap(); diff --git a/mithril-aggregator/src/database/provider/test_helper.rs b/mithril-aggregator/src/database/provider/test_helper.rs new file mode 100644 index 00000000000..545912907b0 --- /dev/null +++ b/mithril-aggregator/src/database/provider/test_helper.rs @@ -0,0 +1,94 @@ +use mithril_common::{entities::Epoch, StdError}; +use sqlite::Connection; +use uuid::Uuid; + +use crate::database::{migration::get_migrations, provider::UpdateSingleSignatureRecordProvider}; + +use super::SingleSignatureRecord; + +pub fn setup_single_signature_records( + total_epoch: u64, + total_open_message: u64, + total_signer: u64, +) -> Vec { + let mut single_signature_records = Vec::new(); + for epoch in 1..=total_epoch { + for open_message_idx in 1..=total_open_message { + let open_message_id = Uuid::new_v4(); + for signer_idx in 1..=total_signer { + let single_signature_id = epoch + + (epoch + 1) * open_message_idx + + (epoch + 1) * (open_message_idx + 1) * signer_idx; + single_signature_records.push(SingleSignatureRecord { + open_message_id, + signer_id: format!("signer-{signer_idx}"), + registration_epoch_setting_id: Epoch(epoch), + lottery_indexes: (1..=single_signature_id).collect(), + signature: format!("signature-{single_signature_id}"), + created_at: format!("created-at-{single_signature_id}"), + }); + } + } + } + single_signature_records +} + +pub fn setup_single_signature_db( + connection: &Connection, + single_signature_records: Vec, +) -> Result<(), StdError> { + for migration in get_migrations() { + connection.execute(&migration.alterations)?; + } + + if single_signature_records.is_empty() { + return Ok(()); + } + + let query = { + // leverage the expanded parameter from this provider which is unit + // tested on its own above. + let update_provider = UpdateSingleSignatureRecordProvider::new(connection); + let (sql_values, _) = update_provider + .get_update_condition(single_signature_records.first().unwrap()) + .expand(); + format!("insert into single_signature {sql_values}") + }; + + for single_signature_record in single_signature_records { + let mut statement = connection.prepare(&query)?; + + statement + .bind( + 1, + single_signature_record.open_message_id.to_string().as_str(), + ) + .unwrap(); + statement + .bind(2, single_signature_record.signer_id.as_str()) + .unwrap(); + statement + .bind( + 3, + single_signature_record.registration_epoch_setting_id.0 as i64, + ) + .unwrap(); + statement + .bind( + 4, + serde_json::to_string(&single_signature_record.lottery_indexes) + .unwrap() + .as_str(), + ) + .unwrap(); + statement + .bind(5, single_signature_record.signature.as_str()) + .unwrap(); + statement + .bind(6, single_signature_record.created_at.as_str()) + .unwrap(); + statement.next().unwrap(); + } + + Ok(()) +} diff --git a/mithril-aggregator/src/dependency.rs b/mithril-aggregator/src/dependency.rs index 98b194bbc1b..b662675a4f2 100644 --- a/mithril-aggregator/src/dependency.rs +++ b/mithril-aggregator/src/dependency.rs @@ -7,6 +7,7 @@ use mithril_common::{ entities::{Certificate, Epoch, ProtocolParameters, SignerWithStake, StakeDistribution}, era::{EraChecker, EraReader}, store::StakeStorer, + test_utils::MithrilFixture, BeaconProvider, }; use sqlite::Connection; @@ -15,11 +16,12 @@ use std::{collections::HashMap, sync::Arc}; use tokio::sync::{Mutex, RwLock}; use crate::{ - artifact_builder::ArtifactBuilderService, configuration::*, database::provider::StakePoolStore, - signable_builder::SignableBuilderService, signer_registerer::SignerRecorder, - CertificatePendingStore, CertificateStore, ProtocolParametersStore, ProtocolParametersStorer, - SignerRegisterer, SignerRegistrationRoundOpener, SingleSignatureStore, Snapshotter, - VerificationKeyStore, VerificationKeyStorer, + artifact_builder::ArtifactBuilderService, certifier_service::CertifierService, + configuration::*, database::provider::StakePoolStore, signable_builder::SignableBuilderService, + signer_registerer::SignerRecorder, ticker_service::TickerService, CertificatePendingStore, + CertificateStore, ProtocolParametersStore, ProtocolParametersStorer, SignerRegisterer, + SignerRegistrationRoundOpener, SingleSignatureStore, Snapshotter, VerificationKeyStore, + VerificationKeyStorer, }; use crate::{event_store::EventMessage, snapshot_stores::SnapshotStore}; use crate::{event_store::TransmitterService, multi_signer::MultiSigner}; @@ -36,7 +38,7 @@ pub struct DependencyManager { pub config: Configuration, /// SQLite database connection - /// This is not a real service but is is needed to instanciate all store + /// This is not a real service but is is needed to instantiate all store /// services. Shall be private dependency. pub sqlite_connection: Arc>, @@ -118,6 +120,12 @@ pub struct DependencyManager { /// Artifact Builder Service pub artifact_builder_service: Arc, + + /// Certifier Service + pub certifier_service: Arc, + + /// Ticker Service + pub ticker_service: Arc, } #[doc(hidden)] @@ -145,6 +153,23 @@ impl DependencyManager { (work_epoch, epoch_to_sign) } + /// `TEST METHOD ONLY` + /// + /// Fill the stores of a [DependencyManager] in a way to simulate an aggregator state + /// using the data from a precomputed fixture. + pub async fn init_state_from_fixture(&self, fixture: &MithrilFixture, target_epochs: &[Epoch]) { + for epoch in target_epochs { + self.protocol_parameters_store + .save_protocol_parameters(*epoch, fixture.protocol_parameters()) + .await + .expect("save_protocol_parameters should not fail"); + self.fill_verification_key_store(*epoch, &fixture.signers_with_stake()) + .await; + self.fill_stakes_store(*epoch, fixture.signers_with_stake()) + .await; + } + } + /// `TEST METHOD ONLY` /// /// Fill the stores of a [DependencyManager] in a way to simulate an aggregator genesis state @@ -187,12 +212,12 @@ impl DependencyManager { } for (epoch, params) in parameters_per_epoch { - self.fill_verification_key_store(epoch, ¶ms.0).await; - self.fill_stakes_store(epoch, params.0.to_vec()).await; self.protocol_parameters_store .save_protocol_parameters(epoch, params.1) .await .expect("save_protocol_parameters should not fail"); + self.fill_verification_key_store(epoch, ¶ms.0).await; + self.fill_stakes_store(epoch, params.0.to_vec()).await; } for certificate in certificate_chain { @@ -230,6 +255,9 @@ impl DependencyManager { second_epoch_signers: Vec, genesis_protocol_parameters: &ProtocolParameters, ) { + self.init_protocol_parameter_store(genesis_protocol_parameters) + .await; + let (work_epoch, epoch_to_sign) = self.get_genesis_epochs().await; for (epoch, signers) in [ (work_epoch, genesis_signers), @@ -238,9 +266,6 @@ impl DependencyManager { self.fill_verification_key_store(epoch, &signers).await; self.fill_stakes_store(epoch, signers).await; } - - self.init_protocol_parameter_store(genesis_protocol_parameters) - .await; } /// `TEST METHOD ONLY` @@ -262,6 +287,10 @@ impl DependencyManager { async fn fill_verification_key_store(&self, target_epoch: Epoch, signers: &[SignerWithStake]) { for signer in signers { + self.signer_recorder + .record_signer_id(signer.party_id.clone()) + .await + .expect("record_signer_id should not fail"); self.verification_key_store .save_verification_key(target_epoch, signer.clone()) .await diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index 7132f9e0244..9baf6172b3d 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -11,10 +11,7 @@ use mithril_common::{ CardanoImmutableDigester, DumbImmutableFileObserver, ImmutableDigester, ImmutableFileObserver, ImmutableFileSystemObserver, }, - entities::{ - Beacon, Certificate, CertificatePending, Epoch, PartyId, ProtocolParameters, - SignerWithStake, SingleSignatures, - }, + entities::{Beacon, CertificatePending, Epoch, PartyId, SingleSignatures}, era::{ adapters::{EraReaderAdapterBuilder, EraReaderDummyAdapter}, EraChecker, EraMarker, EraReader, EraReaderAdapter, SupportedEra, @@ -37,10 +34,12 @@ use warp::Filter; use crate::{ artifact_builder::{ArtifactBuilderService, DummyArtifactBuilder}, + certifier_service::{CertifierService, MithrilCertifierService}, configuration::{ExecutionEnvironment, LIST_SNAPSHOTS_MAX_ITEMS}, database::provider::{ - CertificateStoreAdapter, EpochSettingStore, SignedEntityStoreAdapter, - SignerRegistrationStoreAdapter, SignerStore, StakePoolStore, + CertificateRepository, CertificateStoreAdapter, EpochSettingStore, OpenMessageRepository, + SignedEntityStoreAdapter, SignerRegistrationStoreAdapter, SignerStore, + SingleSignatureRepository, StakePoolStore, }, event_store::{EventMessage, EventStore, TransmitterService}, http_server::routes::router, @@ -61,7 +60,17 @@ use super::{DependenciesBuilderError, Result}; const SQLITE_FILE: &str = "aggregator.sqlite3"; -/// Dependencies container builder +/// ## Dependencies container builder +/// +/// This is meant to create SHARED DEPENDENCIES, ie: dependencies instances that +/// must be shared amongst several Tokio tasks. For example, database +/// repositories are NOT shared dependencies and therefor can be created ad hoc +/// whereas the database connection is a shared dependency. +/// +/// Each shared dependency must implement a `build` and a `get` function. The +/// build function creates the dependency, the get function creates the +/// dependency at first call then return a clone of the Arc containing the +/// dependency for all further calls. pub struct DependenciesBuilder { /// Configuration parameters pub configuration: Configuration, @@ -165,6 +174,9 @@ pub struct DependenciesBuilder { /// Artifact Builder Service pub artifact_builder_service: Option>, + + /// Certifier service + pub certifier_service: Option>, } impl DependenciesBuilder { @@ -204,6 +216,7 @@ impl DependenciesBuilder { signer_recorder: None, signable_builder_service: None, artifact_builder_service: None, + certifier_service: None, } } @@ -212,7 +225,7 @@ impl DependenciesBuilder { ExecutionEnvironment::Production => { self.configuration.get_sqlite_dir().join(SQLITE_FILE) } - _ => ":memory:".into(), + _ => self.configuration.data_stores_directory.clone(), }; let connection = Connection::open(&path) .map(|c| Arc::new(Mutex::new(c))) @@ -274,27 +287,10 @@ impl DependenciesBuilder { } async fn build_snapshot_store(&mut self) -> Result> { - let adapter: Box< - dyn StoreAdapter, - > = match self.configuration.environment { - ExecutionEnvironment::Production => { - let adapter = SignedEntityStoreAdapter::new(self.get_sqlite_connection().await?); - - Box::new(adapter) - } - _ => { - let adapter = MemoryAdapter::new(None).map_err(|e| { - DependenciesBuilderError::Initialization { - message: "Cannot create Memory adapter for Snapshot Store.".to_string(), - error: Some(e.into()), - } - })?; - Box::new(adapter) - } - }; - Ok(Arc::new(LocalSnapshotStore::new( - adapter, + Box::new(SignedEntityStoreAdapter::new( + self.get_sqlite_connection().await?, + )), LIST_SNAPSHOTS_MAX_ITEMS, ))) } @@ -350,7 +346,6 @@ impl DependenciesBuilder { let multi_signer = MultiSignerImpl::new( self.get_verification_key_store().await?, self.get_stake_store().await?, - self.get_single_signature_store().await?, self.get_protocol_parameters_store().await?, ); @@ -407,26 +402,9 @@ impl DependenciesBuilder { } async fn build_certificate_store(&mut self) -> Result> { - let adapter: Box> = - match self.configuration.environment { - ExecutionEnvironment::Production => { - let adapter = CertificateStoreAdapter::new(self.get_sqlite_connection().await?); - - Box::new(adapter) - } - _ => { - let adapter = MemoryAdapter::new(None).map_err(|e| { - DependenciesBuilderError::Initialization { - message: "Cannot create Memory adapter for Certificate Store." - .to_string(), - error: Some(e.into()), - } - })?; - Box::new(adapter) - } - }; - - Ok(Arc::new(CertificateStore::new(adapter))) + Ok(Arc::new(CertificateStore::new(Box::new( + CertificateStoreAdapter::new(self.get_sqlite_connection().await?), + )))) } /// Get a configured [CertificateStore]. @@ -439,29 +417,10 @@ impl DependenciesBuilder { } async fn build_verification_key_store(&mut self) -> Result> { - let adapter: Box< - dyn StoreAdapter>, - > = match self.configuration.environment { - ExecutionEnvironment::Production => { - let adapter = - SignerRegistrationStoreAdapter::new(self.get_sqlite_connection().await?); - - Box::new(adapter) - } - _ => { - let adapter = MemoryAdapter::new(None).map_err(|e| { - DependenciesBuilderError::Initialization { - message: "Cannot create Memory adapter for VerificationKeyStore." - .to_string(), - error: Some(e.into()), - } - })?; - Box::new(adapter) - } - }; - Ok(Arc::new(VerificationKeyStore::new( - adapter, + Box::new(SignerRegistrationStoreAdapter::new( + self.get_sqlite_connection().await?, + )), self.configuration.store_retention_limit, ))) } @@ -519,27 +478,8 @@ impl DependenciesBuilder { } async fn build_protocol_parameters_store(&mut self) -> Result> { - let adapter: Box> = - match self.configuration.environment { - ExecutionEnvironment::Production => { - let adapter = EpochSettingStore::new(self.get_sqlite_connection().await?); - - Box::new(adapter) - } - _ => { - let adapter = MemoryAdapter::new(None).map_err(|e| { - DependenciesBuilderError::Initialization { - message: "Cannot create Memory adapter for ProtocolParametersStore." - .to_string(), - error: Some(e.into()), - } - })?; - Box::new(adapter) - } - }; - Ok(Arc::new(ProtocolParametersStore::new( - adapter, + Box::new(EpochSettingStore::new(self.get_sqlite_connection().await?)), self.configuration.store_retention_limit, ))) } @@ -1044,6 +984,8 @@ impl DependenciesBuilder { signer_recorder: self.get_signer_recorder().await?, signable_builder_service: self.get_signable_builder_service().await?, artifact_builder_service: self.get_artifact_builder_service().await?, + certifier_service: self.get_certifier_service().await?, + ticker_service: self.get_ticker_service().await?, }; Ok(dependency_manager) @@ -1174,4 +1116,41 @@ impl DependenciesBuilder { Ok(self.ticker_service.as_ref().cloned().unwrap()) } + + /// Create [CertifierService] service + pub async fn build_certifier_service(&mut self) -> Result> { + let open_message_repository = Arc::new(OpenMessageRepository::new( + self.get_sqlite_connection().await?, + )); + let single_signature_repository = Arc::new(SingleSignatureRepository::new( + self.get_sqlite_connection().await?, + )); + let certificate_repository = Arc::new(CertificateRepository::new( + self.get_sqlite_connection().await?, + )); + let certificate_verifier = self.get_certificate_verifier().await?; + let genesis_verifier = self.get_genesis_verifier().await?; + let multisigner = self.get_multi_signer().await?; + let logger = self.get_logger().await?; + + Ok(Arc::new(MithrilCertifierService::new( + open_message_repository, + single_signature_repository, + certificate_repository, + certificate_verifier, + genesis_verifier, + multisigner, + Epoch(0), + logger, + ))) + } + + /// [CertifierService] service + pub async fn get_certifier_service(&mut self) -> Result> { + if self.certifier_service.is_none() { + self.certifier_service = Some(self.build_certifier_service().await?); + } + + Ok(self.certifier_service.as_ref().cloned().unwrap()) + } } diff --git a/mithril-aggregator/src/http_server/routes/certificate_routes.rs b/mithril-aggregator/src/http_server/routes/certificate_routes.rs index cc8221bd36c..f5697af0fbc 100644 --- a/mithril-aggregator/src/http_server/routes/certificate_routes.rs +++ b/mithril-aggregator/src/http_server/routes/certificate_routes.rs @@ -183,7 +183,9 @@ mod tests { let (dependency_manager, _) = initialize_dependencies().await; dependency_manager .certificate_store - .save(fake_data::certificate("{certificate_hash}".to_string())) + .save(fake_data::genesis_certificate( + "{certificate_hash}".to_string(), + )) .await .expect("certificate store save should have succeeded"); diff --git a/mithril-aggregator/src/http_server/routes/middlewares.rs b/mithril-aggregator/src/http_server/routes/middlewares.rs index 2a3e60042a0..697b8a9f514 100644 --- a/mithril-aggregator/src/http_server/routes/middlewares.rs +++ b/mithril-aggregator/src/http_server/routes/middlewares.rs @@ -1,4 +1,6 @@ +use crate::certifier_service::CertifierService; use crate::event_store::{EventMessage, TransmitterService}; +use crate::ticker_service::TickerService; use crate::{ dependency::MultiSignerWrapper, CertificatePendingStore, CertificateStore, Configuration, DependencyManager, ProtocolParametersStore, SignerRegisterer, SnapshotStore, @@ -70,3 +72,17 @@ pub fn with_beacon_provider( ) -> impl Filter,), Error = Infallible> + Clone { warp::any().map(move || dependency_manager.beacon_provider.clone()) } + +/// With certifier service middleware +pub fn with_certifier_service( + dependency_manager: Arc, +) -> impl Filter,), Error = Infallible> + Clone { + warp::any().map(move || dependency_manager.certifier_service.clone()) +} + +/// With ticker service middleware +pub fn with_ticker_service( + dependency_manager: Arc, +) -> impl Filter,), Error = Infallible> + Clone { + warp::any().map(move || dependency_manager.ticker_service.clone()) +} diff --git a/mithril-aggregator/src/http_server/routes/signatures_routes.rs b/mithril-aggregator/src/http_server/routes/signatures_routes.rs index 718c52c3fe3..fdf45838519 100644 --- a/mithril-aggregator/src/http_server/routes/signatures_routes.rs +++ b/mithril-aggregator/src/http_server/routes/signatures_routes.rs @@ -16,54 +16,69 @@ fn register_signatures( warp::path!("register-signatures") .and(warp::post()) .and(warp::body::json()) - .and(middlewares::with_multi_signer(dependency_manager)) + .and(middlewares::with_certifier_service( + dependency_manager.clone(), + )) + .and(middlewares::with_ticker_service(dependency_manager)) .and_then(handlers::register_signatures) } mod handlers { + use crate::certifier_service::{CertifierService, CertifierServiceError}; use crate::http_server::routes::reply; - use crate::ProtocolError; - use crate::{ - dependency::MultiSignerWrapper, message_adapters::FromRegisterSingleSignatureAdapter, - }; - use mithril_common::entities::SingleSignatures; + use crate::message_adapters::FromRegisterSingleSignatureAdapter; + use crate::ticker_service::TickerService; + use mithril_common::entities::SignedEntityType; use mithril_common::messages::RegisterSignatureMessage; use slog_scope::{debug, warn}; use std::convert::Infallible; + use std::sync::Arc; use warp::http::StatusCode; /// Register Signatures pub async fn register_signatures( message: RegisterSignatureMessage, - multi_signer: MultiSignerWrapper, + certifier_service: Arc, + ticker_service: Arc, ) -> Result { debug!("⇄ HTTP SERVER: register_signatures/{:?}", message); - async fn register_single_signature( - multi_signer: MultiSignerWrapper, - signature: SingleSignatures, - ) -> Result<(), ProtocolError> { - let multi_signer = multi_signer.write().await; - let message = multi_signer - .get_current_message() + let signed_entity_type = match message.signed_entity_type.clone() { + Some(signed_entity_type) => Ok(signed_entity_type), + None => ticker_service + .get_current_immutable_beacon() .await - .ok_or_else(ProtocolError::UnavailableMessage)?; - multi_signer - .register_single_signature(&message, &signature) - .await - } + .map(SignedEntityType::CardanoImmutableFilesFull), + }; - let signature = FromRegisterSingleSignatureAdapter::adapt(message); - match register_single_signature(multi_signer, signature).await { - Err(ProtocolError::ExistingSingleSignature(party_id)) => { - debug!("register_signatures::already_exist"; "party_id" => ?party_id); - Ok(reply::empty(StatusCode::CONFLICT)) + match signed_entity_type { + Ok(signed_entity_type) => { + let signature = FromRegisterSingleSignatureAdapter::adapt(message); + match certifier_service + .register_single_signature(&signed_entity_type, &signature) + .await + { + Err(err) => match err.downcast_ref::() { + Some(CertifierServiceError::AlreadyCertified(signed_entity_type)) => { + debug!("register_signatures::open_message_already_certified"; "signed_entity_type" => ?signed_entity_type); + Ok(reply::empty(StatusCode::GONE)) + } + Some(CertifierServiceError::NotFound(signed_entity_type)) => { + debug!("register_signatures::not_found"; "signed_entity_type" => ?signed_entity_type); + Ok(reply::empty(StatusCode::NOT_FOUND)) + } + Some(_) | None => { + warn!("register_signatures::error"; "error" => ?err); + Ok(reply::internal_server_error(err.to_string())) + } + }, + Ok(()) => Ok(reply::empty(StatusCode::CREATED)), + } } Err(err) => { - warn!("register_signatures::error"; "error" => ?err); + warn!("register_signatures::cant_retrieve_signed_entity_type"; "error" => ?err); Ok(reply::internal_server_error(err.to_string())) } - Ok(()) => Ok(reply::empty(StatusCode::CREATED)), } } } @@ -72,15 +87,14 @@ mod handlers { mod tests { use crate::http_server::SERVER_BASE_PATH; - use mithril_common::entities::ProtocolMessage; + use mithril_common::entities::SignedEntityType; use mithril_common::messages::RegisterSignatureMessage; use mithril_common::test_utils::apispec::APISpec; - use tokio::sync::RwLock; use warp::http::Method; use warp::test::request; use super::*; - use crate::multi_signer::MockMultiSigner; + use crate::certifier_service::{CertifierServiceError, MockCertifierService}; use crate::{initialize_dependencies, ProtocolError}; fn setup_router( @@ -98,15 +112,12 @@ mod tests { #[tokio::test] async fn test_register_signatures_post_ok() { - let mut mock_multi_signer = MockMultiSigner::new(); - mock_multi_signer - .expect_get_current_message() - .return_once(|| Some(ProtocolMessage::new())); - mock_multi_signer + let mut mock_certifier_service = MockCertifierService::new(); + mock_certifier_service .expect_register_single_signature() - .return_once(|_, _| Ok(())); + .return_once(move |_, _| Ok(())); let (mut dependency_manager, _) = initialize_dependencies().await; - dependency_manager.multi_signer = Arc::new(RwLock::new(mock_multi_signer)); + dependency_manager.certifier_service = Arc::new(mock_certifier_service); let message = RegisterSignatureMessage::dummy(); @@ -132,15 +143,12 @@ mod tests { #[tokio::test] async fn test_register_signatures_post_ko_400() { - let mut mock_multi_signer = MockMultiSigner::new(); - mock_multi_signer - .expect_get_current_message() - .return_once(|| Some(ProtocolMessage::new())); - mock_multi_signer + let mut mock_certifier_service = MockCertifierService::new(); + mock_certifier_service .expect_register_single_signature() - .return_once(|_, _| Ok(())); + .return_once(move |_, _| Ok(())); let (mut dependency_manager, _) = initialize_dependencies().await; - dependency_manager.multi_signer = Arc::new(RwLock::new(mock_multi_signer)); + dependency_manager.certifier_service = Arc::new(mock_certifier_service); let mut message = RegisterSignatureMessage::dummy(); message.signature = "invalid-signature".to_string(); @@ -166,18 +174,54 @@ mod tests { } #[tokio::test] - async fn test_register_signatures_post_ko_409() { + async fn test_register_signatures_post_ko_404() { + let signed_entity_type = SignedEntityType::dummy(); + let message = RegisterSignatureMessage::dummy(); + let mut mock_certifier_service = MockCertifierService::new(); + mock_certifier_service + .expect_register_single_signature() + .return_once(move |_, _| { + Err(Box::new(CertifierServiceError::NotFound( + signed_entity_type, + ))) + }); + let (mut dependency_manager, _) = initialize_dependencies().await; + dependency_manager.certifier_service = Arc::new(mock_certifier_service); + + let method = Method::POST.as_str(); + let path = "/register-signatures"; + + let response = request() + .method(method) + .path(&format!("/{SERVER_BASE_PATH}{path}")) + .json(&message) + .reply(&setup_router(Arc::new(dependency_manager))) + .await; + + APISpec::verify_conformity( + APISpec::get_all_spec_files(), + method, + path, + "application/json", + &message, + &response, + ); + } + + #[tokio::test] + async fn test_register_signatures_post_ko_410() { + let signed_entity_type = SignedEntityType::dummy(); let message = RegisterSignatureMessage::dummy(); - let party_id = message.party_id.clone(); - let mut mock_multi_signer = MockMultiSigner::new(); - mock_multi_signer - .expect_get_current_message() - .return_once(|| Some(ProtocolMessage::new())); - mock_multi_signer + let mut mock_certifier_service = MockCertifierService::new(); + mock_certifier_service .expect_register_single_signature() - .return_once(move |_, _| Err(ProtocolError::ExistingSingleSignature(party_id))); + .return_once(move |_, _| { + Err(Box::new(CertifierServiceError::AlreadyCertified( + signed_entity_type, + ))) + }); let (mut dependency_manager, _) = initialize_dependencies().await; - dependency_manager.multi_signer = Arc::new(RwLock::new(mock_multi_signer)); + dependency_manager.certifier_service = Arc::new(mock_certifier_service); let method = Method::POST.as_str(); let path = "/register-signatures"; @@ -201,15 +245,16 @@ mod tests { #[tokio::test] async fn test_register_signatures_post_ko_500() { - let mut mock_multi_signer = MockMultiSigner::new(); - mock_multi_signer - .expect_get_current_message() - .return_once(|| Some(ProtocolMessage::new())); - mock_multi_signer + let mut mock_certifier_service = MockCertifierService::new(); + mock_certifier_service .expect_register_single_signature() - .return_once(|_, _| Err(ProtocolError::Core("an error occurred".to_string()))); + .return_once(move |_, _| { + Err(Box::new(ProtocolError::Core( + "an error occurred".to_string(), + ))) + }); let (mut dependency_manager, _) = initialize_dependencies().await; - dependency_manager.multi_signer = Arc::new(RwLock::new(mock_multi_signer)); + dependency_manager.certifier_service = Arc::new(mock_certifier_service); let message = RegisterSignatureMessage::dummy(); diff --git a/mithril-aggregator/src/lib.rs b/mithril-aggregator/src/lib.rs index 5782a29b726..944038c2b36 100644 --- a/mithril-aggregator/src/lib.rs +++ b/mithril-aggregator/src/lib.rs @@ -12,7 +12,7 @@ //! You can find more information on how it works reading the [documentation website](https://mithril.network/doc/mithril/mithril-network/aggregator). mod artifact_builder; -mod certificate_creator; +pub mod certifier_service; mod command_args; mod configuration; pub mod database; @@ -38,7 +38,6 @@ pub use crate::configuration::{ }; pub use crate::multi_signer::{MultiSigner, MultiSignerImpl, ProtocolError}; pub use crate::snapshot_stores::{LocalSnapshotStore, SnapshotStore}; -pub use certificate_creator::{CertificateCreator, MithrilCertificateCreator}; pub use command_args::MainOpts; pub use dependency::DependencyManager; pub use message_adapters::{ diff --git a/mithril-aggregator/src/message_adapters/to_certificate_pending_message.rs b/mithril-aggregator/src/message_adapters/to_certificate_pending_message.rs index 924b6e65cc1..3a94da362ee 100644 --- a/mithril-aggregator/src/message_adapters/to_certificate_pending_message.rs +++ b/mithril-aggregator/src/message_adapters/to_certificate_pending_message.rs @@ -11,6 +11,7 @@ impl ToCertificatePendingMessageAdapter { pub fn adapt(certificate_pending: CertificatePending) -> CertificatePendingMessage { CertificatePendingMessage { beacon: certificate_pending.beacon, + signed_entity_type: certificate_pending.signed_entity_type, protocol_parameters: certificate_pending.protocol_parameters, next_protocol_parameters: certificate_pending.next_protocol_parameters, signers: Self::adapt_signers(certificate_pending.signers), @@ -55,7 +56,7 @@ mod tests { let certificate_pending = CertificatePending { signers, next_signers, - ..Default::default() + ..fake_data::certificate_pending() }; let message = ToCertificatePendingMessageAdapter::adapt(certificate_pending); diff --git a/mithril-aggregator/src/multi_signer.rs b/mithril-aggregator/src/multi_signer.rs index caf39d29b81..4db0a3ebddc 100644 --- a/mithril-aggregator/src/multi_signer.rs +++ b/mithril-aggregator/src/multi_signer.rs @@ -1,5 +1,4 @@ use async_trait::async_trait; -use hex::ToHex; use slog_scope::{debug, warn}; use std::sync::Arc; use thiserror::Error; @@ -15,8 +14,8 @@ use mithril_common::{ }; use crate::{ - store::{SingleSignatureStorer, VerificationKeyStorer}, - ProtocolParametersStore, ProtocolParametersStorer, SingleSignatureStore, VerificationKeyStore, + database::provider::OpenMessageWithSingleSignatures, store::VerificationKeyStorer, + ProtocolParametersStore, ProtocolParametersStorer, VerificationKeyStore, }; #[cfg(test)] @@ -78,15 +77,6 @@ pub enum ProtocolError { #[cfg_attr(test, automock)] #[async_trait] pub trait MultiSigner: Sync + Send { - /// Get current message - async fn get_current_message(&self) -> Option; - - /// Update current message - async fn update_current_message( - &mut self, - message: entities::ProtocolMessage, - ) -> Result<(), ProtocolError>; - /// Get current beacon async fn get_current_beacon(&self) -> Option; @@ -184,8 +174,8 @@ pub trait MultiSigner: Sync + Send { /// Get signers for the next epoch with their stake async fn get_next_signers_with_stake(&self) -> Result, ProtocolError>; - /// Registers a single signature - async fn register_single_signature( + /// Verify a single signature + async fn verify_single_signature( &self, message: &entities::ProtocolMessage, signatures: &entities::SingleSignatures, @@ -194,15 +184,12 @@ pub trait MultiSigner: Sync + Send { /// Creates a multi signature from single signatures async fn create_multi_signature( &self, - message: &entities::ProtocolMessage, + open_message: &OpenMessageWithSingleSignatures, ) -> Result, ProtocolError>; } /// MultiSignerImpl is an implementation of the MultiSigner pub struct MultiSignerImpl { - /// Message that is currently signed - current_message: Option, - /// Beacon that is currently used current_beacon: Option, @@ -212,9 +199,6 @@ pub struct MultiSignerImpl { /// Stake store stake_store: Arc, - /// Single signature store - single_signature_store: Arc, - /// Protocol parameters store protocol_parameters_store: Arc, } @@ -224,16 +208,13 @@ impl MultiSignerImpl { pub fn new( verification_key_store: Arc, stake_store: Arc, - single_signature_store: Arc, protocol_parameters_store: Arc, ) -> Self { debug!("New MultiSignerImpl created"); Self { - current_message: None, current_beacon: None, verification_key_store, stake_store, - single_signature_store, protocol_parameters_store, } } @@ -326,23 +307,6 @@ impl MultiSignerImpl { #[async_trait] impl MultiSigner for MultiSignerImpl { - /// Get current message - async fn get_current_message(&self) -> Option { - debug!("Get current message"); - self.current_message.clone() - } - - /// Update current message - async fn update_current_message( - &mut self, - message: entities::ProtocolMessage, - ) -> Result<(), ProtocolError> { - debug!("Update current_message"; "protocol_message" => #?message, "signed message" => message.compute_hash().encode_hex::()); - - self.current_message = Some(message); - Ok(()) - } - async fn get_current_beacon(&self) -> Option { self.current_beacon.clone() } @@ -525,14 +489,14 @@ impl MultiSigner for MultiSignerImpl { .collect()) } - /// Registers a single signature - async fn register_single_signature( + /// Verify a single signature + async fn verify_single_signature( &self, message: &entities::ProtocolMessage, signatures: &entities::SingleSignatures, ) -> Result<(), ProtocolError> { debug!( - "Register single signature from {} at indexes {:?} for message {:?}", + "Verify single signature from {} at indexes {:?} for message {:?}", signatures.party_id, signatures.won_indexes, message ); @@ -569,46 +533,30 @@ impl MultiSigner for MultiSignerImpl { ) .map_err(|e| ProtocolError::Core(e.to_string()))?; - // Register single signature - let beacon = self - .current_beacon - .as_ref() - .ok_or_else(ProtocolError::UnavailableBeacon)?; - - return match self - .single_signature_store - .save_single_signatures(beacon, signatures) - .await? - { - Some(_) => Err(ProtocolError::ExistingSingleSignature( - signatures.party_id.clone(), - )), - None => Ok(()), - }; + Ok(()) } /// Creates a multi signature from single signatures async fn create_multi_signature( &self, - message: &entities::ProtocolMessage, + open_message: &OpenMessageWithSingleSignatures, ) -> Result, ProtocolError> { - debug!("Create multi signature"); - let beacon = self - .current_beacon - .as_ref() - .ok_or_else(ProtocolError::UnavailableBeacon)?; + debug!("MultiSigner:create_multi_signature({open_message:?})"); let protocol_parameters = self .get_protocol_parameters() .await? .ok_or_else(ProtocolError::UnavailableProtocolParameters)?; - let signatures: Vec = self - .single_signature_store - .get_single_signatures(beacon) + let signers_with_stakes = self.get_signers_with_stake().await?; + + let clerk = self + .create_clerk(&signers_with_stakes, &protocol_parameters) .await? - .unwrap_or_default() + .ok_or_else(ProtocolError::UnavailableClerk)?; + let signatures: Vec = open_message + .single_signatures .iter() - .filter_map(|(_party_id, single_signature)| { + .filter_map(|single_signature| { single_signature .to_protocol_signature() .map_err(ProtocolError::Codec) @@ -616,14 +564,10 @@ impl MultiSigner for MultiSignerImpl { }) .collect::>(); - let signers_with_stakes = self.get_signers_with_stake().await?; - - let clerk = self - .create_clerk(&signers_with_stakes, &protocol_parameters) - .await? - .ok_or_else(ProtocolError::UnavailableClerk)?; - - match clerk.aggregate(&signatures, message.compute_hash().as_bytes()) { + match clerk.aggregate( + &signatures, + open_message.protocol_message.compute_hash().as_bytes(), + ) { Ok(multi_signature) => Ok(Some(multi_signature)), Err(ProtocolAggregationError::NotEnoughSignatures(actual, expected)) => { warn!("Could not compute multi-signature: Not enough signatures. Got only {} out of {}.", actual, expected); @@ -637,16 +581,16 @@ impl MultiSigner for MultiSignerImpl { #[cfg(test)] mod tests { use super::*; - use crate::{ - store::{SingleSignatureStore, VerificationKeyStore}, - ProtocolParametersStore, - }; + use crate::{store::VerificationKeyStore, ProtocolParametersStore}; + use chrono::Local; use mithril_common::{ crypto_helper::tests_setup::*, + entities::SignedEntityType, store::{adapter::MemoryAdapter, StakeStore}, test_utils::{fake_data, MithrilFixtureBuilder}, }; use std::{collections::HashMap, sync::Arc}; + use uuid::Uuid; async fn setup_multi_signer() -> MultiSignerImpl { let beacon = fake_data::beacon(); @@ -663,16 +607,6 @@ mod tests { Box::new(MemoryAdapter::::new(None).unwrap()), None, ); - let single_signature_store = SingleSignatureStore::new( - Box::new( - MemoryAdapter::< - entities::Beacon, - HashMap, - >::new(None) - .unwrap(), - ), - None, - ); let protocol_parameters_store = ProtocolParametersStore::new( Box::new( MemoryAdapter::::new(Some(vec![ @@ -696,7 +630,6 @@ mod tests { let mut multi_signer = MultiSignerImpl::new( Arc::new(verification_key_store), Arc::new(stake_store), - Arc::new(single_signature_store), Arc::new(protocol_parameters_store), ); @@ -734,23 +667,6 @@ mod tests { result } - #[tokio::test] - async fn test_multi_signer_current_message_ok() { - let mut multi_signer = setup_multi_signer().await; - - let current_message_expected = setup_message(); - multi_signer - .update_current_message(current_message_expected.clone()) - .await - .expect("update current message failed"); - - let current_message = multi_signer - .get_current_message() - .await - .expect("current message should have been retrieved"); - assert_eq!(current_message_expected, current_message) - } - #[tokio::test] async fn test_multi_signer_protocol_parameters_ok() { let mut multi_signer = setup_multi_signer().await; @@ -852,14 +768,7 @@ mod tests { Epoch::SIGNER_RECORDING_OFFSET as i64 - Epoch::SIGNER_RETRIEVAL_OFFSET, ) .await; - // We have to update the current message AFTER we reached the epoch for - // which the signers registered for the stake distribution to be valid - // hence the multisigner be able to create a Clerk and being able to - // register single signatures - multi_signer - .update_current_message(message.clone()) - .await - .expect("update current message failed"); + let mut signatures = Vec::new(); let mut expected_certificate_signers: Vec = Vec::new(); @@ -880,6 +789,13 @@ mod tests { } } + for signature in &signatures { + multi_signer + .verify_single_signature(&message, signature) + .await + .expect("single signature should be valid"); + } + let signatures_to_almost_reach_quorum = take_signatures_until_quorum_is_almost_reached( &mut signatures, protocol_parameters.k as usize, @@ -889,36 +805,40 @@ mod tests { "they should be at least one signature that can be registered without reaching the quorum" ); + let mut open_message = OpenMessageWithSingleSignatures { + open_message_id: Uuid::parse_str("193d1442-e89b-43cf-9519-04d8db9a12ff").unwrap(), + epoch: start_epoch, + signed_entity_type: SignedEntityType::CardanoImmutableFilesFull( + multi_signer.current_beacon.clone().unwrap(), + ), + protocol_message: message.clone(), + is_certified: false, + single_signatures: Vec::new(), + created_at: Local::now().naive_local(), + }; + // No signatures registered: multi-signer can't create the multi-signature assert!(multi_signer - .create_multi_signature(&message) + .create_multi_signature(&open_message) .await .expect("create multi signature should not fail") .is_none()); // Add some signatures but not enough to reach the quorum: multi-signer should not create the multi-signature - for signature in signatures_to_almost_reach_quorum { - multi_signer - .register_single_signature(&message, &signature) - .await - .expect("register single signature should not fail"); - } + open_message.single_signatures = signatures_to_almost_reach_quorum; + assert!(multi_signer - .create_multi_signature(&message) + .create_multi_signature(&open_message) .await .expect("create multi signature should not fail") .is_none()); // Add the remaining signatures to reach the quorum: multi-signer should create a multi-signature - for signature in &signatures { - multi_signer - .register_single_signature(&message, signature) - .await - .expect("register single signature should not fail"); - } + open_message.single_signatures.append(&mut signatures); + assert!( multi_signer - .create_multi_signature(&message) + .create_multi_signature(&open_message) .await .expect("create multi signature should not fail") .is_some(), diff --git a/mithril-aggregator/src/runtime/error.rs b/mithril-aggregator/src/runtime/error.rs index 2b7dccd5b2b..75cff727a58 100644 --- a/mithril-aggregator/src/runtime/error.rs +++ b/mithril-aggregator/src/runtime/error.rs @@ -57,15 +57,13 @@ impl From> for RuntimeError { /// Errors returned when the runner cannot fulfil its missions with no subsystem /// to fail. #[derive(Debug, Error)] +// TODO: Are these errors still relevant, do we need to remove them? +#[allow(clippy::enum_variant_names)] pub enum RunnerError { /// Protocol message part is missing #[error("Missing protocol message: '{0}'.")] MissingProtocolMessage(String), - /// Epoch out of bounds - #[error("Epoch out of bounds: '{0}'.")] - EpochOutOfBounds(String), - /// No stack distribution found #[error("Missing stack distribution: '{0}'.")] MissingStakeDistribution(String), @@ -73,8 +71,4 @@ pub enum RunnerError { /// Missing protocol parameters #[error("Missing protocol parameters: '{0}'.")] MissingProtocolParameters(String), - - /// No AVK issued by the multisigner - #[error("No MultiSignature issued: '{0}'.")] - NoComputedMultiSignature(String), } diff --git a/mithril-aggregator/src/runtime/mod.rs b/mithril-aggregator/src/runtime/mod.rs index 778051ee7f6..5bcad8c4ed5 100644 --- a/mithril-aggregator/src/runtime/mod.rs +++ b/mithril-aggregator/src/runtime/mod.rs @@ -1,9 +1,7 @@ mod error; mod runner; mod state_machine; -mod working_certificate; pub use error::RuntimeError; pub use runner::{AggregatorConfig, AggregatorRunner, AggregatorRunnerTrait}; pub use state_machine::*; -pub use working_certificate::WorkingCertificate; diff --git a/mithril-aggregator/src/runtime/runner.rs b/mithril-aggregator/src/runtime/runner.rs index a540d31ccf7..c741508945e 100644 --- a/mithril-aggregator/src/runtime/runner.rs +++ b/mithril-aggregator/src/runtime/runner.rs @@ -1,8 +1,7 @@ use async_trait::async_trait; use chrono::Utc; -use mithril_common::crypto_helper::ProtocolMultiSignature; use mithril_common::entities::Epoch; -use mithril_common::entities::PartyId; +use mithril_common::entities::SignedEntityType; use mithril_common::store::StakeStorer; use slog_scope::{debug, warn}; @@ -17,12 +16,9 @@ use std::path::Path; use std::path::PathBuf; use std::sync::Arc; -use crate::runtime::WorkingCertificate; +use crate::database::provider::OpenMessage; use crate::snapshot_uploaders::SnapshotLocation; use crate::snapshotter::OngoingSnapshot; -use crate::store::SingleSignatureStorer; -use crate::CertificateCreator; -use crate::MithrilCertificateCreator; use crate::RuntimeError; use crate::{DependencyManager, ProtocolError, SnapshotError}; @@ -104,26 +100,19 @@ pub trait AggregatorRunnerTrait: Sync + Send { new_beacon: &Beacon, ) -> Result<(), Box>; - /// Set the message to sign in the multisigner. The digest is only one part - /// of the message, the next signing stake distribution must also be signed - /// as part of the message. - async fn update_message_in_multisigner( + /// Compute the protocol message + async fn compute_protocol_message( &self, digest: String, - ) -> Result<(), Box>; + ) -> Result>; /// Return the actual pending certificate from the multisigner. async fn create_new_pending_certificate_from_multisigner( &self, beacon: Beacon, + signed_entity_type: &SignedEntityType, ) -> Result>; - /// Return the actual working certificate from the multisigner. - async fn create_new_working_certificate( - &self, - certificate_pending: &CertificatePending, - ) -> Result>; - /// Store the given pending certificate. async fn save_pending_certificate( &self, @@ -136,9 +125,10 @@ pub trait AggregatorRunnerTrait: Sync + Send { ) -> Result, Box>; /// Create multi-signature. - async fn create_multi_signature( + async fn create_certificate( &self, - ) -> Result, Box>; + signed_entity_type: &SignedEntityType, + ) -> Result, Box>; /// Create an archive of the cardano node db directory naming it after the given beacon. /// @@ -146,6 +136,7 @@ pub trait AggregatorRunnerTrait: Sync + Send { async fn create_snapshot_archive( &self, beacon: &Beacon, + protocol_message: &ProtocolMessage, ) -> Result>; /// Upload the snapshot at the given location using the configured uploader(s). @@ -156,13 +147,6 @@ pub trait AggregatorRunnerTrait: Sync + Send { ongoing_snapshot: &OngoingSnapshot, ) -> Result, Box>; - /// Create a signed certificate. - async fn create_and_save_certificate( - &self, - working_certificate: &WorkingCertificate, - multi_signature: ProtocolMultiSignature, - ) -> Result>; - /// Create a snapshot and save it to the given locations. async fn create_and_save_snapshot( &self, @@ -176,6 +160,19 @@ pub trait AggregatorRunnerTrait: Sync + Send { &self, beacon: &Beacon, ) -> Result<(), Box>; + + /// Certifier inform new epoch + async fn certifier_inform_new_epoch( + &self, + epoch: &Epoch, + ) -> Result<(), Box>; + + /// Create new open message + async fn create_open_message( + &self, + signed_entity_type: &SignedEntityType, + protocol_message: &ProtocolMessage, + ) -> Result>; } /// The runner responsibility is to expose a code API for the state machine. It @@ -193,33 +190,6 @@ impl AggregatorRunner { dependencies, } } - - fn get_previous_hash_from_last_two_certificates<'a>( - beacon: &Beacon, - last_certificate: Option<&'a Certificate>, - penultimate_certificate: Option<&'a Certificate>, - ) -> Result<&'a str, Box> { - match (penultimate_certificate, last_certificate) { - (Some(penultimate_certificate), Some(last_certificate)) => { - // Check if last certificate is exactly at most one epoch before current epoch - if beacon.epoch - last_certificate.beacon.epoch > Epoch(1) { - return Err(RunnerError::EpochOutOfBounds(format!( - "Last certificate ({:?}) is more than 1 epoch ahead from now ({:?}).", - last_certificate.beacon.epoch, beacon.epoch - )) - .into()); - } - // Check if last certificate is first certificate of its epoch - if penultimate_certificate.beacon.epoch != last_certificate.beacon.epoch { - Ok(&last_certificate.hash) - } else { - Ok(&last_certificate.previous_hash) - } - } - (None, Some(last_certificate)) => Ok(&last_certificate.hash), - _ => Ok(""), - } - } } #[cfg_attr(test, automock)] @@ -407,12 +377,12 @@ impl AggregatorRunnerTrait for AggregatorRunner { .map_err(|e| e.into()) } - async fn update_message_in_multisigner( + async fn compute_protocol_message( &self, digest: String, - ) -> Result<(), Box> { - debug!("RUNNER: update message in multisigner"); - let mut multi_signer = self.dependencies.multi_signer.write().await; + ) -> Result> { + debug!("RUNNER: compute protocol message"); + let multi_signer = self.dependencies.multi_signer.write().await; let mut protocol_message = ProtocolMessage::new(); protocol_message.set_message_part(ProtocolMessagePartKey::SnapshotDigest, digest); protocol_message.set_message_part( @@ -422,15 +392,14 @@ impl AggregatorRunnerTrait for AggregatorRunner { .await? .unwrap_or_default(), ); - multi_signer - .update_current_message(protocol_message) - .await - .map_err(|e| e.into()) + + Ok(protocol_message) } async fn create_new_pending_certificate_from_multisigner( &self, beacon: Beacon, + signed_entity_type: &SignedEntityType, ) -> Result> { debug!("RUNNER: create new pending certificate from multisigner"); let multi_signer = self.dependencies.multi_signer.read().await; @@ -467,6 +436,7 @@ impl AggregatorRunnerTrait for AggregatorRunner { let pending_certificate = CertificatePending::new( beacon, + signed_entity_type.to_owned(), protocol_parameters.into(), next_protocol_parameters.into(), signers, @@ -476,54 +446,6 @@ impl AggregatorRunnerTrait for AggregatorRunner { Ok(pending_certificate) } - async fn create_new_working_certificate( - &self, - certificate_pending: &CertificatePending, - ) -> Result> { - debug!("RUNNER: create new working certificate"); - let multi_signer = self.dependencies.multi_signer.read().await; - - let signers = match multi_signer.get_signers_with_stake().await { - Ok(signers) => signers, - Err(ProtocolError::Beacon(_)) => vec![], - Err(e) => return Err(e.into()), - }; - let protocol_message = multi_signer.get_current_message().await.ok_or_else(|| { - RunnerError::MissingProtocolMessage(format!( - "no message found for beacon '{:?}'.", - certificate_pending.beacon - )) - })?; - let aggregate_verification_key = multi_signer - .compute_stake_distribution_aggregate_verification_key() - .await? - .ok_or_else(|| { - RunnerError::NoComputedMultiSignature(format!( - "No AVK returned by the multisigner for beacon {:?}", - certificate_pending.beacon - )) - })?; - - let certificate_store = self.dependencies.certificate_store.clone(); - let latest_certificates = certificate_store.get_list(2).await?; - let last_certificate = latest_certificates.get(0); - let penultimate_certificate = latest_certificates.get(1); - let previous_hash = AggregatorRunner::get_previous_hash_from_last_two_certificates( - &certificate_pending.beacon, - last_certificate, - penultimate_certificate, - )?; - - Ok(WorkingCertificate::from_pending_certificate( - certificate_pending, - &signers, - &protocol_message, - &aggregate_verification_key, - &Utc::now(), - previous_hash, - )) - } - async fn save_pending_certificate( &self, pending_certificate: CertificatePending, @@ -550,39 +472,26 @@ impl AggregatorRunnerTrait for AggregatorRunner { Ok(certificate_pending) } - async fn create_multi_signature( + async fn create_certificate( &self, - ) -> Result, Box> { + signed_entity_type: &SignedEntityType, + ) -> Result, Box> { debug!("RUNNER: create multi-signature"); - let multi_signer = self.dependencies.multi_signer.read().await; - let message = multi_signer - .get_current_message() + self.dependencies + .certifier_service + .create_certificate(signed_entity_type) .await - .ok_or_else(ProtocolError::UnavailableMessage)?; - - Ok(multi_signer.create_multi_signature(&message).await?) } async fn create_snapshot_archive( &self, beacon: &Beacon, + protocol_message: &ProtocolMessage, ) -> Result> { debug!("RUNNER: create snapshot archive"); let snapshotter = self.dependencies.snapshotter.clone(); - let protocol_message = self - .dependencies - .multi_signer - .read() - .await - .get_current_message() - .await - .ok_or_else(|| { - RunnerError::MissingProtocolMessage(format!( - "no message found for beacon '{beacon:?}'." - )) - })?; let snapshot_digest = protocol_message .get_message_part(&ProtocolMessagePartKey::SnapshotDigest) .ok_or_else(|| { @@ -627,41 +536,6 @@ impl AggregatorRunnerTrait for AggregatorRunner { Ok(vec![location]) } - async fn create_and_save_certificate( - &self, - working_certificate: &WorkingCertificate, - multi_signature: ProtocolMultiSignature, - ) -> Result> { - debug!("RUNNER: create and save certificate"); - let certificate_store = self.dependencies.certificate_store.clone(); - let signatures_party_ids: Vec = self - .dependencies - .single_signature_store - .get_single_signatures(&working_certificate.beacon) - .await? - .unwrap_or_default() - .into_keys() - .collect::>(); - - let certificate = MithrilCertificateCreator::create_certificate( - working_certificate, - &signatures_party_ids, - multi_signature, - )?; - - self.dependencies - .certificate_verifier - .verify_certificate( - &certificate, - certificate_store.clone(), - &self.dependencies.genesis_verifier, - ) - .await?; - let _ = certificate_store.save(certificate.clone()).await?; - - Ok(certificate) - } - async fn create_and_save_snapshot( &self, certificate: Certificate, @@ -723,13 +597,35 @@ impl AggregatorRunnerTrait for AggregatorRunner { Ok(()) } + + async fn certifier_inform_new_epoch( + &self, + epoch: &Epoch, + ) -> Result<(), Box> { + self.dependencies + .certifier_service + .inform_epoch(*epoch) + .await?; + + Ok(()) + } + + async fn create_open_message( + &self, + signed_entity_type: &SignedEntityType, + protocol_message: &ProtocolMessage, + ) -> Result> { + self.dependencies + .certifier_service + .create_open_message(signed_entity_type, protocol_message) + .await + } } #[cfg(test)] pub mod tests { - use crate::dependency::SimulateFromChainParams; + use crate::certifier_service::MockCertifierService; use crate::multi_signer::MockMultiSigner; - use crate::runtime::WorkingCertificate; use crate::snapshotter::OngoingSnapshot; use crate::{ initialize_dependencies, @@ -737,12 +633,9 @@ pub mod tests { }; use crate::{MithrilSignerRegisterer, ProtocolParametersStorer, SignerRegistrationRound}; use mithril_common::chain_observer::FakeObserver; - use mithril_common::crypto_helper::{ - key_decode_hex, tests_setup::setup_certificate_chain, ProtocolMultiSignature, - }; use mithril_common::digesters::DumbImmutableFileObserver; use mithril_common::entities::{ - Beacon, CertificatePending, HexEncodedKey, ProtocolMessage, StakeDistribution, + Beacon, CertificatePending, Epoch, ProtocolMessage, SignedEntityType, StakeDistribution, }; use mithril_common::store::StakeStorer; use mithril_common::test_utils::MithrilFixtureBuilder; @@ -781,7 +674,7 @@ pub mod tests { let runner = AggregatorRunner::new(config, Arc::new(dependencies)); let beacon = fake_data::beacon(); - let mut certificate = fake_data::certificate("certificate_hash".to_string()); + let mut certificate = fake_data::genesis_certificate("certificate_hash".to_string()); certificate.beacon = beacon.clone(); assert!(!runner @@ -958,6 +851,7 @@ pub mod tests { let runner = AggregatorRunner::new(config, deps.clone()); let beacon = runner.get_beacon_from_chain().await.unwrap(); runner.update_beacon(&beacon).await.unwrap(); + let signed_entity_type = SignedEntityType::dummy(); let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); let current_signers = fixture.signers_with_stake()[1..3].to_vec(); @@ -971,13 +865,14 @@ pub mod tests { .await; let mut certificate = runner - .create_new_pending_certificate_from_multisigner(beacon.clone()) + .create_new_pending_certificate_from_multisigner(beacon.clone(), &signed_entity_type) .await .unwrap(); certificate.signers.sort_by_key(|s| s.party_id.clone()); certificate.next_signers.sort_by_key(|s| s.party_id.clone()); let mut expected = CertificatePending::new( beacon, + signed_entity_type, protocol_parameters.clone(), protocol_parameters, current_signers.into_iter().map(|s| s.into()).collect(), @@ -989,136 +884,6 @@ pub mod tests { assert_eq!(expected, certificate); } - #[tokio::test] - async fn test_create_new_working_certificate() { - let (deps, config) = initialize_dependencies().await; - let deps = Arc::new(deps); - let runner = AggregatorRunner::new(config, deps.clone()); - let beacon = runner.get_beacon_from_chain().await.unwrap(); - runner.update_beacon(&beacon).await.unwrap(); - - let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); - - deps.prepare_for_genesis( - fixture.signers_with_stake(), - fixture.signers_with_stake(), - &fixture.protocol_parameters(), - ) - .await; - - runner - .update_message_in_multisigner("a digest".to_string()) - .await - .expect("update_message_in_multisigner should not fail"); - let message = deps - .multi_signer - .read() - .await - .get_current_message() - .await - .unwrap(); - - let aggregate_verification_key = deps - .multi_signer - .read() - .await - .compute_stake_distribution_aggregate_verification_key() - .await - .expect("") - .unwrap(); - - let certificate_pending = runner - .create_new_pending_certificate_from_multisigner(beacon.clone()) - .await - .unwrap(); - - let mut sut = runner - .create_new_working_certificate(&certificate_pending) - .await - .expect("create_new_working_certificate should not fail"); - sut.signers - .sort_by(|left, right| left.party_id.cmp(&right.party_id)); - - let mut expected = WorkingCertificate::from_pending_certificate( - &certificate_pending, - &fixture.signers_with_stake(), - &message, - &aggregate_verification_key, - &sut.initiated_at, - "", - ); - expected - .signers - .sort_by(|left, right| left.party_id.cmp(&right.party_id)); - - assert_eq!(expected, sut); - } - - #[tokio::test] - async fn test_create_new_working_certificate_ko_epoch_gap() { - let (deps, config) = initialize_dependencies().await; - let (certificate_chain, _) = setup_certificate_chain(5, 1); - let mut beacon = certificate_chain.first().unwrap().beacon.clone(); - beacon.epoch += 2; - - deps.init_state_from_chain( - &certificate_chain, - vec![SimulateFromChainParams::SetupMultiSigner], - ) - .await; - let runner = AggregatorRunner::new(config, Arc::new(deps)); - runner - .update_message_in_multisigner("a digest".to_string()) - .await - .expect("update_message_in_multisigner should not fail"); - - let certificate_pending = CertificatePending { - beacon: beacon.clone(), - ..fake_data::certificate_pending() - }; - - let certificate = runner - .create_new_working_certificate(&certificate_pending) - .await; - assert!(certificate.is_err()); - let _err = certificate.unwrap_err(); - } - - #[tokio::test] - async fn test_update_message_in_multisigner() { - let (deps, config) = initialize_dependencies().await; - let deps = Arc::new(deps); - let runner = AggregatorRunner::new(config, deps.clone()); - let digest = "1+2+3+4=10".to_string(); - runner.update_beacon(&fake_data::beacon()).await.unwrap(); - let fixture = MithrilFixtureBuilder::default().build(); - deps.prepare_for_genesis( - fixture.signers_with_stake(), - fixture.signers_with_stake(), - &fixture.protocol_parameters(), - ) - .await; - - runner - .update_message_in_multisigner(digest) - .await - .expect("update_message_in_multisigner should not fail"); - let message = deps - .multi_signer - .read() - .await - .get_current_message() - .await - .unwrap(); - - assert_eq!( - "1+2+3+4=10", - message - .get_message_part(&ProtocolMessagePartKey::SnapshotDigest) - .unwrap() - ); - } - #[tokio::test] async fn test_save_pending_certificate() { let (deps, config) = initialize_dependencies().await; @@ -1170,34 +935,6 @@ pub mod tests { assert_eq!(None, maybe_saved_cert); } - #[tokio::test] - async fn test_create_and_save_certificate_ok() { - let (certificate_chain, _) = setup_certificate_chain(5, 1); - let first_certificate = certificate_chain[0].clone(); - let multi_signature: ProtocolMultiSignature = - key_decode_hex(&first_certificate.multi_signature as &HexEncodedKey).unwrap(); - let multi_signature_clone = multi_signature.clone(); - let working_certificate = WorkingCertificate { - beacon: first_certificate.beacon.clone(), - signers: first_certificate.metadata.signers.clone(), - aggregate_verification_key: first_certificate.aggregate_verification_key.clone(), - protocol_parameters: first_certificate.metadata.protocol_parameters.clone(), - previous_hash: certificate_chain[1].hash.clone(), - message: first_certificate.protocol_message.clone(), - ..WorkingCertificate::fake() - }; - let (mut deps, config) = initialize_dependencies().await; - let mock_multi_signer = MockMultiSigner::new(); - deps.multi_signer = Arc::new(RwLock::new(mock_multi_signer)); - deps.init_state_from_chain(&certificate_chain, vec![]).await; - let runner = AggregatorRunner::new(config, Arc::new(deps)); - - let certificate = runner - .create_and_save_certificate(&working_certificate, multi_signature_clone) - .await; - certificate.expect("a certificate should have been created and saved"); - } - #[tokio::test] async fn test_remove_snapshot_archive_after_upload() { let (deps, config) = initialize_dependencies().await; @@ -1225,16 +962,13 @@ pub mod tests { ProtocolMessagePartKey::SnapshotDigest, "test+digest".to_string(), ); - let mut mock_multi_signer = MockMultiSigner::new(); - mock_multi_signer - .expect_get_current_message() - .return_once(move || Some(message)); + let mock_multi_signer = MockMultiSigner::new(); let (mut deps, config) = initialize_dependencies().await; deps.multi_signer = Arc::new(RwLock::new(mock_multi_signer)); let runner = AggregatorRunner::new(config, Arc::new(deps)); let ongoing_snapshot = runner - .create_snapshot_archive(&beacon) + .create_snapshot_archive(&beacon, &message) .await .expect("create_snapshot_archive should not fail"); @@ -1264,4 +998,19 @@ pub mod tests { runner.update_era_checker(&beacon).await.unwrap(); assert_eq!(beacon.epoch, era_checker.current_epoch()); } + + #[tokio::test] + async fn test_certifier_inform_new_epoch() { + let mut mock_certifier_service = MockCertifierService::new(); + mock_certifier_service + .expect_inform_epoch() + .returning(|_| Ok(())) + .times(1); + + let (mut deps, config) = initialize_dependencies().await; + deps.certifier_service = Arc::new(mock_certifier_service); + + let runner = AggregatorRunner::new(config, Arc::new(deps)); + runner.certifier_inform_new_epoch(&Epoch(1)).await.unwrap(); + } } diff --git a/mithril-aggregator/src/runtime/state_machine.rs b/mithril-aggregator/src/runtime/state_machine.rs index 4fad7538389..86f48a919d0 100644 --- a/mithril-aggregator/src/runtime/state_machine.rs +++ b/mithril-aggregator/src/runtime/state_machine.rs @@ -1,6 +1,9 @@ -use crate::runtime::{AggregatorRunnerTrait, RuntimeError, WorkingCertificate}; +use crate::{ + database::provider::OpenMessage, + runtime::{AggregatorRunnerTrait, RuntimeError}, +}; -use mithril_common::entities::Beacon; +use mithril_common::entities::{Beacon, SignedEntityType}; use slog_scope::{crit, info, trace, warn}; use std::fmt::Display; use std::sync::Arc; @@ -19,7 +22,7 @@ pub struct ReadyState { #[derive(Clone, Debug, PartialEq)] pub struct SigningState { current_beacon: Beacon, - working_certificate: WorkingCertificate, + open_message: OpenMessage, } #[derive(Clone, Debug, PartialEq)] @@ -245,6 +248,9 @@ impl AggregatorRuntime { .update_era_checker(&new_beacon) .await .map_err(|e| RuntimeError::critical("transiting IDLE → READY", Some(e)))?; + self.runner + .certifier_inform_new_epoch(&new_beacon.epoch) + .await?; self.runner.update_stake_distribution(&new_beacon).await?; self.runner .open_signer_registration_round(&new_beacon) @@ -268,31 +274,25 @@ impl AggregatorRuntime { state: SigningState, ) -> Result { trace!("launching transition from SIGNING to IDLE state"); - let multi_signature = self.runner.create_multi_signature().await?; - - let multi_signature = if multi_signature.is_none() { - return Err(RuntimeError::KeepState { - message: "not enough signature yet to aggregate a multi-signature, waiting…" - .to_string(), + let certificate = self + .runner + .create_certificate(&state.open_message.signed_entity_type) + .await? + .ok_or_else(|| RuntimeError::KeepState { + message: "not enough signature yet to create a certificate, waiting…".to_string(), nested_error: None, - }); - } else { - multi_signature.unwrap() - }; + })?; self.runner.drop_pending_certificate().await?; let ongoing_snapshot = self .runner - .create_snapshot_archive(&state.current_beacon) + .create_snapshot_archive(&state.current_beacon, &state.open_message.protocol_message) .await?; let locations = self .runner .upload_snapshot_archive(&ongoing_snapshot) .await?; - let certificate = self - .runner - .create_and_save_certificate(&state.working_certificate, multi_signature) - .await?; + let _ = self .runner .create_and_save_snapshot(certificate, &ongoing_snapshot, locations) @@ -324,26 +324,32 @@ impl AggregatorRuntime { new_beacon: Beacon, ) -> Result { trace!("launching transition from READY to SIGNING state"); + // TODO: Temporary, we need to compute the signed entity type for other types than Cardano immutable files + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(new_beacon.clone()); self.runner.update_beacon(&new_beacon).await?; let digester_result = self.runner.compute_digest(&new_beacon).await?; - self.runner - .update_message_in_multisigner(digester_result) + let protocol_message = self + .runner + .compute_protocol_message(digester_result) + .await?; + let open_message = self + .runner + .create_open_message(&signed_entity_type, &protocol_message) .await?; let certificate_pending = self .runner - .create_new_pending_certificate_from_multisigner(new_beacon.clone()) + .create_new_pending_certificate_from_multisigner( + new_beacon.clone(), + &signed_entity_type, + ) .await?; self.runner .save_pending_certificate(certificate_pending.clone()) .await?; - let working_certificate = self - .runner - .create_new_working_certificate(&certificate_pending) - .await?; let state = SigningState { current_beacon: new_beacon, - working_certificate, + open_message, }; Ok(state) @@ -352,16 +358,13 @@ impl AggregatorRuntime { #[cfg(test)] mod tests { - use std::path::Path; - use crate::snapshotter::OngoingSnapshot; + use crate::database::provider::OpenMessage; use super::super::runner::MockAggregatorRunner; use super::*; - use mithril_common::crypto_helper::tests_setup::setup_certificate_chain; - use mithril_common::crypto_helper::{key_decode_hex, ProtocolMultiSignature}; - use mithril_common::entities::HexEncodedKey; + use mithril_common::entities::ProtocolMessage; use mithril_common::era::UnsupportedEraError; use mithril_common::test_utils::fake_data; use mockall::predicate; @@ -433,6 +436,11 @@ mod tests { .with(predicate::eq(fake_data::beacon())) .once() .returning(|_| Ok(())); + runner + .expect_certifier_inform_new_epoch() + .with(predicate::eq(fake_data::beacon().epoch)) + .once() + .returning(|_| Ok(())); let mut runtime = init_runtime( Some(AggregatorState::Idle(IdleState { @@ -485,6 +493,11 @@ mod tests { .with(predicate::eq(fake_data::beacon())) .once() .returning(|_| Ok(())); + runner + .expect_certifier_inform_new_epoch() + .with(predicate::eq(fake_data::beacon().epoch)) + .once() + .returning(|_| Ok(())); let mut runtime = init_runtime( Some(AggregatorState::Idle(IdleState { @@ -578,23 +591,23 @@ mod tests { .once() .returning(|_| Ok(())); runner - .expect_update_message_in_multisigner() + .expect_compute_protocol_message() .with(predicate::eq("whatever".to_string())) .once() - .returning(|_| Ok(())); + .returning(|_| Ok(ProtocolMessage::new())); runner .expect_create_new_pending_certificate_from_multisigner() - .with(predicate::eq(fake_data::beacon())) - .once() - .returning(|_| Ok(fake_data::certificate_pending())); - runner - .expect_create_new_working_certificate() + //.with(predicate::eq(fake_data::beacon())) .once() - .returning(|_| Ok(WorkingCertificate::fake())); + .returning(|_, _| Ok(fake_data::certificate_pending())); runner .expect_save_pending_certificate() .once() .returning(|_| Ok(())); + runner + .expect_create_open_message() + .once() + .returning(|_, _| Ok(OpenMessage::dummy())); let mut runtime = init_runtime( Some(AggregatorState::Ready(ReadyState { @@ -629,7 +642,7 @@ mod tests { beacon }, - working_certificate: WorkingCertificate::fake(), + open_message: OpenMessage::dummy(), }; let mut runtime = init_runtime(Some(AggregatorState::Signing(state)), runner).await; runtime.cycle().await.unwrap(); @@ -645,12 +658,12 @@ mod tests { .once() .returning(|| Ok(fake_data::beacon())); runner - .expect_create_multi_signature() + .expect_create_certificate() .once() - .returning(|| Ok(None)); + .returning(|_| Ok(None)); let state = SigningState { current_beacon: fake_data::beacon(), - working_certificate: WorkingCertificate::fake(), + open_message: OpenMessage::dummy(), }; let mut runtime = init_runtime(Some(AggregatorState::Signing(state)), runner).await; runtime @@ -661,6 +674,7 @@ mod tests { assert_eq!("signing".to_string(), runtime.get_state()); } + /* TODO: create a fake certificate to test the certificate creation. #[tokio::test] async fn signing_multisig_is_created() { let (certificate_chain, _) = setup_certificate_chain(5, 1); @@ -673,8 +687,8 @@ mod tests { .once() .returning(|| Ok(fake_data::beacon())); runner - .expect_create_multi_signature() - .return_once(move || Ok(Some(multi_signature))); + .expect_create_certificate() + .return_once(move |_| Ok(Some(multi_signature))); runner .expect_drop_pending_certificate() .once() @@ -710,6 +724,7 @@ mod tests { assert_eq!("idle".to_string(), runtime.get_state()); } + */ #[tokio::test] pub async fn critical_error() { diff --git a/mithril-aggregator/src/runtime/working_certificate.rs b/mithril-aggregator/src/runtime/working_certificate.rs deleted file mode 100644 index edf35f0486e..00000000000 --- a/mithril-aggregator/src/runtime/working_certificate.rs +++ /dev/null @@ -1,67 +0,0 @@ -use chrono::{DateTime, Utc}; -use mithril_common::entities::{ - Beacon, CertificatePending, HexEncodedAgregateVerificationKey, ProtocolMessage, - ProtocolParameters, SignerWithStake, -}; - -/// Gather information needed to create a new [Certificate]. -#[derive(Clone, Debug, PartialEq)] -pub struct WorkingCertificate { - /// Current Beacon - pub beacon: Beacon, - - /// Current Protocol parameters - pub protocol_parameters: ProtocolParameters, - - /// Current Signers - pub signers: Vec, - - /// Message that is currently signed - pub message: ProtocolMessage, - - /// Created aggregate verification key - pub aggregate_verification_key: HexEncodedAgregateVerificationKey, - - /// Signing start datetime of current message - pub initiated_at: DateTime, - - /// Hash of the first certificate of the previous epoch - pub previous_hash: String, -} - -impl WorkingCertificate { - /// Create a [WorkingCertificate] using what it can copy from a given [PendingCertificate] - pub fn from_pending_certificate( - pending_certificate: &CertificatePending, - signers: &[SignerWithStake], - protocol_message: &ProtocolMessage, - aggregate_verification_key: &str, - initiated_at: &DateTime, - previous_hash: &str, - ) -> Self { - Self { - beacon: pending_certificate.beacon.clone(), - protocol_parameters: pending_certificate.protocol_parameters.clone(), - signers: signers.to_vec(), - message: protocol_message.clone(), - aggregate_verification_key: aggregate_verification_key.to_string(), - initiated_at: *initiated_at, - previous_hash: previous_hash.to_string(), - } - } - - #[cfg(test)] - pub fn fake() -> Self { - use mithril_common::test_utils::fake_data; - - Self { - beacon: fake_data::beacon(), - protocol_parameters: fake_data::protocol_parameters(), - signers: fake_data::signers_with_stakes(3), - message: ProtocolMessage::new(), - aggregate_verification_key: "avk".to_string(), - initiated_at: Utc::now(), - previous_hash: "hash".to_string(), - } - } -} diff --git a/mithril-aggregator/src/store/pending_certificate_store.rs b/mithril-aggregator/src/store/pending_certificate_store.rs index 112cf44d94b..4fb522705cd 100644 --- a/mithril-aggregator/src/store/pending_certificate_store.rs +++ b/mithril-aggregator/src/store/pending_certificate_store.rs @@ -56,7 +56,7 @@ impl CertificatePendingStore { mod test { use super::*; - use mithril_common::entities::Beacon; + use mithril_common::entities::{Beacon, SignedEntityType}; use mithril_common::store::adapter::DumbStoreAdapter; use mithril_common::test_utils::fake_data; @@ -67,6 +67,7 @@ mod test { let beacon = Beacon::new("testnet".to_string(), 0, 0); let certificate_pending = CertificatePending::new( beacon.clone(), + SignedEntityType::dummy(), fake_data::protocol_parameters(), fake_data::protocol_parameters(), fake_data::signers(4), @@ -100,8 +101,10 @@ mod test { async fn save_certificate_pending_once() { let store = get_certificate_pending_store(false).await; let beacon = Beacon::new("testnet".to_string(), 0, 1); + let signed_entity_type = SignedEntityType::dummy(); let certificate_pending = CertificatePending::new( beacon, + signed_entity_type, fake_data::protocol_parameters(), fake_data::protocol_parameters(), fake_data::signers(1), diff --git a/mithril-aggregator/src/ticker_service.rs b/mithril-aggregator/src/ticker_service.rs index da8ac1e6514..fec996c4851 100644 --- a/mithril-aggregator/src/ticker_service.rs +++ b/mithril-aggregator/src/ticker_service.rs @@ -24,7 +24,7 @@ enum MithrilTickerError { /// Service trait with consistent business oriented API. #[async_trait] -pub trait TickerService { +pub trait TickerService: Send + Sync { /// Return the current Epoch as read from the chain. async fn get_current_epoch(&self) -> StdResult; diff --git a/mithril-aggregator/tests/certificate_chain.rs b/mithril-aggregator/tests/certificate_chain.rs index b78c155bb2c..2981ce3d09a 100644 --- a/mithril-aggregator/tests/certificate_chain.rs +++ b/mithril-aggregator/tests/certificate_chain.rs @@ -1,10 +1,10 @@ mod test_extensions; -use mithril_aggregator::VerificationKeyStorer; +use mithril_aggregator::{Configuration, VerificationKeyStorer}; use mithril_common::{ chain_observer::ChainObserver, entities::ProtocolParameters, test_utils::MithrilFixtureBuilder, }; -use test_extensions::RuntimeTester; +use test_extensions::{utilities::get_test_dir, RuntimeTester}; #[tokio::test] async fn certificate_chain() { @@ -13,7 +13,12 @@ async fn certificate_chain() { m: 100, phi_f: 0.95, }; - let mut tester = RuntimeTester::build(protocol_parameters.clone()).await; + let configuration = Configuration { + protocol_parameters: protocol_parameters.clone(), + data_stores_directory: get_test_dir("certificate_chain").join("aggregator.sqlite3"), + ..Configuration::new_sample() + }; + let mut tester = RuntimeTester::build(configuration).await; comment!("Create signers & declare stake distribution"); let fixture = MithrilFixtureBuilder::default() @@ -55,7 +60,15 @@ async fn certificate_chain() { cycle!(tester, "signing"); tester.register_signers(&signers).await.unwrap(); cycle_err!(tester, "signing"); - tester.send_single_signatures(&signers).await.unwrap(); + let signed_entity_type = tester + .open_message_observer + .get_current_immutable_entity_type() + .await + .unwrap(); + tester + .send_single_signatures(&signed_entity_type, &signers) + .await + .unwrap(); comment!("The state machine should have issued a multisignature"); cycle!(tester, "idle"); @@ -71,7 +84,15 @@ async fn certificate_chain() { tester.increase_immutable_number().await.unwrap(); cycle!(tester, "ready"); cycle!(tester, "signing"); - tester.send_single_signatures(&signers).await.unwrap(); + let signed_entity_type = tester + .open_message_observer + .get_current_immutable_entity_type() + .await + .unwrap(); + tester + .send_single_signatures(&signed_entity_type, &signers) + .await + .unwrap(); cycle!(tester, "idle"); let (last_certificates, snapshots) = tester.get_last_certificates_and_snapshots().await.unwrap(); @@ -130,7 +151,15 @@ async fn certificate_chain() { "Signers register & send signatures, the new certificate should be link to the first of the previous epoch" ); tester.register_signers(&new_signers).await.unwrap(); - tester.send_single_signatures(&signers).await.unwrap(); + let signed_entity_type = tester + .open_message_observer + .get_current_immutable_entity_type() + .await + .unwrap(); + tester + .send_single_signatures(&signed_entity_type, &signers) + .await + .unwrap(); cycle!(tester, "idle"); let (last_certificates, snapshots) = tester.get_last_certificates_and_snapshots().await.unwrap(); @@ -165,7 +194,16 @@ async fn certificate_chain() { tester.increase_immutable_number().await.unwrap(); cycle!(tester, "ready"); cycle!(tester, "signing"); - tester.send_single_signatures(&signers).await.unwrap(); + + let signed_entity_type = tester + .open_message_observer + .get_current_immutable_entity_type() + .await + .unwrap(); + tester + .send_single_signatures(&signed_entity_type, &signers) + .await + .unwrap(); cycle!(tester, "idle"); comment!( @@ -177,7 +215,16 @@ async fn certificate_chain() { tester.increase_immutable_number().await.unwrap(); cycle!(tester, "ready"); cycle!(tester, "signing"); - tester.send_single_signatures(&new_signers).await.unwrap(); + let signed_entity_type = tester + .open_message_observer + .get_current_immutable_entity_type() + .await + .unwrap(); + + tester + .send_single_signatures(&signed_entity_type, &new_signers) + .await + .unwrap(); cycle!(tester, "idle"); let (last_certificates, snapshots) = diff --git a/mithril-aggregator/tests/create_certificate.rs b/mithril-aggregator/tests/create_certificate.rs index 6b602f45c63..fdcb56f0c04 100644 --- a/mithril-aggregator/tests/create_certificate.rs +++ b/mithril-aggregator/tests/create_certificate.rs @@ -2,11 +2,12 @@ mod test_extensions; use std::collections::BTreeSet; +use mithril_aggregator::Configuration; use mithril_common::{ entities::{ProtocolMessagePartKey, ProtocolParameters}, test_utils::MithrilFixtureBuilder, }; -use test_extensions::RuntimeTester; +use test_extensions::{utilities::get_test_dir, RuntimeTester}; #[tokio::test] async fn create_certificate() { @@ -15,7 +16,12 @@ async fn create_certificate() { m: 100, phi_f: 0.95, }; - let mut tester = RuntimeTester::build(protocol_parameters.clone()).await; + let configuration = Configuration { + protocol_parameters: protocol_parameters.clone(), + data_stores_directory: get_test_dir("create_certificate").join("aggregator.sqlite3"), + ..Configuration::new_sample() + }; + let mut tester = RuntimeTester::build(configuration).await; comment!("create signers & declare stake distribution"); let fixture = MithrilFixtureBuilder::default() @@ -61,9 +67,14 @@ async fn create_certificate() { cycle!(tester, "signing"); comment!("signers send their single signature"); + let signed_entity_type = tester + .open_message_observer + .get_current_immutable_entity_type() + .await + .unwrap(); let signers_who_sign = &signers[0..=6]; tester - .send_single_signatures(signers_who_sign) + .send_single_signatures(&signed_entity_type, signers_who_sign) .await .unwrap(); diff --git a/mithril-aggregator/tests/era_checker.rs b/mithril-aggregator/tests/era_checker.rs index fc933bc5247..c98f6ea8c0e 100644 --- a/mithril-aggregator/tests/era_checker.rs +++ b/mithril-aggregator/tests/era_checker.rs @@ -1,5 +1,5 @@ mod test_extensions; -use mithril_aggregator::RuntimeError; +use mithril_aggregator::{Configuration, RuntimeError}; use mithril_common::{ chain_observer::ChainObserver, entities::{Epoch, ProtocolParameters}, @@ -7,7 +7,7 @@ use mithril_common::{ test_utils::MithrilFixtureBuilder, }; -use test_extensions::RuntimeTester; +use test_extensions::{utilities::get_test_dir, RuntimeTester}; // NOTE: Due to the shared nature of the Logger, there cannot be two methods in // the same test file. Because the logger is wiped of memory when the first @@ -19,7 +19,12 @@ async fn testing_eras() { m: 100, phi_f: 0.95, }; - let mut tester = RuntimeTester::build(protocol_parameters.clone()).await; + let configuration = Configuration { + protocol_parameters: protocol_parameters.clone(), + data_stores_directory: get_test_dir("testing_eras").join("aggregator.sqlite3"), + ..Configuration::new_sample() + }; + let mut tester = RuntimeTester::build(configuration).await; tester.era_reader_adapter.set_markers(vec![ EraMarker::new("unsupported", Some(Epoch(0))), EraMarker::new(&SupportedEra::dummy().to_string(), Some(Epoch(12))), diff --git a/mithril-aggregator/tests/simple.rs b/mithril-aggregator/tests/simple.rs index 4aaf6a9a75c..b79066f4b1a 100644 --- a/mithril-aggregator/tests/simple.rs +++ b/mithril-aggregator/tests/simple.rs @@ -1,10 +1,11 @@ mod test_extensions; +use mithril_aggregator::Configuration; use mithril_common::{ entities::{ProtocolParameters, SignerWithStake}, test_utils::MithrilFixtureBuilder, }; -use test_extensions::RuntimeTester; +use test_extensions::{utilities::get_test_dir, RuntimeTester}; #[tokio::test] async fn simple_scenario() { @@ -13,7 +14,12 @@ async fn simple_scenario() { m: 100, phi_f: 0.65, }; - let mut tester = RuntimeTester::build(protocol_parameters.clone()).await; + let configuration = Configuration { + protocol_parameters: protocol_parameters.clone(), + data_stores_directory: get_test_dir("simple_scenario").join("aggregator.sqlite3"), + ..Configuration::new_sample() + }; + let mut tester = RuntimeTester::build(configuration).await; comment!("Create signers & declare stake distribution"); let fixture = MithrilFixtureBuilder::default() diff --git a/mithril-aggregator/tests/test_extensions/mod.rs b/mithril-aggregator/tests/test_extensions/mod.rs index 4f52a7a8b34..7ed2e75de46 100644 --- a/mithril-aggregator/tests/test_extensions/mod.rs +++ b/mithril-aggregator/tests/test_extensions/mod.rs @@ -6,6 +6,7 @@ pub mod runtime_tester; #[macro_use] pub mod utilities; +pub mod open_message_observer; //pub use dependency::initialize_dependencies; pub use runtime_tester::RuntimeTester; diff --git a/mithril-aggregator/tests/test_extensions/open_message_observer.rs b/mithril-aggregator/tests/test_extensions/open_message_observer.rs new file mode 100644 index 00000000000..f0206b4bc27 --- /dev/null +++ b/mithril-aggregator/tests/test_extensions/open_message_observer.rs @@ -0,0 +1,53 @@ +use mithril_aggregator::{ + certifier_service::CertifierService, database::provider::OpenMessageWithSingleSignatures, +}; +use mithril_common::{entities::SignedEntityType, BeaconProvider}; +use std::sync::Arc; + +// An observer that allow to inspect currently available open messages. +pub struct OpenMessageObserver { + pub beacon_provider: Arc, + pub certifier_service: Arc, +} + +impl OpenMessageObserver { + // [OpenMessageObserver] factory + pub fn new( + beacon_provider: Arc, + certifier_service: Arc, + ) -> Self { + Self { + beacon_provider, + certifier_service, + } + } + + // Get the current [open message][OpenMessageWithSingleSignatures] for [cardano immutables][SignedEntityType::CardanoImmutableFilesFull] + pub async fn get_current_immutable_message( + &self, + ) -> Result, String> { + let immutable_signer_entity_type = SignedEntityType::CardanoImmutableFilesFull( + self.beacon_provider + .get_current_beacon() + .await + .map_err(|e| format!("Querying the current beacon should not fail: {e:?}"))?, + ); + + self.certifier_service + .get_open_message(&immutable_signer_entity_type) + .await + .map_err(|e| { + format!( + "Requesting current open message of type CardanoImmutableFilesFull should be not fail: {e:?}" + ) + }) + } + + // Get the [entity type][SignedEntityType::CardanoImmutableFilesFull] of the current current open message + pub async fn get_current_immutable_entity_type(&self) -> Result { + match self.get_current_immutable_message().await? { + None => Err("An open message should be available for cardano immutables".to_string()), + Some(message) => Ok(message.signed_entity_type), + } + } +} diff --git a/mithril-aggregator/tests/test_extensions/runtime_tester.rs b/mithril-aggregator/tests/test_extensions/runtime_tester.rs index 74f54f8d58e..85f21741d0b 100644 --- a/mithril-aggregator/tests/test_extensions/runtime_tester.rs +++ b/mithril-aggregator/tests/test_extensions/runtime_tester.rs @@ -10,15 +10,16 @@ use slog::Drain; use std::sync::Arc; use tokio::sync::mpsc::UnboundedReceiver; +use crate::test_extensions::open_message_observer::OpenMessageObserver; use mithril_aggregator::{ AggregatorRuntime, Configuration, DumbSnapshotUploader, DumbSnapshotter, ProtocolParametersStorer, SignerRegisterer, }; -use mithril_common::crypto_helper::{key_encode_hex, ProtocolClerk, ProtocolGenesisSigner}; +use mithril_common::crypto_helper::{ProtocolClerk, ProtocolGenesisSigner}; use mithril_common::digesters::DumbImmutableFileObserver; use mithril_common::entities::{ - Certificate, Epoch, ImmutableFileNumber, ProtocolParameters, SignerWithStake, SingleSignatures, - Snapshot, StakeDistribution, + Certificate, Epoch, ImmutableFileNumber, SignedEntityType, SignerWithStake, Snapshot, + StakeDistribution, }; use mithril_common::{chain_observer::FakeObserver, digesters::DumbImmutableDigester}; @@ -52,11 +53,12 @@ pub struct RuntimeTester { pub runtime: AggregatorRuntime, pub receiver: UnboundedReceiver, pub era_reader_adapter: Arc, + pub open_message_observer: OpenMessageObserver, _logs_guard: slog_scope::GlobalLoggerGuard, } impl RuntimeTester { - pub async fn build(default_protocol_parameters: ProtocolParameters) -> Self { + pub async fn build(configuration: Configuration) -> Self { let snapshot_uploader = Arc::new(DumbSnapshotUploader::new()); let chain_observer = Arc::new(FakeObserver::default()); let immutable_file_observer = Arc::new(DumbImmutableFileObserver::default()); @@ -68,10 +70,6 @@ impl RuntimeTester { &SupportedEra::dummy().to_string(), Some(Epoch(0)), )])); - let configuration = Configuration { - protocol_parameters: default_protocol_parameters, - ..Configuration::new_sample() - }; let mut deps_builder = DependenciesBuilder::new(configuration); deps_builder.snapshot_uploader = Some(snapshot_uploader.clone()); deps_builder.chain_observer = Some(chain_observer.clone()); @@ -86,6 +84,10 @@ impl RuntimeTester { let drain = slog_async::Async::new(drain).build().fuse(); let log = slog_scope::set_global_logger(slog::Logger::root(Arc::new(drain), slog::o!())); let receiver = deps_builder.get_event_transmitter_receiver().await.unwrap(); + let open_message_observer = OpenMessageObserver::new( + deps_builder.get_beacon_provider().await.unwrap(), + deps_builder.get_certifier_service().await.unwrap(), + ); Self { snapshot_uploader, @@ -98,6 +100,7 @@ impl RuntimeTester { runtime, receiver, era_reader_adapter, + open_message_observer, _logs_guard: log, } } @@ -152,6 +155,13 @@ impl RuntimeTester { .get_current_beacon() .await .map_err(|e| format!("Querying the current beacon should not fail: {e:?}"))?; + self.deps_builder + .get_certifier_service() + .await + .unwrap() + .inform_epoch(beacon.epoch) + .await + .expect("inform_epoch should not fail"); let protocol_parameters = self .deps_builder .get_protocol_parameters_store() @@ -225,6 +235,13 @@ impl RuntimeTester { .await .ok_or("a new epoch should have been issued")?; self.update_digester_digest().await?; + self.deps_builder + .get_certifier_service() + .await + .unwrap() + .inform_epoch(new_epoch) + .await + .expect("inform_epoch should not fail"); Ok(new_epoch) } @@ -247,28 +264,21 @@ impl RuntimeTester { /// "Send", actually register, the given single signatures in the multi-signers pub async fn send_single_signatures( &mut self, + signed_entity_type: &SignedEntityType, signers: &[SignerFixture], ) -> Result<(), String> { - let lock = self.deps_builder.get_multi_signer().await.unwrap(); - let multisigner = lock.read().await; - let message = multisigner - .get_current_message() + let certifier_service = self.deps_builder.get_certifier_service().await.unwrap(); + let message = certifier_service + .get_open_message(signed_entity_type) .await - .ok_or("There should be a message to be signed.")?; + .unwrap() + .ok_or("There should be a message to be signed.")? + .protocol_message; for signer_fixture in signers { - if let Some(signature) = signer_fixture - .protocol_signer - .sign(message.compute_hash().as_bytes()) - { - let single_signatures = SingleSignatures::new( - signer_fixture.signer_with_stake.party_id.to_owned(), - key_encode_hex(&signature).expect("hex encoding should not fail"), - signature.indexes, - ); - - multisigner - .register_single_signature(&message, &single_signatures) + if let Some(single_signatures) = signer_fixture.sign(&message) { + certifier_service + .register_single_signature(signed_entity_type, &single_signatures) .await .map_err(|e| { format!("registering a winning lottery signature should not fail: {e:?}") @@ -353,7 +363,7 @@ impl RuntimeTester { Ok(fixture.signers_fixture()) } - // Update the digester result using the current beacon + /// Update the digester result using the current beacon pub async fn update_digester_digest(&mut self) -> Result<(), String> { let beacon = self .deps_builder @@ -374,7 +384,7 @@ impl RuntimeTester { Ok(()) } - // update the Era markers + /// Update the Era markers pub async fn set_era_markers(&self, markers: Vec) { self.era_reader_adapter.set_markers(markers) } diff --git a/mithril-aggregator/tests/test_extensions/utilities.rs b/mithril-aggregator/tests/test_extensions/utilities.rs index 79a14c2b671..0f85108a42d 100644 --- a/mithril-aggregator/tests/test_extensions/utilities.rs +++ b/mithril-aggregator/tests/test_extensions/utilities.rs @@ -1,8 +1,27 @@ use slog_scope::debug; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{ + path::PathBuf, + sync::atomic::{AtomicUsize, Ordering}, +}; pub static COMMENT_COUNT: AtomicUsize = AtomicUsize::new(0); +/// Create a directory to save test artefacts. This directory is cleaned if it +/// already exists, it is created if not. This directory is kept at the end to +/// allow debuging. +pub fn get_test_dir(subdir_name: &str) -> PathBuf { + let parent_dir = std::env::temp_dir().join("mithril_test").join(subdir_name); + + if parent_dir.exists() { + std::fs::remove_dir_all(&parent_dir) + .unwrap_or_else(|e| panic!("Could not remove dir {parent_dir:?}: {e}")); + } + std::fs::create_dir_all(&parent_dir) + .unwrap_or_else(|e| panic!("Could not create dir {parent_dir:?}: {e}")); + + parent_dir +} + pub fn comment(comment: String) { let old_count = COMMENT_COUNT.fetch_add(1, Ordering::SeqCst); debug!("COMMENT {:02} 💬 {}", old_count + 1, comment); diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index 758a06079ae..6103d267091 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-common" -version = "0.2.40" +version = "0.2.41" authors = { workspace = true } edition = { workspace = true } documentation = { workspace = true } diff --git a/mithril-common/src/database/version_checker.rs b/mithril-common/src/database/version_checker.rs index 70a391fa3f4..ee10e07d689 100644 --- a/mithril-common/src/database/version_checker.rs +++ b/mithril-common/src/database/version_checker.rs @@ -352,7 +352,7 @@ mod tests { db_checker.apply().await.unwrap(); check_database_version(connection.clone(), 1).await; - // re instanciate a new checker with no migration registered (version 0). + // re instantiate a new checker with no migration registered (version 0). let db_checker = DatabaseVersionChecker::new( slog_scope::logger(), ApplicationNodeType::Aggregator, diff --git a/mithril-common/src/entities/certificate_pending.rs b/mithril-common/src/entities/certificate_pending.rs index 60e12db9d14..d779476700d 100644 --- a/mithril-common/src/entities/certificate_pending.rs +++ b/mithril-common/src/entities/certificate_pending.rs @@ -1,12 +1,18 @@ use crate::entities::{Beacon, PartyId, ProtocolParameters, Signer}; use serde::{Deserialize, Serialize}; +use super::SignedEntityType; + /// CertificatePending represents a pending certificate in the process of production -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CertificatePending { /// Current Beacon pub beacon: Beacon, + /// Signed entity type + #[serde(rename = "entity_type")] + pub signed_entity_type: SignedEntityType, + /// Current Protocol parameters #[serde(rename = "protocol")] pub protocol_parameters: ProtocolParameters, @@ -26,6 +32,7 @@ impl CertificatePending { /// CertificatePending factory pub fn new( beacon: Beacon, + signed_entity_type: SignedEntityType, protocol_parameters: ProtocolParameters, next_protocol_parameters: ProtocolParameters, signers: Vec, @@ -33,6 +40,7 @@ impl CertificatePending { ) -> CertificatePending { CertificatePending { beacon, + signed_entity_type, protocol_parameters, next_protocol_parameters, signers, diff --git a/mithril-common/src/entities/signed_entity_type.rs b/mithril-common/src/entities/signed_entity_type.rs index efb2d1483c1..1d7629a70c4 100644 --- a/mithril-common/src/entities/signed_entity_type.rs +++ b/mithril-common/src/entities/signed_entity_type.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize}; use strum_macros::Display; use crate::{sqlite::HydrationError, StdError}; @@ -19,7 +20,7 @@ const ENTITY_TYPE_CARDANO_IMMUTABLE_FILES_FULL: usize = 2; /// are identified by their discriminant (i.e. index in the enum), thus the /// modification of this type should only ever consist of appending new /// variants. -#[derive(Display, Debug, Clone, PartialEq, Eq)] +#[derive(Display, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[strum(serialize_all = "PascalCase")] pub enum SignedEntityType { /// Mithril stake distribution @@ -38,6 +39,13 @@ impl SignedEntityType { Self::MithrilStakeDistribution(Epoch(5)) } + /// Return the epoch from the intern beacon. + pub fn get_epoch(&self) -> Epoch { + match self { + Self::CardanoImmutableFilesFull(b) => b.epoch, + Self::CardanoStakeDistribution(e) | Self::MithrilStakeDistribution(e) => *e, + } + } /// Create an instance from data coming from the database pub fn hydrate(signed_entity_type_id: usize, beacon_str: &str) -> Result { let myself = match signed_entity_type_id { diff --git a/mithril-common/src/entities/single_signatures.rs b/mithril-common/src/entities/single_signatures.rs index a6569e949f3..021b29aabdc 100644 --- a/mithril-common/src/entities/single_signatures.rs +++ b/mithril-common/src/entities/single_signatures.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; /// SingleSignatures represent single signatures originating from a participant in the network /// for a digest at won lottery indexes -#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct SingleSignatures { /// The unique identifier of the signer pub party_id: PartyId, diff --git a/mithril-common/src/era/era_reader.rs b/mithril-common/src/era/era_reader.rs index 660307bc6c6..0c3d8bcac28 100644 --- a/mithril-common/src/era/era_reader.rs +++ b/mithril-common/src/era/era_reader.rs @@ -17,7 +17,7 @@ pub struct EraMarker { } impl EraMarker { - /// instanciate a new [EraMarker]. + /// instantiate a new [EraMarker]. pub fn new(name: &str, epoch: Option) -> Self { let name = name.to_string(); diff --git a/mithril-common/src/messages/certificate_pending.rs b/mithril-common/src/messages/certificate_pending.rs index 2e0fb8ebd4f..a803250ec3d 100644 --- a/mithril-common/src/messages/certificate_pending.rs +++ b/mithril-common/src/messages/certificate_pending.rs @@ -4,16 +4,20 @@ use crate::{ crypto_helper::KESPeriod, entities::{ Beacon, HexEncodedOpCert, HexEncodedVerificationKey, HexEncodedVerificationKeySignature, - PartyId, ProtocolParameters, + PartyId, ProtocolParameters, SignedEntityType, }, }; /// Structure to transport [crate::entities::CertificatePending] data. -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CertificatePendingMessage { /// Current Beacon pub beacon: Beacon, + /// Signed entity type + #[serde(rename = "entity_type")] + pub signed_entity_type: SignedEntityType, + /// Current Protocol parameters #[serde(rename = "protocol")] pub protocol_parameters: ProtocolParameters, @@ -34,6 +38,7 @@ impl CertificatePendingMessage { pub fn dummy() -> Self { Self { beacon: Beacon::default(), + signed_entity_type: SignedEntityType::dummy(), protocol_parameters: ProtocolParameters { k: 5, m: 100, @@ -101,12 +106,14 @@ mod tests { use super::*; fn golden_message() -> CertificatePendingMessage { + let beacon = Beacon { + network: "preview".to_string(), + epoch: Epoch(86), + immutable_file_number: 1728, + }; CertificatePendingMessage { - beacon: Beacon { - network: "preview".to_string(), - epoch: Epoch(86), - immutable_file_number: 1728, - }, + beacon: beacon.clone(), + signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(beacon), protocol_parameters: ProtocolParameters { k: 5, m: 100, @@ -126,6 +133,7 @@ mod tests { fn test_v1() { let json = r#"{ "beacon": {"network": "preview", "epoch": 86, "immutable_file_number": 1728 }, +"entity_type": {"CardanoImmutableFilesFull":{"network":"preview","epoch":86,"immutable_file_number":1728}}, "protocol": {"k": 5, "m": 100, "phi_f": 0.65 }, "next_protocol": {"k": 50, "m": 1000, "phi_f": 0.65 }, "signers": [{"party_id": "123", "verification_key": "7b22766b223a5b3134332c3136312c3235352c34382c37382c35372c3230342c3232302c32352c3232312c3136342c3235322c3234382c31342c35362c3132362c3138362c3133352c3232382c3138382c3134352c3138312c35322c3230302c39372c39392c3231332c34362c302c3139392c3139332c38392c3138372c38382c32392c3133352c3137332c3234342c38362c33362c38332c35342c36372c3136342c362c3133372c39342c37322c362c3130352c3132382c3132382c39332c34382c3137362c31312c342c3234362c3133382c34382c3138302c3133332c39302c3134322c3139322c32342c3139332c3131312c3134322c33312c37362c3131312c3131302c3233342c3135332c39302c3230382c3139322c33312c3132342c39352c3130322c34392c3135382c39392c35322c3232302c3136352c39342c3235312c36382c36392c3132312c31362c3232342c3139345d2c22706f70223a5b3136382c35302c3233332c3139332c31352c3133362c36352c37322c3132332c3134382c3132392c3137362c33382c3139382c3230392c34372c32382c3230342c3137362c3134342c35372c3235312c34322c32382c36362c37362c38392c39372c3135382c36332c35342c3139382c3139342c3137362c3133352c3232312c31342c3138352c3139372c3232352c3230322c39382c3234332c37342c3233332c3232352c3134332c3135312c3134372c3137372c3137302c3131372c36362c3136352c36362c36322c33332c3231362c3233322c37352c36382c3131342c3139352c32322c3130302c36352c34342c3139382c342c3136362c3130322c3233332c3235332c3234302c35392c3137352c36302c3131372c3134322c3131342c3134302c3132322c31372c38372c3131302c3138372c312c31372c31302c3139352c3135342c31332c3234392c38362c35342c3232365d7d"}], diff --git a/mithril-common/src/messages/register_signature.rs b/mithril-common/src/messages/register_signature.rs index dda96922aa4..4c11a4da8b9 100644 --- a/mithril-common/src/messages/register_signature.rs +++ b/mithril-common/src/messages/register_signature.rs @@ -1,10 +1,14 @@ use serde::{Deserialize, Serialize}; -use crate::entities::{HexEncodedSingleSignature, LotteryIndex, PartyId}; +use crate::entities::{HexEncodedSingleSignature, LotteryIndex, PartyId, SignedEntityType}; /// Message structure to register single signature. #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] pub struct RegisterSignatureMessage { + /// Signed entity type + #[serde(rename = "entity_type")] + pub signed_entity_type: Option, + /// The unique identifier of the signer pub party_id: PartyId, @@ -20,6 +24,7 @@ impl RegisterSignatureMessage { /// Return a dummy test entity (test-only). pub fn dummy() -> Self { Self { + signed_entity_type: Some(SignedEntityType::dummy()), party_id: "party_id".to_string(), signature: "7b227369676d61223a5b3137312c3139352c3234322c3235332c3137392c32352c3138372c3135312c3132322c3133302c3230372c3132322c38342c3132352c3134322c3132332c3233352c3134312c3230362c3136392c382c3136302c3138382c36382c35312c3232302c3232342c3231312c3137312c3230372c3231362c3139332c3230352c3139312c3233372c3131312c3232392c3132392c3131392c36362c3134342c3234382c3235322c39322c3234372c35382c37312c39355d2c22706b223a7b22766b223a5b3134332c3136312c3235352c34382c37382c35372c3230342c3232302c32352c3232312c3136342c3235322c3234382c31342c35362c3132362c3138362c3133352c3232382c3138382c3134352c3138312c35322c3230302c39372c39392c3231332c34362c302c3139392c3139332c38392c3138372c38382c32392c3133352c3137332c3234342c38362c33362c38332c35342c36372c3136342c362c3133372c39342c37322c362c3130352c3132382c3132382c39332c34382c3137362c31312c342c3234362c3133382c34382c3138302c3133332c39302c3134322c3139322c32342c3139332c3131312c3134322c33312c37362c3131312c3131302c3233342c3135332c39302c3230382c3139322c33312c3132342c39352c3130322c34392c3135382c39392c35322c3232302c3136352c39342c3235312c36382c36392c3132312c31362c3232342c3139345d2c22706f70223a5b3136382c35302c3233332c3139332c31352c3133362c36352c37322c3132332c3134382c3132392c3137362c33382c3139382c3230392c34372c32382c3230342c3137362c3134342c35372c3235312c34322c32382c36362c37362c38392c39372c3135382c36332c35342c3139382c3139342c3137362c3133352c3232312c31342c3138352c3139372c3232352c3230322c39382c3234332c37342c3233332c3232352c3134332c3135312c3134372c3137372c3137302c3131372c36362c3136352c36362c36322c33332c3231362c3233322c37352c36382c3131342c3139352c32322c3130302c36352c34342c3139382c342c3136362c3130322c3233332c3235332c3234302c35392c3137352c36302c3131372c3134322c3131342c3134302c3132322c31372c38372c3131302c3138372c312c31372c31302c3139352c3135342c31332c3234392c38362c35342c3232365d7d2c227061727479223a302c227374616b65223a3832362c22696e646578223a332c2270617468223a7b2276616c756573223a5b5b3235332c3135382c3135382c3232322c3233352c3137302c3137362c3139392c33332c3230302c36362c32362c3231312c3137392c3132362c3232362c35352c3234302c3138322c33302c3234362c3231352c37362c3135382c31362c3131342c342c3231392c36322c3131352c3235332c322c3139322c3231392c3135322c3137352c3131322c34352c36392c3131322c36382c3139352c31372c34342c3230352c3230342c37382c3233342c3130362c3234362c3230392c33312c3230302c3137312c3130382c32372c3136352c35382c3232392c37342c35382c3139312c3132352c3231385d2c5b3130382c39382c35322c3137372c3131332c3132392c3139342c37312c3133352c3137342c37342c3230352c38392c3137352c312c3230382c3234362c3136312c3132322c3233312c33302c3137382c32362c3234312c39382c35322c3133322c31342c33372c302c3138312c3232342c3130332c34362c3130312c3232322c3139392c36312c3231372c31322c39322c3231362c3139302c3131352c3233362c3134322c3138322c3235332c38312c32352c3138392c342c3235302c35382c34352c3234332c38302c37332c3130322c38332c32342c3130392c3131312c3138305d2c5b3138302c3230382c3234392c35312c3231362c3133352c34342c3134342c372c3132392c36302c36332c3234342c3130342c33362c3232392c34332c31312c3132332c38362c3131352c3131322c3138342c3230382c3135392c3138352c37332c31302c3136392c32372c39362c3231382c39392c3135322c3138312c36362c3230312c36302c3135342c31342c3231312c39342c3232392c3135382c3230382c3136362c3233302c37362c32332c3131382c3137382c3230382c38372c3131372c3233302c31392c3233312c32392c3230362c35382c3232352c32322c39352c3130335d5d2c22696e646578223a302c22686173686572223a6e756c6c7d7d".to_string(), won_indexes: vec![1, 3], @@ -33,6 +38,7 @@ mod tests { fn golden_message() -> RegisterSignatureMessage { RegisterSignatureMessage { + signed_entity_type: None, party_id: "party_id".to_string(), signature: "7b227369676d61223a5b3137312c3139352c3234322c3235332c3137392c32352c3138372c3135312c3132322c3133302c3230372c3132322c38342c3132352c3134322c3132332c3233352c3134312c3230362c3136392c382c3136302c3138382c36382c35312c3232302c3232342c3231312c3137312c3230372c3231362c3139332c3230352c3139312c3233372c3131312c3232392c3132392c3131392c36362c3134342c3234382c3235322c39322c3234372c35382c37312c39355d2c22706b223a7b22766b223a5b3134332c3136312c3235352c34382c37382c35372c3230342c3232302c32352c3232312c3136342c3235322c3234382c31342c35362c3132362c3138362c3133352c3232382c3138382c3134352c3138312c35322c3230302c39372c39392c3231332c34362c302c3139392c3139332c38392c3138372c38382c32392c3133352c3137332c3234342c38362c33362c38332c35342c36372c3136342c362c3133372c39342c37322c362c3130352c3132382c3132382c39332c34382c3137362c31312c342c3234362c3133382c34382c3138302c3133332c39302c3134322c3139322c32342c3139332c3131312c3134322c33312c37362c3131312c3131302c3233342c3135332c39302c3230382c3139322c33312c3132342c39352c3130322c34392c3135382c39392c35322c3232302c3136352c39342c3235312c36382c36392c3132312c31362c3232342c3139345d2c22706f70223a5b3136382c35302c3233332c3139332c31352c3133362c36352c37322c3132332c3134382c3132392c3137362c33382c3139382c3230392c34372c32382c3230342c3137362c3134342c35372c3235312c34322c32382c36362c37362c38392c39372c3135382c36332c35342c3139382c3139342c3137362c3133352c3232312c31342c3138352c3139372c3232352c3230322c39382c3234332c37342c3233332c3232352c3134332c3135312c3134372c3137372c3137302c3131372c36362c3136352c36362c36322c33332c3231362c3233322c37352c36382c3131342c3139352c32322c3130302c36352c34342c3139382c342c3136362c3130322c3233332c3235332c3234302c35392c3137352c36302c3131372c3134322c3131342c3134302c3132322c31372c38372c3131302c3138372c312c31372c31302c3139352c3135342c31332c3234392c38362c35342c3232365d7d2c227061727479223a302c227374616b65223a3832362c22696e646578223a332c2270617468223a7b2276616c756573223a5b5b3235332c3135382c3135382c3232322c3233352c3137302c3137362c3139392c33332c3230302c36362c32362c3231312c3137392c3132362c3232362c35352c3234302c3138322c33302c3234362c3231352c37362c3135382c31362c3131342c342c3231392c36322c3131352c3235332c322c3139322c3231392c3135322c3137352c3131322c34352c36392c3131322c36382c3139352c31372c34342c3230352c3230342c37382c3233342c3130362c3234362c3230392c33312c3230302c3137312c3130382c32372c3136352c35382c3232392c37342c35382c3139312c3132352c3231385d2c5b3130382c39382c35322c3137372c3131332c3132392c3139342c37312c3133352c3137342c37342c3230352c38392c3137352c312c3230382c3234362c3136312c3132322c3233312c33302c3137382c32362c3234312c39382c35322c3133322c31342c33372c302c3138312c3232342c3130332c34362c3130312c3232322c3139392c36312c3231372c31322c39322c3231362c3139302c3131352c3233362c3134322c3138322c3235332c38312c32352c3138392c342c3235302c35382c34352c3234332c38302c37332c3130322c38332c32342c3130392c3131312c3138305d2c5b3138302c3230382c3234392c35312c3231362c3133352c34342c3134342c372c3132392c36302c36332c3234342c3130342c33362c3232392c34332c31312c3132332c38362c3131352c3131322c3138342c3230382c3135392c3138352c37332c31302c3136392c32372c39362c3231382c39392c3135322c3138312c36362c3230312c36302c3135342c31342c3231312c39342c3232392c3135382c3230382c3136362c3233302c37362c32332c3131382c3137382c3230382c38372c3131372c3233302c31392c3233312c32392c3230362c35382c3232352c32322c39352c3130335d5d2c22696e646578223a302c22686173686572223a6e756c6c7d7d".to_string(), won_indexes: vec![1, 3], diff --git a/mithril-common/src/sqlite/cursor.rs b/mithril-common/src/sqlite/cursor.rs index b2e9d2ea340..f412916f379 100644 --- a/mithril-common/src/sqlite/cursor.rs +++ b/mithril-common/src/sqlite/cursor.rs @@ -33,6 +33,6 @@ where fn next(&mut self) -> Option { self.cursor .next() - .map(|res| T::hydrate(res.unwrap()).unwrap()) + .map(|res| T::hydrate(res.map_err(|e| panic!("{e}")).unwrap()).unwrap()) } } diff --git a/mithril-common/src/test_utils/apispec.rs b/mithril-common/src/test_utils/apispec.rs index 3aa747bbb5b..eb02c708041 100644 --- a/mithril-common/src/test_utils/apispec.rs +++ b/mithril-common/src/test_utils/apispec.rs @@ -193,6 +193,7 @@ mod tests { use super::*; use crate::entities; + use crate::messages::CertificatePendingMessage; use crate::test_utils::fake_data; #[test] @@ -204,9 +205,9 @@ mod tests { .validate_request(&Null) .unwrap() .validate_response(&Response::::new(Bytes::from( - json!(fake_data::certificate_pending()) + json!(CertificatePendingMessage::dummy()) .to_string() - .into_bytes() + .into_bytes(), ))) .is_ok()); diff --git a/mithril-common/src/test_utils/fake_data.rs b/mithril-common/src/test_utils/fake_data.rs index 47d0d09f7a4..5c815a7f925 100644 --- a/mithril-common/src/test_utils/fake_data.rs +++ b/mithril-common/src/test_utils/fake_data.rs @@ -1,7 +1,8 @@ //! Fake data builders for testing. use crate::entities::{ - CertificateMetadata, LotteryIndex, ProtocolMessage, ProtocolMessagePartKey, SingleSignatures, + CertificateMetadata, LotteryIndex, ProtocolMessage, ProtocolMessagePartKey, SignedEntityType, + SingleSignatures, }; use crate::{crypto_helper, entities}; @@ -53,6 +54,9 @@ pub fn certificate_pending() -> entities::CertificatePending { // Beacon let beacon = beacon(); + // Signed entity type + let signed_entity_type = SignedEntityType::dummy(); + // Protocol parameters let next_protocol_parameters = protocol_parameters(); let protocol_parameters = protocol_parameters(); @@ -65,6 +69,7 @@ pub fn certificate_pending() -> entities::CertificatePending { // Certificate pending entities::CertificatePending::new( beacon, + signed_entity_type, protocol_parameters, next_protocol_parameters, current_signers, @@ -72,6 +77,15 @@ pub fn certificate_pending() -> entities::CertificatePending { ) } +/// Fake Genesis Certificate +pub fn genesis_certificate(certificate_hash: String) -> entities::Certificate { + let mut certificate = certificate(certificate_hash); + certificate.genesis_signature = certificate.multi_signature; + certificate.multi_signature = "".to_string(); + + certificate +} + /// Fake Certificate pub fn certificate(certificate_hash: String) -> entities::Certificate { // Beacon diff --git a/mithril-common/src/test_utils/mithril_fixture.rs b/mithril-common/src/test_utils/mithril_fixture.rs index 4b98eca908e..a5df6723e2f 100644 --- a/mithril-common/src/test_utils/mithril_fixture.rs +++ b/mithril-common/src/test_utils/mithril_fixture.rs @@ -1,6 +1,16 @@ +use std::sync::Arc; + use crate::{ - crypto_helper::{ProtocolInitializer, ProtocolSigner, ProtocolStakeDistribution}, - entities::{ProtocolParameters, Signer, SignerWithStake, StakeDistribution}, + certificate_chain::CertificateGenesisProducer, + crypto_helper::{ + key_decode_hex, key_encode_hex, OpCert, ProtocolClerk, ProtocolGenesisSigner, + ProtocolInitializer, ProtocolKeyRegistration, ProtocolSigner, + ProtocolSignerVerificationKeySignature, ProtocolStakeDistribution, + }, + entities::{ + Beacon, Certificate, ProtocolMessage, ProtocolParameters, Signer, SignerWithStake, + SingleSignatures, StakeDistribution, + }, }; /// A fixture of Mithril data types. @@ -75,4 +85,78 @@ impl MithrilFixture { pub fn protocol_stake_distribution(&self) -> ProtocolStakeDistribution { self.stake_distribution.clone() } + + /// Create a genesis certificate using the fixture signers for the given beacon + pub fn create_genesis_certificate(&self, beacon: &Beacon) -> Certificate { + let mut key_registration = ProtocolKeyRegistration::init(&self.stake_distribution); + + for signer in self.signers.clone() { + let verification_key = + key_decode_hex(&signer.signer_with_stake.verification_key).unwrap(); + key_registration + .register( + Some(signer.signer_with_stake.party_id.to_owned()), + signer.operational_certificate(), + signer.verification_key_signature(), + signer.signer_with_stake.kes_period, + verification_key, + ) + .unwrap(); + } + + let closed_registration = key_registration.close(); + let clerk = ProtocolClerk::from_registration( + &self.protocol_parameters.clone().into(), + &closed_registration, + ); + let genesis_avk = clerk.compute_avk(); + let genesis_signer = ProtocolGenesisSigner::create_deterministic_genesis_signer(); + let genesis_producer = CertificateGenesisProducer::new(Some(Arc::new(genesis_signer))); + let genesis_protocol_message = + CertificateGenesisProducer::create_genesis_protocol_message(&genesis_avk).unwrap(); + let genesis_signature = genesis_producer + .sign_genesis_protocol_message(genesis_protocol_message) + .unwrap(); + + CertificateGenesisProducer::create_genesis_certificate( + self.protocol_parameters.clone(), + beacon.clone(), + genesis_avk, + genesis_signature, + ) + .unwrap() + } +} + +impl SignerFixture { + /// Sign the given protocol message. + pub fn sign(&self, protocol_message: &ProtocolMessage) -> Option { + self.protocol_signer + .sign(protocol_message.compute_hash().as_bytes()) + .map(|signature| { + let won_indexes = signature.indexes.clone(); + + SingleSignatures::new( + self.signer_with_stake.party_id.to_owned(), + key_encode_hex(signature).unwrap(), + won_indexes, + ) + }) + } + + /// Decode this signer operational certificate if any + pub fn operational_certificate(&self) -> Option { + match &self.signer_with_stake.operational_certificate { + Some(operational_certificate) => key_decode_hex(operational_certificate).unwrap(), + _ => None, + } + } + + /// Decode this verification key signature certificate if any + pub fn verification_key_signature(&self) -> Option { + self.signer_with_stake + .verification_key_signature + .as_ref() + .map(|verification_key_signature| key_decode_hex(verification_key_signature).unwrap()) + } } diff --git a/mithril-signer/Cargo.toml b/mithril-signer/Cargo.toml index 972fd142c49..cefd886f6f8 100644 --- a/mithril-signer/Cargo.toml +++ b/mithril-signer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-signer" -version = "0.2.33" +version = "0.2.34" description = "A Mithril Signer" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-signer/src/message_adapters/from_pending_certificate_message.rs b/mithril-signer/src/message_adapters/from_pending_certificate_message.rs index 5725593fe06..530775173e7 100644 --- a/mithril-signer/src/message_adapters/from_pending_certificate_message.rs +++ b/mithril-signer/src/message_adapters/from_pending_certificate_message.rs @@ -11,6 +11,7 @@ impl FromPendingCertificateMessageAdapter { pub fn adapt(message: CertificatePendingMessage) -> CertificatePending { CertificatePending { beacon: message.beacon, + signed_entity_type: message.signed_entity_type, protocol_parameters: message.protocol_parameters, next_protocol_parameters: message.next_protocol_parameters, signers: Self::adapt_signers(message.signers), diff --git a/mithril-signer/src/message_adapters/to_register_signature_message.rs b/mithril-signer/src/message_adapters/to_register_signature_message.rs index e6984fb8687..0a2aae1b3df 100644 --- a/mithril-signer/src/message_adapters/to_register_signature_message.rs +++ b/mithril-signer/src/message_adapters/to_register_signature_message.rs @@ -6,6 +6,7 @@ pub struct ToRegisterSignatureMessageAdapter; impl ToRegisterSignatureMessageAdapter { pub fn adapt(single_signature: SingleSignatures) -> RegisterSignatureMessage { RegisterSignatureMessage { + signed_entity_type: None, party_id: single_signature.party_id, signature: single_signature.signature, won_indexes: single_signature.won_indexes, diff --git a/mithril-signer/tests/test_extensions/certificate_handler.rs b/mithril-signer/tests/test_extensions/certificate_handler.rs index d0ff6bd61d0..3f688536687 100644 --- a/mithril-signer/tests/test_extensions/certificate_handler.rs +++ b/mithril-signer/tests/test_extensions/certificate_handler.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, sync::Arc}; use async_trait::async_trait; use mithril_common::{ entities::{Beacon, CertificatePending, Epoch, EpochSettings, Signer, SingleSignatures}, + test_utils::fake_data, BeaconProvider, BeaconProviderImpl, }; use mithril_signer::{CertificateHandler, CertificateHandlerError}; @@ -72,7 +73,7 @@ impl CertificateHandler for FakeAggregator { let beacon = self.get_beacon().await?; let mut certificate_pending = CertificatePending { beacon: beacon.clone(), - ..Default::default() + ..fake_data::certificate_pending() }; let store = self.registered_signers.read().await; diff --git a/openapi.yaml b/openapi.yaml index c09fa8fe080..254cf031e05 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4,7 +4,7 @@ info: # `mithril-common/src/lib.rs` file. If you plan to update it # here to reflect changes in the API, please also update the constant in the # Rust file. - version: 0.1.1 + version: 0.1.3 title: Mithril Aggregator Server description: | The REST API provided by a Mithril Aggregator Node in a Mithril network. @@ -52,6 +52,7 @@ paths: Returns the information related to the current pending certificate: * protocol parameters, for current and next epoch (to setup cryptography) * beacon information (where on the chain the pending certificate should be triggered) + * entity type of the message that must be signed * verification keys of the signers, for current and next epoch responses: "200": @@ -229,13 +230,15 @@ paths: "201": description: signatures registration succeeded "400": - description: signer registration bad request + description: signatures registration bad request content: application/json: schema: $ref: "#/components/schemas/Error" - "409": - description: signatures registration already done + "404": + description: open message not found + "410": + description: signatures registration done too late "412": description: API version mismatch default: @@ -315,12 +318,18 @@ components: example: { "network": "mainnet", "epoch": 329, "immutable_file_number": 7060000 } + SignedEntityType: + description: Entity type of the message that is signed + type: object + additionalProperties: true + CertificatePendingMessage: description: CertificatePendingMessage represents all the information related to the certificate currently expecting to receive quorum of single signatures type: object additionalProperties: false required: - beacon + - entity_type - protocol - next_protocol - signers @@ -328,6 +337,8 @@ components: properties: beacon: $ref: "#/components/schemas/Beacon" + entity_type: + $ref: "#/components/schemas/SignedEntityType" protocol: $ref: "#/components/schemas/ProtocolParameters" next_protocol: @@ -409,10 +420,13 @@ components: type: object additionalProperties: false required: + - entity_type - party_id - signature - indexes properties: + entity_type: + $ref: "#/components/schemas/SignedEntityType" party_id: description: The unique identifier of the signer type: string