From e02968d83b61eb0ee9c8d0fab27756dbda960abb Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:20:17 +0200 Subject: [PATCH 01/43] Scaffold integration test for signing with buffered signatures --- ...te_certificate_with_buffered_signatures.rs | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs diff --git a/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs b/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs new file mode 100644 index 00000000000..b44dd92442d --- /dev/null +++ b/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs @@ -0,0 +1,110 @@ +mod test_extensions; + +use mithril_aggregator::Configuration; +use mithril_common::{ + entities::{ + BlockNumber, CardanoDbBeacon, CardanoTransactionsSigningConfig, ChainPoint, Epoch, + ProtocolParameters, SignedEntityType, SignedEntityTypeDiscriminants, SlotNumber, + StakeDistributionParty, TimePoint, + }, + test_utils::MithrilFixtureBuilder, +}; +use test_extensions::{utilities::get_test_dir, ExpectedCertificate, RuntimeTester}; + +#[tokio::test] +async fn create_certificate_with_buffered_signatures() { + let protocol_parameters = ProtocolParameters { + k: 5, + m: 150, + phi_f: 0.95, + }; + let configuration = Configuration { + protocol_parameters: protocol_parameters.clone(), + signed_entity_types: Some(SignedEntityTypeDiscriminants::CardanoTransactions.to_string()), + data_stores_directory: get_test_dir("create_certificate_with_buffered_signatures"), + cardano_transactions_signing_config: CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(0), + step: BlockNumber(30), + }, + ..Configuration::new_sample() + }; + let mut tester = RuntimeTester::build( + TimePoint { + epoch: Epoch(1), + immutable_file_number: 1, + chain_point: ChainPoint { + slot_number: SlotNumber(10), + block_number: BlockNumber(100), + block_hash: "block_hash-100".to_string(), + }, + }, + configuration, + ) + .await; + + comment!("create signers & declare stake distribution"); + let fixture = MithrilFixtureBuilder::default() + .with_signers(10) + .with_protocol_parameters(protocol_parameters.clone()) + .build(); + + tester.init_state_from_fixture(&fixture).await.unwrap(); + + comment!("Bootstrap the genesis certificate"); + tester.register_genesis_certificate(&fixture).await.unwrap(); + + assert_last_certificate_eq!( + tester, + ExpectedCertificate::new_genesis( + CardanoDbBeacon::new("devnet".to_string(), 1, 1), + fixture.compute_and_encode_avk() + ) + ); + + comment!("Increase immutable number"); + tester.increase_immutable_number().await.unwrap(); + + comment!("start the runtime state machine"); + cycle!(tester, "ready"); + + comment!("signers send their single signature before the state machine is signing"); + tester + .send_single_signatures( + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + &fixture.signers_fixture(), + ) + .await + .unwrap(); + cycle!(tester, "signing"); + + comment!("register signers"); + // Note: removing this registration makes the epoch_service unable to build its + // next_protocol_multi_signer because the list of next_signers is empty. + // Todo: Is this a bug ? + tester + .register_signers(&fixture.signers_fixture()) + .await + .unwrap(); + + comment!("Using buffered signatures, the state machine should issue a certificate for the MithrilStakeDistribution"); + cycle!(tester, "ready"); + assert_last_certificate_eq!( + tester, + ExpectedCertificate::new( + CardanoDbBeacon::new("devnet".to_string(), 1, 2), + StakeDistributionParty::from_signers(fixture.signers_with_stake()).as_slice(), + fixture.compute_and_encode_avk(), + SignedEntityType::MithrilStakeDistribution(Epoch(1)), + ExpectedCertificate::genesis_identifier(&CardanoDbBeacon::new( + "devnet".to_string(), + 1, + 1 + )), + ) + ); + + tester.increase_epoch().await.unwrap(); + cycle!(tester, "idle"); + + cycle!(tester, "ready"); +} From b038620939817e48a44430cba45e0cdb683b9e27 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:22:49 +0200 Subject: [PATCH 02/43] Uncouple signatures registration from certifier in integration tests By computing the Signed Entity Type and the protocol message without having to get the current open message. Required to be able to buffer incoming single signatures as an open message is not yet opened. --- .../test_extensions/aggregator_observer.rs | 47 +++++++++---------- .../tests/test_extensions/runtime_tester.rs | 31 ++++++++---- 2 files changed, 42 insertions(+), 36 deletions(-) diff --git a/mithril-aggregator/tests/test_extensions/aggregator_observer.rs b/mithril-aggregator/tests/test_extensions/aggregator_observer.rs index af879a3e031..8965064f969 100644 --- a/mithril-aggregator/tests/test_extensions/aggregator_observer.rs +++ b/mithril-aggregator/tests/test_extensions/aggregator_observer.rs @@ -43,7 +43,7 @@ impl AggregatorObserver { self.ticker_service.get_current_time_point().await.unwrap() } - /// Get the current [open message][OpenMessageWithSingleSignatures] for the given message type + /// Get the current [open message][OpenMessage] for the given message type pub async fn get_current_open_message( &self, discriminant: SignedEntityTypeDiscriminants, @@ -53,20 +53,31 @@ impl AggregatorObserver { self.certifier_service .get_open_message(&signed_entity_type) .await - .with_context(|| "Requesting current open message of type CardanoImmutableFilesFull should be not fail") + .with_context(|| { + format!( + "Requesting current open message of type '{discriminant}' should be not fail" + ) + }) } - /// Get the [entity type][SignedEntityType::CardanoImmutableFilesFull] of the current current open message - pub async fn get_current_signed_entity_type( + /// Compute the full [SignedEntityType] for the given discriminant based on the current + /// [TimePoint]. + /// + /// Note: It computes what would be the signed entity type if the aggregator was working on this + /// discriminant now. + /// Consequently, it doesn't mean that an open message is available for this type. + pub async fn build_current_signed_entity_type( &self, discriminant: SignedEntityTypeDiscriminants, ) -> StdResult { - match self.get_current_open_message(discriminant).await? { - None => Err(anyhow!( - "An open message should be available for cardano immutables" - )), - Some(message) => Ok(message.signed_entity_type), - } + let time_point = self + .ticker_service + .get_current_time_point() + .await + .with_context(|| "Querying the current beacon should not fail")?; + + self.signed_entity_config + .time_point_to_signed_entity(discriminant, &time_point) } /// Get the last certificate produced by the aggregator @@ -98,22 +109,6 @@ impl AggregatorObserver { Ok(last_tx_snapshot) } - async fn build_current_signed_entity_type( - &self, - discriminant: SignedEntityTypeDiscriminants, - ) -> StdResult { - let time_point = self - .ticker_service - .get_current_time_point() - .await - .with_context(|| "Querying the current beacon should not fail")?; - - Ok(self - .signed_entity_config - .time_point_to_signed_entity(discriminant, &time_point) - .unwrap()) - } - pub async fn is_last_signed_entity( &self, signed_entity_type_expected: &SignedEntityType, diff --git a/mithril-aggregator/tests/test_extensions/runtime_tester.rs b/mithril-aggregator/tests/test_extensions/runtime_tester.rs index fe7d8ca65bb..e6215b0959e 100644 --- a/mithril-aggregator/tests/test_extensions/runtime_tester.rs +++ b/mithril-aggregator/tests/test_extensions/runtime_tester.rs @@ -9,6 +9,7 @@ use mithril_aggregator::{ AggregatorRuntime, Configuration, DependencyContainer, DumbSnapshotUploader, DumbSnapshotter, SignerRegistrationError, }; +use mithril_common::entities::ProtocolMessagePartKey; use mithril_common::{ cardano_block_scanner::{DumbBlockScanner, ScannedBlock}, chain_observer::{ChainObserver, FakeObserver}, @@ -388,16 +389,26 @@ impl RuntimeTester { let certifier_service = self.dependencies.certifier_service.clone(); let signed_entity_type = self .observer - .get_current_signed_entity_type(discriminant) + .build_current_signed_entity_type(discriminant) .await?; - let message = certifier_service - .get_open_message(&signed_entity_type) - .await - .with_context(|| { - format!("An open message should exist for signed_entity_type: {signed_entity_type}") - })? - .ok_or(anyhow!("There should be a message to be signed."))? - .protocol_message; + + // Code copied from `AggregatorRunner::compute_protocol_message` + // Todo: Refactor this code to avoid code duplication by making the signable_builder_service + // able to retrieve the next avk by itself. + let mut message = self + .dependencies + .signable_builder_service + .compute_protocol_message(signed_entity_type.clone()) + .await?; + + let epoch_service = self.dependencies.epoch_service.read().await; + message.set_message_part( + ProtocolMessagePartKey::NextAggregateVerificationKey, + epoch_service + .next_aggregate_verification_key()? + .to_json_hex() + .with_context(|| "convert next avk to json hex failure")?, + ); for signer_fixture in signers { if let Some(single_signatures) = signer_fixture.sign(&message) { @@ -493,7 +504,7 @@ impl RuntimeTester { ) -> StdResult<()> { let signed_entity_type = self .observer - .get_current_signed_entity_type(discriminant) + .build_current_signed_entity_type(discriminant) .await?; let mut open_message = self .open_message_repository From 33bd0272264a769d5fe230ea0f7576cbf3674a57 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 2 Sep 2024 18:11:52 +0200 Subject: [PATCH 03/43] Promote aggregator `certifier` module to directory Since a certifier decorator will be added, this will avoid stacking too much files or too much the certifier file. --- .../src/services/{certifier.rs => certifier/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mithril-aggregator/src/services/{certifier.rs => certifier/mod.rs} (100%) diff --git a/mithril-aggregator/src/services/certifier.rs b/mithril-aggregator/src/services/certifier/mod.rs similarity index 100% rename from mithril-aggregator/src/services/certifier.rs rename to mithril-aggregator/src/services/certifier/mod.rs From 047e26bed437c3bd607f4502819649be7731ba5f Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 2 Sep 2024 18:25:00 +0200 Subject: [PATCH 04/43] Rearrange `certifier` module - `interface`: contains the trait & error definition - `certifier_service`: the implementation --- .../services/certifier/certifier_service.rs | 880 ++++++++++++++++ .../src/services/certifier/interface.rs | 116 ++ .../src/services/certifier/mod.rs | 997 +----------------- 3 files changed, 1000 insertions(+), 993 deletions(-) create mode 100644 mithril-aggregator/src/services/certifier/certifier_service.rs create mode 100644 mithril-aggregator/src/services/certifier/interface.rs diff --git a/mithril-aggregator/src/services/certifier/certifier_service.rs b/mithril-aggregator/src/services/certifier/certifier_service.rs new file mode 100644 index 00000000000..af50ad52d53 --- /dev/null +++ b/mithril-aggregator/src/services/certifier/certifier_service.rs @@ -0,0 +1,880 @@ +use anyhow::Context; +use async_trait::async_trait; +use chrono::Utc; +use slog::Logger; +use slog_scope::{debug, info, trace, warn}; +use std::sync::Arc; +use tokio::sync::RwLock; + +use mithril_common::certificate_chain::CertificateVerifier; +use mithril_common::crypto_helper::{ProtocolGenesisVerifier, PROTOCOL_VERSION}; +use mithril_common::entities::{ + Certificate, CertificateMetadata, CertificateSignature, Epoch, ProtocolMessage, + SignedEntityType, SingleSignatures, StakeDistributionParty, +}; +use mithril_common::{CardanoNetwork, StdResult, TickerService}; + +use crate::database::record::{OpenMessageRecord, OpenMessageWithSingleSignaturesRecord}; +use crate::database::repository::{ + CertificateRepository, OpenMessageRepository, SingleSignatureRepository, +}; +use crate::dependency_injection::EpochServiceWrapper; +use crate::entities::OpenMessage; +use crate::services::{CertifierService, CertifierServiceError}; +use crate::MultiSigner; + +/// Mithril CertifierService implementation +pub struct MithrilCertifierService { + network: CardanoNetwork, + open_message_repository: Arc, + single_signature_repository: Arc, + certificate_repository: Arc, + certificate_verifier: Arc, + genesis_verifier: Arc, + multi_signer: Arc>, + // todo: should be removed after removing immutable file number from the certificate metadata + ticker_service: Arc, + epoch_service: EpochServiceWrapper, + _logger: Logger, +} + +impl MithrilCertifierService { + /// instantiate the service + #[allow(clippy::too_many_arguments)] + pub fn new( + network: CardanoNetwork, + open_message_repository: Arc, + single_signature_repository: Arc, + certificate_repository: Arc, + certificate_verifier: Arc, + genesis_verifier: Arc, + multi_signer: Arc>, + ticker_service: Arc, + epoch_service: EpochServiceWrapper, + logger: Logger, + ) -> Self { + Self { + network, + open_message_repository, + single_signature_repository, + certificate_repository, + multi_signer, + certificate_verifier, + genesis_verifier, + ticker_service, + epoch_service, + _logger: logger, + } + } + + async fn get_open_message_record( + &self, + signed_entity_type: &SignedEntityType, + ) -> StdResult> { + debug!( + "CertifierService::get_open_message_record(signed_entity_type: {signed_entity_type:?})" + ); + + let open_message_with_single_signatures = self + .open_message_repository + .get_open_message_with_single_signatures(signed_entity_type) + .await + .with_context(|| format!("Certifier can not get open message with single signatures for signed entity type: '{signed_entity_type}'"))?; + + Ok(open_message_with_single_signatures) + } +} + +#[async_trait] +impl CertifierService for MithrilCertifierService { + async fn inform_epoch(&self, epoch: Epoch) -> StdResult<()> { + debug!("CertifierService::inform_epoch(epoch: {epoch:?})"); + let nb = self + .open_message_repository + .clean_epoch(epoch) + .await + .with_context(|| { + format!("Certifier can not clean open messages from epoch '{epoch}'") + })?; + info!("MithrilCertifierService: Informed of a new Epoch: {epoch:?}. Cleaned {nb} open messages along with their single signatures."); + + 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:?}"); + trace!("CertifierService::register_single_signature"; "complete_single_signatures" => #?signature); + + let open_message = self + .get_open_message_record(signed_entity_type) + .await.with_context(|| format!("CertifierService can not get open message record for signed_entity_type: '{signed_entity_type}'"))? + .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()); + } + + if open_message.is_expired { + warn!("CertifierService::register_single_signature: open message {signed_entity_type:?} has expired, cannot register single signature."); + + return Err(CertifierServiceError::Expired(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.clone().into()) + .await.with_context(|| format!("Certifier can not create the single signature from single_signature: '{signature:?}', open_message: '{open_message:?}'"))?; + 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 open_message = self + .open_message_repository + .create_open_message( + signed_entity_type.get_epoch_when_signed_entity_type_is_signed(), + signed_entity_type, + protocol_message, + ) + .await + .with_context(|| { + format!( + "Certifier can not create open message from protocol_message: '{:?}, epoch: '{}''", + protocol_message, + signed_entity_type.get_epoch_when_signed_entity_type_is_signed() + ) + })?; + 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.into()) + } + + async fn get_open_message( + &self, + signed_entity_type: &SignedEntityType, + ) -> StdResult> { + debug!("CertifierService::get_open_message(signed_entity_type: {signed_entity_type:?})"); + + let open_message = self + .open_message_repository + .get_open_message_with_single_signatures(signed_entity_type) + .await + .with_context(|| format!("Certifier can not get open message with single signatures for signed entity type: '{signed_entity_type}'"))? + .map(|record| record.into()); + + Ok(open_message) + } + + async fn mark_open_message_if_expired( + &self, + signed_entity_type: &SignedEntityType, + ) -> StdResult> { + debug!("CertifierService::mark_open_message_if_expired"); + + let mut open_message_record = self + .open_message_repository + .get_expired_open_message(signed_entity_type) + .await + .with_context(|| "Certifier can not get expired open messages")?; + if let Some(open_message_record) = open_message_record.as_mut() { + open_message_record.is_expired = true; + self.open_message_repository + .update_open_message(open_message_record) + .await + .with_context(|| "Certifier can not update open message to mark it as expired")?; + } + + Ok(open_message_record.map(|record| record.into())) + } + + async fn create_certificate( + &self, + signed_entity_type: &SignedEntityType, + ) -> StdResult> { + debug!("CertifierService::create_certificate(signed_entity_type: {signed_entity_type:?})"); + let open_message_record = self + .get_open_message_record(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()) + })?; + let open_message: OpenMessage = open_message_record.clone().into(); + + 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()); + } + + if open_message.is_expired { + warn!("CertifierService::create_certificate: open message {signed_entity_type:?} is expired, cannot create certificate."); + + return Err(CertifierServiceError::Expired(signed_entity_type.clone()).into()); + } + + let multi_signer = self.multi_signer.read().await; + let multi_signature = match multi_signer.create_multi_signature(&open_message).await? { + None => { + debug!("CertifierService::create_certificate: No multi-signature could be created for open message {signed_entity_type:?}"); + return Ok(None); + } + Some(signature) => { + info!("CertifierService::create_certificate: multi-signature created for open message {signed_entity_type:?}"); + signature + } + }; + + let epoch_service = self.epoch_service.read().await; + let signer_ids = open_message.get_signers_id(); + let signers = epoch_service + .current_signers_with_stake()? + .clone() + .into_iter() + .filter(|signer| signer_ids.contains(&signer.party_id)) + .collect::>(); + + let protocol_version = PROTOCOL_VERSION.to_string(); + let initiated_at = open_message.created_at; + let sealed_at = Utc::now(); + let immutable_file_number = self + .ticker_service + .get_current_time_point() + .await + .with_context(|| "Could not retrieve current beacon to create certificate")? + .immutable_file_number; + let metadata = CertificateMetadata::new( + self.network.to_string(), + immutable_file_number, + protocol_version, + epoch_service.current_protocol_parameters()?.clone(), + initiated_at, + sealed_at, + StakeDistributionParty::from_signers(signers), + ); + let parent_certificate_hash = self + .certificate_repository + .get_master_certificate_for_epoch::(open_message.epoch) + .await + .with_context(|| { + format!( + "Certifier can not get master certificate for epoch: '{}'", + open_message.epoch + ) + })? + .map(|cert| cert.hash) + .ok_or_else(|| Box::new(CertifierServiceError::NoParentCertificateFound))?; + + let certificate = Certificate::new( + parent_certificate_hash, + open_message.epoch, + metadata, + open_message.protocol_message.clone(), + epoch_service.current_aggregate_verification_key()?.clone(), + CertificateSignature::MultiSignature(signed_entity_type.clone(), multi_signature), + ); + + self.certificate_verifier + .verify_certificate(&certificate, &self.genesis_verifier.to_verification_key()) + .await + .with_context(|| { + format!( + "CertificateVerifier can not verify certificate with hash: '{}'", + certificate.hash + ) + })?; + + let certificate = self + .certificate_repository + .create_certificate(certificate) + .await + .with_context(|| {format!( + "Certifier can not create certificate for signed entity type: '{signed_entity_type}'") + })?; + + let mut open_message_certified: OpenMessageRecord = open_message_record.into(); + open_message_certified.is_certified = true; + self.open_message_repository + .update_open_message(&open_message_certified) + .await + .with_context(|| format!("Certifier can not update open message for signed entity type: '{signed_entity_type}'")) + ?; + + Ok(Some(certificate)) + } + + async fn get_certificate_by_hash(&self, hash: &str) -> StdResult> { + self.certificate_repository.get_certificate(hash).await + } + + async fn get_latest_certificates(&self, last_n: usize) -> StdResult> { + self.certificate_repository + .get_latest_certificates(last_n) + .await + .with_context(|| format!("Certifier can not get last '{last_n}' certificates")) + } + + async fn verify_certificate_chain(&self, epoch: Epoch) -> StdResult<()> { + if let Some(certificate) = self + .certificate_repository + .get_latest_certificates::(1) + .await? + .first() + { + if epoch.has_gap_with(&certificate.epoch) { + return Err(CertifierServiceError::CertificateEpochGap { + certificate_epoch: certificate.epoch, + current_epoch: epoch, + } + .into()); + } + + self.certificate_verifier + .verify_certificate_chain( + certificate.to_owned(), + &self.genesis_verifier.to_verification_key(), + ) + .await + .with_context(|| "CertificateVerifier can not verify certificate chain")?; + + Ok(()) + } else { + Err(CertifierServiceError::CouldNotFindLastCertificate.into()) + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + dependency_injection::DependenciesBuilder, multi_signer::MockMultiSigner, + services::FakeEpochService, Configuration, + }; + use chrono::{DateTime, Days}; + use mithril_common::{ + entities::{CardanoDbBeacon, ProtocolMessagePartKey}, + test_utils::{fake_data, MithrilFixture, MithrilFixtureBuilder}, + }; + + use super::*; + + impl MithrilCertifierService { + async fn from_deps( + network: CardanoNetwork, + mut dependency_builder: DependenciesBuilder, + ) -> Self { + let connection = dependency_builder.get_sqlite_connection().await.unwrap(); + 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 ticker_service = dependency_builder.get_ticker_service().await.unwrap(); + let epoch_service = dependency_builder.get_epoch_service().await.unwrap(); + let logger = dependency_builder.get_logger().unwrap(); + + Self::new( + network, + open_message_repository, + single_signature_repository, + certificate_repository, + certificate_verifier, + genesis_verifier, + multi_signer, + ticker_service, + epoch_service, + logger, + ) + } + } + + /// Note: If current_epoch is provided the [EpochService] will be automatically initialized + async fn setup_certifier_service_with_network( + network: CardanoNetwork, + fixture: &MithrilFixture, + epochs_with_signers: &[Epoch], + current_epoch: Option, + ) -> MithrilCertifierService { + let configuration = Configuration::new_sample(); + let mut dependency_builder = DependenciesBuilder::new(configuration); + + if let Some(epoch) = current_epoch { + dependency_builder.epoch_service = Some(Arc::new(RwLock::new( + FakeEpochService::from_fixture(epoch, fixture), + ))); + } + + let dependency_manager = dependency_builder + .build_dependency_container() + .await + .unwrap(); + dependency_manager + .init_state_from_fixture(fixture, epochs_with_signers) + .await; + + MithrilCertifierService::from_deps(network, dependency_builder).await + } + + async fn setup_certifier_service( + fixture: &MithrilFixture, + epochs_with_signers: &[Epoch], + current_epoch: Option, + ) -> MithrilCertifierService { + setup_certifier_service_with_network( + fake_data::network(), + fixture, + epochs_with_signers, + current_epoch, + ) + .await + } + + #[tokio::test] + async fn should_clean_epoch_when_inform_epoch() { + let beacon = CardanoDbBeacon::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 certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; + 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_mark_open_message_expired_when_exists() { + let beacon = CardanoDbBeacon::new("devnet".to_string(), 3, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); + let certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; + let mut open_message = certifier_service + .open_message_repository + .create_open_message(beacon.epoch, &signed_entity_type, &protocol_message) + .await + .unwrap(); + open_message.expires_at = Some( + DateTime::parse_from_rfc3339("2000-01-19T13:43:05.618857482Z") + .unwrap() + .with_timezone(&Utc), + ); + certifier_service + .open_message_repository + .update_open_message(&open_message) + .await + .unwrap(); + + let open_message = certifier_service + .mark_open_message_if_expired(&signed_entity_type) + .await + .expect("mark_open_message_if_expired should not fail"); + assert!(open_message.is_some()); + assert!(open_message.unwrap().is_expired); + } + + #[tokio::test] + async fn should_not_mark_open_message_expired_when_does_not_expire() { + let beacon = CardanoDbBeacon::new("devnet".to_string(), 3, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); + let certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; + let mut open_message = certifier_service + .open_message_repository + .create_open_message(beacon.epoch, &signed_entity_type, &protocol_message) + .await + .unwrap(); + open_message.expires_at = None; + certifier_service + .open_message_repository + .update_open_message(&open_message) + .await + .unwrap(); + + let open_message = certifier_service + .mark_open_message_if_expired(&signed_entity_type) + .await + .expect("mark_open_message_if_expired should not fail"); + assert!(open_message.is_none()); + } + + #[tokio::test] + async fn should_not_mark_open_message_expired_when_has_not_expired_yet() { + let beacon = CardanoDbBeacon::new("devnet".to_string(), 3, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); + let certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; + let mut open_message = certifier_service + .open_message_repository + .create_open_message(beacon.epoch, &signed_entity_type, &protocol_message) + .await + .unwrap(); + open_message.expires_at = Some(Utc::now().checked_add_days(Days::new(1)).unwrap()); + certifier_service + .open_message_repository + .update_open_message(&open_message) + .await + .unwrap(); + + let open_message = certifier_service + .mark_open_message_if_expired(&signed_entity_type) + .await + .expect("mark_open_message_if_expired should not fail"); + assert!(open_message.is_none()); + } + + #[tokio::test] + async fn should_register_valid_single_signature() { + let beacon = CardanoDbBeacon::new("devnet".to_string(), 3, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epochs_with_signers = (1..=3).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); + let certifier_service = + setup_certifier_service(&fixture, &epochs_with_signers, Some(beacon.epoch)).await; + + 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 = CardanoDbBeacon::new("devnet".to_string(), 3, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let mut protocol_message = ProtocolMessage::new(); + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); + let certifier_service = + setup_certifier_service(&fixture, &epochs_with_signers, Some(beacon.epoch)).await; + + 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 = CardanoDbBeacon::new("devnet".to_string(), 3, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); + let certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; + let mut open_message = certifier_service + .open_message_repository + .create_open_message(beacon.epoch, &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_not_register_single_signature_for_expired_open_message() { + let beacon = CardanoDbBeacon::new("devnet".to_string(), 3, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); + let certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; + let mut open_message = certifier_service + .open_message_repository + .create_open_message(beacon.epoch, &signed_entity_type, &protocol_message) + .await + .unwrap(); + open_message.is_expired = 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 network = fake_data::network(); + let beacon = CardanoDbBeacon::new(network.to_string(), 3, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + let epochs_with_signers = (1..=3).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(3).build(); + let certifier_service = setup_certifier_service_with_network( + network, + &fixture, + &epochs_with_signers, + Some(beacon.epoch), + ) + .await; + + certifier_service + .create_open_message(&signed_entity_type, &protocol_message) + .await + .unwrap(); + + let genesis_certificate = + fixture.create_genesis_certificate(network.to_string(), beacon.epoch - 1, 1); + 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.genesis_verifier.to_verification_key(), + ) + .await + .unwrap(); + + let open_message = certifier_service + .get_open_message(&signed_entity_type) + .await + .unwrap() + .unwrap(); + assert!(open_message.is_certified); + + let certificate_retrieved = certifier_service + .get_certificate_by_hash(&certificate_created.hash) + .await + .unwrap() + .unwrap(); + assert_eq!(certificate_created, certificate_retrieved); + + let latest_certificates = certifier_service.get_latest_certificates(10).await.unwrap(); + assert!(!latest_certificates.is_empty()); + } + + #[tokio::test] + async fn should_not_create_certificate_for_open_message_not_created() { + let beacon = CardanoDbBeacon::new("devnet".to_string(), 1, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let epochs_with_signers = (1..=5).map(Epoch).collect::>(); + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; + 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 = CardanoDbBeacon::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 certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; + 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 = CardanoDbBeacon::new("devnet".to_string(), 1, 1); + let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); + let protocol_message = ProtocolMessage::new(); + 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, None).await; + 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()); + } + + #[tokio::test] + async fn test_epoch_gap_certificate_chain() { + let builder = MithrilFixtureBuilder::default(); + let certifier_service = setup_certifier_service(&builder.build(), &[], None).await; + let certificate = fake_data::genesis_certificate("whatever"); + let epoch = certificate.epoch + 2; + certifier_service + .certificate_repository + .create_certificate(certificate) + .await + .unwrap(); + let error = certifier_service + .verify_certificate_chain(epoch) + .await + .unwrap_err(); + + if let Some(err) = error.downcast_ref::() { + assert!( + matches!(err, CertifierServiceError::CertificateEpochGap {certificate_epoch: _, current_epoch} if *current_epoch == epoch) + ); + } else { + panic!("Unexpected error {error:?}"); + } + } + + #[tokio::test] + async fn test_epoch_gap_certificate_chain_ok() { + let builder = MithrilFixtureBuilder::default(); + let certifier_service = setup_certifier_service(&builder.build(), &[], None).await; + let certificate = fake_data::genesis_certificate("whatever"); + let epoch = certificate.epoch + 1; + certifier_service + .certificate_repository + .create_certificate(certificate) + .await + .unwrap(); + let error = certifier_service + .verify_certificate_chain(epoch) + .await + .unwrap_err(); + + if let Some(err) = error.downcast_ref::() { + assert!(!matches!( + err, + CertifierServiceError::CertificateEpochGap { + certificate_epoch: _, + current_epoch: _ + } + )); + } + } +} diff --git a/mithril-aggregator/src/services/certifier/interface.rs b/mithril-aggregator/src/services/certifier/interface.rs new file mode 100644 index 00000000000..5885cb2534e --- /dev/null +++ b/mithril-aggregator/src/services/certifier/interface.rs @@ -0,0 +1,116 @@ +use async_trait::async_trait; +use thiserror::Error; + +use mithril_common::entities::{ + Certificate, Epoch, ProtocolMessage, SignedEntityType, SingleSignatures, +}; +use mithril_common::StdResult; + +use crate::entities::OpenMessage; + +/// 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 open message is expired, no more single signatures may be + /// attached to it nor be certified again. + #[error("Open message for beacon {0:?} is expired.")] + Expired(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, + + /// No certificate for this epoch + #[error("There is an epoch gap between the last certificate epoch ({certificate_epoch:?}) and current epoch ({current_epoch:?})")] + CertificateEpochGap { + /// Epoch of the last issued certificate + certificate_epoch: Epoch, + + /// Given current epoch + current_epoch: Epoch, + }, + + /// Could not verify certificate chain because could not find last certificate. + #[error("No certificate found.")] + CouldNotFindLastCertificate, +} + +/// ## CertifierService +/// +/// This service manages the open message and their beacon transitions. It can +/// ultimately transform open messages into certificates. +#[cfg_attr(test, mockall::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>; + + /// Mark the open message if it has expired. + async fn mark_open_message_if_expired( + &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>; + + /// Returns a certificate from its hash. + async fn get_certificate_by_hash(&self, hash: &str) -> StdResult>; + + /// Returns the list of the latest created certificates. + async fn get_latest_certificates(&self, last_n: usize) -> StdResult>; + + /// Verify the certificate chain and epoch gap. This will return an error if + /// there is at least an epoch between the given epoch and the most recent + /// certificate. + async fn verify_certificate_chain(&self, epoch: Epoch) -> StdResult<()>; +} diff --git a/mithril-aggregator/src/services/certifier/mod.rs b/mithril-aggregator/src/services/certifier/mod.rs index a28d3130f5e..243d092d664 100644 --- a/mithril-aggregator/src/services/certifier/mod.rs +++ b/mithril-aggregator/src/services/certifier/mod.rs @@ -5,997 +5,8 @@ //! single signatures and deal with the multi_signer for aggregate signature //! creation. -use anyhow::Context; -use async_trait::async_trait; -use chrono::Utc; -use mithril_common::{ - certificate_chain::CertificateVerifier, - crypto_helper::{ProtocolGenesisVerifier, PROTOCOL_VERSION}, - entities::{ - Certificate, CertificateMetadata, CertificateSignature, Epoch, ProtocolMessage, - SignedEntityType, SingleSignatures, StakeDistributionParty, - }, - CardanoNetwork, StdResult, TickerService, -}; -use slog::Logger; -use slog_scope::{debug, error, info, trace, warn}; -use std::sync::Arc; -use thiserror::Error; -use tokio::sync::RwLock; +mod certifier_service; +mod interface; -use crate::{ - database::record::{OpenMessageRecord, OpenMessageWithSingleSignaturesRecord}, - database::repository::{ - CertificateRepository, OpenMessageRepository, SingleSignatureRepository, - }, - entities::OpenMessage, - MultiSigner, -}; - -use crate::dependency_injection::EpochServiceWrapper; - -#[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 open message is expired, no more single signatures may be - /// attached to it nor be certified again. - #[error("Open message for beacon {0:?} is expired.")] - Expired(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, - - /// No certificate for this epoch - #[error("There is an epoch gap between the last certificate epoch ({certificate_epoch:?}) and current epoch ({current_epoch:?})")] - CertificateEpochGap { - /// Epoch of the last issued certificate - certificate_epoch: Epoch, - - /// Given current epoch - current_epoch: Epoch, - }, - - /// Could not verify certificate chain because could not find last certificate. - #[error("No certificate found.")] - CouldNotFindLastCertificate, -} - -/// ## 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>; - - /// Mark the open message if it has expired. - async fn mark_open_message_if_expired( - &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>; - - /// Returns a certificate from its hash. - async fn get_certificate_by_hash(&self, hash: &str) -> StdResult>; - - /// Returns the list of the latest created certificates. - async fn get_latest_certificates(&self, last_n: usize) -> StdResult>; - - /// Verify the certificate chain and epoch gap. This will return an error if - /// there is at least an epoch between the given epoch and the most recent - /// certificate. - async fn verify_certificate_chain(&self, epoch: Epoch) -> StdResult<()>; -} - -/// Mithril CertifierService implementation -pub struct MithrilCertifierService { - network: CardanoNetwork, - open_message_repository: Arc, - single_signature_repository: Arc, - certificate_repository: Arc, - certificate_verifier: Arc, - genesis_verifier: Arc, - multi_signer: Arc>, - // todo: should be removed after removing immutable file number from the certificate metadata - ticker_service: Arc, - epoch_service: EpochServiceWrapper, - _logger: Logger, -} - -impl MithrilCertifierService { - /// instantiate the service - #[allow(clippy::too_many_arguments)] - pub fn new( - network: CardanoNetwork, - open_message_repository: Arc, - single_signature_repository: Arc, - certificate_repository: Arc, - certificate_verifier: Arc, - genesis_verifier: Arc, - multi_signer: Arc>, - ticker_service: Arc, - epoch_service: EpochServiceWrapper, - logger: Logger, - ) -> Self { - Self { - network, - open_message_repository, - single_signature_repository, - certificate_repository, - multi_signer, - certificate_verifier, - genesis_verifier, - ticker_service, - epoch_service, - _logger: logger, - } - } - - async fn get_open_message_record( - &self, - signed_entity_type: &SignedEntityType, - ) -> StdResult> { - debug!( - "CertifierService::get_open_message_record(signed_entity_type: {signed_entity_type:?})" - ); - - let open_message_with_single_signatures = self - .open_message_repository - .get_open_message_with_single_signatures(signed_entity_type) - .await - .with_context(|| format!("Certifier can not get open message with single signatures for signed entity type: '{signed_entity_type}'"))?; - - Ok(open_message_with_single_signatures) - } -} - -#[async_trait] -impl CertifierService for MithrilCertifierService { - async fn inform_epoch(&self, epoch: Epoch) -> StdResult<()> { - debug!("CertifierService::inform_epoch(epoch: {epoch:?})"); - let nb = self - .open_message_repository - .clean_epoch(epoch) - .await - .with_context(|| { - format!("Certifier can not clean open messages from epoch '{epoch}'") - })?; - info!("MithrilCertifierService: Informed of a new Epoch: {epoch:?}. Cleaned {nb} open messages along with their single signatures."); - - 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:?}"); - trace!("CertifierService::register_single_signature"; "complete_single_signatures" => #?signature); - - let open_message = self - .get_open_message_record(signed_entity_type) - .await.with_context(|| format!("CertifierService can not get open message record for signed_entity_type: '{signed_entity_type}'"))? - .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()); - } - - if open_message.is_expired { - warn!("CertifierService::register_single_signature: open message {signed_entity_type:?} has expired, cannot register single signature."); - - return Err(CertifierServiceError::Expired(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.clone().into()) - .await.with_context(|| format!("Certifier can not create the single signature from single_signature: '{signature:?}', open_message: '{open_message:?}'"))?; - 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 open_message = self - .open_message_repository - .create_open_message( - signed_entity_type.get_epoch_when_signed_entity_type_is_signed(), - signed_entity_type, - protocol_message, - ) - .await - .with_context(|| { - format!( - "Certifier can not create open message from protocol_message: '{:?}, epoch: '{}''", - protocol_message, - signed_entity_type.get_epoch_when_signed_entity_type_is_signed() - ) - })?; - 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.into()) - } - - async fn get_open_message( - &self, - signed_entity_type: &SignedEntityType, - ) -> StdResult> { - debug!("CertifierService::get_open_message(signed_entity_type: {signed_entity_type:?})"); - - let open_message = self - .open_message_repository - .get_open_message_with_single_signatures(signed_entity_type) - .await - .with_context(|| format!("Certifier can not get open message with single signatures for signed entity type: '{signed_entity_type}'"))? - .map(|record| record.into()); - - Ok(open_message) - } - - async fn mark_open_message_if_expired( - &self, - signed_entity_type: &SignedEntityType, - ) -> StdResult> { - debug!("CertifierService::mark_open_message_if_expired"); - - let mut open_message_record = self - .open_message_repository - .get_expired_open_message(signed_entity_type) - .await - .with_context(|| "Certifier can not get expired open messages")?; - if let Some(open_message_record) = open_message_record.as_mut() { - open_message_record.is_expired = true; - self.open_message_repository - .update_open_message(open_message_record) - .await - .with_context(|| "Certifier can not update open message to mark it as expired")?; - } - - Ok(open_message_record.map(|record| record.into())) - } - - async fn create_certificate( - &self, - signed_entity_type: &SignedEntityType, - ) -> StdResult> { - debug!("CertifierService::create_certificate(signed_entity_type: {signed_entity_type:?})"); - let open_message_record = self - .get_open_message_record(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()) - })?; - let open_message: OpenMessage = open_message_record.clone().into(); - - 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()); - } - - if open_message.is_expired { - warn!("CertifierService::create_certificate: open message {signed_entity_type:?} is expired, cannot create certificate."); - - return Err(CertifierServiceError::Expired(signed_entity_type.clone()).into()); - } - - let multi_signer = self.multi_signer.read().await; - let multi_signature = match multi_signer.create_multi_signature(&open_message).await? { - None => { - debug!("CertifierService::create_certificate: No multi-signature could be created for open message {signed_entity_type:?}"); - return Ok(None); - } - Some(signature) => { - info!("CertifierService::create_certificate: multi-signature created for open message {signed_entity_type:?}"); - signature - } - }; - - let epoch_service = self.epoch_service.read().await; - let signer_ids = open_message.get_signers_id(); - let signers = epoch_service - .current_signers_with_stake()? - .clone() - .into_iter() - .filter(|signer| signer_ids.contains(&signer.party_id)) - .collect::>(); - - let protocol_version = PROTOCOL_VERSION.to_string(); - let initiated_at = open_message.created_at; - let sealed_at = Utc::now(); - let immutable_file_number = self - .ticker_service - .get_current_time_point() - .await - .with_context(|| "Could not retrieve current beacon to create certificate")? - .immutable_file_number; - let metadata = CertificateMetadata::new( - self.network.to_string(), - immutable_file_number, - protocol_version, - epoch_service.current_protocol_parameters()?.clone(), - initiated_at, - sealed_at, - StakeDistributionParty::from_signers(signers), - ); - let parent_certificate_hash = self - .certificate_repository - .get_master_certificate_for_epoch::(open_message.epoch) - .await - .with_context(|| { - format!( - "Certifier can not get master certificate for epoch: '{}'", - open_message.epoch - ) - })? - .map(|cert| cert.hash) - .ok_or_else(|| Box::new(CertifierServiceError::NoParentCertificateFound))?; - - let certificate = Certificate::new( - parent_certificate_hash, - open_message.epoch, - metadata, - open_message.protocol_message.clone(), - epoch_service.current_aggregate_verification_key()?.clone(), - CertificateSignature::MultiSignature(signed_entity_type.clone(), multi_signature), - ); - - self.certificate_verifier - .verify_certificate(&certificate, &self.genesis_verifier.to_verification_key()) - .await - .with_context(|| { - format!( - "CertificateVerifier can not verify certificate with hash: '{}'", - certificate.hash - ) - })?; - - let certificate = self - .certificate_repository - .create_certificate(certificate) - .await - .with_context(|| {format!( - "Certifier can not create certificate for signed entity type: '{signed_entity_type}'") - })?; - - let mut open_message_certified: OpenMessageRecord = open_message_record.into(); - open_message_certified.is_certified = true; - self.open_message_repository - .update_open_message(&open_message_certified) - .await - .with_context(|| format!("Certifier can not update open message for signed entity type: '{signed_entity_type}'")) - ?; - - Ok(Some(certificate)) - } - - async fn get_certificate_by_hash(&self, hash: &str) -> StdResult> { - self.certificate_repository.get_certificate(hash).await - } - - async fn get_latest_certificates(&self, last_n: usize) -> StdResult> { - self.certificate_repository - .get_latest_certificates(last_n) - .await - .with_context(|| format!("Certifier can not get last '{last_n}' certificates")) - } - - async fn verify_certificate_chain(&self, epoch: Epoch) -> StdResult<()> { - if let Some(certificate) = self - .certificate_repository - .get_latest_certificates::(1) - .await? - .first() - { - if epoch.has_gap_with(&certificate.epoch) { - return Err(CertifierServiceError::CertificateEpochGap { - certificate_epoch: certificate.epoch, - current_epoch: epoch, - } - .into()); - } - - self.certificate_verifier - .verify_certificate_chain( - certificate.to_owned(), - &self.genesis_verifier.to_verification_key(), - ) - .await - .with_context(|| "CertificateVerifier can not verify certificate chain")?; - - Ok(()) - } else { - Err(CertifierServiceError::CouldNotFindLastCertificate.into()) - } - } -} - -#[cfg(test)] -mod tests { - use crate::{ - dependency_injection::DependenciesBuilder, multi_signer::MockMultiSigner, - services::FakeEpochService, Configuration, - }; - use chrono::{DateTime, Days}; - use mithril_common::{ - entities::{CardanoDbBeacon, ProtocolMessagePartKey}, - test_utils::{fake_data, MithrilFixture, MithrilFixtureBuilder}, - }; - - use super::*; - - impl MithrilCertifierService { - async fn from_deps( - network: CardanoNetwork, - mut dependency_builder: DependenciesBuilder, - ) -> Self { - let connection = dependency_builder.get_sqlite_connection().await.unwrap(); - 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 ticker_service = dependency_builder.get_ticker_service().await.unwrap(); - let epoch_service = dependency_builder.get_epoch_service().await.unwrap(); - let logger = dependency_builder.get_logger().unwrap(); - - Self::new( - network, - open_message_repository, - single_signature_repository, - certificate_repository, - certificate_verifier, - genesis_verifier, - multi_signer, - ticker_service, - epoch_service, - logger, - ) - } - } - - /// Note: If current_epoch is provided the [EpochService] will be automatically initialized - async fn setup_certifier_service_with_network( - network: CardanoNetwork, - fixture: &MithrilFixture, - epochs_with_signers: &[Epoch], - current_epoch: Option, - ) -> MithrilCertifierService { - let configuration = Configuration::new_sample(); - let mut dependency_builder = DependenciesBuilder::new(configuration); - - if let Some(epoch) = current_epoch { - dependency_builder.epoch_service = Some(Arc::new(RwLock::new( - FakeEpochService::from_fixture(epoch, fixture), - ))); - } - - let dependency_manager = dependency_builder - .build_dependency_container() - .await - .unwrap(); - dependency_manager - .init_state_from_fixture(fixture, epochs_with_signers) - .await; - - MithrilCertifierService::from_deps(network, dependency_builder).await - } - - async fn setup_certifier_service( - fixture: &MithrilFixture, - epochs_with_signers: &[Epoch], - current_epoch: Option, - ) -> MithrilCertifierService { - setup_certifier_service_with_network( - fake_data::network(), - fixture, - epochs_with_signers, - current_epoch, - ) - .await - } - - #[tokio::test] - async fn should_clean_epoch_when_inform_epoch() { - let beacon = CardanoDbBeacon::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 certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; - 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_mark_open_message_expired_when_exists() { - let beacon = CardanoDbBeacon::new("devnet".to_string(), 3, 1); - let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); - let protocol_message = ProtocolMessage::new(); - let epochs_with_signers = (1..=5).map(Epoch).collect::>(); - let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); - let certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; - let mut open_message = certifier_service - .open_message_repository - .create_open_message(beacon.epoch, &signed_entity_type, &protocol_message) - .await - .unwrap(); - open_message.expires_at = Some( - DateTime::parse_from_rfc3339("2000-01-19T13:43:05.618857482Z") - .unwrap() - .with_timezone(&Utc), - ); - certifier_service - .open_message_repository - .update_open_message(&open_message) - .await - .unwrap(); - - let open_message = certifier_service - .mark_open_message_if_expired(&signed_entity_type) - .await - .expect("mark_open_message_if_expired should not fail"); - assert!(open_message.is_some()); - assert!(open_message.unwrap().is_expired); - } - - #[tokio::test] - async fn should_not_mark_open_message_expired_when_does_not_expire() { - let beacon = CardanoDbBeacon::new("devnet".to_string(), 3, 1); - let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); - let protocol_message = ProtocolMessage::new(); - let epochs_with_signers = (1..=5).map(Epoch).collect::>(); - let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); - let certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; - let mut open_message = certifier_service - .open_message_repository - .create_open_message(beacon.epoch, &signed_entity_type, &protocol_message) - .await - .unwrap(); - open_message.expires_at = None; - certifier_service - .open_message_repository - .update_open_message(&open_message) - .await - .unwrap(); - - let open_message = certifier_service - .mark_open_message_if_expired(&signed_entity_type) - .await - .expect("mark_open_message_if_expired should not fail"); - assert!(open_message.is_none()); - } - - #[tokio::test] - async fn should_not_mark_open_message_expired_when_has_not_expired_yet() { - let beacon = CardanoDbBeacon::new("devnet".to_string(), 3, 1); - let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); - let protocol_message = ProtocolMessage::new(); - let epochs_with_signers = (1..=5).map(Epoch).collect::>(); - let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); - let certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; - let mut open_message = certifier_service - .open_message_repository - .create_open_message(beacon.epoch, &signed_entity_type, &protocol_message) - .await - .unwrap(); - open_message.expires_at = Some(Utc::now().checked_add_days(Days::new(1)).unwrap()); - certifier_service - .open_message_repository - .update_open_message(&open_message) - .await - .unwrap(); - - let open_message = certifier_service - .mark_open_message_if_expired(&signed_entity_type) - .await - .expect("mark_open_message_if_expired should not fail"); - assert!(open_message.is_none()); - } - - #[tokio::test] - async fn should_register_valid_single_signature() { - let beacon = CardanoDbBeacon::new("devnet".to_string(), 3, 1); - let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); - let protocol_message = ProtocolMessage::new(); - let epochs_with_signers = (1..=3).map(Epoch).collect::>(); - let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); - let certifier_service = - setup_certifier_service(&fixture, &epochs_with_signers, Some(beacon.epoch)).await; - - 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 = CardanoDbBeacon::new("devnet".to_string(), 3, 1); - let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); - let mut protocol_message = ProtocolMessage::new(); - let epochs_with_signers = (1..=5).map(Epoch).collect::>(); - let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); - let certifier_service = - setup_certifier_service(&fixture, &epochs_with_signers, Some(beacon.epoch)).await; - - 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 = CardanoDbBeacon::new("devnet".to_string(), 3, 1); - let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); - let protocol_message = ProtocolMessage::new(); - let epochs_with_signers = (1..=5).map(Epoch).collect::>(); - let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); - let certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; - let mut open_message = certifier_service - .open_message_repository - .create_open_message(beacon.epoch, &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_not_register_single_signature_for_expired_open_message() { - let beacon = CardanoDbBeacon::new("devnet".to_string(), 3, 1); - let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); - let protocol_message = ProtocolMessage::new(); - let epochs_with_signers = (1..=5).map(Epoch).collect::>(); - let fixture = MithrilFixtureBuilder::default().with_signers(1).build(); - let certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; - let mut open_message = certifier_service - .open_message_repository - .create_open_message(beacon.epoch, &signed_entity_type, &protocol_message) - .await - .unwrap(); - open_message.is_expired = 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 network = fake_data::network(); - let beacon = CardanoDbBeacon::new(network.to_string(), 3, 1); - let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); - let protocol_message = ProtocolMessage::new(); - let epochs_with_signers = (1..=3).map(Epoch).collect::>(); - let fixture = MithrilFixtureBuilder::default().with_signers(3).build(); - let certifier_service = setup_certifier_service_with_network( - network, - &fixture, - &epochs_with_signers, - Some(beacon.epoch), - ) - .await; - - certifier_service - .create_open_message(&signed_entity_type, &protocol_message) - .await - .unwrap(); - - let genesis_certificate = - fixture.create_genesis_certificate(network.to_string(), beacon.epoch - 1, 1); - 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.genesis_verifier.to_verification_key(), - ) - .await - .unwrap(); - - let open_message = certifier_service - .get_open_message(&signed_entity_type) - .await - .unwrap() - .unwrap(); - assert!(open_message.is_certified); - - let certificate_retrieved = certifier_service - .get_certificate_by_hash(&certificate_created.hash) - .await - .unwrap() - .unwrap(); - assert_eq!(certificate_created, certificate_retrieved); - - let latest_certificates = certifier_service.get_latest_certificates(10).await.unwrap(); - assert!(!latest_certificates.is_empty()); - } - - #[tokio::test] - async fn should_not_create_certificate_for_open_message_not_created() { - let beacon = CardanoDbBeacon::new("devnet".to_string(), 1, 1); - let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); - let epochs_with_signers = (1..=5).map(Epoch).collect::>(); - let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); - let certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; - 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 = CardanoDbBeacon::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 certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; - 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 = CardanoDbBeacon::new("devnet".to_string(), 1, 1); - let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); - let protocol_message = ProtocolMessage::new(); - 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, None).await; - 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()); - } - - #[tokio::test] - async fn test_epoch_gap_certificate_chain() { - let builder = MithrilFixtureBuilder::default(); - let certifier_service = setup_certifier_service(&builder.build(), &[], None).await; - let certificate = fake_data::genesis_certificate("whatever"); - let epoch = certificate.epoch + 2; - certifier_service - .certificate_repository - .create_certificate(certificate) - .await - .unwrap(); - let error = certifier_service - .verify_certificate_chain(epoch) - .await - .unwrap_err(); - - if let Some(err) = error.downcast_ref::() { - assert!( - matches!(err, CertifierServiceError::CertificateEpochGap {certificate_epoch: _, current_epoch} if *current_epoch == epoch) - ); - } else { - panic!("Unexpected error {error:?}"); - } - } - - #[tokio::test] - async fn test_epoch_gap_certificate_chain_ok() { - let builder = MithrilFixtureBuilder::default(); - let certifier_service = setup_certifier_service(&builder.build(), &[], None).await; - let certificate = fake_data::genesis_certificate("whatever"); - let epoch = certificate.epoch + 1; - certifier_service - .certificate_repository - .create_certificate(certificate) - .await - .unwrap(); - let error = certifier_service - .verify_certificate_chain(epoch) - .await - .unwrap_err(); - - if let Some(err) = error.downcast_ref::() { - assert!(!matches!( - err, - CertifierServiceError::CertificateEpochGap { - certificate_epoch: _, - current_epoch: _ - } - )); - } - } -} +pub use certifier_service::*; +pub use interface::*; From 10af4a06565f6a182ada0ed9d570b8531b4f5ea4 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 2 Sep 2024 18:31:42 +0200 Subject: [PATCH 05/43] Scaffold `BufferedCertifierService` decorator --- .../src/dependency_injection/builder.rs | 18 ++-- .../services/certifier/buffered_certifier.rs | 95 +++++++++++++++++++ .../src/services/certifier/mod.rs | 2 + 3 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 mithril-aggregator/src/services/certifier/buffered_certifier.rs diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index 2e9750a83b4..77ff6fee791 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -51,6 +51,7 @@ use mithril_persistence::{ store::adapter::{MemoryAdapter, SQLiteAdapter, StoreAdapter}, }; +use super::{DependenciesBuilderError, EpochServiceWrapper, Result}; use crate::{ artifact_builder::{ CardanoImmutableFilesFullArtifactBuilder, CardanoStakeDistributionArtifactBuilder, @@ -65,10 +66,11 @@ use crate::{ event_store::{EventMessage, EventStore, TransmitterService}, http_server::routes::router, services::{ - AggregatorUpkeepService, CardanoTransactionsImporter, CertifierService, MessageService, - MithrilCertifierService, MithrilEpochService, MithrilMessageService, MithrilProverService, - MithrilSignedEntityService, MithrilStakeDistributionService, ProverService, - SignedEntityService, StakeDistributionService, UpkeepService, + AggregatorUpkeepService, BufferedCertifierService, CardanoTransactionsImporter, + CertifierService, MessageService, MithrilCertifierService, MithrilEpochService, + MithrilMessageService, MithrilProverService, MithrilSignedEntityService, + MithrilStakeDistributionService, ProverService, SignedEntityService, + StakeDistributionService, UpkeepService, }, tools::{CExplorerSignerRetriever, GcpFileUploader, GenesisToolsDependency, SignersImporter}, AggregatorConfig, AggregatorRunner, AggregatorRuntime, CertificatePendingStore, @@ -78,8 +80,6 @@ use crate::{ Snapshotter, SnapshotterCompressionAlgorithm, VerificationKeyStorer, }; -use super::{DependenciesBuilderError, EpochServiceWrapper, Result}; - const SQLITE_FILE: &str = "aggregator.sqlite3"; const SQLITE_FILE_CARDANO_TRANSACTION: &str = "cardano-transaction.sqlite3"; @@ -1440,7 +1440,7 @@ impl DependenciesBuilder { let epoch_service = self.get_epoch_service().await?; let logger = self.get_logger()?; - Ok(Arc::new(MithrilCertifierService::new( + let certifier = Arc::new(MithrilCertifierService::new( cardano_network, open_message_repository, single_signature_repository, @@ -1451,7 +1451,9 @@ impl DependenciesBuilder { ticker_service, epoch_service, logger, - ))) + )); + + Ok(Arc::new(BufferedCertifierService::new(certifier))) } /// [CertifierService] service diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs new file mode 100644 index 00000000000..4d587faf99f --- /dev/null +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -0,0 +1,95 @@ +use async_trait::async_trait; +use std::sync::Arc; + +use mithril_common::entities::{ + Certificate, Epoch, ProtocolMessage, SignedEntityType, SingleSignatures, +}; +use mithril_common::StdResult; + +use crate::entities::OpenMessage; +use crate::services::CertifierService; + +/// A decorator of [CertifierService] that buffers that can buffer registration of single signatures +/// when the open message is not yet created. +/// +/// When an open message is created, buffered single signatures for the open message type are +/// registered. +pub struct BufferedCertifierService { + certifier_service: Arc, +} + +impl BufferedCertifierService { + /// Create a new instance of `BufferedCertifierService`. + pub fn new(certifier_service: Arc) -> Self { + Self { certifier_service } + } +} + +#[async_trait] +impl CertifierService for BufferedCertifierService { + async fn inform_epoch(&self, epoch: Epoch) -> StdResult<()> { + self.certifier_service.inform_epoch(epoch).await + } + + async fn register_single_signature( + &self, + signed_entity_type: &SignedEntityType, + signature: &SingleSignatures, + ) -> StdResult<()> { + self.certifier_service + .register_single_signature(signed_entity_type, signature) + .await + } + + async fn create_open_message( + &self, + signed_entity_type: &SignedEntityType, + protocol_message: &ProtocolMessage, + ) -> StdResult { + self.certifier_service + .create_open_message(signed_entity_type, protocol_message) + .await + } + + async fn get_open_message( + &self, + signed_entity_type: &SignedEntityType, + ) -> StdResult> { + self.certifier_service + .get_open_message(signed_entity_type) + .await + } + + async fn mark_open_message_if_expired( + &self, + signed_entity_type: &SignedEntityType, + ) -> StdResult> { + self.certifier_service + .mark_open_message_if_expired(signed_entity_type) + .await + } + + async fn create_certificate( + &self, + signed_entity_type: &SignedEntityType, + ) -> StdResult> { + self.certifier_service + .create_certificate(signed_entity_type) + .await + } + + async fn get_certificate_by_hash(&self, hash: &str) -> StdResult> { + self.certifier_service.get_certificate_by_hash(hash).await + } + + async fn get_latest_certificates(&self, last_n: usize) -> StdResult> { + self.certifier_service.get_latest_certificates(last_n).await + } + + async fn verify_certificate_chain(&self, epoch: Epoch) -> StdResult<()> { + self.certifier_service.verify_certificate_chain(epoch).await + } +} + +#[cfg(test)] +mod tests {} diff --git a/mithril-aggregator/src/services/certifier/mod.rs b/mithril-aggregator/src/services/certifier/mod.rs index 243d092d664..bfc330c7679 100644 --- a/mithril-aggregator/src/services/certifier/mod.rs +++ b/mithril-aggregator/src/services/certifier/mod.rs @@ -5,8 +5,10 @@ //! single signatures and deal with the multi_signer for aggregate signature //! creation. +mod buffered_certifier; mod certifier_service; mod interface; +pub use buffered_certifier::*; pub use certifier_service::*; pub use interface::*; From c6b0d45a7ee28100125cb7aa1c4e56c1412a4f92 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 3 Sep 2024 12:28:09 +0200 Subject: [PATCH 06/43] Define a store for buffered signatures with an in-memory impl Straigth to the point for now. --- .../services/certifier/buffered_certifier.rs | 90 ++++++++++++++++++- .../src/services/certifier/interface.rs | 23 ++++- 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs index 4d587faf99f..e6a54f15cb8 100644 --- a/mithril-aggregator/src/services/certifier/buffered_certifier.rs +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -1,13 +1,16 @@ use async_trait::async_trait; +use std::collections::BTreeMap; use std::sync::Arc; +use tokio::sync::RwLock; use mithril_common::entities::{ - Certificate, Epoch, ProtocolMessage, SignedEntityType, SingleSignatures, + Certificate, Epoch, ProtocolMessage, SignedEntityType, SignedEntityTypeDiscriminants, + SingleSignatures, }; use mithril_common::StdResult; use crate::entities::OpenMessage; -use crate::services::CertifierService; +use crate::services::{BufferedSingleSignatureStore, CertifierService}; /// A decorator of [CertifierService] that buffers that can buffer registration of single signatures /// when the open message is not yet created. @@ -91,5 +94,86 @@ impl CertifierService for BufferedCertifierService { } } +/// An in-memory implementation of [BufferedSingleSignatureStore]. +pub struct InMemoryBufferedSingleSignatureStore { + store: RwLock>>, +} + +impl Default for InMemoryBufferedSingleSignatureStore { + fn default() -> Self { + Self { + store: RwLock::new(BTreeMap::new()), + } + } +} + +#[async_trait] +impl BufferedSingleSignatureStore for InMemoryBufferedSingleSignatureStore { + async fn buffer_signature( + &self, + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + signature: &SingleSignatures, + ) -> StdResult<()> { + let mut store = self.store.write().await; + let signatures = store + .entry(signed_entity_type_discriminants) + .or_insert_with(Vec::new); + signatures.push(signature.clone()); + Ok(()) + } + + async fn get_buffered_signatures( + &self, + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + ) -> StdResult> { + let store = self.store.read().await; + Ok(store + .get(&signed_entity_type_discriminants) + .cloned() + .unwrap_or_default()) + } +} + #[cfg(test)] -mod tests {} +mod tests { + use mithril_common::test_utils::fake_data; + + use super::*; + + #[tokio::test] + async fn store_and_retrieve_signatures_in_buffered_store() { + let store = InMemoryBufferedSingleSignatureStore::default(); + + let ctx = SignedEntityTypeDiscriminants::CardanoTransactions; + store + .buffer_signature(ctx, &fake_data::single_signatures(vec![1])) + .await + .unwrap(); + store + .buffer_signature(ctx, &fake_data::single_signatures(vec![2])) + .await + .unwrap(); + + // Different signed entity type to test that the store is able to differentiate between them + let msd = SignedEntityTypeDiscriminants::MithrilStakeDistribution; + store + .buffer_signature(msd, &fake_data::single_signatures(vec![3])) + .await + .unwrap(); + + let buffered_signatures_ctx = store.get_buffered_signatures(ctx).await.unwrap(); + assert_eq!( + vec![ + fake_data::single_signatures(vec![1]), + fake_data::single_signatures(vec![2]) + ], + buffered_signatures_ctx + ); + + let buffered_signatures_msd = store.get_buffered_signatures(msd).await.unwrap(); + assert_eq!( + vec![fake_data::single_signatures(vec![3])], + buffered_signatures_msd + ); + } +} diff --git a/mithril-aggregator/src/services/certifier/interface.rs b/mithril-aggregator/src/services/certifier/interface.rs index 5885cb2534e..01226d1ac87 100644 --- a/mithril-aggregator/src/services/certifier/interface.rs +++ b/mithril-aggregator/src/services/certifier/interface.rs @@ -2,7 +2,8 @@ use async_trait::async_trait; use thiserror::Error; use mithril_common::entities::{ - Certificate, Epoch, ProtocolMessage, SignedEntityType, SingleSignatures, + Certificate, Epoch, ProtocolMessage, SignedEntityType, SignedEntityTypeDiscriminants, + SingleSignatures, }; use mithril_common::StdResult; @@ -114,3 +115,23 @@ pub trait CertifierService: Sync + Send { /// certificate. async fn verify_certificate_chain(&self, epoch: Epoch) -> StdResult<()>; } + +/// ## BufferedSignatureStore +/// +/// Allow to buffer single signatures for later use when an open message isn't available yet. +#[cfg_attr(test, mockall::automock)] +#[async_trait] +pub trait BufferedSingleSignatureStore: Sync + Send { + /// Buffer a single signature for later use. + async fn buffer_signature( + &self, + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + signature: &SingleSignatures, + ) -> StdResult<()>; + + /// Get the buffered single signatures for the given signed entity discriminant. + async fn get_buffered_signatures( + &self, + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + ) -> StdResult>; +} From 4c203a5bc8f31119ac177eb38219ec73edb61c03 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 3 Sep 2024 15:57:11 +0200 Subject: [PATCH 07/43] Make buffer certifier buffer signature if no open message is available --- .../src/dependency_injection/builder.rs | 14 ++-- .../services/certifier/buffered_certifier.rs | 80 ++++++++++++++++++- 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index 77ff6fee791..059ed059f97 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -67,10 +67,10 @@ use crate::{ http_server::routes::router, services::{ AggregatorUpkeepService, BufferedCertifierService, CardanoTransactionsImporter, - CertifierService, MessageService, MithrilCertifierService, MithrilEpochService, - MithrilMessageService, MithrilProverService, MithrilSignedEntityService, - MithrilStakeDistributionService, ProverService, SignedEntityService, - StakeDistributionService, UpkeepService, + CertifierService, InMemoryBufferedSingleSignatureStore, MessageService, + MithrilCertifierService, MithrilEpochService, MithrilMessageService, MithrilProverService, + MithrilSignedEntityService, MithrilStakeDistributionService, ProverService, + SignedEntityService, StakeDistributionService, UpkeepService, }, tools::{CExplorerSignerRetriever, GcpFileUploader, GenesisToolsDependency, SignersImporter}, AggregatorConfig, AggregatorRunner, AggregatorRuntime, CertificatePendingStore, @@ -1453,7 +1453,11 @@ impl DependenciesBuilder { logger, )); - Ok(Arc::new(BufferedCertifierService::new(certifier))) + Ok(Arc::new(BufferedCertifierService::new( + certifier, + Arc::new(InMemoryBufferedSingleSignatureStore::default()), + self.get_logger()?, + ))) } /// [CertifierService] service diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs index e6a54f15cb8..616e43f0e72 100644 --- a/mithril-aggregator/src/services/certifier/buffered_certifier.rs +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use slog::{debug, Logger}; use std::collections::BTreeMap; use std::sync::Arc; use tokio::sync::RwLock; @@ -10,7 +11,7 @@ use mithril_common::entities::{ use mithril_common::StdResult; use crate::entities::OpenMessage; -use crate::services::{BufferedSingleSignatureStore, CertifierService}; +use crate::services::{BufferedSingleSignatureStore, CertifierService, CertifierServiceError}; /// A decorator of [CertifierService] that buffers that can buffer registration of single signatures /// when the open message is not yet created. @@ -19,12 +20,22 @@ use crate::services::{BufferedSingleSignatureStore, CertifierService}; /// registered. pub struct BufferedCertifierService { certifier_service: Arc, + buffered_single_signature_store: Arc, + logger: Logger, } impl BufferedCertifierService { /// Create a new instance of `BufferedCertifierService`. - pub fn new(certifier_service: Arc) -> Self { - Self { certifier_service } + pub fn new( + certifier_service: Arc, + buffered_single_signature_store: Arc, + logger: Logger, + ) -> Self { + Self { + certifier_service, + buffered_single_signature_store, + logger, + } } } @@ -39,9 +50,29 @@ impl CertifierService for BufferedCertifierService { signed_entity_type: &SignedEntityType, signature: &SingleSignatures, ) -> StdResult<()> { - self.certifier_service + match self + .certifier_service .register_single_signature(signed_entity_type, signature) .await + { + Ok(res) => Ok(res), + Err(error) => match error.downcast_ref::() { + Some(CertifierServiceError::NotFound(..)) => { + debug!( + self.logger, + "No OpenMessage available for signed entity - Buffering single signature"; + "signed_entity_type" => ?signed_entity_type, + "party_id" => &signature.party_id + ); + + self.buffered_single_signature_store + .buffer_signature(signed_entity_type.into(), signature) + .await?; + Ok(()) + } + _ => Err(error), + }, + } } async fn create_open_message( @@ -138,6 +169,9 @@ impl BufferedSingleSignatureStore for InMemoryBufferedSingleSignatureStore { mod tests { use mithril_common::test_utils::fake_data; + use crate::services::{CertifierServiceError, MockCertifierService}; + use crate::test_tools::TestLogger; + use super::*; #[tokio::test] @@ -176,4 +210,42 @@ mod tests { buffered_signatures_msd ); } + + #[tokio::test] + async fn buffer_signature_if_decorated_certifier_as_no_opened_message() { + let mut inner_certifier = MockCertifierService::new(); + inner_certifier + .expect_register_single_signature() + .returning(|_, _| { + Err( + CertifierServiceError::NotFound(SignedEntityType::MithrilStakeDistribution( + Epoch(5), + )) + .into(), + ) + }); + + let store = Arc::new(InMemoryBufferedSingleSignatureStore::default()); + let certifier = BufferedCertifierService::new( + Arc::new(inner_certifier), + store.clone(), + TestLogger::stdout(), + ); + certifier + .register_single_signature( + &SignedEntityType::MithrilStakeDistribution(Epoch(5)), + &fake_data::single_signatures(vec![1]), + ) + .await + .unwrap(); + + let buffered_signatures = store + .get_buffered_signatures(SignedEntityTypeDiscriminants::MithrilStakeDistribution) + .await + .unwrap(); + assert_eq!( + buffered_signatures, + vec![fake_data::single_signatures(vec![1])] + ); + } } From fa55f44767093833352af70b51039b7bcbec0253 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 3 Sep 2024 17:27:00 +0200 Subject: [PATCH 08/43] Make buffered certifier add buffered signatures when opening a new message For the message type, the buffered signatures will be removed from the buffer. --- .../services/certifier/buffered_certifier.rs | 256 ++++++++++++++---- .../src/services/certifier/interface.rs | 7 + 2 files changed, 217 insertions(+), 46 deletions(-) diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs index 616e43f0e72..ceddfafcfb4 100644 --- a/mithril-aggregator/src/services/certifier/buffered_certifier.rs +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -80,9 +80,31 @@ impl CertifierService for BufferedCertifierService { signed_entity_type: &SignedEntityType, protocol_message: &ProtocolMessage, ) -> StdResult { - self.certifier_service + let creation_result = self + .certifier_service .create_open_message(signed_entity_type, protocol_message) - .await + .await; + + // TODO: does the error in this block should make this method fails or should they just + // be logged? + if let Ok(_open_message) = &creation_result { + let buffered_signatures = self + .buffered_single_signature_store + .get_buffered_signatures(signed_entity_type.into()) + .await?; + + for signature in &buffered_signatures { + self.certifier_service + .register_single_signature(signed_entity_type, signature) + .await?; + } + + self.buffered_single_signature_store + .remove_buffered_signatures(signed_entity_type.into(), buffered_signatures) + .await?; + } + + creation_result } async fn get_open_message( @@ -130,6 +152,17 @@ pub struct InMemoryBufferedSingleSignatureStore { store: RwLock>>, } +#[cfg(test)] +impl InMemoryBufferedSingleSignatureStore { + pub(crate) fn with_data( + initial_data: BTreeMap>, + ) -> Self { + Self { + store: RwLock::new(initial_data), + } + } +} + impl Default for InMemoryBufferedSingleSignatureStore { fn default() -> Self { Self { @@ -163,74 +196,148 @@ impl BufferedSingleSignatureStore for InMemoryBufferedSingleSignatureStore { .cloned() .unwrap_or_default()) } + + async fn remove_buffered_signatures( + &self, + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + single_signatures: Vec, + ) -> StdResult<()> { + let mut store = self.store.write().await; + + for signature in single_signatures { + if let Some(signatures) = store.get_mut(&signed_entity_type_discriminants) { + signatures.retain(|s| s != &signature); + } + } + + Ok(()) + } } #[cfg(test)] mod tests { use mithril_common::test_utils::fake_data; + use mockall::predicate::eq; use crate::services::{CertifierServiceError, MockCertifierService}; use crate::test_tools::TestLogger; use super::*; - #[tokio::test] - async fn store_and_retrieve_signatures_in_buffered_store() { - let store = InMemoryBufferedSingleSignatureStore::default(); + fn mock_certifier(certifier_mock_config: F) -> Arc + where + F: FnOnce(&mut MockCertifierService), + { + let mut certifier = MockCertifierService::new(); + certifier_mock_config(&mut certifier); + Arc::new(certifier) + } - let ctx = SignedEntityTypeDiscriminants::CardanoTransactions; - store - .buffer_signature(ctx, &fake_data::single_signatures(vec![1])) - .await - .unwrap(); - store - .buffer_signature(ctx, &fake_data::single_signatures(vec![2])) - .await - .unwrap(); + mod in_memory_buffered_single_signature_store_tests { + use super::*; - // Different signed entity type to test that the store is able to differentiate between them - let msd = SignedEntityTypeDiscriminants::MithrilStakeDistribution; - store - .buffer_signature(msd, &fake_data::single_signatures(vec![3])) - .await - .unwrap(); + #[tokio::test] + async fn store_and_retrieve_signatures() { + let store = InMemoryBufferedSingleSignatureStore::default(); - let buffered_signatures_ctx = store.get_buffered_signatures(ctx).await.unwrap(); - assert_eq!( - vec![ - fake_data::single_signatures(vec![1]), - fake_data::single_signatures(vec![2]) - ], - buffered_signatures_ctx - ); + let ctx = SignedEntityTypeDiscriminants::CardanoTransactions; + store + .buffer_signature(ctx, &fake_data::single_signatures(vec![1])) + .await + .unwrap(); + store + .buffer_signature(ctx, &fake_data::single_signatures(vec![2])) + .await + .unwrap(); - let buffered_signatures_msd = store.get_buffered_signatures(msd).await.unwrap(); - assert_eq!( - vec![fake_data::single_signatures(vec![3])], - buffered_signatures_msd - ); + // Different signed entity type to test that the store is able to differentiate between them + let msd = SignedEntityTypeDiscriminants::MithrilStakeDistribution; + store + .buffer_signature(msd, &fake_data::single_signatures(vec![3])) + .await + .unwrap(); + + let buffered_signatures_ctx = store.get_buffered_signatures(ctx).await.unwrap(); + assert_eq!( + vec![ + fake_data::single_signatures(vec![1]), + fake_data::single_signatures(vec![2]) + ], + buffered_signatures_ctx + ); + + let buffered_signatures_msd = store.get_buffered_signatures(msd).await.unwrap(); + assert_eq!( + vec![fake_data::single_signatures(vec![3])], + buffered_signatures_msd + ); + } + + #[tokio::test] + async fn remove_buffered_signatures() { + let store = InMemoryBufferedSingleSignatureStore::with_data(BTreeMap::from([ + ( + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + vec![ + fake_data::single_signatures(vec![1]), + fake_data::single_signatures(vec![2]), + fake_data::single_signatures(vec![3]), + ], + ), + ( + SignedEntityTypeDiscriminants::CardanoTransactions, + vec![fake_data::single_signatures(vec![10])], + ), + ])); + + store + .remove_buffered_signatures( + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + vec![ + fake_data::single_signatures(vec![1]), + fake_data::single_signatures(vec![3]), + ], + ) + .await + .unwrap(); + + let remaining_msd_sigs = store + .get_buffered_signatures(SignedEntityTypeDiscriminants::MithrilStakeDistribution) + .await + .unwrap(); + assert_eq!( + vec![fake_data::single_signatures(vec![2])], + remaining_msd_sigs + ); + + let remaining_ctx_sigs = store + .get_buffered_signatures(SignedEntityTypeDiscriminants::CardanoTransactions) + .await + .unwrap(); + assert_eq!( + vec![fake_data::single_signatures(vec![10])], + remaining_ctx_sigs, + "CardanoTransactions signatures should have been left untouched" + ); + } } #[tokio::test] async fn buffer_signature_if_decorated_certifier_as_no_opened_message() { - let mut inner_certifier = MockCertifierService::new(); - inner_certifier - .expect_register_single_signature() - .returning(|_, _| { - Err( - CertifierServiceError::NotFound(SignedEntityType::MithrilStakeDistribution( - Epoch(5), - )) - .into(), - ) - }); - let store = Arc::new(InMemoryBufferedSingleSignatureStore::default()); let certifier = BufferedCertifierService::new( - Arc::new(inner_certifier), + mock_certifier(|mock| { + mock.expect_register_single_signature().returning(|_, _| { + Err(CertifierServiceError::NotFound( + SignedEntityType::MithrilStakeDistribution(Epoch(5)), + ) + .into()) + }); + }), store.clone(), TestLogger::stdout(), ); + certifier .register_single_signature( &SignedEntityType::MithrilStakeDistribution(Epoch(5)), @@ -248,4 +355,61 @@ mod tests { vec![fake_data::single_signatures(vec![1])] ); } + + #[tokio::test] + async fn buffered_signatures_are_moved_to_newly_opened_message() { + let store = Arc::new(InMemoryBufferedSingleSignatureStore::with_data( + BTreeMap::from([ + ( + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + vec![ + fake_data::single_signatures(vec![1]), + fake_data::single_signatures(vec![2]), + ], + ), + ( + SignedEntityTypeDiscriminants::CardanoTransactions, + vec![fake_data::single_signatures(vec![10])], + ), + ]), + )); + let certifier = BufferedCertifierService::new( + mock_certifier(|mock| { + mock.expect_create_open_message() + .returning(|_, _| Ok(OpenMessage::dummy())); + + // Those configuration Asserts that the buffered signatures are registered + mock.expect_register_single_signature() + .with( + eq(SignedEntityType::MithrilStakeDistribution(Epoch(5))), + eq(fake_data::single_signatures(vec![1])), + ) + .once() + .returning(|_, _| Ok(())); + mock.expect_register_single_signature() + .with( + eq(SignedEntityType::MithrilStakeDistribution(Epoch(5))), + eq(fake_data::single_signatures(vec![2])), + ) + .once() + .returning(|_, _| Ok(())); + }), + store.clone(), + TestLogger::stdout(), + ); + + certifier + .create_open_message( + &SignedEntityType::MithrilStakeDistribution(Epoch(5)), + &ProtocolMessage::new(), + ) + .await + .unwrap(); + + let remaining_sigs = store + .get_buffered_signatures(SignedEntityTypeDiscriminants::MithrilStakeDistribution) + .await + .unwrap(); + assert!(remaining_sigs.is_empty()); + } } diff --git a/mithril-aggregator/src/services/certifier/interface.rs b/mithril-aggregator/src/services/certifier/interface.rs index 01226d1ac87..be249517212 100644 --- a/mithril-aggregator/src/services/certifier/interface.rs +++ b/mithril-aggregator/src/services/certifier/interface.rs @@ -134,4 +134,11 @@ pub trait BufferedSingleSignatureStore: Sync + Send { &self, signed_entity_type_discriminants: SignedEntityTypeDiscriminants, ) -> StdResult>; + + /// Remove the given single signatures from the buffer. + async fn remove_buffered_signatures( + &self, + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + single_signatures: Vec, + ) -> StdResult<()>; } From 40dcab7f365f82f8dbc14447705dee1dccdf2b65 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:45:36 +0200 Subject: [PATCH 09/43] Add a dedicated error when registering invalid single signatures in certifier To allow certifier consumer to segregate this error. --- .../src/services/certifier/certifier_service.rs | 15 +++++++++++++-- .../src/services/certifier/interface.rs | 6 +++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/mithril-aggregator/src/services/certifier/certifier_service.rs b/mithril-aggregator/src/services/certifier/certifier_service.rs index af50ad52d53..11d945490c2 100644 --- a/mithril-aggregator/src/services/certifier/certifier_service.rs +++ b/mithril-aggregator/src/services/certifier/certifier_service.rs @@ -132,7 +132,10 @@ impl CertifierService for MithrilCertifierService { let multi_signer = self.multi_signer.read().await; multi_signer .verify_single_signature(&open_message.protocol_message, signature) - .await?; + .await + .map_err(|err| { + CertifierServiceError::InvalidSingleSignature(signed_entity_type.clone(), err) + })?; let single_signature = self .single_signature_repository @@ -622,10 +625,18 @@ mod tests { signatures.push(signature); } } - certifier_service + let err = certifier_service .register_single_signature(&signed_entity_type, &signatures[0]) .await .expect_err("register_single_signature should fail"); + + assert!( + matches!( + err.downcast_ref::(), + Some(CertifierServiceError::InvalidSingleSignature(..)) + ), + "Expected CertifierServiceError, got: '{err:?}'" + ); } #[tokio::test] diff --git a/mithril-aggregator/src/services/certifier/interface.rs b/mithril-aggregator/src/services/certifier/interface.rs index be249517212..be8bb3bdb0b 100644 --- a/mithril-aggregator/src/services/certifier/interface.rs +++ b/mithril-aggregator/src/services/certifier/interface.rs @@ -5,7 +5,7 @@ use mithril_common::entities::{ Certificate, Epoch, ProtocolMessage, SignedEntityType, SignedEntityTypeDiscriminants, SingleSignatures, }; -use mithril_common::StdResult; +use mithril_common::{StdError, StdResult}; use crate::entities::OpenMessage; @@ -26,6 +26,10 @@ pub enum CertifierServiceError { #[error("Open message for beacon {0:?} is expired.")] Expired(SignedEntityType), + /// An invalid signature was provided. + #[error("Invalid single signature for {0:?}.")] + InvalidSingleSignature(SignedEntityType, #[source] StdError), + /// 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." From ec0053dfb51cf0913552d67934b1dde359ce94bf Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 4 Sep 2024 15:47:04 +0200 Subject: [PATCH 10/43] Move `InMemoryBufferedSingleSignatureStore` to store module To keep the `buffered_certifier` lean --- .../src/dependency_injection/builder.rs | 15 +- mithril-aggregator/src/lib.rs | 3 +- .../services/certifier/buffered_certifier.rs | 163 +---------------- .../store/buffered_single_signatures_store.rs | 167 ++++++++++++++++++ mithril-aggregator/src/store/mod.rs | 2 + 5 files changed, 183 insertions(+), 167 deletions(-) create mode 100644 mithril-aggregator/src/store/buffered_single_signatures_store.rs diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index 059ed059f97..ee4c852b72f 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -67,17 +67,18 @@ use crate::{ http_server::routes::router, services::{ AggregatorUpkeepService, BufferedCertifierService, CardanoTransactionsImporter, - CertifierService, InMemoryBufferedSingleSignatureStore, MessageService, - MithrilCertifierService, MithrilEpochService, MithrilMessageService, MithrilProverService, - MithrilSignedEntityService, MithrilStakeDistributionService, ProverService, - SignedEntityService, StakeDistributionService, UpkeepService, + CertifierService, MessageService, MithrilCertifierService, MithrilEpochService, + MithrilMessageService, MithrilProverService, MithrilSignedEntityService, + MithrilStakeDistributionService, ProverService, SignedEntityService, + StakeDistributionService, UpkeepService, }, tools::{CExplorerSignerRetriever, GcpFileUploader, GenesisToolsDependency, SignersImporter}, AggregatorConfig, AggregatorRunner, AggregatorRuntime, CertificatePendingStore, CompressedArchiveSnapshotter, Configuration, DependencyContainer, DumbSnapshotUploader, - DumbSnapshotter, LocalSnapshotUploader, MithrilSignerRegisterer, MultiSigner, MultiSignerImpl, - ProtocolParametersStorer, RemoteSnapshotUploader, SnapshotUploader, SnapshotUploaderType, - Snapshotter, SnapshotterCompressionAlgorithm, VerificationKeyStorer, + DumbSnapshotter, InMemoryBufferedSingleSignatureStore, LocalSnapshotUploader, + MithrilSignerRegisterer, MultiSigner, MultiSignerImpl, ProtocolParametersStorer, + RemoteSnapshotUploader, SnapshotUploader, SnapshotUploaderType, Snapshotter, + SnapshotterCompressionAlgorithm, VerificationKeyStorer, }; const SQLITE_FILE: &str = "aggregator.sqlite3"; diff --git a/mithril-aggregator/src/lib.rs b/mithril-aggregator/src/lib.rs index baa5318d921..46fd274f1fd 100644 --- a/mithril-aggregator/src/lib.rs +++ b/mithril-aggregator/src/lib.rs @@ -53,7 +53,8 @@ pub use snapshotter::{ SnapshotterCompressionAlgorithm, }; pub use store::{ - CertificatePendingStore, ProtocolParametersStorer, VerificationKeyStore, VerificationKeyStorer, + CertificatePendingStore, InMemoryBufferedSingleSignatureStore, ProtocolParametersStorer, + VerificationKeyStore, VerificationKeyStorer, }; pub use tools::{ CExplorerSignerRetriever, SignersImporter, SignersImporterPersister, SignersImporterRetriever, diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs index ceddfafcfb4..4066f9ffb23 100644 --- a/mithril-aggregator/src/services/certifier/buffered_certifier.rs +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -1,8 +1,6 @@ use async_trait::async_trait; use slog::{debug, Logger}; -use std::collections::BTreeMap; use std::sync::Arc; -use tokio::sync::RwLock; use mithril_common::entities::{ Certificate, Epoch, ProtocolMessage, SignedEntityType, SignedEntityTypeDiscriminants, @@ -147,80 +145,16 @@ impl CertifierService for BufferedCertifierService { } } -/// An in-memory implementation of [BufferedSingleSignatureStore]. -pub struct InMemoryBufferedSingleSignatureStore { - store: RwLock>>, -} - -#[cfg(test)] -impl InMemoryBufferedSingleSignatureStore { - pub(crate) fn with_data( - initial_data: BTreeMap>, - ) -> Self { - Self { - store: RwLock::new(initial_data), - } - } -} - -impl Default for InMemoryBufferedSingleSignatureStore { - fn default() -> Self { - Self { - store: RwLock::new(BTreeMap::new()), - } - } -} - -#[async_trait] -impl BufferedSingleSignatureStore for InMemoryBufferedSingleSignatureStore { - async fn buffer_signature( - &self, - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, - signature: &SingleSignatures, - ) -> StdResult<()> { - let mut store = self.store.write().await; - let signatures = store - .entry(signed_entity_type_discriminants) - .or_insert_with(Vec::new); - signatures.push(signature.clone()); - Ok(()) - } - - async fn get_buffered_signatures( - &self, - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, - ) -> StdResult> { - let store = self.store.read().await; - Ok(store - .get(&signed_entity_type_discriminants) - .cloned() - .unwrap_or_default()) - } - - async fn remove_buffered_signatures( - &self, - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, - single_signatures: Vec, - ) -> StdResult<()> { - let mut store = self.store.write().await; - - for signature in single_signatures { - if let Some(signatures) = store.get_mut(&signed_entity_type_discriminants) { - signatures.retain(|s| s != &signature); - } - } - - Ok(()) - } -} - #[cfg(test)] mod tests { - use mithril_common::test_utils::fake_data; use mockall::predicate::eq; + use std::collections::BTreeMap; + + use mithril_common::test_utils::fake_data; use crate::services::{CertifierServiceError, MockCertifierService}; use crate::test_tools::TestLogger; + use crate::InMemoryBufferedSingleSignatureStore; use super::*; @@ -233,95 +167,6 @@ mod tests { Arc::new(certifier) } - mod in_memory_buffered_single_signature_store_tests { - use super::*; - - #[tokio::test] - async fn store_and_retrieve_signatures() { - let store = InMemoryBufferedSingleSignatureStore::default(); - - let ctx = SignedEntityTypeDiscriminants::CardanoTransactions; - store - .buffer_signature(ctx, &fake_data::single_signatures(vec![1])) - .await - .unwrap(); - store - .buffer_signature(ctx, &fake_data::single_signatures(vec![2])) - .await - .unwrap(); - - // Different signed entity type to test that the store is able to differentiate between them - let msd = SignedEntityTypeDiscriminants::MithrilStakeDistribution; - store - .buffer_signature(msd, &fake_data::single_signatures(vec![3])) - .await - .unwrap(); - - let buffered_signatures_ctx = store.get_buffered_signatures(ctx).await.unwrap(); - assert_eq!( - vec![ - fake_data::single_signatures(vec![1]), - fake_data::single_signatures(vec![2]) - ], - buffered_signatures_ctx - ); - - let buffered_signatures_msd = store.get_buffered_signatures(msd).await.unwrap(); - assert_eq!( - vec![fake_data::single_signatures(vec![3])], - buffered_signatures_msd - ); - } - - #[tokio::test] - async fn remove_buffered_signatures() { - let store = InMemoryBufferedSingleSignatureStore::with_data(BTreeMap::from([ - ( - SignedEntityTypeDiscriminants::MithrilStakeDistribution, - vec![ - fake_data::single_signatures(vec![1]), - fake_data::single_signatures(vec![2]), - fake_data::single_signatures(vec![3]), - ], - ), - ( - SignedEntityTypeDiscriminants::CardanoTransactions, - vec![fake_data::single_signatures(vec![10])], - ), - ])); - - store - .remove_buffered_signatures( - SignedEntityTypeDiscriminants::MithrilStakeDistribution, - vec![ - fake_data::single_signatures(vec![1]), - fake_data::single_signatures(vec![3]), - ], - ) - .await - .unwrap(); - - let remaining_msd_sigs = store - .get_buffered_signatures(SignedEntityTypeDiscriminants::MithrilStakeDistribution) - .await - .unwrap(); - assert_eq!( - vec![fake_data::single_signatures(vec![2])], - remaining_msd_sigs - ); - - let remaining_ctx_sigs = store - .get_buffered_signatures(SignedEntityTypeDiscriminants::CardanoTransactions) - .await - .unwrap(); - assert_eq!( - vec![fake_data::single_signatures(vec![10])], - remaining_ctx_sigs, - "CardanoTransactions signatures should have been left untouched" - ); - } - } - #[tokio::test] async fn buffer_signature_if_decorated_certifier_as_no_opened_message() { let store = Arc::new(InMemoryBufferedSingleSignatureStore::default()); diff --git a/mithril-aggregator/src/store/buffered_single_signatures_store.rs b/mithril-aggregator/src/store/buffered_single_signatures_store.rs new file mode 100644 index 00000000000..7cfdca83423 --- /dev/null +++ b/mithril-aggregator/src/store/buffered_single_signatures_store.rs @@ -0,0 +1,167 @@ +use async_trait::async_trait; +use std::collections::BTreeMap; +use tokio::sync::RwLock; + +use mithril_common::entities::{SignedEntityTypeDiscriminants, SingleSignatures}; +use mithril_common::StdResult; + +use crate::services::BufferedSingleSignatureStore; + +/// An in-memory implementation of [BufferedSingleSignatureStore]. +pub struct InMemoryBufferedSingleSignatureStore { + store: RwLock>>, +} + +#[cfg(test)] +impl InMemoryBufferedSingleSignatureStore { + pub(crate) fn with_data( + initial_data: BTreeMap>, + ) -> Self { + Self { + store: RwLock::new(initial_data), + } + } +} + +impl Default for InMemoryBufferedSingleSignatureStore { + fn default() -> Self { + Self { + store: RwLock::new(BTreeMap::new()), + } + } +} + +#[async_trait] +impl BufferedSingleSignatureStore for InMemoryBufferedSingleSignatureStore { + async fn buffer_signature( + &self, + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + signature: &SingleSignatures, + ) -> StdResult<()> { + let mut store = self.store.write().await; + let signatures = store + .entry(signed_entity_type_discriminants) + .or_insert_with(Vec::new); + signatures.push(signature.clone()); + Ok(()) + } + + async fn get_buffered_signatures( + &self, + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + ) -> StdResult> { + let store = self.store.read().await; + Ok(store + .get(&signed_entity_type_discriminants) + .cloned() + .unwrap_or_default()) + } + + async fn remove_buffered_signatures( + &self, + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + single_signatures: Vec, + ) -> StdResult<()> { + let mut store = self.store.write().await; + + for signature in single_signatures { + if let Some(signatures) = store.get_mut(&signed_entity_type_discriminants) { + signatures.retain(|s| s != &signature); + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use mithril_common::test_utils::fake_data; + + use super::*; + + #[tokio::test] + async fn store_and_retrieve_signatures() { + let store = InMemoryBufferedSingleSignatureStore::default(); + + let ctx = SignedEntityTypeDiscriminants::CardanoTransactions; + store + .buffer_signature(ctx, &fake_data::single_signatures(vec![1])) + .await + .unwrap(); + store + .buffer_signature(ctx, &fake_data::single_signatures(vec![2])) + .await + .unwrap(); + + // Different signed entity type to test that the store is able to differentiate between them + let msd = SignedEntityTypeDiscriminants::MithrilStakeDistribution; + store + .buffer_signature(msd, &fake_data::single_signatures(vec![3])) + .await + .unwrap(); + + let buffered_signatures_ctx = store.get_buffered_signatures(ctx).await.unwrap(); + assert_eq!( + vec![ + fake_data::single_signatures(vec![1]), + fake_data::single_signatures(vec![2]) + ], + buffered_signatures_ctx + ); + + let buffered_signatures_msd = store.get_buffered_signatures(msd).await.unwrap(); + assert_eq!( + vec![fake_data::single_signatures(vec![3])], + buffered_signatures_msd + ); + } + + #[tokio::test] + async fn remove_buffered_signatures() { + let store = InMemoryBufferedSingleSignatureStore::with_data(BTreeMap::from([ + ( + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + vec![ + fake_data::single_signatures(vec![1]), + fake_data::single_signatures(vec![2]), + fake_data::single_signatures(vec![3]), + ], + ), + ( + SignedEntityTypeDiscriminants::CardanoTransactions, + vec![fake_data::single_signatures(vec![10])], + ), + ])); + + store + .remove_buffered_signatures( + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + vec![ + fake_data::single_signatures(vec![1]), + fake_data::single_signatures(vec![3]), + ], + ) + .await + .unwrap(); + + let remaining_msd_sigs = store + .get_buffered_signatures(SignedEntityTypeDiscriminants::MithrilStakeDistribution) + .await + .unwrap(); + assert_eq!( + vec![fake_data::single_signatures(vec![2])], + remaining_msd_sigs + ); + + let remaining_ctx_sigs = store + .get_buffered_signatures(SignedEntityTypeDiscriminants::CardanoTransactions) + .await + .unwrap(); + assert_eq!( + vec![fake_data::single_signatures(vec![10])], + remaining_ctx_sigs, + "CardanoTransactions signatures should have been left untouched" + ); + } +} diff --git a/mithril-aggregator/src/store/mod.rs b/mithril-aggregator/src/store/mod.rs index bdf83e93323..3b3af0ce3f1 100644 --- a/mithril-aggregator/src/store/mod.rs +++ b/mithril-aggregator/src/store/mod.rs @@ -1,7 +1,9 @@ +mod buffered_single_signatures_store; mod pending_certificate_store; mod protocol_parameters_store; mod verification_key_store; +pub use buffered_single_signatures_store::InMemoryBufferedSingleSignatureStore; pub use pending_certificate_store::CertificatePendingStore; pub use protocol_parameters_store::ProtocolParametersStorer; pub use verification_key_store::{VerificationKeyStore, VerificationKeyStorer}; From 9ad80bdece842006e14e20bbd2fc2fdac8fe0ba0 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 4 Sep 2024 16:54:17 +0200 Subject: [PATCH 11/43] Don't fail but log when transferring buffered signatures to new message --- .../services/certifier/buffered_certifier.rs | 152 +++++++++++++++--- 1 file changed, 134 insertions(+), 18 deletions(-) diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs index 4066f9ffb23..cb78689cb51 100644 --- a/mithril-aggregator/src/services/certifier/buffered_certifier.rs +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use slog::{debug, Logger}; +use slog::{debug, warn, Logger}; use std::sync::Arc; use mithril_common::entities::{ @@ -35,6 +35,29 @@ impl BufferedCertifierService { logger, } } + + async fn try_register_buffered_signatures_to_current_open_message( + &self, + signed_entity_type: &SignedEntityType, + ) -> StdResult<()> { + let discriminant: SignedEntityTypeDiscriminants = signed_entity_type.into(); + let buffered_signatures = self + .buffered_single_signature_store + .get_buffered_signatures(discriminant) + .await?; + + for signature in &buffered_signatures { + self.certifier_service + .register_single_signature(signed_entity_type, signature) + .await?; + } + + self.buffered_single_signature_store + .remove_buffered_signatures(discriminant, buffered_signatures) + .await?; + + Ok(()) + } } #[async_trait] @@ -63,6 +86,8 @@ impl CertifierService for BufferedCertifierService { "party_id" => &signature.party_id ); + // validate signature + self.buffered_single_signature_store .buffer_signature(signed_entity_type.into(), signature) .await?; @@ -78,28 +103,30 @@ impl CertifierService for BufferedCertifierService { signed_entity_type: &SignedEntityType, protocol_message: &ProtocolMessage, ) -> StdResult { + // IMPORTANT: this method should not fail if the open message creation succeeds. + // else: + // 1 - state machine won't create a pending certificate for the signed entity type + // 2 - Without a pending certificate, the signers won't send their signatures + // 3 - state machine will retry the transition to signing and, since an open message was + // opened for the signed entity type, it will try the next on the list. + // 4 - since the state machine never was in signing it will never try to aggregate + // signatures for the signed entity type + let creation_result = self .certifier_service .create_open_message(signed_entity_type, protocol_message) .await; - // TODO: does the error in this block should make this method fails or should they just - // be logged? - if let Ok(_open_message) = &creation_result { - let buffered_signatures = self - .buffered_single_signature_store - .get_buffered_signatures(signed_entity_type.into()) - .await?; - - for signature in &buffered_signatures { - self.certifier_service - .register_single_signature(signed_entity_type, signature) - .await?; + if creation_result.is_ok() { + if let Err(error) = self + .try_register_buffered_signatures_to_current_open_message(signed_entity_type) + .await + { + warn!(self.logger, "Failed to register buffered signatures to the new open message"; + "signed_entity_type" => ?signed_entity_type, + "error" => ?error + ); } - - self.buffered_single_signature_store - .remove_buffered_signatures(signed_entity_type.into(), buffered_signatures) - .await?; } creation_result @@ -147,12 +174,15 @@ impl CertifierService for BufferedCertifierService { #[cfg(test)] mod tests { + use anyhow::anyhow; use mockall::predicate::eq; use std::collections::BTreeMap; use mithril_common::test_utils::fake_data; - use crate::services::{CertifierServiceError, MockCertifierService}; + use crate::services::{ + CertifierServiceError, MockBufferedSingleSignatureStore, MockCertifierService, + }; use crate::test_tools::TestLogger; use crate::InMemoryBufferedSingleSignatureStore; @@ -167,6 +197,15 @@ mod tests { Arc::new(certifier) } + fn mock_store(store_mock_config: F) -> Arc + where + F: FnOnce(&mut MockBufferedSingleSignatureStore), + { + let mut store = MockBufferedSingleSignatureStore::new(); + store_mock_config(&mut store); + Arc::new(store) + } + #[tokio::test] async fn buffer_signature_if_decorated_certifier_as_no_opened_message() { let store = Arc::new(InMemoryBufferedSingleSignatureStore::default()); @@ -257,4 +296,81 @@ mod tests { .unwrap(); assert!(remaining_sigs.is_empty()); } + + mod when_failing_to_transfer_buffered_signature_to_new_open_message { + use super::*; + + async fn run_scenario( + certifier_mock_config: impl FnOnce(&mut MockCertifierService), + store_mock_config: impl FnOnce(&mut MockBufferedSingleSignatureStore), + ) { + let store = mock_store(store_mock_config); + let certifier = BufferedCertifierService::new( + mock_certifier(certifier_mock_config), + store, + TestLogger::stdout(), + ); + + certifier + .create_open_message( + &SignedEntityType::MithrilStakeDistribution(Epoch(5)), + &ProtocolMessage::new(), + ) + .await + .expect("Transferring buffered signatures to new open message should not fail"); + } + + #[tokio::test] + async fn do_not_return_an_error_if_getting_buffer_signatures_fail() { + run_scenario( + |mock| { + mock.expect_create_open_message() + .returning(|_, _| Ok(OpenMessage::dummy())); + mock.expect_register_single_signature() + .returning(|_, _| Ok(())); + }, + |mock| { + mock.expect_get_buffered_signatures() + .returning(|_| Err(anyhow!("get_buffered_signatures error"))); + }, + ) + .await; + } + + #[tokio::test] + async fn do_not_return_an_error_if_getting_registering_signature_fail() { + run_scenario( + |mock| { + mock.expect_create_open_message() + .returning(|_, _| Ok(OpenMessage::dummy())); + mock.expect_register_single_signature() + .returning(|_, _| Err(anyhow!("register_single_signature error"))); + }, + |mock| { + mock.expect_get_buffered_signatures() + .returning(|_| Ok(vec![fake_data::single_signatures(vec![1])])); + }, + ) + .await; + } + + #[tokio::test] + async fn do_not_return_an_error_if_removing_buffered_signatures_fail() { + run_scenario( + |mock| { + mock.expect_create_open_message() + .returning(|_, _| Ok(OpenMessage::dummy())); + mock.expect_register_single_signature() + .returning(|_, _| Ok(())); + }, + |mock| { + mock.expect_get_buffered_signatures() + .returning(|_| Ok(vec![fake_data::single_signatures(vec![1])])); + mock.expect_remove_buffered_signatures() + .returning(|_, _| Err(anyhow!("remove_buffered_signatures error"))); + }, + ) + .await; + } + } } From eeef28099f212670eb7ed6cd50442e3cc52aa397 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:00:41 +0200 Subject: [PATCH 12/43] Scaffold a sqlite BufferedSingleSignatureStore With the `BufferedSingleSignatureRepository` and record. And implement the `get_buffered_signatures` on the repository. Insert/delete op will come after. --- mithril-aggregator/src/database/migration.rs | 18 ++ .../get_buffered_single_signature.rs | 123 +++++++++++ ...rt_or_replace_buffered_single_signature.rs | 95 ++++++++ .../query/buffered_single_signature/mod.rs | 5 + mithril-aggregator/src/database/query/mod.rs | 2 + .../buffered_single_signature_record.rs | 202 ++++++++++++++++++ mithril-aggregator/src/database/record/mod.rs | 2 + .../buffered_single_signature_repository.rs | 175 +++++++++++++++ .../src/database/repository/mod.rs | 2 + .../src/database/test_helper.rs | 69 +++++- 10 files changed, 690 insertions(+), 3 deletions(-) create mode 100644 mithril-aggregator/src/database/query/buffered_single_signature/get_buffered_single_signature.rs create mode 100644 mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs create mode 100644 mithril-aggregator/src/database/query/buffered_single_signature/mod.rs create mode 100644 mithril-aggregator/src/database/record/buffered_single_signature_record.rs create mode 100644 mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs diff --git a/mithril-aggregator/src/database/migration.rs b/mithril-aggregator/src/database/migration.rs index 3e17a951ee1..b8709621b6d 100644 --- a/mithril-aggregator/src/database/migration.rs +++ b/mithril-aggregator/src/database/migration.rs @@ -758,6 +758,24 @@ pragma foreign_keys=true; 26, r#" create unique index signed_entity_unique_index on signed_entity(signed_entity_type_id, beacon); +"#, + ), + // Migration 27 + SqlMigration::new( + 27, + r#" +create table buffered_single_signature ( + signed_entity_type_id integer not null, + epoch integer not null, + party_id text not null, + lottery_indexes json not null, + signature text not null, + created_at text not null, + primary key (signed_entity_type_id, epoch, party_id) +); + +create index buffered_single_signature_signed_entity_type_id on buffered_single_signature(signed_entity_type_id); +create index buffered_single_signature_party_id_index on buffered_single_signature(party_id); "#, ), ] diff --git a/mithril-aggregator/src/database/query/buffered_single_signature/get_buffered_single_signature.rs b/mithril-aggregator/src/database/query/buffered_single_signature/get_buffered_single_signature.rs new file mode 100644 index 00000000000..ea1f707ddfa --- /dev/null +++ b/mithril-aggregator/src/database/query/buffered_single_signature/get_buffered_single_signature.rs @@ -0,0 +1,123 @@ +use sqlite::Value; + +#[cfg(test)] +use mithril_common::entities::Epoch; +use mithril_common::entities::SignedEntityTypeDiscriminants; +use mithril_persistence::sqlite::{Query, SourceAlias, SqLiteEntity, WhereCondition}; + +use crate::database::record::BufferedSingleSignatureRecord; + +/// Simple queries to retrieve [BufferedSingleSignatureRecord] from the sqlite database. +pub struct GetBufferedSingleSignatureQuery { + condition: WhereCondition, +} + +impl GetBufferedSingleSignatureQuery { + #[cfg(test)] + pub(crate) fn all() -> Self { + Self { + condition: WhereCondition::default(), + } + } + + pub fn by_discriminant( + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + ) -> Self { + Self { + condition: WhereCondition::new( + "signed_entity_type_id = ?*", + vec![Value::Integer( + signed_entity_type_discriminants.index() as i64 + )], + ), + } + } +} + +impl Query for GetBufferedSingleSignatureQuery { + type Entity = BufferedSingleSignatureRecord; + + fn filters(&self) -> WhereCondition { + self.condition.clone() + } + + fn get_definition(&self, condition: &str) -> String { + let aliases = SourceAlias::new(&[("{:buffered_single_signature:}", "b")]); + let projection = Self::Entity::get_projection().expand(aliases); + format!("select {projection} from buffered_single_signature as b where {condition} order by ROWID desc") + } +} + +#[cfg(test)] +mod tests { + use mithril_common::entities::SignedEntityTypeDiscriminants::{ + CardanoImmutableFilesFull, CardanoTransactions, MithrilStakeDistribution, + }; + use mithril_persistence::sqlite::ConnectionExtensions; + + use crate::database::test_helper::{insert_buffered_single_signatures, main_db_connection}; + + use super::*; + + #[test] + fn test_get_all() { + let connection = main_db_connection().unwrap(); + let records = BufferedSingleSignatureRecord::fakes(&[ + ("party1", MithrilStakeDistribution), + ("party2", CardanoTransactions), + ("party3", MithrilStakeDistribution), + ]); + insert_buffered_single_signatures(&connection, records.clone()).unwrap(); + + let stored_records: Vec = connection + .fetch_collect(GetBufferedSingleSignatureQuery::all()) + .unwrap(); + + assert_eq!( + records.into_iter().rev().collect::>(), + stored_records + ); + } + + #[test] + fn test_get_buffered_single_signature_records_by_discriminant() { + let connection = main_db_connection().unwrap(); + let msd_records = BufferedSingleSignatureRecord::fakes(&[ + ("party1", MithrilStakeDistribution), + ("party2", MithrilStakeDistribution), + ]); + let ctx_records = BufferedSingleSignatureRecord::fakes(&[("party3", CardanoTransactions)]); + insert_buffered_single_signatures( + &connection, + [msd_records.clone(), ctx_records.clone()].concat(), + ) + .unwrap(); + + let stored_msd_records: Vec = connection + .fetch_collect(GetBufferedSingleSignatureQuery::by_discriminant( + MithrilStakeDistribution, + )) + .unwrap(); + assert_eq!( + msd_records.into_iter().rev().collect::>(), + stored_msd_records + ); + + let stored_ctx_records: Vec = connection + .fetch_collect(GetBufferedSingleSignatureQuery::by_discriminant( + CardanoTransactions, + )) + .unwrap(); + assert_eq!( + ctx_records.into_iter().rev().collect::>(), + stored_ctx_records + ); + + let cursor = connection + .fetch(GetBufferedSingleSignatureQuery::by_discriminant( + CardanoImmutableFilesFull, + )) + .unwrap(); + assert_eq!(0, cursor.count()); + } +} diff --git a/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs b/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs new file mode 100644 index 00000000000..20cc8d5d186 --- /dev/null +++ b/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs @@ -0,0 +1,95 @@ +use sqlite::Value; + +use mithril_persistence::sqlite::{Query, SourceAlias, SqLiteEntity, WhereCondition}; + +use crate::database::record::BufferedSingleSignatureRecord; + +/// Query to insert or replace [BufferedSingleSignatureRecord] in the sqlite database +pub struct InsertOrReplaceBufferedSingleSignatureRecordQuery { + condition: WhereCondition, +} + +impl InsertOrReplaceBufferedSingleSignatureRecordQuery { + pub fn one(record: BufferedSingleSignatureRecord) -> Self { + let condition = + WhereCondition::new( + "(party_id, epoch, signed_entity_type_id, lottery_indexes, signature, created_at) values (?*, ?*, ?*, ?*, ?*, ?*)", + vec![ + Value::String(record.party_id), + Value::Integer( + record.epoch.try_into().unwrap(), + ), + Value::Integer(record.signed_entity_type_id.index() as i64), + Value::String(serde_json::to_string(&record.lottery_indexes).unwrap()), + Value::String(record.signature), + Value::String(record.created_at.to_rfc3339()), + ], + ); + + Self { condition } + } +} + +impl Query for InsertOrReplaceBufferedSingleSignatureRecordQuery { + type Entity = BufferedSingleSignatureRecord; + + fn filters(&self) -> WhereCondition { + self.condition.clone() + } + + 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(&[( + "{:buffered_single_signature:}", + "buffered_single_signature", + )])); + + format!( + "insert or replace into buffered_single_signature {condition} returning {projection}" + ) + } +} + +#[cfg(test)] +mod tests { + use mithril_persistence::sqlite::ConnectionExtensions; + + use crate::database::test_helper::{ + main_db_connection, setup_buffered_single_signature_records, + }; + + use super::*; + + #[test] + fn test_insert_or_replace_single_signature_record() { + let records = setup_buffered_single_signature_records(1, 3); + + let connection = main_db_connection().unwrap(); + + for buffered_signature_record in records.clone() { + let single_signature_record_saved = connection + .fetch_first(InsertOrReplaceBufferedSingleSignatureRecordQuery::one( + buffered_signature_record.clone(), + )) + .unwrap(); + assert_eq!( + Some(buffered_signature_record), + single_signature_record_saved + ); + } + + for mut buffered_signature_record in records { + buffered_signature_record.lottery_indexes.push(5); + let single_signature_record_saved = connection + .fetch_first(InsertOrReplaceBufferedSingleSignatureRecordQuery::one( + buffered_signature_record.clone(), + )) + .unwrap(); + assert_eq!( + Some(buffered_signature_record), + single_signature_record_saved + ); + } + } +} diff --git a/mithril-aggregator/src/database/query/buffered_single_signature/mod.rs b/mithril-aggregator/src/database/query/buffered_single_signature/mod.rs new file mode 100644 index 00000000000..4108de685bc --- /dev/null +++ b/mithril-aggregator/src/database/query/buffered_single_signature/mod.rs @@ -0,0 +1,5 @@ +mod get_buffered_single_signature; +mod insert_or_replace_buffered_single_signature; + +pub use get_buffered_single_signature::*; +pub use insert_or_replace_buffered_single_signature::*; diff --git a/mithril-aggregator/src/database/query/mod.rs b/mithril-aggregator/src/database/query/mod.rs index 9eab281ce42..c5010c32ac1 100644 --- a/mithril-aggregator/src/database/query/mod.rs +++ b/mithril-aggregator/src/database/query/mod.rs @@ -1,4 +1,5 @@ //! Aggregator related database queries +mod buffered_single_signature; mod certificate; mod epoch_setting; mod open_message; @@ -8,6 +9,7 @@ mod signer_registration; mod single_signature; mod stake_pool; +pub use buffered_single_signature::*; pub use certificate::*; pub use epoch_setting::*; pub use open_message::*; diff --git a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs new file mode 100644 index 00000000000..dbc5c0a6d2f --- /dev/null +++ b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs @@ -0,0 +1,202 @@ +use chrono::{DateTime, Utc}; + +use mithril_common::entities::{ + Epoch, HexEncodedSingleSignature, LotteryIndex, SignedEntityTypeDiscriminants, SingleSignatures, +}; +use mithril_common::{StdError, StdResult}; +use mithril_persistence::sqlite::{HydrationError, Projection, SqLiteEntity}; + +/// `BufferedSingleSignatureRecord` record is the representation of a buffered single_signature +/// that may be used for upcoming open messages. +#[derive(Debug, PartialEq, Clone)] +pub struct BufferedSingleSignatureRecord { + /// Party id. + pub party_id: String, + + /// Epoch at which the buffered single signature was created + pub epoch: Epoch, + + /// Signed entity type discriminant. + pub signed_entity_type_id: SignedEntityTypeDiscriminants, + + /// Lottery indexes + pub lottery_indexes: Vec, + + /// The STM single signature of the message + pub signature: HexEncodedSingleSignature, + + /// Date and time when the single_signature was created + pub created_at: DateTime, +} + +impl BufferedSingleSignatureRecord { + pub(crate) fn try_from_single_signatures( + other: &SingleSignatures, + signed_entity_id: SignedEntityTypeDiscriminants, + epoch: Epoch, + ) -> StdResult { + let record = BufferedSingleSignatureRecord { + signed_entity_type_id: signed_entity_id, + party_id: other.party_id.to_owned(), + epoch, + lottery_indexes: other.won_indexes.to_owned(), + signature: other.signature.to_json_hex()?, + created_at: Utc::now(), + }; + + Ok(record) + } +} + +#[cfg(test)] +impl BufferedSingleSignatureRecord { + pub(crate) fn fake>( + party_id: T, + discriminants: SignedEntityTypeDiscriminants, + ) -> Self { + use mithril_common::test_utils::fake_keys; + + let signature = fake_keys::single_signature()[0].try_into().unwrap(); + Self::try_from_single_signatures( + &SingleSignatures { + party_id: party_id.into(), + signature, + won_indexes: vec![10, 15], + signed_message: None, + }, + discriminants, + Epoch(12), + ) + .unwrap() + } + + pub(crate) fn fakes + Clone>( + input: &[(T, SignedEntityTypeDiscriminants)], + ) -> Vec { + input + .iter() + .map(|(party_id, discriminants)| Self::fake(party_id.clone(), *discriminants)) + .collect() + } + + /// Returns a copy of the record with the date replaced by "1st of January 1970". + pub(crate) fn with_stripped_date(&self) -> Self { + Self { + created_at: DateTime::::default(), + ..self.clone() + } + } +} + +impl TryFrom for SingleSignatures { + type Error = StdError; + + fn try_from(value: BufferedSingleSignatureRecord) -> Result { + let signatures = SingleSignatures { + party_id: value.party_id, + won_indexes: value.lottery_indexes, + signature: value.signature.try_into()?, + signed_message: None, + }; + + Ok(signatures) + } +} + +impl SqLiteEntity for BufferedSingleSignatureRecord { + fn hydrate(row: sqlite::Row) -> Result + where + Self: Sized, + { + let party_id = row.read::<&str, _>(0).to_string(); + let epoch = row.read::(1); + let signed_entity_type_id = usize::try_from(row.read::(2)).map_err(|e| { + panic!("Integer field signed_entity_type_id cannot be turned into usize: {e}") + })?; + let lottery_indexes_str = row.read::<&str, _>(3); + let signature = row.read::<&str, _>(4).to_string(); + let created_at = row.read::<&str, _>(5); + + let record = Self { + party_id, + epoch: Epoch( + epoch.try_into().map_err(|e| { + HydrationError::InvalidData(format!( + "Could not cast i64 ({epoch}) to u64. Error: '{e}'" + )) + })?, + ), + signed_entity_type_id: SignedEntityTypeDiscriminants::from_id(signed_entity_type_id).map_err( + |e| { + HydrationError::InvalidData(format!( + "Could not turn i64 ({signed_entity_type_id}) to SignedEntityTypeDiscriminants. Error: '{e}'" + )) + }, + )?, + lottery_indexes: serde_json::from_str(lottery_indexes_str).map_err(|e| { + HydrationError::InvalidData(format!( + "Could not turn string '{lottery_indexes_str}' to Vec. Error: {e}" + )) + })?, + signature, + created_at: DateTime::parse_from_rfc3339(created_at) + .map_err(|e| { + HydrationError::InvalidData(format!( + "Could not turn string '{created_at}' to rfc3339 Datetime. Error: {e}" + )) + })? + .with_timezone(&Utc), + }; + + Ok(record) + } + + fn get_projection() -> Projection { + let mut projection = Projection::default(); + projection.add_field("party_id", "{:buffered_single_signature:}.party_id", "text"); + projection.add_field("epoch", "{:buffered_single_signature:}.epoch", "integer"); + projection.add_field( + "signed_entity_type_id", + "{:buffered_single_signature:}.signed_entity_type_id", + "integer", + ); + projection.add_field( + "lottery_indexes", + "{:buffered_single_signature:}.lottery_indexes", + "text", + ); + projection.add_field( + "signature", + "{:buffered_single_signature:}.signature", + "text", + ); + projection.add_field( + "created_at", + "{:buffered_single_signature:}.created_at", + "text", + ); + + projection + } +} + +#[cfg(test)] +mod tests { + use mithril_common::test_utils::fake_data; + + use super::*; + + #[test] + fn test_convert_single_signatures() { + let single_signature = fake_data::single_signatures(vec![1, 3, 4, 6, 7, 9]); + let single_signature_record = BufferedSingleSignatureRecord::try_from_single_signatures( + &single_signature, + SignedEntityTypeDiscriminants::CardanoTransactions, + Epoch(1), + ) + .unwrap(); + let single_signature_returned = single_signature_record.try_into().unwrap(); + + assert_eq!(single_signature, single_signature_returned); + } +} diff --git a/mithril-aggregator/src/database/record/mod.rs b/mithril-aggregator/src/database/record/mod.rs index 3d3b78d8a97..7f65aedfcad 100644 --- a/mithril-aggregator/src/database/record/mod.rs +++ b/mithril-aggregator/src/database/record/mod.rs @@ -1,5 +1,6 @@ //! Aggregator related database records +mod buffered_single_signature_record; mod certificate; mod epoch_setting; mod open_message; @@ -10,6 +11,7 @@ mod signer_registration; mod single_signature; mod stake_pool; +pub use buffered_single_signature_record::*; pub use certificate::*; pub use epoch_setting::*; pub use open_message::*; diff --git a/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs b/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs new file mode 100644 index 00000000000..1f017a01f3a --- /dev/null +++ b/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs @@ -0,0 +1,175 @@ +use async_trait::async_trait; +use std::sync::Arc; + +use mithril_common::entities::{SignedEntityTypeDiscriminants, SingleSignatures}; +use mithril_common::{StdError, StdResult}; +use mithril_persistence::sqlite::{ConnectionExtensions, SqliteConnection}; + +use crate::database::query::GetBufferedSingleSignatureQuery; +use crate::database::record::BufferedSingleSignatureRecord; +use crate::services::BufferedSingleSignatureStore; + +/// An implementation of [BufferedSingleSignatureStore] that uses a SQLite database. +pub struct BufferedSingleSignatureRepository { + connection: Arc, +} + +impl BufferedSingleSignatureRepository { + /// Creates a new [BufferedSingleSignatureRepository] instance. + pub fn new(connection_pool: Arc) -> Self { + Self { + connection: connection_pool, + } + } + + #[cfg(test)] + fn get_all(&self) -> StdResult> { + self.connection + .fetch_collect(GetBufferedSingleSignatureQuery::all()) + } + + fn get_by_discriminant( + &self, + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + ) -> StdResult> + where + T: TryFrom, + StdError: From, + { + let records: Vec = + self.connection + .fetch_collect(GetBufferedSingleSignatureQuery::by_discriminant( + signed_entity_type_discriminants, + ))?; + + let mut entities: Vec = Vec::with_capacity(records.len()); + for record in records { + entities.push(record.try_into()?); + } + + Ok(entities) + } +} + +#[async_trait] +impl BufferedSingleSignatureStore for BufferedSingleSignatureRepository { + async fn buffer_signature( + &self, + _signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + _signature: &SingleSignatures, + ) -> StdResult<()> { + todo!() + } + + async fn get_buffered_signatures( + &self, + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + ) -> StdResult> { + self.get_by_discriminant(signed_entity_type_discriminants) + } + + async fn remove_buffered_signatures( + &self, + _signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + _single_signatures: Vec, + ) -> StdResult<()> { + todo!() + } +} + +#[cfg(test)] +mod tests { + use mithril_common::entities::{ + Epoch, LotteryIndex, + SignedEntityTypeDiscriminants::{CardanoTransactions, MithrilStakeDistribution}, + }; + use mithril_common::test_utils::fake_data; + + use crate::database::record::BufferedSingleSignatureRecord; + use crate::database::test_helper::{insert_buffered_single_signatures, main_db_connection}; + + use super::*; + + fn strip_date(records: &[BufferedSingleSignatureRecord]) -> Vec { + records + .iter() + .map(BufferedSingleSignatureRecord::with_stripped_date) + .collect::>() + } + + fn fake_record( + discriminants: SignedEntityTypeDiscriminants, + signature_won_indexes: Vec, + ) -> BufferedSingleSignatureRecord { + BufferedSingleSignatureRecord::try_from_single_signatures( + &fake_data::single_signatures(signature_won_indexes), + discriminants, + Epoch(12), + ) + .unwrap() + } + + #[test] + fn retrieve_all() { + let connection = main_db_connection().unwrap(); + insert_buffered_single_signatures( + &connection, + BufferedSingleSignatureRecord::fakes(&[ + ("party1", CardanoTransactions), + ("party2", CardanoTransactions), + ("party3", MithrilStakeDistribution), + ]), + ) + .unwrap(); + + let store = BufferedSingleSignatureRepository::new(Arc::new(connection)); + + let buffered_signatures_ctx = store.get_all().unwrap(); + assert_eq!( + strip_date(&BufferedSingleSignatureRecord::fakes(&[ + ("party3", MithrilStakeDistribution), + ("party2", CardanoTransactions), + ("party1", CardanoTransactions), + ])), + strip_date(&buffered_signatures_ctx) + ); + } + + #[tokio::test] + async fn retrieve_signatures_by_discriminant() { + let connection = main_db_connection().unwrap(); + insert_buffered_single_signatures( + &connection, + BufferedSingleSignatureRecord::fakes(&[ + ("party1", CardanoTransactions), + ("party2", CardanoTransactions), + ("party3", MithrilStakeDistribution), + ]), + ) + .unwrap(); + + let store = BufferedSingleSignatureRepository::new(Arc::new(connection)); + + let buffered_signatures_ctx = store + .get_by_discriminant::(CardanoTransactions) + .unwrap(); + assert_eq!( + strip_date(&BufferedSingleSignatureRecord::fakes(&[ + ("party2", CardanoTransactions), + ("party1", CardanoTransactions), + ])), + strip_date(&buffered_signatures_ctx) + ); + + let buffered_signatures_msd = store + .get_by_discriminant::(MithrilStakeDistribution) + .unwrap(); + assert_eq!( + strip_date(&BufferedSingleSignatureRecord::fakes(&[( + "party3", + MithrilStakeDistribution + ),])), + strip_date(&buffered_signatures_msd) + ); + } +} diff --git a/mithril-aggregator/src/database/repository/mod.rs b/mithril-aggregator/src/database/repository/mod.rs index ad443a26d3b..6ba624a92ac 100644 --- a/mithril-aggregator/src/database/repository/mod.rs +++ b/mithril-aggregator/src/database/repository/mod.rs @@ -1,4 +1,5 @@ //! Aggregator related database repositories +mod buffered_single_signature_repository; mod cardano_transaction_repository; mod certificate_repository; mod epoch_setting_store; @@ -9,6 +10,7 @@ mod signer_store; mod single_signature_repository; mod stake_pool_store; +pub use buffered_single_signature_repository::*; pub use certificate_repository::*; pub use epoch_setting_store::*; pub use open_message_repository::*; diff --git a/mithril-aggregator/src/database/test_helper.rs b/mithril-aggregator/src/database/test_helper.rs index ba23fb96633..814289d2d70 100644 --- a/mithril-aggregator/src/database/test_helper.rs +++ b/mithril-aggregator/src/database/test_helper.rs @@ -3,7 +3,9 @@ use sqlite::{ConnectionThreadSafe, Value}; use std::path::Path; use uuid::Uuid; -use mithril_common::entities::{ProtocolParameters, SignerWithStake}; +use mithril_common::entities::{ + ProtocolParameters, SignedEntityTypeDiscriminants, SignerWithStake, +}; use mithril_common::{entities::Epoch, test_utils::fake_keys, StdError, StdResult}; use mithril_persistence::sqlite::{ ConnectionBuilder, ConnectionExtensions, ConnectionOptions, Query, SqliteConnection, @@ -11,12 +13,13 @@ use mithril_persistence::sqlite::{ use crate::database::query::{ ImportSignerRecordQuery, InsertCertificateRecordQuery, + InsertOrReplaceBufferedSingleSignatureRecordQuery, InsertOrReplaceSignerRegistrationRecordQuery, InsertOrReplaceStakePoolQuery, InsertSignedEntityRecordQuery, UpdateEpochSettingQuery, UpdateSingleSignatureRecordQuery, }; use crate::database::record::{ - CertificateRecord, SignedEntityRecord, SignerRecord, SignerRegistrationRecord, - SingleSignatureRecord, + BufferedSingleSignatureRecord, CertificateRecord, SignedEntityRecord, SignerRecord, + SignerRegistrationRecord, SingleSignatureRecord, }; /// In-memory sqlite database without foreign key support with migrations applied @@ -92,6 +95,27 @@ pub fn setup_single_signature_records( single_signature_records } +pub fn setup_buffered_single_signature_records( + total_epoch: u64, + total_signer: u64, +) -> Vec { + let mut single_signature_records = Vec::new(); + for epoch in 1..=total_epoch { + for signer_idx in 1..=total_signer { + let single_signature_id = epoch * signer_idx; + single_signature_records.push(BufferedSingleSignatureRecord { + signed_entity_type_id: SignedEntityTypeDiscriminants::CardanoTransactions, + party_id: format!("signer-{signer_idx}"), + epoch: Epoch(epoch), + lottery_indexes: (1..=single_signature_id).collect(), + signature: fake_keys::single_signature()[3].to_string(), + created_at: Utc::now(), + }); + } + } + single_signature_records +} + pub fn insert_single_signatures_in_db( connection: &SqliteConnection, single_signature_records: Vec, @@ -141,6 +165,45 @@ pub fn insert_single_signatures_in_db( Ok(()) } +pub fn insert_buffered_single_signatures( + connection: &SqliteConnection, + buffered_signature_records: Vec, +) -> StdResult<()> { + if buffered_signature_records.is_empty() { + return Ok(()); + } + + let query = { + // leverage the expanded parameter from this query which is unit + // tested on its own above. + let (sql_values, _) = InsertOrReplaceBufferedSingleSignatureRecordQuery::one( + buffered_signature_records.first().unwrap().clone(), + ) + .filters() + .expand(); + format!("insert into buffered_single_signature {sql_values}") + }; + + for record in buffered_signature_records { + let mut statement = connection.prepare(&query)?; + + statement.bind::<&[(_, Value)]>(&[ + (1, record.party_id.into()), + (2, Value::Integer(*record.epoch as i64)), + ( + 3, + Value::Integer(record.signed_entity_type_id.index() as i64), + ), + (4, serde_json::to_string(&record.lottery_indexes)?.into()), + (5, record.signature.into()), + (6, record.created_at.to_rfc3339().into()), + ])?; + statement.next()?; + } + + Ok(()) +} + pub fn insert_certificate_records>( connection: &ConnectionThreadSafe, records: Vec, From 0821617528836022431bebc0a5e0f79ba569743e Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:18:42 +0200 Subject: [PATCH 13/43] Remove epoch from `BufferedSingleSignatureRecord` As we are not guaranteed to obtains it when a signature is buffered since the probable source of the current epoch, the epoch service, may not be ready. --- mithril-aggregator/src/database/migration.rs | 3 +- .../get_buffered_single_signature.rs | 2 -- ...rt_or_replace_buffered_single_signature.rs | 5 +--- .../buffered_single_signature_record.rs | 28 ++++--------------- .../buffered_single_signature_repository.rs | 18 ++---------- .../src/database/test_helper.rs | 10 +++---- 6 files changed, 14 insertions(+), 52 deletions(-) diff --git a/mithril-aggregator/src/database/migration.rs b/mithril-aggregator/src/database/migration.rs index b8709621b6d..5608437f718 100644 --- a/mithril-aggregator/src/database/migration.rs +++ b/mithril-aggregator/src/database/migration.rs @@ -766,12 +766,11 @@ create unique index signed_entity_unique_index on signed_entity(signed_entity_ty r#" create table buffered_single_signature ( signed_entity_type_id integer not null, - epoch integer not null, party_id text not null, lottery_indexes json not null, signature text not null, created_at text not null, - primary key (signed_entity_type_id, epoch, party_id) + primary key (signed_entity_type_id, party_id) ); create index buffered_single_signature_signed_entity_type_id on buffered_single_signature(signed_entity_type_id); diff --git a/mithril-aggregator/src/database/query/buffered_single_signature/get_buffered_single_signature.rs b/mithril-aggregator/src/database/query/buffered_single_signature/get_buffered_single_signature.rs index ea1f707ddfa..02518f8f1a3 100644 --- a/mithril-aggregator/src/database/query/buffered_single_signature/get_buffered_single_signature.rs +++ b/mithril-aggregator/src/database/query/buffered_single_signature/get_buffered_single_signature.rs @@ -1,7 +1,5 @@ use sqlite::Value; -#[cfg(test)] -use mithril_common::entities::Epoch; use mithril_common::entities::SignedEntityTypeDiscriminants; use mithril_persistence::sqlite::{Query, SourceAlias, SqLiteEntity, WhereCondition}; diff --git a/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs b/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs index 20cc8d5d186..8070ec728c4 100644 --- a/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs +++ b/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs @@ -13,12 +13,9 @@ impl InsertOrReplaceBufferedSingleSignatureRecordQuery { pub fn one(record: BufferedSingleSignatureRecord) -> Self { let condition = WhereCondition::new( - "(party_id, epoch, signed_entity_type_id, lottery_indexes, signature, created_at) values (?*, ?*, ?*, ?*, ?*, ?*)", + "(party_id, signed_entity_type_id, lottery_indexes, signature, created_at) values (?*, ?*, ?*, ?*, ?*)", vec![ Value::String(record.party_id), - Value::Integer( - record.epoch.try_into().unwrap(), - ), Value::Integer(record.signed_entity_type_id.index() as i64), Value::String(serde_json::to_string(&record.lottery_indexes).unwrap()), Value::String(record.signature), diff --git a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs index dbc5c0a6d2f..6036c3b1b2b 100644 --- a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs +++ b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, Utc}; use mithril_common::entities::{ - Epoch, HexEncodedSingleSignature, LotteryIndex, SignedEntityTypeDiscriminants, SingleSignatures, + HexEncodedSingleSignature, LotteryIndex, SignedEntityTypeDiscriminants, SingleSignatures, }; use mithril_common::{StdError, StdResult}; use mithril_persistence::sqlite::{HydrationError, Projection, SqLiteEntity}; @@ -13,9 +13,6 @@ pub struct BufferedSingleSignatureRecord { /// Party id. pub party_id: String, - /// Epoch at which the buffered single signature was created - pub epoch: Epoch, - /// Signed entity type discriminant. pub signed_entity_type_id: SignedEntityTypeDiscriminants, @@ -25,7 +22,7 @@ pub struct BufferedSingleSignatureRecord { /// The STM single signature of the message pub signature: HexEncodedSingleSignature, - /// Date and time when the single_signature was created + /// Date and time when the buffered single signature was created pub created_at: DateTime, } @@ -33,12 +30,10 @@ impl BufferedSingleSignatureRecord { pub(crate) fn try_from_single_signatures( other: &SingleSignatures, signed_entity_id: SignedEntityTypeDiscriminants, - epoch: Epoch, ) -> StdResult { let record = BufferedSingleSignatureRecord { signed_entity_type_id: signed_entity_id, party_id: other.party_id.to_owned(), - epoch, lottery_indexes: other.won_indexes.to_owned(), signature: other.signature.to_json_hex()?, created_at: Utc::now(), @@ -65,7 +60,6 @@ impl BufferedSingleSignatureRecord { signed_message: None, }, discriminants, - Epoch(12), ) .unwrap() } @@ -109,23 +103,15 @@ impl SqLiteEntity for BufferedSingleSignatureRecord { Self: Sized, { let party_id = row.read::<&str, _>(0).to_string(); - let epoch = row.read::(1); - let signed_entity_type_id = usize::try_from(row.read::(2)).map_err(|e| { + let signed_entity_type_id = usize::try_from(row.read::(1)).map_err(|e| { panic!("Integer field signed_entity_type_id cannot be turned into usize: {e}") })?; - let lottery_indexes_str = row.read::<&str, _>(3); - let signature = row.read::<&str, _>(4).to_string(); - let created_at = row.read::<&str, _>(5); + let lottery_indexes_str = row.read::<&str, _>(2); + let signature = row.read::<&str, _>(3).to_string(); + let created_at = row.read::<&str, _>(4); let record = Self { party_id, - epoch: Epoch( - epoch.try_into().map_err(|e| { - HydrationError::InvalidData(format!( - "Could not cast i64 ({epoch}) to u64. Error: '{e}'" - )) - })?, - ), signed_entity_type_id: SignedEntityTypeDiscriminants::from_id(signed_entity_type_id).map_err( |e| { HydrationError::InvalidData(format!( @@ -154,7 +140,6 @@ impl SqLiteEntity for BufferedSingleSignatureRecord { fn get_projection() -> Projection { let mut projection = Projection::default(); projection.add_field("party_id", "{:buffered_single_signature:}.party_id", "text"); - projection.add_field("epoch", "{:buffered_single_signature:}.epoch", "integer"); projection.add_field( "signed_entity_type_id", "{:buffered_single_signature:}.signed_entity_type_id", @@ -192,7 +177,6 @@ mod tests { let single_signature_record = BufferedSingleSignatureRecord::try_from_single_signatures( &single_signature, SignedEntityTypeDiscriminants::CardanoTransactions, - Epoch(1), ) .unwrap(); let single_signature_returned = single_signature_record.try_into().unwrap(); diff --git a/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs b/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs index 1f017a01f3a..099f49371d2 100644 --- a/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs +++ b/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs @@ -79,11 +79,9 @@ impl BufferedSingleSignatureStore for BufferedSingleSignatureRepository { #[cfg(test)] mod tests { - use mithril_common::entities::{ - Epoch, LotteryIndex, - SignedEntityTypeDiscriminants::{CardanoTransactions, MithrilStakeDistribution}, + use mithril_common::entities::SignedEntityTypeDiscriminants::{ + CardanoTransactions, MithrilStakeDistribution, }; - use mithril_common::test_utils::fake_data; use crate::database::record::BufferedSingleSignatureRecord; use crate::database::test_helper::{insert_buffered_single_signatures, main_db_connection}; @@ -97,18 +95,6 @@ mod tests { .collect::>() } - fn fake_record( - discriminants: SignedEntityTypeDiscriminants, - signature_won_indexes: Vec, - ) -> BufferedSingleSignatureRecord { - BufferedSingleSignatureRecord::try_from_single_signatures( - &fake_data::single_signatures(signature_won_indexes), - discriminants, - Epoch(12), - ) - .unwrap() - } - #[test] fn retrieve_all() { let connection = main_db_connection().unwrap(); diff --git a/mithril-aggregator/src/database/test_helper.rs b/mithril-aggregator/src/database/test_helper.rs index 814289d2d70..a0e6f2d893c 100644 --- a/mithril-aggregator/src/database/test_helper.rs +++ b/mithril-aggregator/src/database/test_helper.rs @@ -106,7 +106,6 @@ pub fn setup_buffered_single_signature_records( single_signature_records.push(BufferedSingleSignatureRecord { signed_entity_type_id: SignedEntityTypeDiscriminants::CardanoTransactions, party_id: format!("signer-{signer_idx}"), - epoch: Epoch(epoch), lottery_indexes: (1..=single_signature_id).collect(), signature: fake_keys::single_signature()[3].to_string(), created_at: Utc::now(), @@ -189,14 +188,13 @@ pub fn insert_buffered_single_signatures( statement.bind::<&[(_, Value)]>(&[ (1, record.party_id.into()), - (2, Value::Integer(*record.epoch as i64)), ( - 3, + 2, Value::Integer(record.signed_entity_type_id.index() as i64), ), - (4, serde_json::to_string(&record.lottery_indexes)?.into()), - (5, record.signature.into()), - (6, record.created_at.to_rfc3339().into()), + (3, serde_json::to_string(&record.lottery_indexes)?.into()), + (4, record.signature.into()), + (5, record.created_at.to_rfc3339().into()), ])?; statement.next()?; } From 795d3eb4a09145e252424acf4fe01785c409a75d Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:27:32 +0200 Subject: [PATCH 14/43] Add unique constraint on `buffered_single_signature:signature` column --- mithril-aggregator/src/database/migration.rs | 3 +- .../buffered_single_signature_record.rs | 54 ++++++++++++++++--- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/mithril-aggregator/src/database/migration.rs b/mithril-aggregator/src/database/migration.rs index 5608437f718..dbab71e2aaa 100644 --- a/mithril-aggregator/src/database/migration.rs +++ b/mithril-aggregator/src/database/migration.rs @@ -770,7 +770,8 @@ create table buffered_single_signature ( lottery_indexes json not null, signature text not null, created_at text not null, - primary key (signed_entity_type_id, party_id) + primary key (signed_entity_type_id, party_id), + unique(signature) ); create index buffered_single_signature_signed_entity_type_id on buffered_single_signature(signed_entity_type_id); diff --git a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs index 6036c3b1b2b..226ff7cecd2 100644 --- a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs +++ b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs @@ -47,19 +47,39 @@ impl BufferedSingleSignatureRecord { impl BufferedSingleSignatureRecord { pub(crate) fn fake>( party_id: T, - discriminants: SignedEntityTypeDiscriminants, + discriminant: SignedEntityTypeDiscriminants, ) -> Self { - use mithril_common::test_utils::fake_keys; + // Note: due to the unique constraint on the signature column, we want to make sure that + // the signatures are different for party_id/discriminant pairs. + // We can't just reuse fake_data::single_signatures as they are static. + use mithril_common::entities::{ProtocolMessage, ProtocolParameters}; + use mithril_common::test_utils::{ + MithrilFixtureBuilder, StakeDistributionGenerationMethod, + }; + + let party_id = party_id.into(); + let fixture = MithrilFixtureBuilder::default() + .with_signers(1) + .with_stake_distribution(StakeDistributionGenerationMethod::Custom( + std::collections::BTreeMap::from([( + format!("{:<032}", format!("{party_id}{discriminant}")), + 100, + )]), + )) + .with_protocol_parameters(ProtocolParameters::new(1, 1, 1.0)) + .build(); + let signature = fixture.signers_fixture()[0] + .sign(&ProtocolMessage::default()) + .unwrap(); - let signature = fake_keys::single_signature()[0].try_into().unwrap(); Self::try_from_single_signatures( &SingleSignatures { - party_id: party_id.into(), - signature, + party_id, + signature: signature.signature, won_indexes: vec![10, 15], signed_message: None, }, - discriminants, + discriminant, ) .unwrap() } @@ -167,6 +187,9 @@ impl SqLiteEntity for BufferedSingleSignatureRecord { #[cfg(test)] mod tests { + use mithril_common::entities::SignedEntityTypeDiscriminants::{ + CardanoTransactions, MithrilStakeDistribution, + }; use mithril_common::test_utils::fake_data; use super::*; @@ -176,11 +199,28 @@ mod tests { let single_signature = fake_data::single_signatures(vec![1, 3, 4, 6, 7, 9]); let single_signature_record = BufferedSingleSignatureRecord::try_from_single_signatures( &single_signature, - SignedEntityTypeDiscriminants::CardanoTransactions, + CardanoTransactions, ) .unwrap(); let single_signature_returned = single_signature_record.try_into().unwrap(); assert_eq!(single_signature, single_signature_returned); } + + #[test] + fn building_fake_generate_different_protocol_single_signature() { + assert_eq!( + BufferedSingleSignatureRecord::fake("party_1", CardanoTransactions).signature, + BufferedSingleSignatureRecord::fake("party_1", CardanoTransactions).signature + ); + + assert_ne!( + BufferedSingleSignatureRecord::fake("party_1", CardanoTransactions).signature, + BufferedSingleSignatureRecord::fake("party_2", CardanoTransactions).signature + ); + assert_ne!( + BufferedSingleSignatureRecord::fake("party_1", CardanoTransactions).signature, + BufferedSingleSignatureRecord::fake("party_1", MithrilStakeDistribution).signature + ); + } } From d7e4c0d15e11c711310042ecd84661125d0992d4 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:43:36 +0200 Subject: [PATCH 15/43] Fix crypto_helper:setup_protocol_initializer crash if party id is less than 32 chars --- .../src/database/record/buffered_single_signature_record.rs | 6 +----- mithril-common/src/crypto_helper/tests_setup.rs | 4 +++- mithril-common/src/test_utils/fixture_builder.rs | 6 +++--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs index 226ff7cecd2..9e55068a1b0 100644 --- a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs +++ b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs @@ -59,12 +59,8 @@ impl BufferedSingleSignatureRecord { let party_id = party_id.into(); let fixture = MithrilFixtureBuilder::default() - .with_signers(1) .with_stake_distribution(StakeDistributionGenerationMethod::Custom( - std::collections::BTreeMap::from([( - format!("{:<032}", format!("{party_id}{discriminant}")), - 100, - )]), + std::collections::BTreeMap::from([(format!("{party_id}{discriminant}"), 100)]), )) .with_protocol_parameters(ProtocolParameters::new(1, 1, 1.0)) .build(); diff --git a/mithril-common/src/crypto_helper/tests_setup.rs b/mithril-common/src/crypto_helper/tests_setup.rs index 4713e0fc475..733bda9f55c 100644 --- a/mithril-common/src/crypto_helper/tests_setup.rs +++ b/mithril-common/src/crypto_helper/tests_setup.rs @@ -57,7 +57,9 @@ fn setup_protocol_initializer( stake: Stake, protocol_parameters: &ProtocolParameters, ) -> ProtocolInitializer { - let protocol_initializer_seed: [u8; 32] = party_id.as_bytes()[..32].try_into().unwrap(); + let protocol_initializer_seed: [u8; 32] = format!("{party_id:<032}").as_bytes()[..32] + .try_into() + .unwrap(); let mut protocol_initializer_rng = ChaCha20Rng::from_seed(protocol_initializer_seed); let kes_period = kes_secret_key_path.as_ref().map(|_| 0); let protocol_initializer: ProtocolInitializer = ProtocolInitializer::setup( diff --git a/mithril-common/src/test_utils/fixture_builder.rs b/mithril-common/src/test_utils/fixture_builder.rs index fd937c7900d..1ba9667f9e6 100644 --- a/mithril-common/src/test_utils/fixture_builder.rs +++ b/mithril-common/src/test_utils/fixture_builder.rs @@ -138,7 +138,7 @@ impl MithrilFixtureBuilder { &mut kes_keys_seed, ) } else { - format!("{party_index:<032}") + party_index.to_string() } }); signers_party_ids.collect::>() @@ -265,11 +265,11 @@ mod tests { #[test] fn changing_party_id_seed_change_all_builded_party_ids() { let first_signers = MithrilFixtureBuilder::default() - .with_signers(100) + .with_signers(20) .build() .signers_with_stake(); let different_party_id_seed_signers = MithrilFixtureBuilder::default() - .with_signers(100) + .with_signers(20) .with_party_id_seed([1u8; 32]) .build() .signers_with_stake(); From 5a7f9ea5ce0e5dfbf7c549cf298073ff64126dff Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:15:40 +0200 Subject: [PATCH 16/43] Fix buffered_single_signature table constraints Using `insert or replace` sqlite clause will replace anything that violate either a pk or unique constraint, meaning that to replacing only when the pk constraint is violated can only be done at the table level and not on the query level. --- mithril-aggregator/src/database/migration.rs | 4 +- ...rt_or_replace_buffered_single_signature.rs | 139 +++++++++++++----- .../buffered_single_signature_record.rs | 6 +- .../src/database/test_helper.rs | 28 +--- .../src/entities/single_signatures.rs | 12 +- 5 files changed, 120 insertions(+), 69 deletions(-) diff --git a/mithril-aggregator/src/database/migration.rs b/mithril-aggregator/src/database/migration.rs index dbab71e2aaa..09aaba9f56a 100644 --- a/mithril-aggregator/src/database/migration.rs +++ b/mithril-aggregator/src/database/migration.rs @@ -770,8 +770,8 @@ create table buffered_single_signature ( lottery_indexes json not null, signature text not null, created_at text not null, - primary key (signed_entity_type_id, party_id), - unique(signature) + primary key (signed_entity_type_id, party_id) on conflict replace, + unique(signature) on conflict abort ); create index buffered_single_signature_signed_entity_type_id on buffered_single_signature(signed_entity_type_id); diff --git a/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs b/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs index 8070ec728c4..e64e59de683 100644 --- a/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs +++ b/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs @@ -13,10 +13,10 @@ impl InsertOrReplaceBufferedSingleSignatureRecordQuery { pub fn one(record: BufferedSingleSignatureRecord) -> Self { let condition = WhereCondition::new( - "(party_id, signed_entity_type_id, lottery_indexes, signature, created_at) values (?*, ?*, ?*, ?*, ?*)", + "(signed_entity_type_id, party_id, lottery_indexes, signature, created_at) values (?*, ?*, ?*, ?*, ?*)", vec![ - Value::String(record.party_id), Value::Integer(record.signed_entity_type_id.index() as i64), + Value::String(record.party_id), Value::String(serde_json::to_string(&record.lottery_indexes).unwrap()), Value::String(record.signature), Value::String(record.created_at.to_rfc3339()), @@ -42,51 +42,124 @@ impl Query for InsertOrReplaceBufferedSingleSignatureRecordQuery { "buffered_single_signature", )])); - format!( - "insert or replace into buffered_single_signature {condition} returning {projection}" - ) + format!("insert into buffered_single_signature {condition} returning {projection}") } } #[cfg(test)] mod tests { + use chrono::{Duration, Utc}; + use mithril_common::entities::SignedEntityTypeDiscriminants::{ + CardanoImmutableFilesFull, CardanoStakeDistribution, + }; use mithril_persistence::sqlite::ConnectionExtensions; - use crate::database::test_helper::{ - main_db_connection, setup_buffered_single_signature_records, - }; + use crate::database::query::GetBufferedSingleSignatureQuery; + use crate::database::test_helper::main_db_connection; use super::*; #[test] - fn test_insert_or_replace_single_signature_record() { - let records = setup_buffered_single_signature_records(1, 3); + fn insert_records_in_empty_db() { + let connection = main_db_connection().unwrap(); + + let record = BufferedSingleSignatureRecord::fake("party_8", CardanoImmutableFilesFull); + let inserted_record = connection + .fetch_first(InsertOrReplaceBufferedSingleSignatureRecordQuery::one( + record.clone(), + )) + .unwrap(); + assert_eq!(Some(record), inserted_record); + } + + #[test] + #[should_panic] + fn cant_insert_two_record_sharing_the_same_signature() { let connection = main_db_connection().unwrap(); - for buffered_signature_record in records.clone() { - let single_signature_record_saved = connection - .fetch_first(InsertOrReplaceBufferedSingleSignatureRecordQuery::one( - buffered_signature_record.clone(), - )) - .unwrap(); - assert_eq!( - Some(buffered_signature_record), - single_signature_record_saved + let record = BufferedSingleSignatureRecord::fake("party_8", CardanoImmutableFilesFull); + let other_record = BufferedSingleSignatureRecord { + party_id: "party_10".to_string(), + signed_entity_type_id: CardanoStakeDistribution, + ..record.clone() + }; + + connection + .fetch_first(InsertOrReplaceBufferedSingleSignatureRecordQuery::one( + record, + )) + .unwrap(); + connection + .fetch_first(InsertOrReplaceBufferedSingleSignatureRecordQuery::one( + other_record, + )) + .expect_err( + "Unique constraint on signature should prevent inserting the same signature twice", ); - } - - for mut buffered_signature_record in records { - buffered_signature_record.lottery_indexes.push(5); - let single_signature_record_saved = connection - .fetch_first(InsertOrReplaceBufferedSingleSignatureRecordQuery::one( - buffered_signature_record.clone(), - )) - .unwrap(); - assert_eq!( - Some(buffered_signature_record), - single_signature_record_saved - ); - } + } + + #[test] + fn cant_inserted_same_record_twice_should_replace_first_insert() { + let connection = main_db_connection().unwrap(); + + let record = BufferedSingleSignatureRecord::fake("party_8", CardanoImmutableFilesFull); + + connection + .fetch_first(InsertOrReplaceBufferedSingleSignatureRecordQuery::one( + record.clone(), + )) + .unwrap(); + let inserted_record = connection + .fetch_first(InsertOrReplaceBufferedSingleSignatureRecordQuery::one( + record.clone(), + )) + .unwrap(); + + assert_eq!(Some(record), inserted_record); + } + + #[test] + fn inserting_record_with_same_party_id_and_discriminant_replace_previous_record() { + let connection = main_db_connection().unwrap(); + + let record = BufferedSingleSignatureRecord { + party_id: "party_15".to_string(), + signed_entity_type_id: CardanoStakeDistribution, + lottery_indexes: vec![1, 2, 3], + signature: "a signature".to_string(), + created_at: Utc::now(), + }; + connection + .fetch_first(InsertOrReplaceBufferedSingleSignatureRecordQuery::one( + record.clone(), + )) + .unwrap(); + let count = connection + .fetch(GetBufferedSingleSignatureQuery::all()) + .unwrap() + .count(); + + assert_eq!(1, count); + + let updated_record = BufferedSingleSignatureRecord { + party_id: record.party_id.clone(), + signed_entity_type_id: record.signed_entity_type_id, + lottery_indexes: vec![7, 8, 9], + signature: "another signature".to_string(), + created_at: Utc::now() + Duration::minutes(18), + }; + let replaced_record = connection + .fetch_first(InsertOrReplaceBufferedSingleSignatureRecordQuery::one( + updated_record.clone(), + )) + .unwrap(); + let count = connection + .fetch(GetBufferedSingleSignatureQuery::all()) + .unwrap() + .count(); + + assert_eq!(Some(updated_record), replaced_record); + assert_eq!(1, count); } } diff --git a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs index 9e55068a1b0..e1433fca614 100644 --- a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs +++ b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs @@ -118,10 +118,10 @@ impl SqLiteEntity for BufferedSingleSignatureRecord { where Self: Sized, { - let party_id = row.read::<&str, _>(0).to_string(); - let signed_entity_type_id = usize::try_from(row.read::(1)).map_err(|e| { + let signed_entity_type_id = usize::try_from(row.read::(0)).map_err(|e| { panic!("Integer field signed_entity_type_id cannot be turned into usize: {e}") })?; + let party_id = row.read::<&str, _>(1).to_string(); let lottery_indexes_str = row.read::<&str, _>(2); let signature = row.read::<&str, _>(3).to_string(); let created_at = row.read::<&str, _>(4); @@ -155,12 +155,12 @@ impl SqLiteEntity for BufferedSingleSignatureRecord { fn get_projection() -> Projection { let mut projection = Projection::default(); - projection.add_field("party_id", "{:buffered_single_signature:}.party_id", "text"); projection.add_field( "signed_entity_type_id", "{:buffered_single_signature:}.signed_entity_type_id", "integer", ); + projection.add_field("party_id", "{:buffered_single_signature:}.party_id", "text"); projection.add_field( "lottery_indexes", "{:buffered_single_signature:}.lottery_indexes", diff --git a/mithril-aggregator/src/database/test_helper.rs b/mithril-aggregator/src/database/test_helper.rs index a0e6f2d893c..13d698edaff 100644 --- a/mithril-aggregator/src/database/test_helper.rs +++ b/mithril-aggregator/src/database/test_helper.rs @@ -3,9 +3,7 @@ use sqlite::{ConnectionThreadSafe, Value}; use std::path::Path; use uuid::Uuid; -use mithril_common::entities::{ - ProtocolParameters, SignedEntityTypeDiscriminants, SignerWithStake, -}; +use mithril_common::entities::{ProtocolParameters, SignerWithStake}; use mithril_common::{entities::Epoch, test_utils::fake_keys, StdError, StdResult}; use mithril_persistence::sqlite::{ ConnectionBuilder, ConnectionExtensions, ConnectionOptions, Query, SqliteConnection, @@ -95,26 +93,6 @@ pub fn setup_single_signature_records( single_signature_records } -pub fn setup_buffered_single_signature_records( - total_epoch: u64, - total_signer: u64, -) -> Vec { - let mut single_signature_records = Vec::new(); - for epoch in 1..=total_epoch { - for signer_idx in 1..=total_signer { - let single_signature_id = epoch * signer_idx; - single_signature_records.push(BufferedSingleSignatureRecord { - signed_entity_type_id: SignedEntityTypeDiscriminants::CardanoTransactions, - party_id: format!("signer-{signer_idx}"), - lottery_indexes: (1..=single_signature_id).collect(), - signature: fake_keys::single_signature()[3].to_string(), - created_at: Utc::now(), - }); - } - } - single_signature_records -} - pub fn insert_single_signatures_in_db( connection: &SqliteConnection, single_signature_records: Vec, @@ -187,11 +165,11 @@ pub fn insert_buffered_single_signatures( let mut statement = connection.prepare(&query)?; statement.bind::<&[(_, Value)]>(&[ - (1, record.party_id.into()), ( - 2, + 1, Value::Integer(record.signed_entity_type_id.index() as i64), ), + (2, record.party_id.into()), (3, serde_json::to_string(&record.lottery_indexes)?.into()), (4, record.signature.into()), (5, record.created_at.to_rfc3339().into()), diff --git a/mithril-common/src/entities/single_signatures.rs b/mithril-common/src/entities/single_signatures.rs index 1cd5f66f423..958dfb15557 100644 --- a/mithril-common/src/entities/single_signatures.rs +++ b/mithril-common/src/entities/single_signatures.rs @@ -31,13 +31,13 @@ pub struct SingleSignatures { impl SingleSignatures { /// `SingleSignatures` factory - pub fn new( - party_id: PartyId, + pub fn new>( + party_id: T, signature: ProtocolSingleSignature, won_indexes: Vec, ) -> SingleSignatures { SingleSignatures { - party_id, + party_id: party_id.into(), signature, won_indexes, signed_message: None, @@ -45,14 +45,14 @@ impl SingleSignatures { } /// `SingleSignatures` factory including the signed message - pub fn new_with_signed_message( - party_id: PartyId, + pub fn new_with_signed_message>( + party_id: T, signature: ProtocolSingleSignature, won_indexes: Vec, signed_message: String, ) -> SingleSignatures { SingleSignatures { - party_id, + party_id: party_id.into(), signature, won_indexes, signed_message: Some(signed_message), From 407e48106d40dc3000ccc9ac678058c02f769de3 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:20:49 +0200 Subject: [PATCH 17/43] Implement `buffer_signature` on sqlite store --- .../buffered_single_signature_repository.rs | 102 +++++++++++++++++- 1 file changed, 98 insertions(+), 4 deletions(-) diff --git a/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs b/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs index 099f49371d2..10cc687408e 100644 --- a/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs +++ b/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs @@ -1,3 +1,4 @@ +use anyhow::Context; use async_trait::async_trait; use std::sync::Arc; @@ -5,7 +6,9 @@ use mithril_common::entities::{SignedEntityTypeDiscriminants, SingleSignatures}; use mithril_common::{StdError, StdResult}; use mithril_persistence::sqlite::{ConnectionExtensions, SqliteConnection}; -use crate::database::query::GetBufferedSingleSignatureQuery; +use crate::database::query::{ + GetBufferedSingleSignatureQuery, InsertOrReplaceBufferedSingleSignatureRecordQuery, +}; use crate::database::record::BufferedSingleSignatureRecord; use crate::services::BufferedSingleSignatureStore; @@ -55,10 +58,21 @@ impl BufferedSingleSignatureRepository { impl BufferedSingleSignatureStore for BufferedSingleSignatureRepository { async fn buffer_signature( &self, - _signed_entity_type_discriminants: SignedEntityTypeDiscriminants, - _signature: &SingleSignatures, + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + signature: &SingleSignatures, ) -> StdResult<()> { - todo!() + let record = BufferedSingleSignatureRecord::try_from_single_signatures( + signature, + signed_entity_type_discriminants, + ) + .with_context(|| "Failed to convert SingleSignatures to BufferedSingleSignatureRecord")?; + + self.connection + .fetch_first(InsertOrReplaceBufferedSingleSignatureRecordQuery::one( + record, + ))?; + + Ok(()) } async fn get_buffered_signatures( @@ -82,6 +96,7 @@ mod tests { use mithril_common::entities::SignedEntityTypeDiscriminants::{ CardanoTransactions, MithrilStakeDistribution, }; + use mithril_common::test_utils::fake_keys; use crate::database::record::BufferedSingleSignatureRecord; use crate::database::test_helper::{insert_buffered_single_signatures, main_db_connection}; @@ -158,4 +173,83 @@ mod tests { strip_date(&buffered_signatures_msd) ); } + + #[tokio::test] + async fn store_signatures() { + let connection = main_db_connection().unwrap(); + let store = BufferedSingleSignatureRepository::new(Arc::new(connection)); + + // Multiple signatures of the same signed entity type + { + store + .buffer_signature( + CardanoTransactions, + &SingleSignatures::new( + "party1", + fake_keys::single_signature()[0].try_into().unwrap(), + vec![1], + ), + ) + .await + .unwrap(); + store + .buffer_signature( + CardanoTransactions, + &SingleSignatures::new( + "party2", + fake_keys::single_signature()[1].try_into().unwrap(), + vec![2], + ), + ) + .await + .unwrap(); + + let buffered_signatures = store + .get_buffered_signatures(CardanoTransactions) + .await + .unwrap(); + assert_eq!( + vec![ + SingleSignatures::new( + "party2", + fake_keys::single_signature()[1].try_into().unwrap(), + vec![2], + ), + SingleSignatures::new( + "party1", + fake_keys::single_signature()[0].try_into().unwrap(), + vec![1], + ), + ], + buffered_signatures + ); + } + // Another signed entity type to test that the store is able to differentiate between them + { + store + .buffer_signature( + MithrilStakeDistribution, + &SingleSignatures::new( + "party3", + fake_keys::single_signature()[2].try_into().unwrap(), + vec![3], + ), + ) + .await + .unwrap(); + + let buffered_signatures = store + .get_buffered_signatures(MithrilStakeDistribution) + .await + .unwrap(); + assert_eq!( + vec![SingleSignatures::new( + "party3", + fake_keys::single_signature()[2].try_into().unwrap(), + vec![3], + )], + buffered_signatures + ); + } + } } From b37bcfea8dc9330bcf1ab653d48233491638559d Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:35:25 +0200 Subject: [PATCH 18/43] Implement `remove_buffered_signatures` on sqlite store --- .../delete_buffered_single_signature.rs | 105 ++++++++++++++++++ .../query/buffered_single_signature/mod.rs | 2 + .../buffered_single_signature_repository.rs | 58 +++++++++- 3 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 mithril-aggregator/src/database/query/buffered_single_signature/delete_buffered_single_signature.rs diff --git a/mithril-aggregator/src/database/query/buffered_single_signature/delete_buffered_single_signature.rs b/mithril-aggregator/src/database/query/buffered_single_signature/delete_buffered_single_signature.rs new file mode 100644 index 00000000000..ba095ad7b59 --- /dev/null +++ b/mithril-aggregator/src/database/query/buffered_single_signature/delete_buffered_single_signature.rs @@ -0,0 +1,105 @@ +use sqlite::Value; + +use mithril_common::entities::{PartyId, SignedEntityTypeDiscriminants}; +use mithril_persistence::sqlite::{Query, SourceAlias, SqLiteEntity, WhereCondition}; + +use crate::database::record::BufferedSingleSignatureRecord; + +/// Query to delete old [BufferedSingleSignatureRecord] from the sqlite database +pub struct DeleteBufferedSingleSignatureQuery { + condition: WhereCondition, +} + +impl DeleteBufferedSingleSignatureQuery { + pub fn by_discriminant_and_party_ids( + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + party_ids: Vec, + ) -> Self { + let ids_values = party_ids.into_iter().map(Value::String).collect(); + + Self { + condition: WhereCondition::new( + "signed_entity_type_id = ?*", + vec![Value::Integer( + signed_entity_type_discriminants.index() as i64 + )], + ) + .and_where(WhereCondition::where_in("party_id", ids_values)), + } + } +} + +impl Query for DeleteBufferedSingleSignatureQuery { + type Entity = BufferedSingleSignatureRecord; + + fn filters(&self) -> WhereCondition { + self.condition.clone() + } + + 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(&[( + "{:buffered_single_signature:}", + "buffered_single_signature", + )])); + + format!("delete from buffered_single_signature where {condition} returning {projection}") + } +} + +#[cfg(test)] +mod tests { + use mithril_common::entities::SignedEntityTypeDiscriminants::{ + CardanoTransactions, MithrilStakeDistribution, + }; + use mithril_persistence::sqlite::ConnectionExtensions; + + use crate::database::query::GetBufferedSingleSignatureQuery; + use crate::database::test_helper::{insert_buffered_single_signatures, main_db_connection}; + + use super::*; + + #[test] + fn test_delete_buffered_single_signature_records_by_discriminant_and_party_ids() { + let connection = main_db_connection().unwrap(); + let records = BufferedSingleSignatureRecord::fakes(&[ + ("party_1", MithrilStakeDistribution), + ("party_2", MithrilStakeDistribution), + ("party_3", MithrilStakeDistribution), + ("party_1", CardanoTransactions), + ("party_2", CardanoTransactions), + ]); + insert_buffered_single_signatures(&connection, records.clone()).unwrap(); + + let cursor = connection + .fetch( + DeleteBufferedSingleSignatureQuery::by_discriminant_and_party_ids( + MithrilStakeDistribution, + vec!["party_1".into(), "party_3".into()], + ), + ) + .unwrap(); + assert_eq!(2, cursor.count()); + + let cursor = connection + .fetch(GetBufferedSingleSignatureQuery::all()) + .unwrap(); + assert_eq!(3, cursor.count()); + + let cursor = connection + .fetch( + DeleteBufferedSingleSignatureQuery::by_discriminant_and_party_ids( + CardanoTransactions, + vec!["party_1".into(), "party_2".into()], + ), + ) + .unwrap(); + assert_eq!(2, cursor.count()); + + let cursor = connection + .fetch(GetBufferedSingleSignatureQuery::all()) + .unwrap(); + assert_eq!(1, cursor.count()); + } +} diff --git a/mithril-aggregator/src/database/query/buffered_single_signature/mod.rs b/mithril-aggregator/src/database/query/buffered_single_signature/mod.rs index 4108de685bc..6a63cb371e3 100644 --- a/mithril-aggregator/src/database/query/buffered_single_signature/mod.rs +++ b/mithril-aggregator/src/database/query/buffered_single_signature/mod.rs @@ -1,5 +1,7 @@ +mod delete_buffered_single_signature; mod get_buffered_single_signature; mod insert_or_replace_buffered_single_signature; +pub use delete_buffered_single_signature::*; pub use get_buffered_single_signature::*; pub use insert_or_replace_buffered_single_signature::*; diff --git a/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs b/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs index 10cc687408e..cdd8815d7a3 100644 --- a/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs +++ b/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs @@ -7,7 +7,8 @@ use mithril_common::{StdError, StdResult}; use mithril_persistence::sqlite::{ConnectionExtensions, SqliteConnection}; use crate::database::query::{ - GetBufferedSingleSignatureQuery, InsertOrReplaceBufferedSingleSignatureRecordQuery, + DeleteBufferedSingleSignatureQuery, GetBufferedSingleSignatureQuery, + InsertOrReplaceBufferedSingleSignatureRecordQuery, }; use crate::database::record::BufferedSingleSignatureRecord; use crate::services::BufferedSingleSignatureStore; @@ -84,10 +85,18 @@ impl BufferedSingleSignatureStore for BufferedSingleSignatureRepository { async fn remove_buffered_signatures( &self, - _signed_entity_type_discriminants: SignedEntityTypeDiscriminants, - _single_signatures: Vec, + signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + single_signatures: Vec, ) -> StdResult<()> { - todo!() + let signatures_party_ids = single_signatures.into_iter().map(|s| s.party_id).collect(); + self.connection.fetch_first( + DeleteBufferedSingleSignatureQuery::by_discriminant_and_party_ids( + signed_entity_type_discriminants, + signatures_party_ids, + ), + )?; + + Ok(()) } } @@ -252,4 +261,45 @@ mod tests { ); } } + + #[tokio::test] + async fn remove_buffered_signatures() { + let connection = main_db_connection().unwrap(); + insert_buffered_single_signatures( + &connection, + BufferedSingleSignatureRecord::fakes(&[ + ("party1", MithrilStakeDistribution), + ("party2", MithrilStakeDistribution), + ("party3", MithrilStakeDistribution), + ("party4", CardanoTransactions), + ]), + ) + .unwrap(); + + let store = BufferedSingleSignatureRepository::new(Arc::new(connection)); + + store + .remove_buffered_signatures( + MithrilStakeDistribution, + vec![ + BufferedSingleSignatureRecord::fake("party1", MithrilStakeDistribution) + .try_into() + .unwrap(), + BufferedSingleSignatureRecord::fake("party3", MithrilStakeDistribution) + .try_into() + .unwrap(), + ], + ) + .await + .unwrap(); + + let remaining_msd_sigs = store.get_all().unwrap(); + assert_eq!( + strip_date(&BufferedSingleSignatureRecord::fakes(&[ + ("party4", CardanoTransactions), + ("party2", MithrilStakeDistribution), + ])), + strip_date(&remaining_msd_sigs) + ); + } } From f07dafd6169f58ea5a731fc1597088c959dbc59e Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:28:08 +0200 Subject: [PATCH 19/43] Remove unique(signature) constraint on buffered sig table As the current api we use only handle constraints violation through panics, making it difficult to use sqlite contraints to enforce domain rules. --- mithril-aggregator/src/database/migration.rs | 3 +-- ...rt_or_replace_buffered_single_signature.rs | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/mithril-aggregator/src/database/migration.rs b/mithril-aggregator/src/database/migration.rs index 09aaba9f56a..5608437f718 100644 --- a/mithril-aggregator/src/database/migration.rs +++ b/mithril-aggregator/src/database/migration.rs @@ -770,8 +770,7 @@ create table buffered_single_signature ( lottery_indexes json not null, signature text not null, created_at text not null, - primary key (signed_entity_type_id, party_id) on conflict replace, - unique(signature) on conflict abort + primary key (signed_entity_type_id, party_id) ); create index buffered_single_signature_signed_entity_type_id on buffered_single_signature(signed_entity_type_id); diff --git a/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs b/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs index e64e59de683..abcc04a082f 100644 --- a/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs +++ b/mithril-aggregator/src/database/query/buffered_single_signature/insert_or_replace_buffered_single_signature.rs @@ -42,7 +42,9 @@ impl Query for InsertOrReplaceBufferedSingleSignatureRecordQuery { "buffered_single_signature", )])); - format!("insert into buffered_single_signature {condition} returning {projection}") + format!( + "insert or replace into buffered_single_signature {condition} returning {projection}" + ) } } @@ -74,8 +76,7 @@ mod tests { } #[test] - #[should_panic] - fn cant_insert_two_record_sharing_the_same_signature() { + fn allow_to_insert_record_with_different_party_id_and_discriminant_but_different_signature() { let connection = main_db_connection().unwrap(); let record = BufferedSingleSignatureRecord::fake("party_8", CardanoImmutableFilesFull); @@ -94,13 +95,17 @@ mod tests { .fetch_first(InsertOrReplaceBufferedSingleSignatureRecordQuery::one( other_record, )) - .expect_err( - "Unique constraint on signature should prevent inserting the same signature twice", - ); + .unwrap(); + + let count = connection + .fetch(GetBufferedSingleSignatureQuery::all()) + .unwrap() + .count(); + assert_eq!(2, count); } #[test] - fn cant_inserted_same_record_twice_should_replace_first_insert() { + fn inserting_same_record_twice_should_replace_first_insert() { let connection = main_db_connection().unwrap(); let record = BufferedSingleSignatureRecord::fake("party_8", CardanoImmutableFilesFull); From c3d1b0fe1c391b973e4598dab5a902a1c4053cb0 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:04:07 +0200 Subject: [PATCH 20/43] Skip invalid signatures when registering buffered data As the main reason that they're invalid is that they're signing another message. --- .../services/certifier/buffered_certifier.rs | 75 +++++++++++++++++-- 1 file changed, 69 insertions(+), 6 deletions(-) diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs index cb78689cb51..fb6e19d7d17 100644 --- a/mithril-aggregator/src/services/certifier/buffered_certifier.rs +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use slog::{debug, warn, Logger}; +use slog::{debug, trace, warn, Logger}; use std::sync::Arc; use mithril_common::entities::{ @@ -45,15 +45,33 @@ impl BufferedCertifierService { .buffered_single_signature_store .get_buffered_signatures(discriminant) .await?; + let mut signatures_to_remove = vec![]; - for signature in &buffered_signatures { - self.certifier_service - .register_single_signature(signed_entity_type, signature) - .await?; + for signature in buffered_signatures { + match self + .certifier_service + .register_single_signature(signed_entity_type, &signature) + .await + { + Ok(..) => { + signatures_to_remove.push(signature); + } + Err(error) => match error.downcast_ref::() { + Some(CertifierServiceError::InvalidSingleSignature(..)) => { + trace!(self.logger, "Skipping invalid signature for signed entity '{signed_entity_type:?}'"; + "party_id" => &signature.party_id, + "error" => ?error, + ); + } + _ => { + anyhow::bail!(error); + } + }, + } } self.buffered_single_signature_store - .remove_buffered_signatures(discriminant, buffered_signatures) + .remove_buffered_signatures(discriminant, signatures_to_remove) .await?; Ok(()) @@ -298,6 +316,8 @@ mod tests { } mod when_failing_to_transfer_buffered_signature_to_new_open_message { + use mockall::predicate::always; + use super::*; async fn run_scenario( @@ -320,6 +340,49 @@ mod tests { .expect("Transferring buffered signatures to new open message should not fail"); } + #[tokio::test] + async fn skip_invalid_signatures() { + run_scenario( + |mock| { + mock.expect_create_open_message() + .returning(|_, _| Ok(OpenMessage::dummy())); + + mock.expect_register_single_signature() + .with(always(), eq(fake_data::single_signatures(vec![1]))) + .returning(|_, _| Ok(())) + .once(); + mock.expect_register_single_signature() + .with(always(), eq(fake_data::single_signatures(vec![2]))) + .returning(|_, _| { + Err(CertifierServiceError::InvalidSingleSignature( + OpenMessage::dummy().signed_entity_type, + anyhow!("Invalid signature"), + ) + .into()) + }) + .once(); + mock.expect_register_single_signature() + .with(always(), eq(fake_data::single_signatures(vec![3]))) + .returning(|_, _| Ok(())) + .once(); + }, + |mock| { + mock.expect_get_buffered_signatures().returning(|_| { + Ok(vec![ + fake_data::single_signatures(vec![1]), + fake_data::single_signatures(vec![2]), + fake_data::single_signatures(vec![3]), + ]) + }); + mock.expect_remove_buffered_signatures() + // Only non-skipped signatures should be removed + .withf(|_, sig_to_remove| sig_to_remove.len() == 2) + .returning(|_, _| Ok(())); + }, + ) + .await; + } + #[tokio::test] async fn do_not_return_an_error_if_getting_buffer_signatures_fail() { run_scenario( From 20981a1e41a4c64f33b67c2cb00c053c1ecb6868 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 11 Sep 2024 16:20:31 +0200 Subject: [PATCH 21/43] Remove RwLock wrapping from aggregator multi-signer As it's not needed anymore since it doesn't own data. The only two times that the RwLock was used was only on reading. --- .../src/dependency_injection/builder.rs | 8 ++++---- .../src/dependency_injection/containers.rs | 5 +---- .../services/certifier/certifier_service.rs | 18 ++++++++++-------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index ee4c852b72f..9c9486dc4c4 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -116,7 +116,7 @@ pub struct DependenciesBuilder { pub snapshot_uploader: Option>, /// Multisigner service. - pub multi_signer: Option>>, + pub multi_signer: Option>, /// Certificate pending store. pub certificate_pending_store: Option>, @@ -450,14 +450,14 @@ impl DependenciesBuilder { Ok(self.snapshot_uploader.as_ref().cloned().unwrap()) } - async fn build_multi_signer(&mut self) -> Result>> { + async fn build_multi_signer(&mut self) -> Result> { let multi_signer = MultiSignerImpl::new(self.get_epoch_service().await?); - Ok(Arc::new(RwLock::new(multi_signer))) + Ok(Arc::new(multi_signer)) } /// Get a configured multi signer - pub async fn get_multi_signer(&mut self) -> Result>> { + pub async fn get_multi_signer(&mut self) -> Result> { if self.multi_signer.is_none() { self.multi_signer = Some(self.build_multi_signer().await?); } diff --git a/mithril-aggregator/src/dependency_injection/containers.rs b/mithril-aggregator/src/dependency_injection/containers.rs index e89798084c6..16266a44a18 100644 --- a/mithril-aggregator/src/dependency_injection/containers.rs +++ b/mithril-aggregator/src/dependency_injection/containers.rs @@ -36,9 +36,6 @@ use crate::{ SignerRegistrationRoundOpener, Snapshotter, VerificationKeyStorer, }; -/// MultiSignerWrapper wraps a [MultiSigner] -pub type MultiSignerWrapper = Arc>; - /// EpochServiceWrapper wraps a [EpochService] pub type EpochServiceWrapper = Arc>; @@ -67,7 +64,7 @@ pub struct DependencyContainer { pub snapshot_uploader: Arc, /// Multisigner service. - pub multi_signer: MultiSignerWrapper, + pub multi_signer: Arc, /// Certificate pending store. pub certificate_pending_store: Arc, diff --git a/mithril-aggregator/src/services/certifier/certifier_service.rs b/mithril-aggregator/src/services/certifier/certifier_service.rs index 11d945490c2..d477df6e44d 100644 --- a/mithril-aggregator/src/services/certifier/certifier_service.rs +++ b/mithril-aggregator/src/services/certifier/certifier_service.rs @@ -4,7 +4,6 @@ use chrono::Utc; use slog::Logger; use slog_scope::{debug, info, trace, warn}; use std::sync::Arc; -use tokio::sync::RwLock; use mithril_common::certificate_chain::CertificateVerifier; use mithril_common::crypto_helper::{ProtocolGenesisVerifier, PROTOCOL_VERSION}; @@ -31,7 +30,7 @@ pub struct MithrilCertifierService { certificate_repository: Arc, certificate_verifier: Arc, genesis_verifier: Arc, - multi_signer: Arc>, + multi_signer: Arc, // todo: should be removed after removing immutable file number from the certificate metadata ticker_service: Arc, epoch_service: EpochServiceWrapper, @@ -48,7 +47,7 @@ impl MithrilCertifierService { certificate_repository: Arc, certificate_verifier: Arc, genesis_verifier: Arc, - multi_signer: Arc>, + multi_signer: Arc, ticker_service: Arc, epoch_service: EpochServiceWrapper, logger: Logger, @@ -129,8 +128,7 @@ impl CertifierService for MithrilCertifierService { return Err(CertifierServiceError::Expired(signed_entity_type.clone()).into()); } - let multi_signer = self.multi_signer.read().await; - multi_signer + self.multi_signer .verify_single_signature(&open_message.protocol_message, signature) .await .map_err(|err| { @@ -241,8 +239,11 @@ impl CertifierService for MithrilCertifierService { return Err(CertifierServiceError::Expired(signed_entity_type.clone()).into()); } - let multi_signer = self.multi_signer.read().await; - let multi_signature = match multi_signer.create_multi_signature(&open_message).await? { + let multi_signature = match self + .multi_signer + .create_multi_signature(&open_message) + .await? + { None => { debug!("CertifierService::create_certificate: No multi-signature could be created for open message {signed_entity_type:?}"); return Ok(None); @@ -383,6 +384,7 @@ mod tests { entities::{CardanoDbBeacon, ProtocolMessagePartKey}, test_utils::{fake_data, MithrilFixture, MithrilFixtureBuilder}, }; + use tokio::sync::RwLock; use super::*; @@ -825,7 +827,7 @@ mod tests { let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); let mut certifier_service = setup_certifier_service(&fixture, &epochs_with_signers, None).await; - certifier_service.multi_signer = Arc::new(RwLock::new(mock_multi_signer)); + certifier_service.multi_signer = Arc::new(mock_multi_signer); certifier_service .create_open_message(&signed_entity_type, &protocol_message) .await From 9aac98661bc102c47376bcec3df15bcbb95b0f1f Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:27:19 +0200 Subject: [PATCH 22/43] Add `AsMessage` trait to allow multiple type to be sign or verified --- .../certificate_chain/certificate_genesis.rs | 10 ++++++---- .../src/entities/protocol_message.rs | 8 ++++++++ mithril-common/src/protocol/mod.rs | 18 +++++++++++++++++ mithril-common/src/protocol/multi_signer.rs | 20 +++++++++---------- mithril-common/src/protocol/single_signer.rs | 7 ++++--- .../src/test_utils/mithril_fixture.rs | 12 +++++------ 6 files changed, 51 insertions(+), 24 deletions(-) diff --git a/mithril-common/src/certificate_chain/certificate_genesis.rs b/mithril-common/src/certificate_chain/certificate_genesis.rs index 6774c2c1aaa..e7e91ad7509 100644 --- a/mithril-common/src/certificate_chain/certificate_genesis.rs +++ b/mithril-common/src/certificate_chain/certificate_genesis.rs @@ -14,7 +14,9 @@ use crate::{ Certificate, CertificateMetadata, CertificateSignature, Epoch, ImmutableFileNumber, ProtocolMessage, ProtocolMessagePartKey, ProtocolParameters, }, - era_deprecate, StdResult, + era_deprecate, + protocol::AsMessage, + StdResult, }; /// [CertificateGenesisProducer] related errors. @@ -51,15 +53,15 @@ impl CertificateGenesisProducer { } /// Sign the Genesis protocol message (test only) - pub fn sign_genesis_protocol_message( + pub fn sign_genesis_protocol_message( &self, - genesis_protocol_message: ProtocolMessage, + genesis_message: T, ) -> Result { Ok(self .genesis_signer .as_ref() .ok_or_else(CertificateGenesisProducerError::MissingGenesisSigner)? - .sign(genesis_protocol_message.compute_hash().as_bytes())) + .sign(genesis_message.message_string().as_bytes())) } era_deprecate!("Remove immutable_file_number"); diff --git a/mithril-common/src/entities/protocol_message.rs b/mithril-common/src/entities/protocol_message.rs index 4eb3d70b1b2..2c27c6df74f 100644 --- a/mithril-common/src/entities/protocol_message.rs +++ b/mithril-common/src/entities/protocol_message.rs @@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::{collections::BTreeMap, fmt::Display}; +use crate::protocol::AsMessage; + /// The key of a ProtocolMessage #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub enum ProtocolMessagePartKey { @@ -95,6 +97,12 @@ impl ProtocolMessage { } } +impl AsMessage for ProtocolMessage { + fn message_string(&self) -> String { + self.compute_hash() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/mithril-common/src/protocol/mod.rs b/mithril-common/src/protocol/mod.rs index 860d3ab90f0..9366191ee31 100644 --- a/mithril-common/src/protocol/mod.rs +++ b/mithril-common/src/protocol/mod.rs @@ -11,3 +11,21 @@ mod single_signer; pub use multi_signer::MultiSigner; pub use signer_builder::{SignerBuilder, SignerBuilderError}; pub use single_signer::SingleSigner; + +/// Trait to convert a type to a message that can be signed or verified by the Mithril protocol. +pub trait AsMessage: Sync + Send { + /// Return a String representation of the message. + fn message_string(&self) -> String; +} + +impl AsMessage for String { + fn message_string(&self) -> String { + self.clone() + } +} + +impl AsMessage for &str { + fn message_string(&self) -> String { + self.to_string() + } +} diff --git a/mithril-common/src/protocol/multi_signer.rs b/mithril-common/src/protocol/multi_signer.rs index 994b705a3db..df2ddf49313 100644 --- a/mithril-common/src/protocol/multi_signer.rs +++ b/mithril-common/src/protocol/multi_signer.rs @@ -6,7 +6,8 @@ use crate::{ ProtocolAggregateVerificationKey, ProtocolAggregationError, ProtocolClerk, ProtocolMultiSignature, }, - entities::{ProtocolMessage, SingleSignatures}, + entities::SingleSignatures, + protocol::AsMessage, StdResult, }; @@ -25,10 +26,10 @@ impl MultiSigner { } /// Aggregate the given single signatures into a multi-signature - pub fn aggregate_single_signatures( + pub fn aggregate_single_signatures( &self, single_signatures: &[SingleSignatures], - protocol_message: &ProtocolMessage, + message: &T, ) -> Result { let protocol_signatures: Vec<_> = single_signatures .iter() @@ -36,10 +37,7 @@ impl MultiSigner { .collect(); self.protocol_clerk - .aggregate( - &protocol_signatures, - protocol_message.compute_hash().as_bytes(), - ) + .aggregate(&protocol_signatures, message.message_string().as_bytes()) .map(|multi_sig| multi_sig.into()) } @@ -49,9 +47,9 @@ impl MultiSigner { } /// Verify a single signature - pub fn verify_single_signature( + pub fn verify_single_signature( &self, - message: &ProtocolMessage, + message: &T, single_signature: &SingleSignatures, ) -> StdResult<()> { let protocol_signature = single_signature.to_protocol_signature(); @@ -76,7 +74,7 @@ impl MultiSigner { &vk, &stake, &avk, - message.compute_hash().as_bytes(), + message.message_string().as_bytes(), ) .with_context(|| { format!( @@ -94,7 +92,7 @@ mod test { use mithril_stm::StmSignatureError; use crate::{ - entities::{ProtocolMessagePartKey, ProtocolParameters}, + entities::{ProtocolMessage, ProtocolMessagePartKey, ProtocolParameters}, protocol::SignerBuilder, test_utils::fake_keys, test_utils::{MithrilFixture, MithrilFixtureBuilder, StakeDistributionGenerationMethod}, diff --git a/mithril-common/src/protocol/single_signer.rs b/mithril-common/src/protocol/single_signer.rs index 97a430e2642..dce40c6cea3 100644 --- a/mithril-common/src/protocol/single_signer.rs +++ b/mithril-common/src/protocol/single_signer.rs @@ -1,6 +1,7 @@ use crate::{ crypto_helper::ProtocolSigner, - entities::{PartyId, ProtocolMessage, SingleSignatures}, + entities::{PartyId, SingleSignatures}, + protocol::AsMessage, StdResult, }; @@ -22,8 +23,8 @@ impl SingleSigner { /// Issue a single signature for the given message. /// /// If no lottery are won None will be returned. - pub fn sign(&self, message: &ProtocolMessage) -> StdResult> { - let signed_message = message.compute_hash(); + pub fn sign(&self, message: &T) -> StdResult> { + let signed_message = message.message_string(); match self.protocol_signer.sign(signed_message.as_bytes()) { Some(signature) => { let won_indexes = signature.indexes.clone(); diff --git a/mithril-common/src/test_utils/mithril_fixture.rs b/mithril-common/src/test_utils/mithril_fixture.rs index f473d7042af..2901363923e 100644 --- a/mithril-common/src/test_utils/mithril_fixture.rs +++ b/mithril-common/src/test_utils/mithril_fixture.rs @@ -15,10 +15,10 @@ use crate::{ }, entities::{ Certificate, Epoch, HexEncodedAggregateVerificationKey, ImmutableFileNumber, PartyId, - ProtocolMessage, ProtocolParameters, Signer, SignerWithStake, SingleSignatures, Stake, - StakeDistribution, StakeDistributionParty, + ProtocolParameters, Signer, SignerWithStake, SingleSignatures, Stake, StakeDistribution, + StakeDistributionParty, }, - protocol::SignerBuilder, + protocol::{AsMessage, SignerBuilder}, }; /// A fixture of Mithril data types. @@ -199,7 +199,7 @@ impl MithrilFixture { /// Make all underlying signers sign the given message, filter the resulting list to remove /// the signers that did not sign because they loosed the lottery. - pub fn sign_all(&self, message: &ProtocolMessage) -> Vec { + pub fn sign_all(&self, message: &T) -> Vec { self.signers .par_iter() .filter_map(|s| s.sign(message)) @@ -227,9 +227,9 @@ impl From for Vec { impl SignerFixture { /// Sign the given protocol message. - pub fn sign(&self, protocol_message: &ProtocolMessage) -> Option { + pub fn sign(&self, message: &T) -> Option { self.protocol_signer - .sign(protocol_message.compute_hash().as_bytes()) + .sign(message.message_string().as_bytes()) .map(|signature| { let won_indexes = signature.indexes.clone(); From 44ad88be4e093eb67aef7e9daadf35844b08b1df Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:43:56 +0200 Subject: [PATCH 23/43] Make aggregator multi signer able to verify signature issued for the next epoch Meaning that the `EpochService` must store it (trivial since we already built one to compute the next avk). Also in order to be able to verify sigend_message of single signatures the multisigner verify methods had to change to take string instead of protocol message (we can't use the `AsMessage` trait as this will prevent the `MultiSigner` from being made into an object). --- mithril-aggregator/src/multi_signer.rs | 136 +++++++++++++----- .../services/certifier/certifier_service.rs | 3 +- .../src/services/epoch_service.rs | 35 ++++- 3 files changed, 134 insertions(+), 40 deletions(-) diff --git a/mithril-aggregator/src/multi_signer.rs b/mithril-aggregator/src/multi_signer.rs index a001b8e3dc7..52aa75387fe 100644 --- a/mithril-aggregator/src/multi_signer.rs +++ b/mithril-aggregator/src/multi_signer.rs @@ -5,6 +5,7 @@ use slog_scope::{debug, warn}; use mithril_common::{ crypto_helper::{ProtocolAggregationError, ProtocolMultiSignature}, entities::{self}, + protocol::MultiSigner as ProtocolMultiSigner, StdResult, }; @@ -21,7 +22,14 @@ pub trait MultiSigner: Sync + Send { /// Verify a single signature async fn verify_single_signature( &self, - message: &entities::ProtocolMessage, + message: &str, + signatures: &entities::SingleSignatures, + ) -> StdResult<()>; + + /// Verify a single signature using the stake distribution of the next epoch + async fn verify_single_signature_for_next_epoch( + &self, + message: &str, signatures: &entities::SingleSignatures, ) -> StdResult<()>; @@ -43,31 +51,56 @@ impl MultiSignerImpl { debug!("New MultiSignerImpl created"); Self { epoch_service } } -} -#[async_trait] -impl MultiSigner for MultiSignerImpl { - /// Verify a single signature - async fn verify_single_signature( + fn run_verify_single_signature( &self, - message: &entities::ProtocolMessage, + message: &str, single_signature: &entities::SingleSignatures, + protocol_multi_signer: &ProtocolMultiSigner, ) -> StdResult<()> { debug!( "Verify single signature from {} at indexes {:?} for message {:?}", single_signature.party_id, single_signature.won_indexes, message ); + protocol_multi_signer + .verify_single_signature(&message, single_signature) + .with_context(|| { + format!("Multi Signer can not verify single signature for message '{message:?}'") + }) + } +} + +#[async_trait] +impl MultiSigner for MultiSignerImpl { + /// Verify a single signature + async fn verify_single_signature( + &self, + message: &str, + single_signature: &entities::SingleSignatures, + ) -> StdResult<()> { let epoch_service = self.epoch_service.read().await; let protocol_multi_signer = epoch_service.protocol_multi_signer().with_context(|| { "Multi Signer could not get protocol multi-signer from epoch service" })?; - protocol_multi_signer - .verify_single_signature(message, single_signature) - .with_context(|| { - format!("Multi Signer can not verify single signature for message '{message:?}'") - }) + self.run_verify_single_signature(message, single_signature, protocol_multi_signer) + } + + async fn verify_single_signature_for_next_epoch( + &self, + message: &str, + single_signature: &entities::SingleSignatures, + ) -> StdResult<()> { + let epoch_service = self.epoch_service.read().await; + let next_protocol_multi_signer = + epoch_service + .next_protocol_multi_signer() + .with_context(|| { + "Multi Signer could not get next protocol multi-signer from epoch service" + })?; + + self.run_verify_single_signature(message, single_signature, next_protocol_multi_signer) } /// Creates a multi signature from single signatures @@ -101,17 +134,18 @@ impl MultiSigner for MultiSignerImpl { #[cfg(test)] mod tests { - use super::*; - use crate::services::FakeEpochService; - use mithril_common::entities::SignerWithStake; - use mithril_common::{ - crypto_helper::tests_setup::*, - entities::{CardanoDbBeacon, Epoch, SignedEntityType}, - test_utils::{fake_data, MithrilFixtureBuilder}, - }; use std::sync::Arc; use tokio::sync::RwLock; + use mithril_common::crypto_helper::tests_setup::*; + use mithril_common::entities::{CardanoDbBeacon, Epoch, SignedEntityType, SignerWithStake}; + use mithril_common::protocol::AsMessage; + use mithril_common::test_utils::{fake_data, MithrilFixtureBuilder}; + + use crate::services::FakeEpochService; + + use super::*; + fn take_signatures_until_quorum_is_almost_reached( signatures: &mut Vec, quorum: usize, @@ -132,6 +166,52 @@ mod tests { result } + #[tokio::test] + async fn test_verify_single_signature() { + let epoch = Epoch(5); + let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); + let next_fixture = MithrilFixtureBuilder::default().with_signers(4).build(); + let multi_signer = + MultiSignerImpl::new(Arc::new(RwLock::new(FakeEpochService::with_data( + epoch, + &fixture.protocol_parameters(), + &next_fixture.protocol_parameters(), + &next_fixture.protocol_parameters(), + &fixture.signers_with_stake(), + &next_fixture.signers_with_stake(), + )))); + + { + let message = setup_message(); + let signature = fixture.signers_fixture()[0].sign(&message).unwrap(); + + multi_signer + .verify_single_signature(&message.message_string(), &signature) + .await + .unwrap(); + + multi_signer.verify_single_signature_for_next_epoch(&message.message_string(), &signature).await.expect_err( + "single signature issued in the current epoch should not be valid for the next epoch", + ); + } + { + let message = setup_message(); + let next_epoch_signature = next_fixture.signers_fixture()[0].sign(&message).unwrap(); + + multi_signer + .verify_single_signature_for_next_epoch( + &message.message_string(), + &next_epoch_signature, + ) + .await + .unwrap(); + + multi_signer.verify_single_signature(&message.message_string(), &next_epoch_signature).await.expect_err( + "single signature issued in the next epoch should not be valid for the current epoch", + ); + } + } + #[tokio::test] async fn test_multi_signer_multi_signature_ok() { let epoch = Epoch(5); @@ -147,25 +227,15 @@ mod tests { let mut expected_certificate_signers: Vec = Vec::new(); for signer_fixture in fixture.signers_fixture() { - if let Some(signature) = signer_fixture - .protocol_signer - .sign(message.compute_hash().as_bytes()) - { - let won_indexes = signature.indexes.clone(); - - signatures.push(entities::SingleSignatures::new( - signer_fixture.signer_with_stake.party_id.to_owned(), - signature.into(), - won_indexes, - )); - + if let Some(signature) = signer_fixture.sign(&message) { + signatures.push(signature); expected_certificate_signers.push(signer_fixture.signer_with_stake.to_owned()) } } for signature in &signatures { multi_signer - .verify_single_signature(&message, signature) + .verify_single_signature(&message.message_string(), signature) .await .expect("single signature should be valid"); } diff --git a/mithril-aggregator/src/services/certifier/certifier_service.rs b/mithril-aggregator/src/services/certifier/certifier_service.rs index d477df6e44d..293a5d6b26f 100644 --- a/mithril-aggregator/src/services/certifier/certifier_service.rs +++ b/mithril-aggregator/src/services/certifier/certifier_service.rs @@ -11,6 +11,7 @@ use mithril_common::entities::{ Certificate, CertificateMetadata, CertificateSignature, Epoch, ProtocolMessage, SignedEntityType, SingleSignatures, StakeDistributionParty, }; +use mithril_common::protocol::AsMessage; use mithril_common::{CardanoNetwork, StdResult, TickerService}; use crate::database::record::{OpenMessageRecord, OpenMessageWithSingleSignaturesRecord}; @@ -129,7 +130,7 @@ impl CertifierService for MithrilCertifierService { } self.multi_signer - .verify_single_signature(&open_message.protocol_message, signature) + .verify_single_signature(&open_message.protocol_message.message_string(), signature) .await .map_err(|err| { CertifierServiceError::InvalidSingleSignature(signed_entity_type.clone(), err) diff --git a/mithril-aggregator/src/services/epoch_service.rs b/mithril-aggregator/src/services/epoch_service.rs index 187c6009097..e2926963670 100644 --- a/mithril-aggregator/src/services/epoch_service.rs +++ b/mithril-aggregator/src/services/epoch_service.rs @@ -78,6 +78,9 @@ pub trait EpochService: Sync + Send { /// Get the [protocol multi signer][ProtocolMultiSigner] for the current epoch fn protocol_multi_signer(&self) -> StdResult<&ProtocolMultiSigner>; + + /// Get the [protocol multi signer][ProtocolMultiSigner] for the next epoch + fn next_protocol_multi_signer(&self) -> StdResult<&ProtocolMultiSigner>; } struct EpochData { @@ -95,6 +98,7 @@ struct ComputedEpochData { aggregate_verification_key: ProtocolAggregateVerificationKey, next_aggregate_verification_key: ProtocolAggregateVerificationKey, protocol_multi_signer: ProtocolMultiSigner, + next_protocol_multi_signer: ProtocolMultiSigner, } /// Implementation of the [epoch service][EpochService]. @@ -267,6 +271,7 @@ impl EpochService for MithrilEpochService { next_aggregate_verification_key: next_protocol_multi_signer .compute_aggregate_verification_key(), protocol_multi_signer, + next_protocol_multi_signer, }); Ok(()) @@ -315,6 +320,10 @@ impl EpochService for MithrilEpochService { fn protocol_multi_signer(&self) -> StdResult<&ProtocolMultiSigner> { Ok(&self.unwrap_computed_data()?.protocol_multi_signer) } + + fn next_protocol_multi_signer(&self) -> StdResult<&ProtocolMultiSigner> { + Ok(&self.unwrap_computed_data()?.next_protocol_multi_signer) + } } #[cfg(test)] @@ -371,6 +380,7 @@ impl FakeEpochService { next_aggregate_verification_key: next_protocol_multi_signer .compute_aggregate_verification_key(), protocol_multi_signer, + next_protocol_multi_signer, }), inform_epoch_error: false, update_protocol_parameters_error: false, @@ -497,6 +507,10 @@ impl EpochService for FakeEpochService { fn protocol_multi_signer(&self) -> StdResult<&ProtocolMultiSigner> { Ok(&self.unwrap_computed_data()?.protocol_multi_signer) } + + fn next_protocol_multi_signer(&self) -> StdResult<&ProtocolMultiSigner> { + Ok(&self.unwrap_computed_data()?.next_protocol_multi_signer) + } } #[cfg(test)] @@ -738,15 +752,16 @@ mod tests { let avk = fixture.compute_avk(); let epoch = Epoch(4); let mut service = build_service(epoch, &fixture, &[]).await; + let signer_builder = SignerBuilder::new( + &fixture.signers_with_stake(), + &fixture.protocol_parameters(), + ) + .unwrap(); service.computed_epoch_data = Some(ComputedEpochData { aggregate_verification_key: avk.clone(), next_aggregate_verification_key: avk.clone(), - protocol_multi_signer: SignerBuilder::new( - &fixture.signers_with_stake(), - &fixture.protocol_parameters(), - ) - .unwrap() - .build_multi_signer(), + protocol_multi_signer: signer_builder.build_multi_signer(), + next_protocol_multi_signer: signer_builder.build_multi_signer(), }); service @@ -841,6 +856,10 @@ mod tests { "protocol_multi_signer", service.protocol_multi_signer().err(), ), + ( + "next_protocol_multi_signer", + service.next_protocol_multi_signer().err(), + ), ] { let error = res.unwrap_or_else(|| panic!("getting {name} should have returned an error")); @@ -881,6 +900,10 @@ mod tests { "protocol_multi_signer", service.protocol_multi_signer().err(), ), + ( + "next_protocol_multi_signer", + service.next_protocol_multi_signer().err(), + ), ] { let error = res.unwrap_or_else(|| panic!("getting {name} should have returned an error")); From 028cf29da6245ffa6181175483882aa7a5a1a13d Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:00:25 +0200 Subject: [PATCH 24/43] Check signature against message before buffering --- .../buffered_single_signature_record.rs | 23 +-- .../src/dependency_injection/builder.rs | 3 +- .../services/certifier/buffered_certifier.rs | 146 ++++++++++++++---- .../src/entities/single_signatures.rs | 33 ++++ .../src/test_utils/mithril_fixture.rs | 6 +- 5 files changed, 153 insertions(+), 58 deletions(-) diff --git a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs index e1433fca614..d0f987073e7 100644 --- a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs +++ b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs @@ -52,29 +52,8 @@ impl BufferedSingleSignatureRecord { // Note: due to the unique constraint on the signature column, we want to make sure that // the signatures are different for party_id/discriminant pairs. // We can't just reuse fake_data::single_signatures as they are static. - use mithril_common::entities::{ProtocolMessage, ProtocolParameters}; - use mithril_common::test_utils::{ - MithrilFixtureBuilder, StakeDistributionGenerationMethod, - }; - - let party_id = party_id.into(); - let fixture = MithrilFixtureBuilder::default() - .with_stake_distribution(StakeDistributionGenerationMethod::Custom( - std::collections::BTreeMap::from([(format!("{party_id}{discriminant}"), 100)]), - )) - .with_protocol_parameters(ProtocolParameters::new(1, 1, 1.0)) - .build(); - let signature = fixture.signers_fixture()[0] - .sign(&ProtocolMessage::default()) - .unwrap(); - Self::try_from_single_signatures( - &SingleSignatures { - party_id, - signature: signature.signature, - won_indexes: vec![10, 15], - signed_message: None, - }, + &SingleSignatures::fake_with_signed_message(party_id.into(), discriminant.to_string()), discriminant, ) .unwrap() diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index 9c9486dc4c4..b5084cb5a6e 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -1448,7 +1448,7 @@ impl DependenciesBuilder { certificate_repository, certificate_verifier, genesis_verifier, - multi_signer, + multi_signer.clone(), ticker_service, epoch_service, logger, @@ -1456,6 +1456,7 @@ impl DependenciesBuilder { Ok(Arc::new(BufferedCertifierService::new( certifier, + multi_signer, Arc::new(InMemoryBufferedSingleSignatureStore::default()), self.get_logger()?, ))) diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs index fb6e19d7d17..d4a5a06c1da 100644 --- a/mithril-aggregator/src/services/certifier/buffered_certifier.rs +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -10,6 +10,7 @@ use mithril_common::StdResult; use crate::entities::OpenMessage; use crate::services::{BufferedSingleSignatureStore, CertifierService, CertifierServiceError}; +use crate::MultiSigner; /// A decorator of [CertifierService] that buffers that can buffer registration of single signatures /// when the open message is not yet created. @@ -18,6 +19,7 @@ use crate::services::{BufferedSingleSignatureStore, CertifierService, CertifierS /// registered. pub struct BufferedCertifierService { certifier_service: Arc, + multi_signer: Arc, buffered_single_signature_store: Arc, logger: Logger, } @@ -26,11 +28,13 @@ impl BufferedCertifierService { /// Create a new instance of `BufferedCertifierService`. pub fn new( certifier_service: Arc, + multi_signer: Arc, buffered_single_signature_store: Arc, logger: Logger, ) -> Self { Self { certifier_service, + multi_signer, buffered_single_signature_store, logger, } @@ -97,19 +101,28 @@ impl CertifierService for BufferedCertifierService { Ok(res) => Ok(res), Err(error) => match error.downcast_ref::() { Some(CertifierServiceError::NotFound(..)) => { - debug!( - self.logger, - "No OpenMessage available for signed entity - Buffering single signature"; - "signed_entity_type" => ?signed_entity_type, - "party_id" => &signature.party_id - ); - - // validate signature - - self.buffered_single_signature_store - .buffer_signature(signed_entity_type.into(), signature) - .await?; - Ok(()) + match &signature.signed_message { + // Only buffer signatures that have a signed message so we can validate them + Some(signed_message) => { + debug!( + self.logger, + "No OpenMessage available for signed entity - Buffering single signature"; + "signed_entity_type" => ?signed_entity_type, + "party_id" => &signature.party_id + ); + + self.multi_signer + .verify_single_signature(signed_message, signature) + .await?; + + self.buffered_single_signature_store + .buffer_signature(signed_entity_type.into(), signature) + .await?; + + Ok(()) + } + None => Err(error), + } } _ => Err(error), }, @@ -198,6 +211,7 @@ mod tests { use mithril_common::test_utils::fake_data; + use crate::multi_signer::MockMultiSigner; use crate::services::{ CertifierServiceError, MockBufferedSingleSignatureStore, MockCertifierService, }; @@ -206,15 +220,22 @@ mod tests { use super::*; - fn mock_certifier(certifier_mock_config: F) -> Arc - where - F: FnOnce(&mut MockCertifierService), - { + fn mock_certifier( + certifier_mock_config: impl FnOnce(&mut MockCertifierService), + ) -> Arc { let mut certifier = MockCertifierService::new(); certifier_mock_config(&mut certifier); Arc::new(certifier) } + fn mock_multi_signer( + multi_signer_mock_config: impl FnOnce(&mut MockMultiSigner), + ) -> Arc { + let mut multi_signer = MockMultiSigner::new(); + multi_signer_mock_config(&mut multi_signer); + Arc::new(multi_signer) + } + fn mock_store(store_mock_config: F) -> Arc where F: FnOnce(&mut MockBufferedSingleSignatureStore), @@ -224,37 +245,94 @@ mod tests { Arc::new(store) } - #[tokio::test] - async fn buffer_signature_if_decorated_certifier_as_no_opened_message() { + /// Run a scenario where we try to register a signature (using a fixed signed entity type). + /// + /// Return the registration result and the list of buffered signatures after the registration. + async fn run_register_signature_scenario( + decorated_certifier_mock_config: impl FnOnce(&mut MockCertifierService), + multi_signer_mock_config: impl FnOnce(&mut MockMultiSigner), + signature_to_register: &SingleSignatures, + ) -> (StdResult<()>, Vec) { let store = Arc::new(InMemoryBufferedSingleSignatureStore::default()); let certifier = BufferedCertifierService::new( - mock_certifier(|mock| { - mock.expect_register_single_signature().returning(|_, _| { - Err(CertifierServiceError::NotFound( - SignedEntityType::MithrilStakeDistribution(Epoch(5)), - ) - .into()) - }); - }), + mock_certifier(decorated_certifier_mock_config), + mock_multi_signer(multi_signer_mock_config), store.clone(), TestLogger::stdout(), ); - certifier + let registration_result = certifier .register_single_signature( &SignedEntityType::MithrilStakeDistribution(Epoch(5)), - &fake_data::single_signatures(vec![1]), + signature_to_register, ) - .await - .unwrap(); + .await; let buffered_signatures = store .get_buffered_signatures(SignedEntityTypeDiscriminants::MithrilStakeDistribution) .await .unwrap(); + + (registration_result, buffered_signatures) + } + + #[tokio::test] + async fn buffer_signature_with_valid_signed_message_if_decorated_certifier_as_no_opened_message( + ) { + let signature = SingleSignatures::fake_with_signed_message("party_1", "a message"); + let (registration_result, buffered_signatures_after_registration) = + run_register_signature_scenario( + |mock_certifier| { + mock_certifier + .expect_register_single_signature() + .returning(|_, _| { + Err(CertifierServiceError::NotFound( + SignedEntityType::MithrilStakeDistribution(Epoch(5)), + ) + .into()) + }); + }, + |mock_multi_signer| { + mock_multi_signer + .expect_verify_single_signature() + .returning(|_, _| Ok(())); + }, + &signature, + ) + .await; + + registration_result.expect("Registration should have succeed"); + assert_eq!(buffered_signatures_after_registration, vec![signature]); + } + + #[tokio::test] + async fn dont_buffer_signature_without_signed_message_if_decorated_certifier_as_no_opened_message( + ) { + let signature = SingleSignatures { + signed_message: None, + ..SingleSignatures::fake_with_signed_message("party_1", "a message") + }; + let (registration_result, buffered_signatures_after_registration) = + run_register_signature_scenario( + |mock_certifier| { + mock_certifier + .expect_register_single_signature() + .returning(|_, _| { + Err(CertifierServiceError::NotFound( + SignedEntityType::MithrilStakeDistribution(Epoch(5)), + ) + .into()) + }); + }, + |_mock_multi_signer| {}, + &signature, + ) + .await; + + registration_result.expect_err("Registration should have failed"); assert_eq!( - buffered_signatures, - vec![fake_data::single_signatures(vec![1])] + buffered_signatures_after_registration, + Vec::::new() ); } @@ -296,6 +374,7 @@ mod tests { .once() .returning(|_, _| Ok(())); }), + mock_multi_signer(|_| {}), store.clone(), TestLogger::stdout(), ); @@ -327,6 +406,7 @@ mod tests { let store = mock_store(store_mock_config); let certifier = BufferedCertifierService::new( mock_certifier(certifier_mock_config), + mock_multi_signer(|_| {}), store, TestLogger::stdout(), ); diff --git a/mithril-common/src/entities/single_signatures.rs b/mithril-common/src/entities/single_signatures.rs index 958dfb15557..2fbc54c6503 100644 --- a/mithril-common/src/entities/single_signatures.rs +++ b/mithril-common/src/entities/single_signatures.rs @@ -65,6 +65,39 @@ impl SingleSignatures { } } +cfg_test_tools! { +impl SingleSignatures { + /// Create a fake [SingleSignatures] with valid cryptographic data for testing purposes. + /// + /// The embedded signature will be computed based on the given `message`. + /// + /// TODO: this method is slow due to the fixture creation, we should either make + /// the fixture faster or find a faster alternative. + pub fn fake_with_signed_message, T2: Into>(party_id: T1, message: T2) -> Self { + use crate::entities::{ProtocolParameters}; + use crate::test_utils::{MithrilFixtureBuilder, StakeDistributionGenerationMethod}; + + let party_id = party_id.into(); + let message = message.into(); + + let fixture = MithrilFixtureBuilder::default() + .with_stake_distribution(StakeDistributionGenerationMethod::Custom( + std::collections::BTreeMap::from([(party_id.to_string(), 100)]), + )) + .with_protocol_parameters(ProtocolParameters::new(1, 1, 1.0)) + .build(); + let signature = fixture.signers_fixture()[0].sign(&message).unwrap(); + + Self { + party_id, + signature: signature.signature, + won_indexes: vec![10, 15], + signed_message: Some(message), + } + } +} +} + impl Debug for SingleSignatures { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let is_pretty_printing = f.alternate(); diff --git a/mithril-common/src/test_utils/mithril_fixture.rs b/mithril-common/src/test_utils/mithril_fixture.rs index 2901363923e..f7ba68ae551 100644 --- a/mithril-common/src/test_utils/mithril_fixture.rs +++ b/mithril-common/src/test_utils/mithril_fixture.rs @@ -228,15 +228,17 @@ impl From for Vec { impl SignerFixture { /// Sign the given protocol message. pub fn sign(&self, message: &T) -> Option { + let message = message.message_string(); self.protocol_signer - .sign(message.message_string().as_bytes()) + .sign(message.as_bytes()) .map(|signature| { let won_indexes = signature.indexes.clone(); - SingleSignatures::new( + SingleSignatures::new_with_signed_message( self.signer_with_stake.party_id.to_owned(), signature.into(), won_indexes, + message, ) }) } From c7f0878b6a4ea5d309b224839bbb44b734f5a7ef Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:24:16 +0200 Subject: [PATCH 25/43] Check signature against message using next stake distribution If check using the current one fails, as a signer may detect epoch change before the aggregator and issue single signatures. --- .../services/certifier/buffered_certifier.rs | 199 ++++++++++++++---- 1 file changed, 155 insertions(+), 44 deletions(-) diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs index d4a5a06c1da..0ba41d2ae34 100644 --- a/mithril-aggregator/src/services/certifier/buffered_certifier.rs +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -111,15 +111,36 @@ impl CertifierService for BufferedCertifierService { "party_id" => &signature.party_id ); - self.multi_signer + if let Err(error) = self + .multi_signer .verify_single_signature(signed_message, signature) - .await?; + .await + { + // Signers may detect epoch changes before the aggregator and send + // new signatures using the next epoch stake distribution + debug!( + self.logger, + "Signature is invalid for current epoch stake distribution, trying next epoch"; + "validation_error" => ?error + ); + + self.multi_signer + .verify_single_signature_for_next_epoch( + signed_message, + signature, + ) + .await + .map_err(|err| { + CertifierServiceError::InvalidSingleSignature( + signed_entity_type.clone(), + err, + ) + })?; + } self.buffered_single_signature_store .buffer_signature(signed_entity_type.into(), signature) - .await?; - - Ok(()) + .await } None => Err(error), } @@ -277,7 +298,7 @@ mod tests { } #[tokio::test] - async fn buffer_signature_with_valid_signed_message_if_decorated_certifier_as_no_opened_message( + async fn when_registering_single_signature_dont_buffer_signature_if_decorated_certifier_succeed( ) { let signature = SingleSignatures::fake_with_signed_message("party_1", "a message"); let (registration_result, buffered_signatures_after_registration) = @@ -285,57 +306,147 @@ mod tests { |mock_certifier| { mock_certifier .expect_register_single_signature() - .returning(|_, _| { - Err(CertifierServiceError::NotFound( - SignedEntityType::MithrilStakeDistribution(Epoch(5)), - ) - .into()) - }); - }, - |mock_multi_signer| { - mock_multi_signer - .expect_verify_single_signature() .returning(|_, _| Ok(())); }, - &signature, - ) - .await; - - registration_result.expect("Registration should have succeed"); - assert_eq!(buffered_signatures_after_registration, vec![signature]); - } - - #[tokio::test] - async fn dont_buffer_signature_without_signed_message_if_decorated_certifier_as_no_opened_message( - ) { - let signature = SingleSignatures { - signed_message: None, - ..SingleSignatures::fake_with_signed_message("party_1", "a message") - }; - let (registration_result, buffered_signatures_after_registration) = - run_register_signature_scenario( - |mock_certifier| { - mock_certifier - .expect_register_single_signature() - .returning(|_, _| { - Err(CertifierServiceError::NotFound( - SignedEntityType::MithrilStakeDistribution(Epoch(5)), - ) - .into()) - }); - }, |_mock_multi_signer| {}, &signature, ) .await; - registration_result.expect_err("Registration should have failed"); + registration_result.expect("Registration should have succeed"); assert_eq!( buffered_signatures_after_registration, Vec::::new() ); } + mod when_registering_single_signature_if_decorated_certifier_as_no_opened_message { + use super::*; + + #[tokio::test] + async fn buffer_signature_with_valid_signed_message_for_current_epoch() { + let signature = SingleSignatures::fake_with_signed_message("party_1", "a message"); + let (registration_result, buffered_signatures_after_registration) = + run_register_signature_scenario( + |mock_certifier| { + mock_certifier + .expect_register_single_signature() + .returning(|_, _| { + Err(CertifierServiceError::NotFound( + SignedEntityType::MithrilStakeDistribution(Epoch(5)), + ) + .into()) + }); + }, + |mock_multi_signer| { + mock_multi_signer + .expect_verify_single_signature() + .returning(|_, _| Ok(())); + }, + &signature, + ) + .await; + + registration_result.expect("Registration should have succeed"); + assert_eq!(buffered_signatures_after_registration, vec![signature]); + } + + #[tokio::test] + async fn buffer_signature_with_valid_signed_message_for_next_epoch() { + let signature = SingleSignatures::fake_with_signed_message("party_1", "a message"); + let (registration_result, buffered_signatures_after_registration) = + run_register_signature_scenario( + |mock_certifier| { + mock_certifier + .expect_register_single_signature() + .returning(|_, _| { + Err(CertifierServiceError::NotFound( + SignedEntityType::MithrilStakeDistribution(Epoch(5)), + ) + .into()) + }); + }, + |mock_multi_signer| { + mock_multi_signer + .expect_verify_single_signature() + .returning(|_, _| Err(anyhow!("Invalid signature"))); + mock_multi_signer + .expect_verify_single_signature_for_next_epoch() + .returning(|_, _| Ok(())); + }, + &signature, + ) + .await; + + registration_result.expect("Registration should have succeed"); + assert_eq!(buffered_signatures_after_registration, vec![signature]); + } + + #[tokio::test] + async fn dont_buffer_signature_with_invalid_signed_message() { + let signature = SingleSignatures::fake_with_signed_message("party_1", "a message"); + let (registration_result, buffered_signatures_after_registration) = + run_register_signature_scenario( + |mock_certifier| { + mock_certifier + .expect_register_single_signature() + .returning(|_, _| { + Err(CertifierServiceError::NotFound( + SignedEntityType::MithrilStakeDistribution(Epoch(5)), + ) + .into()) + }); + }, + |mock_multi_signer| { + mock_multi_signer + .expect_verify_single_signature() + .returning(|_, _| Err(anyhow!("Invalid signature for current epoch"))); + mock_multi_signer + .expect_verify_single_signature_for_next_epoch() + .returning(|_, _| Err(anyhow!("Invalid signature for next epoch"))); + }, + &signature, + ) + .await; + + registration_result.expect_err("Registration should have failed"); + assert_eq!( + buffered_signatures_after_registration, + Vec::::new() + ); + } + + #[tokio::test] + async fn dont_buffer_signature_without_signed_message() { + let signature = SingleSignatures { + signed_message: None, + ..SingleSignatures::fake_with_signed_message("party_1", "a message") + }; + let (registration_result, buffered_signatures_after_registration) = + run_register_signature_scenario( + |mock_certifier| { + mock_certifier + .expect_register_single_signature() + .returning(|_, _| { + Err(CertifierServiceError::NotFound( + SignedEntityType::MithrilStakeDistribution(Epoch(5)), + ) + .into()) + }); + }, + |_mock_multi_signer| {}, + &signature, + ) + .await; + + registration_result.expect_err("Registration should have failed"); + assert_eq!( + buffered_signatures_after_registration, + Vec::::new() + ); + } + } + #[tokio::test] async fn buffered_signatures_are_moved_to_newly_opened_message() { let store = Arc::new(InMemoryBufferedSingleSignatureStore::with_data( From a903d92cea34f1d5cbf4ab087ae25d0bdd32b9f9 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:49:36 +0200 Subject: [PATCH 26/43] Add a status to certifier signature registration This allow caller to know if the signature was registered for immediate use or buffered. --- .../http_server/routes/signatures_routes.rs | 46 ++++++++++++++++--- .../services/certifier/buffered_certifier.rs | 35 ++++++++------ .../services/certifier/certifier_service.rs | 6 +-- .../src/services/certifier/interface.rs | 12 ++++- openapi.yaml | 2 + 5 files changed, 77 insertions(+), 24 deletions(-) diff --git a/mithril-aggregator/src/http_server/routes/signatures_routes.rs b/mithril-aggregator/src/http_server/routes/signatures_routes.rs index 8e74c54710a..31a9bd06937 100644 --- a/mithril-aggregator/src/http_server/routes/signatures_routes.rs +++ b/mithril-aggregator/src/http_server/routes/signatures_routes.rs @@ -33,7 +33,7 @@ mod handlers { use crate::{ http_server::routes::reply, message_adapters::FromRegisterSingleSignatureAdapter, - services::{CertifierService, CertifierServiceError}, + services::{CertifierService, CertifierServiceError, RegistrationStatus}, }; /// Register Signatures @@ -76,7 +76,8 @@ mod handlers { Ok(reply::server_error(err)) } }, - Ok(()) => Ok(reply::empty(StatusCode::CREATED)), + Ok(RegistrationStatus::Registered) => Ok(reply::empty(StatusCode::CREATED)), + Ok(RegistrationStatus::Buffered) => Ok(reply::empty(StatusCode::ACCEPTED)), } } } @@ -95,7 +96,7 @@ mod tests { use crate::{ http_server::SERVER_BASE_PATH, initialize_dependencies, - services::{CertifierServiceError, MockCertifierService}, + services::{CertifierServiceError, MockCertifierService, RegistrationStatus}, }; use super::*; @@ -114,11 +115,11 @@ mod tests { } #[tokio::test] - async fn test_register_signatures_post_ok() { + async fn test_register_signatures_post_ok_201() { let mut mock_certifier_service = MockCertifierService::new(); mock_certifier_service .expect_register_single_signature() - .return_once(move |_, _| Ok(())); + .return_once(move |_, _| Ok(RegistrationStatus::Registered)); let mut dependency_manager = initialize_dependencies().await; dependency_manager.certifier_service = Arc::new(mock_certifier_service); @@ -146,12 +147,45 @@ mod tests { .unwrap(); } + #[tokio::test] + async fn test_register_signatures_post_ok_202() { + let mut mock_certifier_service = MockCertifierService::new(); + mock_certifier_service + .expect_register_single_signature() + .return_once(move |_, _| Ok(RegistrationStatus::Buffered)); + let mut dependency_manager = initialize_dependencies().await; + dependency_manager.certifier_service = Arc::new(mock_certifier_service); + + let message = RegisterSignatureMessage::dummy(); + + 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, + &StatusCode::ACCEPTED, + ) + .unwrap(); + } + #[tokio::test] async fn test_register_signatures_post_ko_400() { let mut mock_certifier_service = MockCertifierService::new(); mock_certifier_service .expect_register_single_signature() - .return_once(move |_, _| Ok(())); + .return_once(move |_, _| Ok(RegistrationStatus::Registered)); let mut dependency_manager = initialize_dependencies().await; dependency_manager.certifier_service = Arc::new(mock_certifier_service); diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs index 0ba41d2ae34..3fa076e58dc 100644 --- a/mithril-aggregator/src/services/certifier/buffered_certifier.rs +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -9,7 +9,9 @@ use mithril_common::entities::{ use mithril_common::StdResult; use crate::entities::OpenMessage; -use crate::services::{BufferedSingleSignatureStore, CertifierService, CertifierServiceError}; +use crate::services::{ + BufferedSingleSignatureStore, CertifierService, CertifierServiceError, RegistrationStatus, +}; use crate::MultiSigner; /// A decorator of [CertifierService] that buffers that can buffer registration of single signatures @@ -92,7 +94,7 @@ impl CertifierService for BufferedCertifierService { &self, signed_entity_type: &SignedEntityType, signature: &SingleSignatures, - ) -> StdResult<()> { + ) -> StdResult { match self .certifier_service .register_single_signature(signed_entity_type, signature) @@ -140,7 +142,9 @@ impl CertifierService for BufferedCertifierService { self.buffered_single_signature_store .buffer_signature(signed_entity_type.into(), signature) - .await + .await?; + + Ok(RegistrationStatus::Buffered) } None => Err(error), } @@ -273,7 +277,7 @@ mod tests { decorated_certifier_mock_config: impl FnOnce(&mut MockCertifierService), multi_signer_mock_config: impl FnOnce(&mut MockMultiSigner), signature_to_register: &SingleSignatures, - ) -> (StdResult<()>, Vec) { + ) -> (StdResult, Vec) { let store = Arc::new(InMemoryBufferedSingleSignatureStore::default()); let certifier = BufferedCertifierService::new( mock_certifier(decorated_certifier_mock_config), @@ -306,14 +310,15 @@ mod tests { |mock_certifier| { mock_certifier .expect_register_single_signature() - .returning(|_, _| Ok(())); + .returning(|_, _| Ok(RegistrationStatus::Registered)); }, |_mock_multi_signer| {}, &signature, ) .await; - registration_result.expect("Registration should have succeed"); + let status = registration_result.expect("Registration should have succeed"); + assert_eq!(status, RegistrationStatus::Registered); assert_eq!( buffered_signatures_after_registration, Vec::::new() @@ -347,7 +352,8 @@ mod tests { ) .await; - registration_result.expect("Registration should have succeed"); + let status = registration_result.expect("Registration should have succeed"); + assert_eq!(status, RegistrationStatus::Buffered); assert_eq!(buffered_signatures_after_registration, vec![signature]); } @@ -378,7 +384,8 @@ mod tests { ) .await; - registration_result.expect("Registration should have succeed"); + let status = registration_result.expect("Registration should have succeed"); + assert_eq!(status, RegistrationStatus::Buffered); assert_eq!(buffered_signatures_after_registration, vec![signature]); } @@ -476,14 +483,14 @@ mod tests { eq(fake_data::single_signatures(vec![1])), ) .once() - .returning(|_, _| Ok(())); + .returning(|_, _| Ok(RegistrationStatus::Registered)); mock.expect_register_single_signature() .with( eq(SignedEntityType::MithrilStakeDistribution(Epoch(5))), eq(fake_data::single_signatures(vec![2])), ) .once() - .returning(|_, _| Ok(())); + .returning(|_, _| Ok(RegistrationStatus::Registered)); }), mock_multi_signer(|_| {}), store.clone(), @@ -540,7 +547,7 @@ mod tests { mock.expect_register_single_signature() .with(always(), eq(fake_data::single_signatures(vec![1]))) - .returning(|_, _| Ok(())) + .returning(|_, _| Ok(RegistrationStatus::Registered)) .once(); mock.expect_register_single_signature() .with(always(), eq(fake_data::single_signatures(vec![2]))) @@ -554,7 +561,7 @@ mod tests { .once(); mock.expect_register_single_signature() .with(always(), eq(fake_data::single_signatures(vec![3]))) - .returning(|_, _| Ok(())) + .returning(|_, _| Ok(RegistrationStatus::Registered)) .once(); }, |mock| { @@ -581,7 +588,7 @@ mod tests { mock.expect_create_open_message() .returning(|_, _| Ok(OpenMessage::dummy())); mock.expect_register_single_signature() - .returning(|_, _| Ok(())); + .returning(|_, _| Ok(RegistrationStatus::Registered)); }, |mock| { mock.expect_get_buffered_signatures() @@ -615,7 +622,7 @@ mod tests { mock.expect_create_open_message() .returning(|_, _| Ok(OpenMessage::dummy())); mock.expect_register_single_signature() - .returning(|_, _| Ok(())); + .returning(|_, _| Ok(RegistrationStatus::Registered)); }, |mock| { mock.expect_get_buffered_signatures() diff --git a/mithril-aggregator/src/services/certifier/certifier_service.rs b/mithril-aggregator/src/services/certifier/certifier_service.rs index 293a5d6b26f..5d2fe9203c5 100644 --- a/mithril-aggregator/src/services/certifier/certifier_service.rs +++ b/mithril-aggregator/src/services/certifier/certifier_service.rs @@ -20,7 +20,7 @@ use crate::database::repository::{ }; use crate::dependency_injection::EpochServiceWrapper; use crate::entities::OpenMessage; -use crate::services::{CertifierService, CertifierServiceError}; +use crate::services::{CertifierService, CertifierServiceError, RegistrationStatus}; use crate::MultiSigner; /// Mithril CertifierService implementation @@ -105,7 +105,7 @@ impl CertifierService for MithrilCertifierService { &self, signed_entity_type: &SignedEntityType, signature: &SingleSignatures, - ) -> StdResult<()> { + ) -> StdResult { debug!("CertifierService::register_single_signature(signed_entity_type: {signed_entity_type:?}, single_signatures: {signature:?}"); trace!("CertifierService::register_single_signature"; "complete_single_signatures" => #?signature); @@ -143,7 +143,7 @@ impl CertifierService for MithrilCertifierService { 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(()) + Ok(RegistrationStatus::Registered) } async fn create_open_message( diff --git a/mithril-aggregator/src/services/certifier/interface.rs b/mithril-aggregator/src/services/certifier/interface.rs index be8bb3bdb0b..96701d31abb 100644 --- a/mithril-aggregator/src/services/certifier/interface.rs +++ b/mithril-aggregator/src/services/certifier/interface.rs @@ -51,6 +51,16 @@ pub enum CertifierServiceError { CouldNotFindLastCertificate, } +/// Status of a successful registration of a single signature. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum RegistrationStatus { + /// The signature was registered and will be used for the next certificate. + Registered, + + /// The signature was buffered, it will be used later. + Buffered, +} + /// ## CertifierService /// /// This service manages the open message and their beacon transitions. It can @@ -70,7 +80,7 @@ pub trait CertifierService: Sync + Send { &self, signed_entity_type: &SignedEntityType, signature: &SingleSignatures, - ) -> StdResult<()>; + ) -> 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 diff --git a/openapi.yaml b/openapi.yaml index 40822ccb130..0e6d2c44d50 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -567,6 +567,8 @@ paths: responses: "201": description: signatures registration succeeded + "202": + description: signatures registration buffered for later processing "400": description: signatures registration bad request content: From d9185fe05431d364f9619bfc31188f1a9c7b68d2 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Fri, 13 Sep 2024 15:02:10 +0200 Subject: [PATCH 27/43] Use sqlite store in buffered certifier tests In order to remove the InMemory store. Doing so required to be more precise with the tests signatures used. IE most"register_signature" tests needs a single signature that have a signed message but when reading them from the store the store doesn't keep the signed message leading to problems with some assert_eq. --- .../services/certifier/buffered_certifier.rs | 73 ++++++++++++------- .../src/entities/single_signatures.rs | 18 +++-- 2 files changed, 57 insertions(+), 34 deletions(-) diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs index 3fa076e58dc..579a1701458 100644 --- a/mithril-aggregator/src/services/certifier/buffered_certifier.rs +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -232,16 +232,19 @@ impl CertifierService for BufferedCertifierService { mod tests { use anyhow::anyhow; use mockall::predicate::eq; - use std::collections::BTreeMap; + use mithril_common::entities::SignedEntityTypeDiscriminants::{ + CardanoTransactions, MithrilStakeDistribution, + }; use mithril_common::test_utils::fake_data; + use crate::database::repository::BufferedSingleSignatureRepository; + use crate::database::test_helper::main_db_connection; use crate::multi_signer::MockMultiSigner; use crate::services::{ CertifierServiceError, MockBufferedSingleSignatureStore, MockCertifierService, }; use crate::test_tools::TestLogger; - use crate::InMemoryBufferedSingleSignatureStore; use super::*; @@ -278,7 +281,9 @@ mod tests { multi_signer_mock_config: impl FnOnce(&mut MockMultiSigner), signature_to_register: &SingleSignatures, ) -> (StdResult, Vec) { - let store = Arc::new(InMemoryBufferedSingleSignatureStore::default()); + let store = Arc::new(BufferedSingleSignatureRepository::new(Arc::new( + main_db_connection().unwrap(), + ))); let certifier = BufferedCertifierService::new( mock_certifier(decorated_certifier_mock_config), mock_multi_signer(multi_signer_mock_config), @@ -294,7 +299,7 @@ mod tests { .await; let buffered_signatures = store - .get_buffered_signatures(SignedEntityTypeDiscriminants::MithrilStakeDistribution) + .get_buffered_signatures(MithrilStakeDistribution) .await .unwrap(); @@ -330,7 +335,6 @@ mod tests { #[tokio::test] async fn buffer_signature_with_valid_signed_message_for_current_epoch() { - let signature = SingleSignatures::fake_with_signed_message("party_1", "a message"); let (registration_result, buffered_signatures_after_registration) = run_register_signature_scenario( |mock_certifier| { @@ -348,18 +352,20 @@ mod tests { .expect_verify_single_signature() .returning(|_, _| Ok(())); }, - &signature, + &SingleSignatures::fake_with_signed_message("party_1", "a message"), ) .await; let status = registration_result.expect("Registration should have succeed"); assert_eq!(status, RegistrationStatus::Buffered); - assert_eq!(buffered_signatures_after_registration, vec![signature]); + assert_eq!( + buffered_signatures_after_registration, + vec![SingleSignatures::fake("party_1", "a message")] + ); } #[tokio::test] async fn buffer_signature_with_valid_signed_message_for_next_epoch() { - let signature = SingleSignatures::fake_with_signed_message("party_1", "a message"); let (registration_result, buffered_signatures_after_registration) = run_register_signature_scenario( |mock_certifier| { @@ -380,13 +386,16 @@ mod tests { .expect_verify_single_signature_for_next_epoch() .returning(|_, _| Ok(())); }, - &signature, + &SingleSignatures::fake_with_signed_message("party_1", "a message"), ) .await; let status = registration_result.expect("Registration should have succeed"); assert_eq!(status, RegistrationStatus::Buffered); - assert_eq!(buffered_signatures_after_registration, vec![signature]); + assert_eq!( + buffered_signatures_after_registration, + vec![SingleSignatures::fake("party_1", "a message")] + ); } #[tokio::test] @@ -456,21 +465,29 @@ mod tests { #[tokio::test] async fn buffered_signatures_are_moved_to_newly_opened_message() { - let store = Arc::new(InMemoryBufferedSingleSignatureStore::with_data( - BTreeMap::from([ - ( - SignedEntityTypeDiscriminants::MithrilStakeDistribution, - vec![ - fake_data::single_signatures(vec![1]), - fake_data::single_signatures(vec![2]), - ], - ), - ( - SignedEntityTypeDiscriminants::CardanoTransactions, - vec![fake_data::single_signatures(vec![10])], - ), - ]), - )); + let store = Arc::new(BufferedSingleSignatureRepository::new(Arc::new( + main_db_connection().unwrap(), + ))); + for (signed_type, signature) in [ + ( + MithrilStakeDistribution, + SingleSignatures::fake("party_1", "message 1"), + ), + ( + MithrilStakeDistribution, + SingleSignatures::fake("party_2", "message 2"), + ), + ( + CardanoTransactions, + SingleSignatures::fake("party_3", "message 3"), + ), + ] { + store + .buffer_signature(signed_type, &signature) + .await + .unwrap(); + } + let certifier = BufferedCertifierService::new( mock_certifier(|mock| { mock.expect_create_open_message() @@ -480,14 +497,14 @@ mod tests { mock.expect_register_single_signature() .with( eq(SignedEntityType::MithrilStakeDistribution(Epoch(5))), - eq(fake_data::single_signatures(vec![1])), + eq(SingleSignatures::fake("party_1", "message 1")), ) .once() .returning(|_, _| Ok(RegistrationStatus::Registered)); mock.expect_register_single_signature() .with( eq(SignedEntityType::MithrilStakeDistribution(Epoch(5))), - eq(fake_data::single_signatures(vec![2])), + eq(SingleSignatures::fake("party_2", "message 2")), ) .once() .returning(|_, _| Ok(RegistrationStatus::Registered)); @@ -506,7 +523,7 @@ mod tests { .unwrap(); let remaining_sigs = store - .get_buffered_signatures(SignedEntityTypeDiscriminants::MithrilStakeDistribution) + .get_buffered_signatures(MithrilStakeDistribution) .await .unwrap(); assert!(remaining_sigs.is_empty()); diff --git a/mithril-common/src/entities/single_signatures.rs b/mithril-common/src/entities/single_signatures.rs index 2fbc54c6503..b0b54a4aa26 100644 --- a/mithril-common/src/entities/single_signatures.rs +++ b/mithril-common/src/entities/single_signatures.rs @@ -68,11 +68,16 @@ impl SingleSignatures { cfg_test_tools! { impl SingleSignatures { /// Create a fake [SingleSignatures] with valid cryptographic data for testing purposes. - /// - /// The embedded signature will be computed based on the given `message`. - /// - /// TODO: this method is slow due to the fixture creation, we should either make - /// the fixture faster or find a faster alternative. + pub fn fake, T2: Into>(party_id: T1, message: T2) -> Self { + Self { + signed_message: None, + ..Self::fake_with_signed_message(party_id, message) + } + } + + /// Create a fake [SingleSignatures] with valid cryptographic data for testing purposes. + // TODO: this method is slow due to the fixture creation, we should either make + // the fixture faster or find a faster alternative. pub fn fake_with_signed_message, T2: Into>(party_id: T1, message: T2) -> Self { use crate::entities::{ProtocolParameters}; use crate::test_utils::{MithrilFixtureBuilder, StakeDistributionGenerationMethod}; @@ -104,7 +109,8 @@ impl Debug for SingleSignatures { let mut debug = f.debug_struct("SingleSignatures"); debug .field("party_id", &self.party_id) - .field("won_indexes", &format_args!("{:?}", self.won_indexes)); + .field("won_indexes", &format_args!("{:?}", self.won_indexes)) + .field("signed_message", &self.signed_message); match is_pretty_printing { true => debug From 34a7219a6bb12976c98b5a8593035f4ccfd30441 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Fri, 13 Sep 2024 15:12:04 +0200 Subject: [PATCH 28/43] Use sqlite buffer store everywhere and remove in memory version As it has no more use. --- .../src/dependency_injection/builder.rs | 21 ++- mithril-aggregator/src/lib.rs | 3 +- .../store/buffered_single_signatures_store.rs | 167 ------------------ mithril-aggregator/src/store/mod.rs | 2 - 4 files changed, 11 insertions(+), 182 deletions(-) delete mode 100644 mithril-aggregator/src/store/buffered_single_signatures_store.rs diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index b5084cb5a6e..cdbf68e0bd7 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -59,9 +59,9 @@ use crate::{ }, configuration::ExecutionEnvironment, database::repository::{ - CertificateRepository, EpochSettingStore, OpenMessageRepository, SignedEntityStore, - SignedEntityStorer, SignerRegistrationStore, SignerStore, SingleSignatureRepository, - StakePoolStore, + BufferedSingleSignatureRepository, CertificateRepository, EpochSettingStore, + OpenMessageRepository, SignedEntityStore, SignedEntityStorer, SignerRegistrationStore, + SignerStore, SingleSignatureRepository, StakePoolStore, }, event_store::{EventMessage, EventStore, TransmitterService}, http_server::routes::router, @@ -75,10 +75,9 @@ use crate::{ tools::{CExplorerSignerRetriever, GcpFileUploader, GenesisToolsDependency, SignersImporter}, AggregatorConfig, AggregatorRunner, AggregatorRuntime, CertificatePendingStore, CompressedArchiveSnapshotter, Configuration, DependencyContainer, DumbSnapshotUploader, - DumbSnapshotter, InMemoryBufferedSingleSignatureStore, LocalSnapshotUploader, - MithrilSignerRegisterer, MultiSigner, MultiSignerImpl, ProtocolParametersStorer, - RemoteSnapshotUploader, SnapshotUploader, SnapshotUploaderType, Snapshotter, - SnapshotterCompressionAlgorithm, VerificationKeyStorer, + DumbSnapshotter, LocalSnapshotUploader, MithrilSignerRegisterer, MultiSigner, MultiSignerImpl, + ProtocolParametersStorer, RemoteSnapshotUploader, SnapshotUploader, SnapshotUploaderType, + Snapshotter, SnapshotterCompressionAlgorithm, VerificationKeyStorer, }; const SQLITE_FILE: &str = "aggregator.sqlite3"; @@ -1429,10 +1428,10 @@ impl DependenciesBuilder { let cardano_network = self.configuration.get_network().with_context(|| { "Dependencies Builder can not get Cardano network while building the chain observer" })?; + let sqlite_connection = self.get_sqlite_connection().await?; let open_message_repository = self.get_open_message_repository().await?; - let single_signature_repository = Arc::new(SingleSignatureRepository::new( - self.get_sqlite_connection().await?, - )); + let single_signature_repository = + Arc::new(SingleSignatureRepository::new(sqlite_connection.clone())); let certificate_repository = self.get_certificate_repository().await?; let certificate_verifier = self.get_certificate_verifier().await?; let genesis_verifier = self.get_genesis_verifier().await?; @@ -1457,7 +1456,7 @@ impl DependenciesBuilder { Ok(Arc::new(BufferedCertifierService::new( certifier, multi_signer, - Arc::new(InMemoryBufferedSingleSignatureStore::default()), + Arc::new(BufferedSingleSignatureRepository::new(sqlite_connection)), self.get_logger()?, ))) } diff --git a/mithril-aggregator/src/lib.rs b/mithril-aggregator/src/lib.rs index 46fd274f1fd..baa5318d921 100644 --- a/mithril-aggregator/src/lib.rs +++ b/mithril-aggregator/src/lib.rs @@ -53,8 +53,7 @@ pub use snapshotter::{ SnapshotterCompressionAlgorithm, }; pub use store::{ - CertificatePendingStore, InMemoryBufferedSingleSignatureStore, ProtocolParametersStorer, - VerificationKeyStore, VerificationKeyStorer, + CertificatePendingStore, ProtocolParametersStorer, VerificationKeyStore, VerificationKeyStorer, }; pub use tools::{ CExplorerSignerRetriever, SignersImporter, SignersImporterPersister, SignersImporterRetriever, diff --git a/mithril-aggregator/src/store/buffered_single_signatures_store.rs b/mithril-aggregator/src/store/buffered_single_signatures_store.rs deleted file mode 100644 index 7cfdca83423..00000000000 --- a/mithril-aggregator/src/store/buffered_single_signatures_store.rs +++ /dev/null @@ -1,167 +0,0 @@ -use async_trait::async_trait; -use std::collections::BTreeMap; -use tokio::sync::RwLock; - -use mithril_common::entities::{SignedEntityTypeDiscriminants, SingleSignatures}; -use mithril_common::StdResult; - -use crate::services::BufferedSingleSignatureStore; - -/// An in-memory implementation of [BufferedSingleSignatureStore]. -pub struct InMemoryBufferedSingleSignatureStore { - store: RwLock>>, -} - -#[cfg(test)] -impl InMemoryBufferedSingleSignatureStore { - pub(crate) fn with_data( - initial_data: BTreeMap>, - ) -> Self { - Self { - store: RwLock::new(initial_data), - } - } -} - -impl Default for InMemoryBufferedSingleSignatureStore { - fn default() -> Self { - Self { - store: RwLock::new(BTreeMap::new()), - } - } -} - -#[async_trait] -impl BufferedSingleSignatureStore for InMemoryBufferedSingleSignatureStore { - async fn buffer_signature( - &self, - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, - signature: &SingleSignatures, - ) -> StdResult<()> { - let mut store = self.store.write().await; - let signatures = store - .entry(signed_entity_type_discriminants) - .or_insert_with(Vec::new); - signatures.push(signature.clone()); - Ok(()) - } - - async fn get_buffered_signatures( - &self, - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, - ) -> StdResult> { - let store = self.store.read().await; - Ok(store - .get(&signed_entity_type_discriminants) - .cloned() - .unwrap_or_default()) - } - - async fn remove_buffered_signatures( - &self, - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, - single_signatures: Vec, - ) -> StdResult<()> { - let mut store = self.store.write().await; - - for signature in single_signatures { - if let Some(signatures) = store.get_mut(&signed_entity_type_discriminants) { - signatures.retain(|s| s != &signature); - } - } - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use mithril_common::test_utils::fake_data; - - use super::*; - - #[tokio::test] - async fn store_and_retrieve_signatures() { - let store = InMemoryBufferedSingleSignatureStore::default(); - - let ctx = SignedEntityTypeDiscriminants::CardanoTransactions; - store - .buffer_signature(ctx, &fake_data::single_signatures(vec![1])) - .await - .unwrap(); - store - .buffer_signature(ctx, &fake_data::single_signatures(vec![2])) - .await - .unwrap(); - - // Different signed entity type to test that the store is able to differentiate between them - let msd = SignedEntityTypeDiscriminants::MithrilStakeDistribution; - store - .buffer_signature(msd, &fake_data::single_signatures(vec![3])) - .await - .unwrap(); - - let buffered_signatures_ctx = store.get_buffered_signatures(ctx).await.unwrap(); - assert_eq!( - vec![ - fake_data::single_signatures(vec![1]), - fake_data::single_signatures(vec![2]) - ], - buffered_signatures_ctx - ); - - let buffered_signatures_msd = store.get_buffered_signatures(msd).await.unwrap(); - assert_eq!( - vec![fake_data::single_signatures(vec![3])], - buffered_signatures_msd - ); - } - - #[tokio::test] - async fn remove_buffered_signatures() { - let store = InMemoryBufferedSingleSignatureStore::with_data(BTreeMap::from([ - ( - SignedEntityTypeDiscriminants::MithrilStakeDistribution, - vec![ - fake_data::single_signatures(vec![1]), - fake_data::single_signatures(vec![2]), - fake_data::single_signatures(vec![3]), - ], - ), - ( - SignedEntityTypeDiscriminants::CardanoTransactions, - vec![fake_data::single_signatures(vec![10])], - ), - ])); - - store - .remove_buffered_signatures( - SignedEntityTypeDiscriminants::MithrilStakeDistribution, - vec![ - fake_data::single_signatures(vec![1]), - fake_data::single_signatures(vec![3]), - ], - ) - .await - .unwrap(); - - let remaining_msd_sigs = store - .get_buffered_signatures(SignedEntityTypeDiscriminants::MithrilStakeDistribution) - .await - .unwrap(); - assert_eq!( - vec![fake_data::single_signatures(vec![2])], - remaining_msd_sigs - ); - - let remaining_ctx_sigs = store - .get_buffered_signatures(SignedEntityTypeDiscriminants::CardanoTransactions) - .await - .unwrap(); - assert_eq!( - vec![fake_data::single_signatures(vec![10])], - remaining_ctx_sigs, - "CardanoTransactions signatures should have been left untouched" - ); - } -} diff --git a/mithril-aggregator/src/store/mod.rs b/mithril-aggregator/src/store/mod.rs index 3b3af0ce3f1..bdf83e93323 100644 --- a/mithril-aggregator/src/store/mod.rs +++ b/mithril-aggregator/src/store/mod.rs @@ -1,9 +1,7 @@ -mod buffered_single_signatures_store; mod pending_certificate_store; mod protocol_parameters_store; mod verification_key_store; -pub use buffered_single_signatures_store::InMemoryBufferedSingleSignatureStore; pub use pending_certificate_store::CertificatePendingStore; pub use protocol_parameters_store::ProtocolParametersStorer; pub use verification_key_store::{VerificationKeyStore, VerificationKeyStorer}; From 76ffeae7f76f59312246a8c70919cca59557b519 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:39:01 +0200 Subject: [PATCH 29/43] Add `authentication_status` to SingleSignatures entity It will be used to know if a single signature can be buffered or not. --- .../record/buffered_single_signature_record.rs | 1 + .../src/database/record/single_signature.rs | 1 + .../message_adapters/from_register_signature.rs | 3 ++- mithril-common/src/entities/single_signatures.rs | 16 ++++++++++++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs index d0f987073e7..967d8891169 100644 --- a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs +++ b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs @@ -86,6 +86,7 @@ impl TryFrom for SingleSignatures { won_indexes: value.lottery_indexes, signature: value.signature.try_into()?, signed_message: None, + authentication_status: Default::default(), }; Ok(signatures) diff --git a/mithril-aggregator/src/database/record/single_signature.rs b/mithril-aggregator/src/database/record/single_signature.rs index ae35a229336..3eef83af506 100644 --- a/mithril-aggregator/src/database/record/single_signature.rs +++ b/mithril-aggregator/src/database/record/single_signature.rs @@ -55,6 +55,7 @@ impl TryFrom for SingleSignatures { won_indexes: value.lottery_indexes, signature: value.signature.try_into()?, signed_message: None, + authentication_status: Default::default(), }; Ok(signatures) diff --git a/mithril-aggregator/src/message_adapters/from_register_signature.rs b/mithril-aggregator/src/message_adapters/from_register_signature.rs index ad1dd3a759f..e3121624134 100644 --- a/mithril-aggregator/src/message_adapters/from_register_signature.rs +++ b/mithril-aggregator/src/message_adapters/from_register_signature.rs @@ -1,6 +1,6 @@ use anyhow::Context; use mithril_common::{ - entities::SingleSignatures, + entities::{SingleSignatureAuthenticationStatus, SingleSignatures}, messages::{RegisterSignatureMessage, TryFromMessageAdapter}, StdResult, }; @@ -23,6 +23,7 @@ impl TryFromMessageAdapter })?, won_indexes: register_single_signature_message.won_indexes, signed_message: register_single_signature_message.signed_message, + authentication_status: SingleSignatureAuthenticationStatus::Unauthenticated, }; Ok(signatures) diff --git a/mithril-common/src/entities/single_signatures.rs b/mithril-common/src/entities/single_signatures.rs index b0b54a4aa26..e47fcb5f476 100644 --- a/mithril-common/src/entities/single_signatures.rs +++ b/mithril-common/src/entities/single_signatures.rs @@ -27,6 +27,19 @@ pub struct SingleSignatures { /// for the signed entity type. #[serde(skip_serializing_if = "Option::is_none")] pub signed_message: Option, + + /// Status of the authentication of the signer that emitted the signature + pub authentication_status: SingleSignatureAuthenticationStatus, +} + +/// Status of the authentication of the signer that emitted the signature +#[derive(Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +pub enum SingleSignatureAuthenticationStatus { + /// We could authenticate the signer that emitted the signature + Authenticated, + /// We could not authenticate the signer that emitted the signature + #[default] + Unauthenticated, } impl SingleSignatures { @@ -41,6 +54,7 @@ impl SingleSignatures { signature, won_indexes, signed_message: None, + authentication_status: SingleSignatureAuthenticationStatus::Unauthenticated, } } @@ -56,6 +70,7 @@ impl SingleSignatures { signature, won_indexes, signed_message: Some(signed_message), + authentication_status: SingleSignatureAuthenticationStatus::Unauthenticated, } } @@ -98,6 +113,7 @@ impl SingleSignatures { signature: signature.signature, won_indexes: vec![10, 15], signed_message: Some(message), + authentication_status: SingleSignatureAuthenticationStatus::Unauthenticated, } } } From a9ba1ffac1cb3efc01aa82e4efc793f0cafbd999 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:01:34 +0200 Subject: [PATCH 30/43] Buffer signature if they're authenticated Instead of validating their `signed_message` as this will be handled by another component beforehand. --- .../services/certifier/buffered_certifier.rs | 155 +++--------------- .../src/entities/single_signatures.rs | 5 + 2 files changed, 29 insertions(+), 131 deletions(-) diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs index 579a1701458..e177bb4693b 100644 --- a/mithril-aggregator/src/services/certifier/buffered_certifier.rs +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -102,52 +102,19 @@ impl CertifierService for BufferedCertifierService { { Ok(res) => Ok(res), Err(error) => match error.downcast_ref::() { - Some(CertifierServiceError::NotFound(..)) => { - match &signature.signed_message { - // Only buffer signatures that have a signed message so we can validate them - Some(signed_message) => { - debug!( - self.logger, - "No OpenMessage available for signed entity - Buffering single signature"; - "signed_entity_type" => ?signed_entity_type, - "party_id" => &signature.party_id - ); - - if let Err(error) = self - .multi_signer - .verify_single_signature(signed_message, signature) - .await - { - // Signers may detect epoch changes before the aggregator and send - // new signatures using the next epoch stake distribution - debug!( - self.logger, - "Signature is invalid for current epoch stake distribution, trying next epoch"; - "validation_error" => ?error - ); - - self.multi_signer - .verify_single_signature_for_next_epoch( - signed_message, - signature, - ) - .await - .map_err(|err| { - CertifierServiceError::InvalidSingleSignature( - signed_entity_type.clone(), - err, - ) - })?; - } - - self.buffered_single_signature_store - .buffer_signature(signed_entity_type.into(), signature) - .await?; - - Ok(RegistrationStatus::Buffered) - } - None => Err(error), - } + Some(CertifierServiceError::NotFound(..)) if signature.is_authenticated() => { + debug!( + self.logger, + "No OpenMessage available for signed entity - Buffering single signature"; + "signed_entity_type" => ?signed_entity_type, + "party_id" => &signature.party_id + ); + + self.buffered_single_signature_store + .buffer_signature(signed_entity_type.into(), signature) + .await?; + + Ok(RegistrationStatus::Buffered) } _ => Err(error), }, @@ -236,6 +203,7 @@ mod tests { use mithril_common::entities::SignedEntityTypeDiscriminants::{ CardanoTransactions, MithrilStakeDistribution, }; + use mithril_common::entities::SingleSignatureAuthenticationStatus; use mithril_common::test_utils::fake_data; use crate::database::repository::BufferedSingleSignatureRepository; @@ -278,7 +246,6 @@ mod tests { /// Return the registration result and the list of buffered signatures after the registration. async fn run_register_signature_scenario( decorated_certifier_mock_config: impl FnOnce(&mut MockCertifierService), - multi_signer_mock_config: impl FnOnce(&mut MockMultiSigner), signature_to_register: &SingleSignatures, ) -> (StdResult, Vec) { let store = Arc::new(BufferedSingleSignatureRepository::new(Arc::new( @@ -286,7 +253,7 @@ mod tests { ))); let certifier = BufferedCertifierService::new( mock_certifier(decorated_certifier_mock_config), - mock_multi_signer(multi_signer_mock_config), + mock_multi_signer(|_| {}), store.clone(), TestLogger::stdout(), ); @@ -309,7 +276,6 @@ mod tests { #[tokio::test] async fn when_registering_single_signature_dont_buffer_signature_if_decorated_certifier_succeed( ) { - let signature = SingleSignatures::fake_with_signed_message("party_1", "a message"); let (registration_result, buffered_signatures_after_registration) = run_register_signature_scenario( |mock_certifier| { @@ -317,8 +283,7 @@ mod tests { .expect_register_single_signature() .returning(|_, _| Ok(RegistrationStatus::Registered)); }, - |_mock_multi_signer| {}, - &signature, + &SingleSignatures::fake("party_1", "a message"), ) .await; @@ -334,7 +299,7 @@ mod tests { use super::*; #[tokio::test] - async fn buffer_signature_with_valid_signed_message_for_current_epoch() { + async fn buffer_signature_if_authenticated() { let (registration_result, buffered_signatures_after_registration) = run_register_signature_scenario( |mock_certifier| { @@ -347,12 +312,10 @@ mod tests { .into()) }); }, - |mock_multi_signer| { - mock_multi_signer - .expect_verify_single_signature() - .returning(|_, _| Ok(())); + &SingleSignatures { + authentication_status: SingleSignatureAuthenticationStatus::Authenticated, + ..SingleSignatures::fake("party_1", "a message") }, - &SingleSignatures::fake_with_signed_message("party_1", "a message"), ) .await; @@ -365,7 +328,7 @@ mod tests { } #[tokio::test] - async fn buffer_signature_with_valid_signed_message_for_next_epoch() { + async fn dont_buffer_signature_if_not_authenticated() { let (registration_result, buffered_signatures_after_registration) = run_register_signature_scenario( |mock_certifier| { @@ -378,80 +341,10 @@ mod tests { .into()) }); }, - |mock_multi_signer| { - mock_multi_signer - .expect_verify_single_signature() - .returning(|_, _| Err(anyhow!("Invalid signature"))); - mock_multi_signer - .expect_verify_single_signature_for_next_epoch() - .returning(|_, _| Ok(())); - }, - &SingleSignatures::fake_with_signed_message("party_1", "a message"), - ) - .await; - - let status = registration_result.expect("Registration should have succeed"); - assert_eq!(status, RegistrationStatus::Buffered); - assert_eq!( - buffered_signatures_after_registration, - vec![SingleSignatures::fake("party_1", "a message")] - ); - } - - #[tokio::test] - async fn dont_buffer_signature_with_invalid_signed_message() { - let signature = SingleSignatures::fake_with_signed_message("party_1", "a message"); - let (registration_result, buffered_signatures_after_registration) = - run_register_signature_scenario( - |mock_certifier| { - mock_certifier - .expect_register_single_signature() - .returning(|_, _| { - Err(CertifierServiceError::NotFound( - SignedEntityType::MithrilStakeDistribution(Epoch(5)), - ) - .into()) - }); - }, - |mock_multi_signer| { - mock_multi_signer - .expect_verify_single_signature() - .returning(|_, _| Err(anyhow!("Invalid signature for current epoch"))); - mock_multi_signer - .expect_verify_single_signature_for_next_epoch() - .returning(|_, _| Err(anyhow!("Invalid signature for next epoch"))); - }, - &signature, - ) - .await; - - registration_result.expect_err("Registration should have failed"); - assert_eq!( - buffered_signatures_after_registration, - Vec::::new() - ); - } - - #[tokio::test] - async fn dont_buffer_signature_without_signed_message() { - let signature = SingleSignatures { - signed_message: None, - ..SingleSignatures::fake_with_signed_message("party_1", "a message") - }; - let (registration_result, buffered_signatures_after_registration) = - run_register_signature_scenario( - |mock_certifier| { - mock_certifier - .expect_register_single_signature() - .returning(|_, _| { - Err(CertifierServiceError::NotFound( - SignedEntityType::MithrilStakeDistribution(Epoch(5)), - ) - .into()) - }); + &SingleSignatures { + authentication_status: SingleSignatureAuthenticationStatus::Unauthenticated, + ..SingleSignatures::fake("party_1", "a message") }, - |_mock_multi_signer| {}, - &signature, ) .await; diff --git a/mithril-common/src/entities/single_signatures.rs b/mithril-common/src/entities/single_signatures.rs index e47fcb5f476..0899485bae1 100644 --- a/mithril-common/src/entities/single_signatures.rs +++ b/mithril-common/src/entities/single_signatures.rs @@ -78,6 +78,11 @@ impl SingleSignatures { pub fn to_protocol_signature(&self) -> StmSig { self.signature.clone().into() } + + /// Check that the signer that emitted the signature is authenticated + pub fn is_authenticated(&self) -> bool { + self.authentication_status == SingleSignatureAuthenticationStatus::Authenticated + } } cfg_test_tools! { From c859a5aa7feeb172be2fd75203f92ec663fbf49f Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:10:51 +0200 Subject: [PATCH 31/43] Remove `multi_signer` dependency from buffered certifier service As it has now no more use. --- .../src/dependency_injection/builder.rs | 3 +-- .../src/services/certifier/buffered_certifier.rs | 16 ---------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index cdbf68e0bd7..3d40c7ad6c1 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -1447,7 +1447,7 @@ impl DependenciesBuilder { certificate_repository, certificate_verifier, genesis_verifier, - multi_signer.clone(), + multi_signer, ticker_service, epoch_service, logger, @@ -1455,7 +1455,6 @@ impl DependenciesBuilder { Ok(Arc::new(BufferedCertifierService::new( certifier, - multi_signer, Arc::new(BufferedSingleSignatureRepository::new(sqlite_connection)), self.get_logger()?, ))) diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs index e177bb4693b..5f5fea93ce2 100644 --- a/mithril-aggregator/src/services/certifier/buffered_certifier.rs +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -12,7 +12,6 @@ use crate::entities::OpenMessage; use crate::services::{ BufferedSingleSignatureStore, CertifierService, CertifierServiceError, RegistrationStatus, }; -use crate::MultiSigner; /// A decorator of [CertifierService] that buffers that can buffer registration of single signatures /// when the open message is not yet created. @@ -21,7 +20,6 @@ use crate::MultiSigner; /// registered. pub struct BufferedCertifierService { certifier_service: Arc, - multi_signer: Arc, buffered_single_signature_store: Arc, logger: Logger, } @@ -30,13 +28,11 @@ impl BufferedCertifierService { /// Create a new instance of `BufferedCertifierService`. pub fn new( certifier_service: Arc, - multi_signer: Arc, buffered_single_signature_store: Arc, logger: Logger, ) -> Self { Self { certifier_service, - multi_signer, buffered_single_signature_store, logger, } @@ -208,7 +204,6 @@ mod tests { use crate::database::repository::BufferedSingleSignatureRepository; use crate::database::test_helper::main_db_connection; - use crate::multi_signer::MockMultiSigner; use crate::services::{ CertifierServiceError, MockBufferedSingleSignatureStore, MockCertifierService, }; @@ -224,14 +219,6 @@ mod tests { Arc::new(certifier) } - fn mock_multi_signer( - multi_signer_mock_config: impl FnOnce(&mut MockMultiSigner), - ) -> Arc { - let mut multi_signer = MockMultiSigner::new(); - multi_signer_mock_config(&mut multi_signer); - Arc::new(multi_signer) - } - fn mock_store(store_mock_config: F) -> Arc where F: FnOnce(&mut MockBufferedSingleSignatureStore), @@ -253,7 +240,6 @@ mod tests { ))); let certifier = BufferedCertifierService::new( mock_certifier(decorated_certifier_mock_config), - mock_multi_signer(|_| {}), store.clone(), TestLogger::stdout(), ); @@ -402,7 +388,6 @@ mod tests { .once() .returning(|_, _| Ok(RegistrationStatus::Registered)); }), - mock_multi_signer(|_| {}), store.clone(), TestLogger::stdout(), ); @@ -434,7 +419,6 @@ mod tests { let store = mock_store(store_mock_config); let certifier = BufferedCertifierService::new( mock_certifier(certifier_mock_config), - mock_multi_signer(|_| {}), store, TestLogger::stdout(), ); From c19f49735495e3632b56b6864085688bb8f8c5f8 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:22:36 +0200 Subject: [PATCH 32/43] Skip (de)serialization of single signature `authentication_status` For retrocompatibility with previously stored signatures (with open messages in aggregator). --- mithril-common/src/entities/single_signatures.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mithril-common/src/entities/single_signatures.rs b/mithril-common/src/entities/single_signatures.rs index 0899485bae1..3ed8e030445 100644 --- a/mithril-common/src/entities/single_signatures.rs +++ b/mithril-common/src/entities/single_signatures.rs @@ -29,6 +29,7 @@ pub struct SingleSignatures { pub signed_message: Option, /// Status of the authentication of the signer that emitted the signature + #[serde(skip)] pub authentication_status: SingleSignatureAuthenticationStatus, } From 5d81f67cd5b82072c4b2b1dd7e4d62110299b545 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:32:20 +0200 Subject: [PATCH 33/43] Make buffer integration test send authenticated signatures --- ...te_certificate_with_buffered_signatures.rs | 2 +- .../tests/test_extensions/runtime_tester.rs | 40 ++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs b/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs index b44dd92442d..066145074b8 100644 --- a/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs +++ b/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs @@ -69,7 +69,7 @@ async fn create_certificate_with_buffered_signatures() { comment!("signers send their single signature before the state machine is signing"); tester - .send_single_signatures( + .send_authenticated_single_signatures( SignedEntityTypeDiscriminants::MithrilStakeDistribution, &fixture.signers_fixture(), ) diff --git a/mithril-aggregator/tests/test_extensions/runtime_tester.rs b/mithril-aggregator/tests/test_extensions/runtime_tester.rs index e6215b0959e..c2ccf35fd16 100644 --- a/mithril-aggregator/tests/test_extensions/runtime_tester.rs +++ b/mithril-aggregator/tests/test_extensions/runtime_tester.rs @@ -9,7 +9,6 @@ use mithril_aggregator::{ AggregatorRuntime, Configuration, DependencyContainer, DumbSnapshotUploader, DumbSnapshotter, SignerRegistrationError, }; -use mithril_common::entities::ProtocolMessagePartKey; use mithril_common::{ cardano_block_scanner::{DumbBlockScanner, ScannedBlock}, chain_observer::{ChainObserver, FakeObserver}, @@ -17,8 +16,8 @@ use mithril_common::{ digesters::{DumbImmutableDigester, DumbImmutableFileObserver}, entities::{ BlockNumber, Certificate, CertificateSignature, ChainPoint, Epoch, ImmutableFileNumber, - SignedEntityType, SignedEntityTypeDiscriminants, SlotNumber, Snapshot, StakeDistribution, - TimePoint, + ProtocolMessagePartKey, SignedEntityType, SignedEntityTypeDiscriminants, + SingleSignatureAuthenticationStatus, SlotNumber, Snapshot, StakeDistribution, TimePoint, }, era::{adapters::EraReaderDummyAdapter, EraMarker, EraReader, SupportedEra}, test_utils::{ @@ -380,11 +379,37 @@ impl RuntimeTester { Ok(()) } - /// "Send", actually register, the given single signatures in the multi-signers + pub async fn send_authenticated_single_signatures( + &mut self, + discriminant: SignedEntityTypeDiscriminants, + signers: &[SignerFixture], + ) -> StdResult<()> { + self.send_single_signatures_with_auth_status( + discriminant, + signers, + SingleSignatureAuthenticationStatus::Authenticated, + ) + .await + } + pub async fn send_single_signatures( &mut self, discriminant: SignedEntityTypeDiscriminants, signers: &[SignerFixture], + ) -> StdResult<()> { + self.send_single_signatures_with_auth_status( + discriminant, + signers, + SingleSignatureAuthenticationStatus::Unauthenticated, + ) + .await + } + + async fn send_single_signatures_with_auth_status( + &mut self, + discriminant: SignedEntityTypeDiscriminants, + signers: &[SignerFixture], + authentication_status: SingleSignatureAuthenticationStatus, ) -> StdResult<()> { let certifier_service = self.dependencies.certifier_service.clone(); let signed_entity_type = self @@ -411,7 +436,12 @@ impl RuntimeTester { ); for signer_fixture in signers { - if let Some(single_signatures) = signer_fixture.sign(&message) { + if let Some(mut single_signatures) = signer_fixture.sign(&message) { + if authentication_status == SingleSignatureAuthenticationStatus::Authenticated { + single_signatures.authentication_status = + SingleSignatureAuthenticationStatus::Authenticated; + } + certifier_service .register_single_signature(&signed_entity_type, &single_signatures) .await From ebdea24a74e6bc61d36a87928950eab08e3f7604 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:46:11 +0200 Subject: [PATCH 34/43] Add `SingleSignatureAuthenticator` in aggregator --- mithril-aggregator/src/lib.rs | 1 + mithril-aggregator/src/tools/mod.rs | 2 + .../tools/single_signature_authenticator.rs | 220 ++++++++++++++++++ .../src/entities/single_signatures.rs | 2 +- 4 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 mithril-aggregator/src/tools/single_signature_authenticator.rs diff --git a/mithril-aggregator/src/lib.rs b/mithril-aggregator/src/lib.rs index baa5318d921..fa955490d73 100644 --- a/mithril-aggregator/src/lib.rs +++ b/mithril-aggregator/src/lib.rs @@ -57,6 +57,7 @@ pub use store::{ }; pub use tools::{ CExplorerSignerRetriever, SignersImporter, SignersImporterPersister, SignersImporterRetriever, + SingleSignatureAuthenticator, }; #[cfg(test)] diff --git a/mithril-aggregator/src/tools/mod.rs b/mithril-aggregator/src/tools/mod.rs index 2f6be81ddc0..1df67dc565f 100644 --- a/mithril-aggregator/src/tools/mod.rs +++ b/mithril-aggregator/src/tools/mod.rs @@ -6,6 +6,7 @@ mod genesis; pub mod mocks; mod remote_file_uploader; mod signer_importer; +mod single_signature_authenticator; pub use certificates_hash_migrator::CertificatesHashMigrator; pub use digest_helpers::extract_digest_from_path; @@ -15,6 +16,7 @@ pub use remote_file_uploader::{GcpFileUploader, RemoteFileUploader}; pub use signer_importer::{ CExplorerSignerRetriever, SignersImporter, SignersImporterPersister, SignersImporterRetriever, }; +pub use single_signature_authenticator::*; #[cfg(test)] pub use remote_file_uploader::MockRemoteFileUploader; diff --git a/mithril-aggregator/src/tools/single_signature_authenticator.rs b/mithril-aggregator/src/tools/single_signature_authenticator.rs new file mode 100644 index 00000000000..4cf2c487e8e --- /dev/null +++ b/mithril-aggregator/src/tools/single_signature_authenticator.rs @@ -0,0 +1,220 @@ +use slog::{debug, Logger}; +use std::sync::Arc; + +use mithril_common::entities::{SingleSignatureAuthenticationStatus, SingleSignatures}; +use mithril_common::StdResult; + +use crate::MultiSigner; + +/// Authenticates single signatures against a signed message. +pub struct SingleSignatureAuthenticator { + multi_signer: Arc, + logger: Logger, +} + +impl SingleSignatureAuthenticator { + /// Creates a new `SingleSignatureAuthenticator`. + pub fn new(multi_signer: Arc, logger: Logger) -> Self { + Self { + multi_signer, + logger, + } + } + + /// Authenticates a single signature against a signed message. + pub async fn authenticate( + &self, + single_signature: &mut SingleSignatures, + signed_message: &str, + ) -> StdResult<()> { + let is_authenticated = match self + .multi_signer + .verify_single_signature(signed_message, single_signature) + .await + { + Ok(()) => { + debug!( + self.logger, + "Single signature party authenticated for current stake distribution"; + "party_id" => &single_signature.party_id, + ); + true + } + Err(_error) => { + // Signers may detect epoch changes before the aggregator and send + // new signatures using the next epoch stake distribution + if self + .multi_signer + .verify_single_signature_for_next_epoch(signed_message, single_signature) + .await + .is_ok() + { + debug!( + self.logger, + "Single signature party authenticated for next stake distribution"; + "party_id" => &single_signature.party_id, + ); + true + } else { + debug!( + self.logger, + "Single signature party not authenticated"; + "party_id" => &single_signature.party_id, + ); + false + } + } + }; + + single_signature.authentication_status = if is_authenticated { + SingleSignatureAuthenticationStatus::Authenticated + } else { + SingleSignatureAuthenticationStatus::Unauthenticated + }; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use anyhow::anyhow; + + use crate::multi_signer::MockMultiSigner; + use crate::test_tools::TestLogger; + + use super::*; + + fn mock_multi_signer( + multi_signer_mock_config: impl FnOnce(&mut MockMultiSigner), + ) -> Arc { + let mut multi_signer = MockMultiSigner::new(); + multi_signer_mock_config(&mut multi_signer); + Arc::new(multi_signer) + } + + #[tokio::test] + async fn single_signature_against_valid_signed_message_for_current_stake_distribution_is_authenticated( + ) { + let signed_message = "signed_message".to_string(); + let mut single_signature = SingleSignatures { + authentication_status: SingleSignatureAuthenticationStatus::Unauthenticated, + ..SingleSignatures::fake("party_id", &signed_message) + }; + + let authenticator = SingleSignatureAuthenticator::new( + mock_multi_signer(|mock_config| { + mock_config + .expect_verify_single_signature() + .returning(|_, _| Ok(())); + }), + TestLogger::stdout(), + ); + + authenticator + .authenticate(&mut single_signature, &signed_message) + .await + .unwrap(); + + assert_eq!( + single_signature.authentication_status, + SingleSignatureAuthenticationStatus::Authenticated + ); + } + + #[tokio::test] + async fn single_signature_against_valid_signed_message_for_next_stake_distribution_is_authenticated( + ) { + let signed_message = "signed_message".to_string(); + let mut single_signature = SingleSignatures { + authentication_status: SingleSignatureAuthenticationStatus::Unauthenticated, + ..SingleSignatures::fake("party_id", &signed_message) + }; + + let authenticator = SingleSignatureAuthenticator::new( + mock_multi_signer(|mock_config| { + mock_config + .expect_verify_single_signature() + .returning(|_, _| Err(anyhow!("error"))); + mock_config + .expect_verify_single_signature_for_next_epoch() + .returning(|_, _| Ok(())); + }), + TestLogger::stdout(), + ); + + authenticator + .authenticate(&mut single_signature, &signed_message) + .await + .unwrap(); + + assert_eq!( + single_signature.authentication_status, + SingleSignatureAuthenticationStatus::Authenticated + ); + } + + #[tokio::test] + async fn single_signature_against_invalid_signed_message_for_current_and_next_stake_distribution_is_not_authenticated( + ) { + let signed_message = "signed_message".to_string(); + let mut single_signature = SingleSignatures { + authentication_status: SingleSignatureAuthenticationStatus::Unauthenticated, + ..SingleSignatures::fake("party_id", &signed_message) + }; + + let authenticator = SingleSignatureAuthenticator::new( + mock_multi_signer(|mock_config| { + mock_config + .expect_verify_single_signature() + .returning(|_, _| Err(anyhow!("verify_single_signature error"))); + mock_config + .expect_verify_single_signature_for_next_epoch() + .returning(|_, _| Err(anyhow!("verify_single_signature_for_next_epoch error"))); + }), + TestLogger::stdout(), + ); + + authenticator + .authenticate(&mut single_signature, &signed_message) + .await + .unwrap(); + + assert_eq!( + single_signature.authentication_status, + SingleSignatureAuthenticationStatus::Unauthenticated + ); + } + + #[tokio::test] + async fn single_signature_previously_authenticated_but_fail_new_authentication_is_now_unauthenticated( + ) { + let signed_message = "signed_message".to_string(); + let mut single_signature = SingleSignatures { + authentication_status: SingleSignatureAuthenticationStatus::Authenticated, + ..SingleSignatures::fake("party_id", &signed_message) + }; + + let authenticator = SingleSignatureAuthenticator::new( + mock_multi_signer(|mock_config| { + mock_config + .expect_verify_single_signature() + .returning(|_, _| Err(anyhow!("verify_single_signature error"))); + mock_config + .expect_verify_single_signature_for_next_epoch() + .returning(|_, _| Err(anyhow!("verify_single_signature_for_next_epoch error"))); + }), + TestLogger::stdout(), + ); + + authenticator + .authenticate(&mut single_signature, &signed_message) + .await + .unwrap(); + + assert_eq!( + single_signature.authentication_status, + SingleSignatureAuthenticationStatus::Unauthenticated + ); + } +} diff --git a/mithril-common/src/entities/single_signatures.rs b/mithril-common/src/entities/single_signatures.rs index 3ed8e030445..0e002fd5a5f 100644 --- a/mithril-common/src/entities/single_signatures.rs +++ b/mithril-common/src/entities/single_signatures.rs @@ -34,7 +34,7 @@ pub struct SingleSignatures { } /// Status of the authentication of the signer that emitted the signature -#[derive(Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] pub enum SingleSignatureAuthenticationStatus { /// We could authenticate the signer that emitted the signature Authenticated, From f51ac9a4230b381c3b55566c9c4d8d7b301fdeef Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:53:35 +0200 Subject: [PATCH 35/43] Wire `SingleSignatureAuthenticator` in aggregator dependencies --- .../src/dependency_injection/builder.rs | 31 +++++++++++++++++-- .../src/dependency_injection/containers.rs | 6 +++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index 3d40c7ad6c1..4c79c8a17cd 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -76,8 +76,9 @@ use crate::{ AggregatorConfig, AggregatorRunner, AggregatorRuntime, CertificatePendingStore, CompressedArchiveSnapshotter, Configuration, DependencyContainer, DumbSnapshotUploader, DumbSnapshotter, LocalSnapshotUploader, MithrilSignerRegisterer, MultiSigner, MultiSignerImpl, - ProtocolParametersStorer, RemoteSnapshotUploader, SnapshotUploader, SnapshotUploaderType, - Snapshotter, SnapshotterCompressionAlgorithm, VerificationKeyStorer, + ProtocolParametersStorer, RemoteSnapshotUploader, SingleSignatureAuthenticator, + SnapshotUploader, SnapshotUploaderType, Snapshotter, SnapshotterCompressionAlgorithm, + VerificationKeyStorer, }; const SQLITE_FILE: &str = "aggregator.sqlite3"; @@ -230,6 +231,9 @@ pub struct DependenciesBuilder { /// Upkeep service pub upkeep_service: Option>, + + /// Single signer authenticator + pub single_signer_authenticator: Option>, } impl DependenciesBuilder { @@ -280,6 +284,7 @@ impl DependenciesBuilder { signed_entity_type_lock: None, transactions_importer: None, upkeep_service: None, + single_signer_authenticator: None, } } @@ -1263,6 +1268,27 @@ impl DependenciesBuilder { Ok(self.upkeep_service.as_ref().cloned().unwrap()) } + async fn build_single_signature_authenticator( + &mut self, + ) -> Result> { + let authenticator = + SingleSignatureAuthenticator::new(self.get_multi_signer().await?, self.get_logger()?); + + Ok(Arc::new(authenticator)) + } + + /// [MithrilSignerRegisterer] service + pub async fn get_single_signature_authenticator( + &mut self, + ) -> Result> { + if self.single_signer_authenticator.is_none() { + self.single_signer_authenticator = + Some(self.build_single_signature_authenticator().await?); + } + + Ok(self.single_signer_authenticator.as_ref().cloned().unwrap()) + } + /// Return an unconfigured [DependencyContainer] pub async fn build_dependency_container(&mut self) -> Result { let dependency_manager = DependencyContainer { @@ -1307,6 +1333,7 @@ impl DependenciesBuilder { prover_service: self.get_prover_service().await?, signed_entity_type_lock: self.get_signed_entity_lock().await?, upkeep_service: self.get_upkeep_service().await?, + single_signer_authenticator: self.get_single_signature_authenticator().await?, }; Ok(dependency_manager) diff --git a/mithril-aggregator/src/dependency_injection/containers.rs b/mithril-aggregator/src/dependency_injection/containers.rs index 16266a44a18..4be9b1766cc 100644 --- a/mithril-aggregator/src/dependency_injection/containers.rs +++ b/mithril-aggregator/src/dependency_injection/containers.rs @@ -33,7 +33,8 @@ use crate::{ signer_registerer::SignerRecorder, snapshot_uploaders::SnapshotUploader, CertificatePendingStore, ProtocolParametersStorer, SignerRegisterer, - SignerRegistrationRoundOpener, Snapshotter, VerificationKeyStorer, + SignerRegistrationRoundOpener, SingleSignatureAuthenticator, Snapshotter, + VerificationKeyStorer, }; /// EpochServiceWrapper wraps a [EpochService] @@ -161,6 +162,9 @@ pub struct DependencyContainer { /// Upkeep service pub upkeep_service: Arc, + + /// Single signer authenticator + pub single_signer_authenticator: Arc, } #[doc(hidden)] From 2d6e9ebc01f647bded52591ce09736d35d0d59b6 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:26:39 +0200 Subject: [PATCH 36/43] Make aggregator `register_signatures` route check signature authenticity --- .../src/http_server/routes/middlewares.rs | 9 ++- .../http_server/routes/signatures_routes.rs | 76 ++++++++++++++++++- .../tools/single_signature_authenticator.rs | 33 ++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) diff --git a/mithril-aggregator/src/http_server/routes/middlewares.rs b/mithril-aggregator/src/http_server/routes/middlewares.rs index 65a1f1d1942..49c3c350ce3 100644 --- a/mithril-aggregator/src/http_server/routes/middlewares.rs +++ b/mithril-aggregator/src/http_server/routes/middlewares.rs @@ -12,7 +12,7 @@ use crate::event_store::{EventMessage, TransmitterService}; use crate::services::{CertifierService, MessageService, ProverService, SignedEntityService}; use crate::{ CertificatePendingStore, Configuration, DependencyContainer, SignerRegisterer, - VerificationKeyStorer, + SingleSignatureAuthenticator, VerificationKeyStorer, }; /// With certificate pending store @@ -113,6 +113,13 @@ pub fn with_prover_service( warp::any().map(move || dependency_manager.prover_service.clone()) } +/// With Single Signature Authenticator +pub fn with_single_signature_authenticator( + dependency_manager: Arc, +) -> impl Filter,), Error = Infallible> + Clone { + warp::any().map(move || dependency_manager.single_signer_authenticator.clone()) +} + pub mod validators { use crate::http_server::validators::ProverTransactionsHashValidator; diff --git a/mithril-aggregator/src/http_server/routes/signatures_routes.rs b/mithril-aggregator/src/http_server/routes/signatures_routes.rs index 31a9bd06937..790f71369c9 100644 --- a/mithril-aggregator/src/http_server/routes/signatures_routes.rs +++ b/mithril-aggregator/src/http_server/routes/signatures_routes.rs @@ -19,6 +19,9 @@ fn register_signatures( .and(middlewares::with_certifier_service( dependency_manager.clone(), )) + .and(middlewares::with_single_signature_authenticator( + dependency_manager, + )) .and_then(handlers::register_signatures) } @@ -34,19 +37,22 @@ mod handlers { http_server::routes::reply, message_adapters::FromRegisterSingleSignatureAdapter, services::{CertifierService, CertifierServiceError, RegistrationStatus}, + unwrap_to_internal_server_error, SingleSignatureAuthenticator, }; /// Register Signatures pub async fn register_signatures( message: RegisterSignatureMessage, certifier_service: Arc, + single_signer_authenticator: Arc, ) -> Result { debug!("⇄ HTTP SERVER: register_signatures/{:?}", message); trace!("⇄ HTTP SERVER: register_signatures"; "complete_message" => #?message ); let signed_entity_type = message.signed_entity_type.clone(); + let signed_message = message.signed_message.clone(); - let signatures = match FromRegisterSingleSignatureAdapter::try_adapt(message) { + let mut signatures = match FromRegisterSingleSignatureAdapter::try_adapt(message) { Ok(signature) => signature, Err(err) => { warn!("register_signatures::payload decoding error"; "error" => ?err); @@ -58,6 +64,15 @@ mod handlers { } }; + if let Some(signed_message) = signed_message { + unwrap_to_internal_server_error!( + single_signer_authenticator + .authenticate(&mut signatures, &signed_message) + .await, + "single_signer_authenticator::error" + ); + } + match certifier_service .register_single_signature(&signed_entity_type, &signatures) .await @@ -97,6 +112,7 @@ mod tests { http_server::SERVER_BASE_PATH, initialize_dependencies, services::{CertifierServiceError, MockCertifierService, RegistrationStatus}, + SingleSignatureAuthenticator, }; use super::*; @@ -114,6 +130,64 @@ mod tests { .and(routes(dependency_manager).with(cors)) } + #[tokio::test] + async fn test_register_signatures_try_to_authenticate_signature_with_signed_message() { + let mut mock_certifier_service = MockCertifierService::new(); + mock_certifier_service + .expect_register_single_signature() + .withf(|_, signature| signature.is_authenticated()) + .once() + .return_once(move |_, _| Ok(RegistrationStatus::Registered)); + let mut dependency_manager = initialize_dependencies().await; + dependency_manager.certifier_service = Arc::new(mock_certifier_service); + dependency_manager.single_signer_authenticator = + Arc::new(SingleSignatureAuthenticator::new_that_authenticate_everything()); + + let message = RegisterSignatureMessage { + signed_message: Some("message".to_string()), + ..RegisterSignatureMessage::dummy() + }; + + let method = Method::POST.as_str(); + let path = "/register-signatures"; + + request() + .method(method) + .path(&format!("/{SERVER_BASE_PATH}{path}")) + .json(&message) + .reply(&setup_router(Arc::new(dependency_manager))) + .await; + } + + #[tokio::test] + async fn test_register_signatures_send_unauthenticated_signature_if_authentication_fail() { + let mut mock_certifier_service = MockCertifierService::new(); + mock_certifier_service + .expect_register_single_signature() + .withf(|_, signature| !signature.is_authenticated()) + .once() + .return_once(move |_, _| Ok(RegistrationStatus::Registered)); + let mut dependency_manager = initialize_dependencies().await; + dependency_manager.certifier_service = Arc::new(mock_certifier_service); + dependency_manager.single_signer_authenticator = + Arc::new(SingleSignatureAuthenticator::new_that_reject_everything()); + + let message = RegisterSignatureMessage { + signed_message: Some("message".to_string()), + ..RegisterSignatureMessage::dummy() + }; + + let method = Method::POST.as_str(); + let path = "/register-signatures"; + + request() + .method(method) + .path(&format!("/{SERVER_BASE_PATH}{path}")) + .json(&message) + .reply(&setup_router(Arc::new(dependency_manager))) + .await; + } + #[tokio::test] async fn test_register_signatures_post_ok_201() { let mut mock_certifier_service = MockCertifierService::new(); diff --git a/mithril-aggregator/src/tools/single_signature_authenticator.rs b/mithril-aggregator/src/tools/single_signature_authenticator.rs index 4cf2c487e8e..87c77492880 100644 --- a/mithril-aggregator/src/tools/single_signature_authenticator.rs +++ b/mithril-aggregator/src/tools/single_signature_authenticator.rs @@ -76,6 +76,39 @@ impl SingleSignatureAuthenticator { } } +#[cfg(test)] +impl SingleSignatureAuthenticator { + pub(crate) fn new_that_authenticate_everything() -> Self { + let mut multi_signer = crate::multi_signer::MockMultiSigner::new(); + multi_signer + .expect_verify_single_signature() + .returning(|_, _| Ok(())); + multi_signer + .expect_verify_single_signature_for_next_epoch() + .returning(|_, _| Ok(())); + + Self { + multi_signer: Arc::new(multi_signer), + logger: crate::test_tools::TestLogger::stdout(), + } + } + + pub(crate) fn new_that_reject_everything() -> Self { + let mut multi_signer = crate::multi_signer::MockMultiSigner::new(); + multi_signer + .expect_verify_single_signature() + .returning(|_, _| Err(anyhow::anyhow!("error"))); + multi_signer + .expect_verify_single_signature_for_next_epoch() + .returning(|_, _| Err(anyhow::anyhow!("error"))); + + Self { + multi_signer: Arc::new(multi_signer), + logger: crate::test_tools::TestLogger::stdout(), + } + } +} + #[cfg(test)] mod tests { use anyhow::anyhow; From 4c32c7bb2522705500816e6db71f8b39d1a49f8f Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 17 Sep 2024 17:35:24 +0200 Subject: [PATCH 37/43] Aggregator `/register-signatures` return 404 when signature with signed_message is unauthenticated As there's no value to send it to the certifier since it will reject it anyway. --- .../http_server/routes/signatures_routes.rs | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/mithril-aggregator/src/http_server/routes/signatures_routes.rs b/mithril-aggregator/src/http_server/routes/signatures_routes.rs index 790f71369c9..6257dea1926 100644 --- a/mithril-aggregator/src/http_server/routes/signatures_routes.rs +++ b/mithril-aggregator/src/http_server/routes/signatures_routes.rs @@ -71,6 +71,14 @@ mod handlers { .await, "single_signer_authenticator::error" ); + + if !signatures.is_authenticated() { + debug!("register_signatures::unauthenticated_signature"); + return Ok(reply::bad_request( + "Could not authenticate signature".to_string(), + "Signature could not be authenticated".to_string(), + )); + } } match certifier_service @@ -160,13 +168,11 @@ mod tests { } #[tokio::test] - async fn test_register_signatures_send_unauthenticated_signature_if_authentication_fail() { + async fn test_register_signatures_return_404_if_authentication_fail() { let mut mock_certifier_service = MockCertifierService::new(); mock_certifier_service .expect_register_single_signature() - .withf(|_, signature| !signature.is_authenticated()) - .once() - .return_once(move |_, _| Ok(RegistrationStatus::Registered)); + .never(); let mut dependency_manager = initialize_dependencies().await; dependency_manager.certifier_service = Arc::new(mock_certifier_service); dependency_manager.single_signer_authenticator = @@ -180,12 +186,23 @@ mod tests { let method = Method::POST.as_str(); let path = "/register-signatures"; - request() + 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, + &StatusCode::BAD_REQUEST, + ) + .unwrap(); } #[tokio::test] From eeb23cf4e8d449bfe9026185e7cc67ea1017d2c3 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 17 Sep 2024 17:10:18 +0200 Subject: [PATCH 38/43] Remove `signed_message` from `SingleSignatures` entity As we won't be able to always set this value (ie: with the upcoming p2p registration). In order to do so an refactor have to be done in `mithril-signer` so the `RegisterSignatureMessage::signed_message` property can be set. --- .../buffered_single_signature_record.rs | 3 +- .../src/database/record/single_signature.rs | 1 - .../from_register_signature.rs | 5 -- .../src/entities/single_signatures.rs | 38 +-------------- mithril-common/src/protocol/single_signer.rs | 31 +----------- .../src/test_utils/mithril_fixture.rs | 3 +- .../to_register_signature_message.rs | 29 +++++++---- mithril-signer/src/runtime/runner.rs | 7 ++- mithril-signer/src/runtime/state_machine.rs | 4 +- .../src/services/aggregator_client.rs | 48 ++++++++++++++----- .../test_extensions/certificate_handler.rs | 5 +- 11 files changed, 71 insertions(+), 103 deletions(-) diff --git a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs index 967d8891169..1205659e4fd 100644 --- a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs +++ b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs @@ -53,7 +53,7 @@ impl BufferedSingleSignatureRecord { // the signatures are different for party_id/discriminant pairs. // We can't just reuse fake_data::single_signatures as they are static. Self::try_from_single_signatures( - &SingleSignatures::fake_with_signed_message(party_id.into(), discriminant.to_string()), + &SingleSignatures::fake(party_id.into(), discriminant.to_string()), discriminant, ) .unwrap() @@ -85,7 +85,6 @@ impl TryFrom for SingleSignatures { party_id: value.party_id, won_indexes: value.lottery_indexes, signature: value.signature.try_into()?, - signed_message: None, authentication_status: Default::default(), }; diff --git a/mithril-aggregator/src/database/record/single_signature.rs b/mithril-aggregator/src/database/record/single_signature.rs index 3eef83af506..c273e9dcd91 100644 --- a/mithril-aggregator/src/database/record/single_signature.rs +++ b/mithril-aggregator/src/database/record/single_signature.rs @@ -54,7 +54,6 @@ impl TryFrom for SingleSignatures { party_id: value.signer_id, won_indexes: value.lottery_indexes, signature: value.signature.try_into()?, - signed_message: None, authentication_status: Default::default(), }; diff --git a/mithril-aggregator/src/message_adapters/from_register_signature.rs b/mithril-aggregator/src/message_adapters/from_register_signature.rs index e3121624134..0ef7f029d51 100644 --- a/mithril-aggregator/src/message_adapters/from_register_signature.rs +++ b/mithril-aggregator/src/message_adapters/from_register_signature.rs @@ -22,7 +22,6 @@ impl TryFromMessageAdapter "'FromRegisterSingleSignatureAdapter' can not convert the single signature" })?, won_indexes: register_single_signature_message.won_indexes, - signed_message: register_single_signature_message.signed_message, authentication_status: SingleSignatureAuthenticationStatus::Unauthenticated, }; @@ -43,9 +42,5 @@ mod tests { .unwrap(); assert_eq!("party_id".to_string(), signatures.party_id); - assert_eq!( - Some("signed_message".to_string()), - signatures.signed_message - ); } } diff --git a/mithril-common/src/entities/single_signatures.rs b/mithril-common/src/entities/single_signatures.rs index 0e002fd5a5f..16e6d2d965c 100644 --- a/mithril-common/src/entities/single_signatures.rs +++ b/mithril-common/src/entities/single_signatures.rs @@ -21,13 +21,6 @@ pub struct SingleSignatures { #[serde(rename = "indexes")] pub won_indexes: Vec, - /// Message that is signed by the signer - /// - /// Used to buffer the signature for later if the aggregator has yet to create an open message - /// for the signed entity type. - #[serde(skip_serializing_if = "Option::is_none")] - pub signed_message: Option, - /// Status of the authentication of the signer that emitted the signature #[serde(skip)] pub authentication_status: SingleSignatureAuthenticationStatus, @@ -54,23 +47,6 @@ impl SingleSignatures { party_id: party_id.into(), signature, won_indexes, - signed_message: None, - authentication_status: SingleSignatureAuthenticationStatus::Unauthenticated, - } - } - - /// `SingleSignatures` factory including the signed message - pub fn new_with_signed_message>( - party_id: T, - signature: ProtocolSingleSignature, - won_indexes: Vec, - signed_message: String, - ) -> SingleSignatures { - SingleSignatures { - party_id: party_id.into(), - signature, - won_indexes, - signed_message: Some(signed_message), authentication_status: SingleSignatureAuthenticationStatus::Unauthenticated, } } @@ -88,18 +64,10 @@ impl SingleSignatures { cfg_test_tools! { impl SingleSignatures { - /// Create a fake [SingleSignatures] with valid cryptographic data for testing purposes. - pub fn fake, T2: Into>(party_id: T1, message: T2) -> Self { - Self { - signed_message: None, - ..Self::fake_with_signed_message(party_id, message) - } - } - /// Create a fake [SingleSignatures] with valid cryptographic data for testing purposes. // TODO: this method is slow due to the fixture creation, we should either make // the fixture faster or find a faster alternative. - pub fn fake_with_signed_message, T2: Into>(party_id: T1, message: T2) -> Self { + pub fn fake, T2: Into>(party_id: T1, message: T2) -> Self { use crate::entities::{ProtocolParameters}; use crate::test_utils::{MithrilFixtureBuilder, StakeDistributionGenerationMethod}; @@ -118,7 +86,6 @@ impl SingleSignatures { party_id, signature: signature.signature, won_indexes: vec![10, 15], - signed_message: Some(message), authentication_status: SingleSignatureAuthenticationStatus::Unauthenticated, } } @@ -131,8 +98,7 @@ impl Debug for SingleSignatures { let mut debug = f.debug_struct("SingleSignatures"); debug .field("party_id", &self.party_id) - .field("won_indexes", &format_args!("{:?}", self.won_indexes)) - .field("signed_message", &self.signed_message); + .field("won_indexes", &format_args!("{:?}", self.won_indexes)); match is_pretty_printing { true => debug diff --git a/mithril-common/src/protocol/single_signer.rs b/mithril-common/src/protocol/single_signer.rs index dce40c6cea3..0871d98f438 100644 --- a/mithril-common/src/protocol/single_signer.rs +++ b/mithril-common/src/protocol/single_signer.rs @@ -29,11 +29,10 @@ impl SingleSigner { Some(signature) => { let won_indexes = signature.indexes.clone(); - Ok(Some(SingleSignatures::new_with_signed_message( + Ok(Some(SingleSignatures::new( self.party_id.to_owned(), signature.into(), won_indexes, - signed_message, ))) } None => Ok(None), @@ -75,32 +74,4 @@ mod test { assert!(signature.is_some()); } - - #[test] - fn embed_signed_message_in_issued_signature() { - let fixture = MithrilFixtureBuilder::default().with_signers(3).build(); - let signers = fixture.signers_fixture(); - let signer = signers.first().unwrap(); - - let (single_signer, _) = SignerBuilder::new( - &fixture.signers_with_stake(), - &fixture.protocol_parameters(), - ) - .unwrap() - .build_test_single_signer( - signer.signer_with_stake.clone(), - signer.kes_secret_key_path(), - ) - .unwrap(); - - let message = ProtocolMessage::default(); - let signature = single_signer - .sign(&message) - .expect("Single signer should be able to issue single signature"); - - assert_eq!( - Some(message.compute_hash()), - signature.and_then(|s| s.signed_message) - ); - } } diff --git a/mithril-common/src/test_utils/mithril_fixture.rs b/mithril-common/src/test_utils/mithril_fixture.rs index f7ba68ae551..c410f904721 100644 --- a/mithril-common/src/test_utils/mithril_fixture.rs +++ b/mithril-common/src/test_utils/mithril_fixture.rs @@ -234,11 +234,10 @@ impl SignerFixture { .map(|signature| { let won_indexes = signature.indexes.clone(); - SingleSignatures::new_with_signed_message( + SingleSignatures::new( self.signer_with_stake.party_id.to_owned(), signature.into(), won_indexes, - message, ) }) } 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 666c935cd5c..9626275baea 100644 --- a/mithril-signer/src/message_adapters/to_register_signature_message.rs +++ b/mithril-signer/src/message_adapters/to_register_signature_message.rs @@ -1,15 +1,23 @@ use anyhow::Context; -use mithril_common::entities::{SignedEntityType, SingleSignatures}; +use mithril_common::entities::{ProtocolMessage, SignedEntityType, SingleSignatures}; use mithril_common::messages::{RegisterSignatureMessage, TryToMessageAdapter}; +use mithril_common::protocol::AsMessage; use mithril_common::StdResult; pub struct ToRegisterSignatureMessageAdapter; -impl TryToMessageAdapter<(SignedEntityType, SingleSignatures), RegisterSignatureMessage> - for ToRegisterSignatureMessageAdapter +impl + TryToMessageAdapter< + (SignedEntityType, SingleSignatures, &ProtocolMessage), + RegisterSignatureMessage, + > for ToRegisterSignatureMessageAdapter { fn try_adapt( - (signed_entity_type, single_signature): (SignedEntityType, SingleSignatures), + (signed_entity_type, single_signature, protocol_message): ( + SignedEntityType, + SingleSignatures, + &ProtocolMessage, + ), ) -> StdResult { let message = RegisterSignatureMessage { signed_entity_type, @@ -18,7 +26,7 @@ impl TryToMessageAdapter<(SignedEntityType, SingleSignatures), RegisterSignature "'ToRegisterSignatureMessageAdapter' can not convert the single signature" })?, won_indexes: single_signature.won_indexes, - signed_message: single_signature.signed_message, + signed_message: Some(protocol_message.message_string()), }; Ok(message) @@ -35,14 +43,15 @@ mod tests { fn adapt_ok() { let message: RegisterSignatureMessage = ToRegisterSignatureMessageAdapter::try_adapt(( SignedEntityType::dummy(), - SingleSignatures { - signed_message: Some("signed_message".to_string()), - ..fake_data::single_signatures([1, 3].to_vec()) - }, + fake_data::single_signatures([1, 3].to_vec()), + &ProtocolMessage::default(), )) .unwrap(); assert_eq!("party_id".to_string(), message.party_id); - assert_eq!(Some("signed_message".to_string()), message.signed_message); + assert_eq!( + Some(ProtocolMessage::default().message_string()), + message.signed_message + ); } } diff --git a/mithril-signer/src/runtime/runner.rs b/mithril-signer/src/runtime/runner.rs index ed6d540ba79..17581540bb5 100644 --- a/mithril-signer/src/runtime/runner.rs +++ b/mithril-signer/src/runtime/runner.rs @@ -61,6 +61,7 @@ pub trait Runner: Send + Sync { &self, signed_entity_type: &SignedEntityType, maybe_signature: Option, + signed_message: &ProtocolMessage, ) -> StdResult<()>; /// Read the current era and update the EraChecker. @@ -390,6 +391,7 @@ impl Runner for SignerRunner { &self, signed_entity_type: &SignedEntityType, maybe_signature: Option, + protocol_message: &ProtocolMessage, ) -> StdResult<()> { debug!("RUNNER: send_single_signature"); @@ -398,7 +400,7 @@ impl Runner for SignerRunner { self.services .certificate_handler - .register_signatures(signed_entity_type, &single_signatures) + .register_signatures(signed_entity_type, &single_signatures, protocol_message) .await?; Ok(()) @@ -1047,7 +1049,7 @@ mod tests { certificate_handler .expect_register_signatures() .once() - .returning(|_, _| Ok(())); + .returning(|_, _, _| Ok(())); services.certificate_handler = Arc::new(certificate_handler); let runner = init_runner(Some(services), None).await; @@ -1055,6 +1057,7 @@ mod tests { .send_single_signature( &SignedEntityType::dummy(), Some(fake_data::single_signatures(vec![2, 5, 12])), + &ProtocolMessage::default(), ) .await .expect("send_single_signature should not fail"); diff --git a/mithril-signer/src/runtime/state_machine.rs b/mithril-signer/src/runtime/state_machine.rs index 057254d0a05..624c84cfeed 100644 --- a/mithril-signer/src/runtime/state_machine.rs +++ b/mithril-signer/src/runtime/state_machine.rs @@ -444,7 +444,7 @@ impl StateMachine { message: format!("Could not compute single signature during 'ready to sign → ready to sign' phase (current epoch {current_epoch:?})"), nested_error: Some(e) })?; - self.runner.send_single_signature(signed_entity_type, single_signatures).await + self.runner.send_single_signature(signed_entity_type, single_signatures, &message).await .map_err(|e| RuntimeError::KeepState { message: format!("Could not send single signature during 'ready to sign → ready to sign' phase (current epoch {current_epoch:?})"), nested_error: Some(e) @@ -952,7 +952,7 @@ mod tests { runner .expect_send_single_signature() .once() - .returning(|_, _| Ok(())); + .returning(|_, _, _| Ok(())); runner .expect_can_sign_signed_entity_type() .once() diff --git a/mithril-signer/src/services/aggregator_client.rs b/mithril-signer/src/services/aggregator_client.rs index ca211fb049c..e004bbc356d 100644 --- a/mithril-signer/src/services/aggregator_client.rs +++ b/mithril-signer/src/services/aggregator_client.rs @@ -8,7 +8,8 @@ use thiserror::Error; use mithril_common::{ api_version::APIVersionProvider, entities::{ - CertificatePending, Epoch, EpochSettings, SignedEntityType, Signer, SingleSignatures, + CertificatePending, Epoch, EpochSettings, ProtocolMessage, SignedEntityType, Signer, + SingleSignatures, }, messages::{ AggregatorFeaturesMessage, CertificatePendingMessage, EpochSettingsMessage, @@ -17,9 +18,6 @@ use mithril_common::{ StdError, MITHRIL_API_VERSION_HEADER, MITHRIL_SIGNER_VERSION_HEADER, }; -#[cfg(test)] -use mockall::automock; - use crate::message_adapters::{ FromEpochSettingsAdapter, FromPendingCertificateMessageAdapter, ToRegisterSignatureMessageAdapter, ToRegisterSignerMessageAdapter, @@ -74,7 +72,7 @@ impl AggregatorClientError { } /// Trait for mocking and testing a `AggregatorClient` -#[cfg_attr(test, automock)] +#[cfg_attr(test, mockall::automock)] #[async_trait] pub trait AggregatorClient: Sync + Send { /// Retrieves epoch settings from the aggregator @@ -98,6 +96,7 @@ pub trait AggregatorClient: Sync + Send { &self, signed_entity_type: &SignedEntityType, signatures: &SingleSignatures, + protocol_message: &ProtocolMessage, ) -> Result<(), AggregatorClientError>; /// Retrieves aggregator features message from the aggregator @@ -280,12 +279,14 @@ impl AggregatorClient for AggregatorHTTPClient { &self, signed_entity_type: &SignedEntityType, signatures: &SingleSignatures, + protocol_message: &ProtocolMessage, ) -> Result<(), AggregatorClientError> { debug!("Register signatures"); let url = format!("{}/register-signatures", self.aggregator_endpoint); let register_single_signature_message = ToRegisterSignatureMessageAdapter::try_adapt(( signed_entity_type.to_owned(), signatures.to_owned(), + protocol_message, )) .map_err(|e| AggregatorClientError::Adapter(anyhow!(e)))?; let response = self @@ -435,6 +436,7 @@ pub(crate) mod dumb { &self, _signed_entity_type: &SignedEntityType, _signatures: &SingleSignatures, + _protocol_message: &ProtocolMessage, ) -> Result<(), AggregatorClientError> { Ok(()) } @@ -933,7 +935,11 @@ mod tests { None, ); let register_signatures = certificate_handler - .register_signatures(&SignedEntityType::dummy(), &single_signatures) + .register_signatures( + &SignedEntityType::dummy(), + &single_signatures, + &ProtocolMessage::default(), + ) .await; register_signatures.expect("unexpected error"); } @@ -954,7 +960,11 @@ mod tests { None, ); let error = certificate_handler - .register_signatures(&SignedEntityType::dummy(), &single_signatures) + .register_signatures( + &SignedEntityType::dummy(), + &single_signatures, + &ProtocolMessage::default(), + ) .await .unwrap_err(); @@ -982,7 +992,11 @@ mod tests { None, ); match certificate_handler - .register_signatures(&SignedEntityType::dummy(), &single_signatures) + .register_signatures( + &SignedEntityType::dummy(), + &single_signatures, + &ProtocolMessage::default(), + ) .await .unwrap_err() { @@ -1006,7 +1020,11 @@ mod tests { None, ); match certificate_handler - .register_signatures(&SignedEntityType::dummy(), &single_signatures) + .register_signatures( + &SignedEntityType::dummy(), + &single_signatures, + &ProtocolMessage::default(), + ) .await .unwrap_err() { @@ -1030,7 +1048,11 @@ mod tests { None, ); match certificate_handler - .register_signatures(&SignedEntityType::dummy(), &single_signatures) + .register_signatures( + &SignedEntityType::dummy(), + &single_signatures, + &ProtocolMessage::default(), + ) .await .unwrap_err() { @@ -1055,7 +1077,11 @@ mod tests { ); let error = certificate_handler - .register_signatures(&SignedEntityType::dummy(), &single_signatures) + .register_signatures( + &SignedEntityType::dummy(), + &single_signatures, + &ProtocolMessage::default(), + ) .await .expect_err("register_signatures should fail"); diff --git a/mithril-signer/tests/test_extensions/certificate_handler.rs b/mithril-signer/tests/test_extensions/certificate_handler.rs index 6fc6611a6d0..4d6864cdcb1 100644 --- a/mithril-signer/tests/test_extensions/certificate_handler.rs +++ b/mithril-signer/tests/test_extensions/certificate_handler.rs @@ -4,8 +4,8 @@ use anyhow::anyhow; use async_trait::async_trait; use mithril_common::{ entities::{ - CertificatePending, Epoch, EpochSettings, SignedEntityConfig, SignedEntityType, - SignedEntityTypeDiscriminants, Signer, SingleSignatures, TimePoint, + CertificatePending, Epoch, EpochSettings, ProtocolMessage, SignedEntityConfig, + SignedEntityType, SignedEntityTypeDiscriminants, Signer, SingleSignatures, TimePoint, }, messages::AggregatorFeaturesMessage, test_utils::fake_data, @@ -157,6 +157,7 @@ impl AggregatorClient for FakeAggregator { &self, _signed_entity_type: &SignedEntityType, _signatures: &SingleSignatures, + _protocol_message: &ProtocolMessage, ) -> Result<(), AggregatorClientError> { Ok(()) } From d06bb6e50dd2bc7dd292ff1aa004439149afec38 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:43:00 +0200 Subject: [PATCH 39/43] refactor: rename `AsMessage` to `ToMessage` And the trait method from `message_string` to `to_message`. --- mithril-aggregator/src/multi_signer.rs | 12 ++++++------ .../src/services/certifier/certifier_service.rs | 4 ++-- .../src/certificate_chain/certificate_genesis.rs | 6 +++--- mithril-common/src/entities/protocol_message.rs | 6 +++--- mithril-common/src/protocol/mod.rs | 12 ++++++------ mithril-common/src/protocol/multi_signer.rs | 10 +++++----- mithril-common/src/protocol/single_signer.rs | 6 +++--- mithril-common/src/test_utils/mithril_fixture.rs | 8 ++++---- .../to_register_signature_message.rs | 6 +++--- 9 files changed, 35 insertions(+), 35 deletions(-) diff --git a/mithril-aggregator/src/multi_signer.rs b/mithril-aggregator/src/multi_signer.rs index 52aa75387fe..84e3ddc4650 100644 --- a/mithril-aggregator/src/multi_signer.rs +++ b/mithril-aggregator/src/multi_signer.rs @@ -139,7 +139,7 @@ mod tests { use mithril_common::crypto_helper::tests_setup::*; use mithril_common::entities::{CardanoDbBeacon, Epoch, SignedEntityType, SignerWithStake}; - use mithril_common::protocol::AsMessage; + use mithril_common::protocol::ToMessage; use mithril_common::test_utils::{fake_data, MithrilFixtureBuilder}; use crate::services::FakeEpochService; @@ -186,11 +186,11 @@ mod tests { let signature = fixture.signers_fixture()[0].sign(&message).unwrap(); multi_signer - .verify_single_signature(&message.message_string(), &signature) + .verify_single_signature(&message.to_message(), &signature) .await .unwrap(); - multi_signer.verify_single_signature_for_next_epoch(&message.message_string(), &signature).await.expect_err( + multi_signer.verify_single_signature_for_next_epoch(&message.to_message(), &signature).await.expect_err( "single signature issued in the current epoch should not be valid for the next epoch", ); } @@ -200,13 +200,13 @@ mod tests { multi_signer .verify_single_signature_for_next_epoch( - &message.message_string(), + &message.to_message(), &next_epoch_signature, ) .await .unwrap(); - multi_signer.verify_single_signature(&message.message_string(), &next_epoch_signature).await.expect_err( + multi_signer.verify_single_signature(&message.to_message(), &next_epoch_signature).await.expect_err( "single signature issued in the next epoch should not be valid for the current epoch", ); } @@ -235,7 +235,7 @@ mod tests { for signature in &signatures { multi_signer - .verify_single_signature(&message.message_string(), signature) + .verify_single_signature(&message.to_message(), signature) .await .expect("single signature should be valid"); } diff --git a/mithril-aggregator/src/services/certifier/certifier_service.rs b/mithril-aggregator/src/services/certifier/certifier_service.rs index 5d2fe9203c5..7770f6cd52f 100644 --- a/mithril-aggregator/src/services/certifier/certifier_service.rs +++ b/mithril-aggregator/src/services/certifier/certifier_service.rs @@ -11,7 +11,7 @@ use mithril_common::entities::{ Certificate, CertificateMetadata, CertificateSignature, Epoch, ProtocolMessage, SignedEntityType, SingleSignatures, StakeDistributionParty, }; -use mithril_common::protocol::AsMessage; +use mithril_common::protocol::ToMessage; use mithril_common::{CardanoNetwork, StdResult, TickerService}; use crate::database::record::{OpenMessageRecord, OpenMessageWithSingleSignaturesRecord}; @@ -130,7 +130,7 @@ impl CertifierService for MithrilCertifierService { } self.multi_signer - .verify_single_signature(&open_message.protocol_message.message_string(), signature) + .verify_single_signature(&open_message.protocol_message.to_message(), signature) .await .map_err(|err| { CertifierServiceError::InvalidSingleSignature(signed_entity_type.clone(), err) diff --git a/mithril-common/src/certificate_chain/certificate_genesis.rs b/mithril-common/src/certificate_chain/certificate_genesis.rs index e7e91ad7509..d35626e74cf 100644 --- a/mithril-common/src/certificate_chain/certificate_genesis.rs +++ b/mithril-common/src/certificate_chain/certificate_genesis.rs @@ -15,7 +15,7 @@ use crate::{ ProtocolMessage, ProtocolMessagePartKey, ProtocolParameters, }, era_deprecate, - protocol::AsMessage, + protocol::ToMessage, StdResult, }; @@ -53,7 +53,7 @@ impl CertificateGenesisProducer { } /// Sign the Genesis protocol message (test only) - pub fn sign_genesis_protocol_message( + pub fn sign_genesis_protocol_message( &self, genesis_message: T, ) -> Result { @@ -61,7 +61,7 @@ impl CertificateGenesisProducer { .genesis_signer .as_ref() .ok_or_else(CertificateGenesisProducerError::MissingGenesisSigner)? - .sign(genesis_message.message_string().as_bytes())) + .sign(genesis_message.to_message().as_bytes())) } era_deprecate!("Remove immutable_file_number"); diff --git a/mithril-common/src/entities/protocol_message.rs b/mithril-common/src/entities/protocol_message.rs index 2c27c6df74f..93b80111a8a 100644 --- a/mithril-common/src/entities/protocol_message.rs +++ b/mithril-common/src/entities/protocol_message.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::{collections::BTreeMap, fmt::Display}; -use crate::protocol::AsMessage; +use crate::protocol::ToMessage; /// The key of a ProtocolMessage #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] @@ -97,8 +97,8 @@ impl ProtocolMessage { } } -impl AsMessage for ProtocolMessage { - fn message_string(&self) -> String { +impl ToMessage for ProtocolMessage { + fn to_message(&self) -> String { self.compute_hash() } } diff --git a/mithril-common/src/protocol/mod.rs b/mithril-common/src/protocol/mod.rs index 9366191ee31..bc99cbae067 100644 --- a/mithril-common/src/protocol/mod.rs +++ b/mithril-common/src/protocol/mod.rs @@ -13,19 +13,19 @@ pub use signer_builder::{SignerBuilder, SignerBuilderError}; pub use single_signer::SingleSigner; /// Trait to convert a type to a message that can be signed or verified by the Mithril protocol. -pub trait AsMessage: Sync + Send { +pub trait ToMessage: Sync + Send { /// Return a String representation of the message. - fn message_string(&self) -> String; + fn to_message(&self) -> String; } -impl AsMessage for String { - fn message_string(&self) -> String { +impl ToMessage for String { + fn to_message(&self) -> String { self.clone() } } -impl AsMessage for &str { - fn message_string(&self) -> String { +impl ToMessage for &str { + fn to_message(&self) -> String { self.to_string() } } diff --git a/mithril-common/src/protocol/multi_signer.rs b/mithril-common/src/protocol/multi_signer.rs index df2ddf49313..52c52240486 100644 --- a/mithril-common/src/protocol/multi_signer.rs +++ b/mithril-common/src/protocol/multi_signer.rs @@ -7,7 +7,7 @@ use crate::{ ProtocolMultiSignature, }, entities::SingleSignatures, - protocol::AsMessage, + protocol::ToMessage, StdResult, }; @@ -26,7 +26,7 @@ impl MultiSigner { } /// Aggregate the given single signatures into a multi-signature - pub fn aggregate_single_signatures( + pub fn aggregate_single_signatures( &self, single_signatures: &[SingleSignatures], message: &T, @@ -37,7 +37,7 @@ impl MultiSigner { .collect(); self.protocol_clerk - .aggregate(&protocol_signatures, message.message_string().as_bytes()) + .aggregate(&protocol_signatures, message.to_message().as_bytes()) .map(|multi_sig| multi_sig.into()) } @@ -47,7 +47,7 @@ impl MultiSigner { } /// Verify a single signature - pub fn verify_single_signature( + pub fn verify_single_signature( &self, message: &T, single_signature: &SingleSignatures, @@ -74,7 +74,7 @@ impl MultiSigner { &vk, &stake, &avk, - message.message_string().as_bytes(), + message.to_message().as_bytes(), ) .with_context(|| { format!( diff --git a/mithril-common/src/protocol/single_signer.rs b/mithril-common/src/protocol/single_signer.rs index 0871d98f438..b707d6bb722 100644 --- a/mithril-common/src/protocol/single_signer.rs +++ b/mithril-common/src/protocol/single_signer.rs @@ -1,7 +1,7 @@ use crate::{ crypto_helper::ProtocolSigner, entities::{PartyId, SingleSignatures}, - protocol::AsMessage, + protocol::ToMessage, StdResult, }; @@ -23,8 +23,8 @@ impl SingleSigner { /// Issue a single signature for the given message. /// /// If no lottery are won None will be returned. - pub fn sign(&self, message: &T) -> StdResult> { - let signed_message = message.message_string(); + pub fn sign(&self, message: &T) -> StdResult> { + let signed_message = message.to_message(); match self.protocol_signer.sign(signed_message.as_bytes()) { Some(signature) => { let won_indexes = signature.indexes.clone(); diff --git a/mithril-common/src/test_utils/mithril_fixture.rs b/mithril-common/src/test_utils/mithril_fixture.rs index c410f904721..4c339834e65 100644 --- a/mithril-common/src/test_utils/mithril_fixture.rs +++ b/mithril-common/src/test_utils/mithril_fixture.rs @@ -18,7 +18,7 @@ use crate::{ ProtocolParameters, Signer, SignerWithStake, SingleSignatures, Stake, StakeDistribution, StakeDistributionParty, }, - protocol::{AsMessage, SignerBuilder}, + protocol::{SignerBuilder, ToMessage}, }; /// A fixture of Mithril data types. @@ -199,7 +199,7 @@ impl MithrilFixture { /// Make all underlying signers sign the given message, filter the resulting list to remove /// the signers that did not sign because they loosed the lottery. - pub fn sign_all(&self, message: &T) -> Vec { + pub fn sign_all(&self, message: &T) -> Vec { self.signers .par_iter() .filter_map(|s| s.sign(message)) @@ -227,8 +227,8 @@ impl From for Vec { impl SignerFixture { /// Sign the given protocol message. - pub fn sign(&self, message: &T) -> Option { - let message = message.message_string(); + pub fn sign(&self, message: &T) -> Option { + let message = message.to_message(); self.protocol_signer .sign(message.as_bytes()) .map(|signature| { 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 9626275baea..460f155ef4e 100644 --- a/mithril-signer/src/message_adapters/to_register_signature_message.rs +++ b/mithril-signer/src/message_adapters/to_register_signature_message.rs @@ -1,7 +1,7 @@ use anyhow::Context; use mithril_common::entities::{ProtocolMessage, SignedEntityType, SingleSignatures}; use mithril_common::messages::{RegisterSignatureMessage, TryToMessageAdapter}; -use mithril_common::protocol::AsMessage; +use mithril_common::protocol::ToMessage; use mithril_common::StdResult; pub struct ToRegisterSignatureMessageAdapter; @@ -26,7 +26,7 @@ impl "'ToRegisterSignatureMessageAdapter' can not convert the single signature" })?, won_indexes: single_signature.won_indexes, - signed_message: Some(protocol_message.message_string()), + signed_message: Some(protocol_message.to_message()), }; Ok(message) @@ -50,7 +50,7 @@ mod tests { assert_eq!("party_id".to_string(), message.party_id); assert_eq!( - Some(ProtocolMessage::default().message_string()), + Some(ProtocolMessage::default().to_message()), message.signed_message ); } From ad4d1dbbb2e225730e2e38dede9f429ccef72905 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 18 Sep 2024 17:16:47 +0200 Subject: [PATCH 40/43] refactor+style: Implement changes asked in PR reviews * Simpify some code docs. * Enhance `delete_buffered_single_signature` tests * Make some naming more clear --- .../delete_buffered_single_signature.rs | 26 ++++++++--- .../buffered_single_signature_record.rs | 11 +++++ .../buffered_single_signature_repository.rs | 25 ++++------- .../http_server/routes/signatures_routes.rs | 16 +++---- mithril-aggregator/src/multi_signer.rs | 8 ++-- .../services/certifier/buffered_certifier.rs | 44 +++++++++---------- .../services/certifier/certifier_service.rs | 8 ++-- .../src/services/certifier/interface.rs | 6 +-- .../tools/single_signature_authenticator.rs | 27 ++++++++---- ...te_certificate_with_buffered_signatures.rs | 3 -- .../test_extensions/aggregator_observer.rs | 4 -- .../src/entities/single_signatures.rs | 2 +- openapi.yaml | 2 +- 13 files changed, 101 insertions(+), 81 deletions(-) diff --git a/mithril-aggregator/src/database/query/buffered_single_signature/delete_buffered_single_signature.rs b/mithril-aggregator/src/database/query/buffered_single_signature/delete_buffered_single_signature.rs index ba095ad7b59..6344ea03d22 100644 --- a/mithril-aggregator/src/database/query/buffered_single_signature/delete_buffered_single_signature.rs +++ b/mithril-aggregator/src/database/query/buffered_single_signature/delete_buffered_single_signature.rs @@ -56,6 +56,7 @@ mod tests { use mithril_persistence::sqlite::ConnectionExtensions; use crate::database::query::GetBufferedSingleSignatureQuery; + use crate::database::record::strip_buffered_sigs_date; use crate::database::test_helper::{insert_buffered_single_signatures, main_db_connection}; use super::*; @@ -82,10 +83,17 @@ mod tests { .unwrap(); assert_eq!(2, cursor.count()); - let cursor = connection - .fetch(GetBufferedSingleSignatureQuery::all()) + let remaining_records: Vec = connection + .fetch_collect(GetBufferedSingleSignatureQuery::all()) .unwrap(); - assert_eq!(3, cursor.count()); + assert_eq!( + strip_buffered_sigs_date(&BufferedSingleSignatureRecord::fakes(&[ + ("party_2", CardanoTransactions), + ("party_1", CardanoTransactions), + ("party_2", MithrilStakeDistribution), + ])), + strip_buffered_sigs_date(&remaining_records) + ); let cursor = connection .fetch( @@ -97,9 +105,15 @@ mod tests { .unwrap(); assert_eq!(2, cursor.count()); - let cursor = connection - .fetch(GetBufferedSingleSignatureQuery::all()) + let remaining_records: Vec = connection + .fetch_collect(GetBufferedSingleSignatureQuery::all()) .unwrap(); - assert_eq!(1, cursor.count()); + assert_eq!( + strip_buffered_sigs_date(&BufferedSingleSignatureRecord::fakes(&[( + "party_2", + MithrilStakeDistribution + ),])), + strip_buffered_sigs_date(&remaining_records) + ); } } diff --git a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs index 1205659e4fd..8207a682ebd 100644 --- a/mithril-aggregator/src/database/record/buffered_single_signature_record.rs +++ b/mithril-aggregator/src/database/record/buffered_single_signature_record.rs @@ -160,6 +160,17 @@ impl SqLiteEntity for BufferedSingleSignatureRecord { } } +/// Test only - strip the date from the given records to make them comparable. +#[cfg(test)] +pub(crate) fn strip_buffered_sigs_date( + records: &[BufferedSingleSignatureRecord], +) -> Vec { + records + .iter() + .map(BufferedSingleSignatureRecord::with_stripped_date) + .collect::>() +} + #[cfg(test)] mod tests { use mithril_common::entities::SignedEntityTypeDiscriminants::{ diff --git a/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs b/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs index cdd8815d7a3..8ce9dac196c 100644 --- a/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs +++ b/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs @@ -107,18 +107,11 @@ mod tests { }; use mithril_common::test_utils::fake_keys; - use crate::database::record::BufferedSingleSignatureRecord; + use crate::database::record::{strip_buffered_sigs_date, BufferedSingleSignatureRecord}; use crate::database::test_helper::{insert_buffered_single_signatures, main_db_connection}; use super::*; - fn strip_date(records: &[BufferedSingleSignatureRecord]) -> Vec { - records - .iter() - .map(BufferedSingleSignatureRecord::with_stripped_date) - .collect::>() - } - #[test] fn retrieve_all() { let connection = main_db_connection().unwrap(); @@ -136,12 +129,12 @@ mod tests { let buffered_signatures_ctx = store.get_all().unwrap(); assert_eq!( - strip_date(&BufferedSingleSignatureRecord::fakes(&[ + strip_buffered_sigs_date(&BufferedSingleSignatureRecord::fakes(&[ ("party3", MithrilStakeDistribution), ("party2", CardanoTransactions), ("party1", CardanoTransactions), ])), - strip_date(&buffered_signatures_ctx) + strip_buffered_sigs_date(&buffered_signatures_ctx) ); } @@ -164,22 +157,22 @@ mod tests { .get_by_discriminant::(CardanoTransactions) .unwrap(); assert_eq!( - strip_date(&BufferedSingleSignatureRecord::fakes(&[ + strip_buffered_sigs_date(&BufferedSingleSignatureRecord::fakes(&[ ("party2", CardanoTransactions), ("party1", CardanoTransactions), ])), - strip_date(&buffered_signatures_ctx) + strip_buffered_sigs_date(&buffered_signatures_ctx) ); let buffered_signatures_msd = store .get_by_discriminant::(MithrilStakeDistribution) .unwrap(); assert_eq!( - strip_date(&BufferedSingleSignatureRecord::fakes(&[( + strip_buffered_sigs_date(&BufferedSingleSignatureRecord::fakes(&[( "party3", MithrilStakeDistribution ),])), - strip_date(&buffered_signatures_msd) + strip_buffered_sigs_date(&buffered_signatures_msd) ); } @@ -295,11 +288,11 @@ mod tests { let remaining_msd_sigs = store.get_all().unwrap(); assert_eq!( - strip_date(&BufferedSingleSignatureRecord::fakes(&[ + strip_buffered_sigs_date(&BufferedSingleSignatureRecord::fakes(&[ ("party4", CardanoTransactions), ("party2", MithrilStakeDistribution), ])), - strip_date(&remaining_msd_sigs) + strip_buffered_sigs_date(&remaining_msd_sigs) ); } } diff --git a/mithril-aggregator/src/http_server/routes/signatures_routes.rs b/mithril-aggregator/src/http_server/routes/signatures_routes.rs index 6257dea1926..f60d6f9f1d7 100644 --- a/mithril-aggregator/src/http_server/routes/signatures_routes.rs +++ b/mithril-aggregator/src/http_server/routes/signatures_routes.rs @@ -36,7 +36,7 @@ mod handlers { use crate::{ http_server::routes::reply, message_adapters::FromRegisterSingleSignatureAdapter, - services::{CertifierService, CertifierServiceError, RegistrationStatus}, + services::{CertifierService, CertifierServiceError, SignatureRegistrationStatus}, unwrap_to_internal_server_error, SingleSignatureAuthenticator, }; @@ -99,8 +99,8 @@ mod handlers { Ok(reply::server_error(err)) } }, - Ok(RegistrationStatus::Registered) => Ok(reply::empty(StatusCode::CREATED)), - Ok(RegistrationStatus::Buffered) => Ok(reply::empty(StatusCode::ACCEPTED)), + Ok(SignatureRegistrationStatus::Registered) => Ok(reply::empty(StatusCode::CREATED)), + Ok(SignatureRegistrationStatus::Buffered) => Ok(reply::empty(StatusCode::ACCEPTED)), } } } @@ -119,7 +119,7 @@ mod tests { use crate::{ http_server::SERVER_BASE_PATH, initialize_dependencies, - services::{CertifierServiceError, MockCertifierService, RegistrationStatus}, + services::{CertifierServiceError, MockCertifierService, SignatureRegistrationStatus}, SingleSignatureAuthenticator, }; @@ -145,7 +145,7 @@ mod tests { .expect_register_single_signature() .withf(|_, signature| signature.is_authenticated()) .once() - .return_once(move |_, _| Ok(RegistrationStatus::Registered)); + .return_once(move |_, _| Ok(SignatureRegistrationStatus::Registered)); let mut dependency_manager = initialize_dependencies().await; dependency_manager.certifier_service = Arc::new(mock_certifier_service); dependency_manager.single_signer_authenticator = @@ -210,7 +210,7 @@ mod tests { let mut mock_certifier_service = MockCertifierService::new(); mock_certifier_service .expect_register_single_signature() - .return_once(move |_, _| Ok(RegistrationStatus::Registered)); + .return_once(move |_, _| Ok(SignatureRegistrationStatus::Registered)); let mut dependency_manager = initialize_dependencies().await; dependency_manager.certifier_service = Arc::new(mock_certifier_service); @@ -243,7 +243,7 @@ mod tests { let mut mock_certifier_service = MockCertifierService::new(); mock_certifier_service .expect_register_single_signature() - .return_once(move |_, _| Ok(RegistrationStatus::Buffered)); + .return_once(move |_, _| Ok(SignatureRegistrationStatus::Buffered)); let mut dependency_manager = initialize_dependencies().await; dependency_manager.certifier_service = Arc::new(mock_certifier_service); @@ -276,7 +276,7 @@ mod tests { let mut mock_certifier_service = MockCertifierService::new(); mock_certifier_service .expect_register_single_signature() - .return_once(move |_, _| Ok(RegistrationStatus::Registered)); + .return_once(move |_, _| Ok(SignatureRegistrationStatus::Registered)); let mut dependency_manager = initialize_dependencies().await; dependency_manager.certifier_service = Arc::new(mock_certifier_service); diff --git a/mithril-aggregator/src/multi_signer.rs b/mithril-aggregator/src/multi_signer.rs index 84e3ddc4650..b4aded9ee96 100644 --- a/mithril-aggregator/src/multi_signer.rs +++ b/mithril-aggregator/src/multi_signer.rs @@ -27,7 +27,7 @@ pub trait MultiSigner: Sync + Send { ) -> StdResult<()>; /// Verify a single signature using the stake distribution of the next epoch - async fn verify_single_signature_for_next_epoch( + async fn verify_single_signature_for_next_stake_distribution( &self, message: &str, signatures: &entities::SingleSignatures, @@ -87,7 +87,7 @@ impl MultiSigner for MultiSignerImpl { self.run_verify_single_signature(message, single_signature, protocol_multi_signer) } - async fn verify_single_signature_for_next_epoch( + async fn verify_single_signature_for_next_stake_distribution( &self, message: &str, single_signature: &entities::SingleSignatures, @@ -190,7 +190,7 @@ mod tests { .await .unwrap(); - multi_signer.verify_single_signature_for_next_epoch(&message.to_message(), &signature).await.expect_err( + multi_signer.verify_single_signature_for_next_stake_distribution(&message.to_message(), &signature).await.expect_err( "single signature issued in the current epoch should not be valid for the next epoch", ); } @@ -199,7 +199,7 @@ mod tests { let next_epoch_signature = next_fixture.signers_fixture()[0].sign(&message).unwrap(); multi_signer - .verify_single_signature_for_next_epoch( + .verify_single_signature_for_next_stake_distribution( &message.to_message(), &next_epoch_signature, ) diff --git a/mithril-aggregator/src/services/certifier/buffered_certifier.rs b/mithril-aggregator/src/services/certifier/buffered_certifier.rs index 5f5fea93ce2..7edf5538798 100644 --- a/mithril-aggregator/src/services/certifier/buffered_certifier.rs +++ b/mithril-aggregator/src/services/certifier/buffered_certifier.rs @@ -10,10 +10,11 @@ use mithril_common::StdResult; use crate::entities::OpenMessage; use crate::services::{ - BufferedSingleSignatureStore, CertifierService, CertifierServiceError, RegistrationStatus, + BufferedSingleSignatureStore, CertifierService, CertifierServiceError, + SignatureRegistrationStatus, }; -/// A decorator of [CertifierService] that buffers that can buffer registration of single signatures +/// A decorator of [CertifierService] that can buffer registration of single signatures /// when the open message is not yet created. /// /// When an open message is created, buffered single signatures for the open message type are @@ -90,7 +91,7 @@ impl CertifierService for BufferedCertifierService { &self, signed_entity_type: &SignedEntityType, signature: &SingleSignatures, - ) -> StdResult { + ) -> StdResult { match self .certifier_service .register_single_signature(signed_entity_type, signature) @@ -110,7 +111,7 @@ impl CertifierService for BufferedCertifierService { .buffer_signature(signed_entity_type.into(), signature) .await?; - Ok(RegistrationStatus::Buffered) + Ok(SignatureRegistrationStatus::Buffered) } _ => Err(error), }, @@ -122,14 +123,8 @@ impl CertifierService for BufferedCertifierService { signed_entity_type: &SignedEntityType, protocol_message: &ProtocolMessage, ) -> StdResult { - // IMPORTANT: this method should not fail if the open message creation succeeds. - // else: - // 1 - state machine won't create a pending certificate for the signed entity type - // 2 - Without a pending certificate, the signers won't send their signatures - // 3 - state machine will retry the transition to signing and, since an open message was - // opened for the signed entity type, it will try the next on the list. - // 4 - since the state machine never was in signing it will never try to aggregate - // signatures for the signed entity type + // IMPORTANT: this method should not fail if the open message creation succeeds + // Otherwise, the state machine won't aggregate signatures for this open message. let creation_result = self .certifier_service @@ -234,7 +229,10 @@ mod tests { async fn run_register_signature_scenario( decorated_certifier_mock_config: impl FnOnce(&mut MockCertifierService), signature_to_register: &SingleSignatures, - ) -> (StdResult, Vec) { + ) -> ( + StdResult, + Vec, + ) { let store = Arc::new(BufferedSingleSignatureRepository::new(Arc::new( main_db_connection().unwrap(), ))); @@ -267,14 +265,14 @@ mod tests { |mock_certifier| { mock_certifier .expect_register_single_signature() - .returning(|_, _| Ok(RegistrationStatus::Registered)); + .returning(|_, _| Ok(SignatureRegistrationStatus::Registered)); }, &SingleSignatures::fake("party_1", "a message"), ) .await; let status = registration_result.expect("Registration should have succeed"); - assert_eq!(status, RegistrationStatus::Registered); + assert_eq!(status, SignatureRegistrationStatus::Registered); assert_eq!( buffered_signatures_after_registration, Vec::::new() @@ -306,7 +304,7 @@ mod tests { .await; let status = registration_result.expect("Registration should have succeed"); - assert_eq!(status, RegistrationStatus::Buffered); + assert_eq!(status, SignatureRegistrationStatus::Buffered); assert_eq!( buffered_signatures_after_registration, vec![SingleSignatures::fake("party_1", "a message")] @@ -379,14 +377,14 @@ mod tests { eq(SingleSignatures::fake("party_1", "message 1")), ) .once() - .returning(|_, _| Ok(RegistrationStatus::Registered)); + .returning(|_, _| Ok(SignatureRegistrationStatus::Registered)); mock.expect_register_single_signature() .with( eq(SignedEntityType::MithrilStakeDistribution(Epoch(5))), eq(SingleSignatures::fake("party_2", "message 2")), ) .once() - .returning(|_, _| Ok(RegistrationStatus::Registered)); + .returning(|_, _| Ok(SignatureRegistrationStatus::Registered)); }), store.clone(), TestLogger::stdout(), @@ -441,7 +439,7 @@ mod tests { mock.expect_register_single_signature() .with(always(), eq(fake_data::single_signatures(vec![1]))) - .returning(|_, _| Ok(RegistrationStatus::Registered)) + .returning(|_, _| Ok(SignatureRegistrationStatus::Registered)) .once(); mock.expect_register_single_signature() .with(always(), eq(fake_data::single_signatures(vec![2]))) @@ -455,7 +453,7 @@ mod tests { .once(); mock.expect_register_single_signature() .with(always(), eq(fake_data::single_signatures(vec![3]))) - .returning(|_, _| Ok(RegistrationStatus::Registered)) + .returning(|_, _| Ok(SignatureRegistrationStatus::Registered)) .once(); }, |mock| { @@ -482,7 +480,7 @@ mod tests { mock.expect_create_open_message() .returning(|_, _| Ok(OpenMessage::dummy())); mock.expect_register_single_signature() - .returning(|_, _| Ok(RegistrationStatus::Registered)); + .returning(|_, _| Ok(SignatureRegistrationStatus::Registered)); }, |mock| { mock.expect_get_buffered_signatures() @@ -493,7 +491,7 @@ mod tests { } #[tokio::test] - async fn do_not_return_an_error_if_getting_registering_signature_fail() { + async fn do_not_return_an_error_if_registering_signature_fail() { run_scenario( |mock| { mock.expect_create_open_message() @@ -516,7 +514,7 @@ mod tests { mock.expect_create_open_message() .returning(|_, _| Ok(OpenMessage::dummy())); mock.expect_register_single_signature() - .returning(|_, _| Ok(RegistrationStatus::Registered)); + .returning(|_, _| Ok(SignatureRegistrationStatus::Registered)); }, |mock| { mock.expect_get_buffered_signatures() diff --git a/mithril-aggregator/src/services/certifier/certifier_service.rs b/mithril-aggregator/src/services/certifier/certifier_service.rs index 7770f6cd52f..a2c26097cfc 100644 --- a/mithril-aggregator/src/services/certifier/certifier_service.rs +++ b/mithril-aggregator/src/services/certifier/certifier_service.rs @@ -20,7 +20,7 @@ use crate::database::repository::{ }; use crate::dependency_injection::EpochServiceWrapper; use crate::entities::OpenMessage; -use crate::services::{CertifierService, CertifierServiceError, RegistrationStatus}; +use crate::services::{CertifierService, CertifierServiceError, SignatureRegistrationStatus}; use crate::MultiSigner; /// Mithril CertifierService implementation @@ -105,7 +105,7 @@ impl CertifierService for MithrilCertifierService { &self, signed_entity_type: &SignedEntityType, signature: &SingleSignatures, - ) -> StdResult { + ) -> StdResult { debug!("CertifierService::register_single_signature(signed_entity_type: {signed_entity_type:?}, single_signatures: {signature:?}"); trace!("CertifierService::register_single_signature"; "complete_single_signatures" => #?signature); @@ -143,7 +143,7 @@ impl CertifierService for MithrilCertifierService { 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(RegistrationStatus::Registered) + Ok(SignatureRegistrationStatus::Registered) } async fn create_open_message( @@ -638,7 +638,7 @@ mod tests { err.downcast_ref::(), Some(CertifierServiceError::InvalidSingleSignature(..)) ), - "Expected CertifierServiceError, got: '{err:?}'" + "Expected CertifierServiceError::InvalidSingleSignature, got: '{err:?}'" ); } diff --git a/mithril-aggregator/src/services/certifier/interface.rs b/mithril-aggregator/src/services/certifier/interface.rs index 96701d31abb..e59c1489573 100644 --- a/mithril-aggregator/src/services/certifier/interface.rs +++ b/mithril-aggregator/src/services/certifier/interface.rs @@ -53,7 +53,7 @@ pub enum CertifierServiceError { /// Status of a successful registration of a single signature. #[derive(Debug, Copy, Clone, PartialEq)] -pub enum RegistrationStatus { +pub enum SignatureRegistrationStatus { /// The signature was registered and will be used for the next certificate. Registered, @@ -80,7 +80,7 @@ pub trait CertifierService: Sync + Send { &self, signed_entity_type: &SignedEntityType, signature: &SingleSignatures, - ) -> StdResult; + ) -> 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 @@ -130,7 +130,7 @@ pub trait CertifierService: Sync + Send { async fn verify_certificate_chain(&self, epoch: Epoch) -> StdResult<()>; } -/// ## BufferedSignatureStore +/// ## BufferedSingleSignatureStore /// /// Allow to buffer single signatures for later use when an open message isn't available yet. #[cfg_attr(test, mockall::automock)] diff --git a/mithril-aggregator/src/tools/single_signature_authenticator.rs b/mithril-aggregator/src/tools/single_signature_authenticator.rs index 87c77492880..768d13705eb 100644 --- a/mithril-aggregator/src/tools/single_signature_authenticator.rs +++ b/mithril-aggregator/src/tools/single_signature_authenticator.rs @@ -45,7 +45,10 @@ impl SingleSignatureAuthenticator { // new signatures using the next epoch stake distribution if self .multi_signer - .verify_single_signature_for_next_epoch(signed_message, single_signature) + .verify_single_signature_for_next_stake_distribution( + signed_message, + single_signature, + ) .await .is_ok() { @@ -84,7 +87,7 @@ impl SingleSignatureAuthenticator { .expect_verify_single_signature() .returning(|_, _| Ok(())); multi_signer - .expect_verify_single_signature_for_next_epoch() + .expect_verify_single_signature_for_next_stake_distribution() .returning(|_, _| Ok(())); Self { @@ -99,7 +102,7 @@ impl SingleSignatureAuthenticator { .expect_verify_single_signature() .returning(|_, _| Err(anyhow::anyhow!("error"))); multi_signer - .expect_verify_single_signature_for_next_epoch() + .expect_verify_single_signature_for_next_stake_distribution() .returning(|_, _| Err(anyhow::anyhow!("error"))); Self { @@ -170,7 +173,7 @@ mod tests { .expect_verify_single_signature() .returning(|_, _| Err(anyhow!("error"))); mock_config - .expect_verify_single_signature_for_next_epoch() + .expect_verify_single_signature_for_next_stake_distribution() .returning(|_, _| Ok(())); }), TestLogger::stdout(), @@ -202,8 +205,12 @@ mod tests { .expect_verify_single_signature() .returning(|_, _| Err(anyhow!("verify_single_signature error"))); mock_config - .expect_verify_single_signature_for_next_epoch() - .returning(|_, _| Err(anyhow!("verify_single_signature_for_next_epoch error"))); + .expect_verify_single_signature_for_next_stake_distribution() + .returning(|_, _| { + Err(anyhow!( + "verify_single_signature_for_next_stake_distribution error" + )) + }); }), TestLogger::stdout(), ); @@ -234,8 +241,12 @@ mod tests { .expect_verify_single_signature() .returning(|_, _| Err(anyhow!("verify_single_signature error"))); mock_config - .expect_verify_single_signature_for_next_epoch() - .returning(|_, _| Err(anyhow!("verify_single_signature_for_next_epoch error"))); + .expect_verify_single_signature_for_next_stake_distribution() + .returning(|_, _| { + Err(anyhow!( + "verify_single_signature_for_next_stake_distribution error" + )) + }); }), TestLogger::stdout(), ); diff --git a/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs b/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs index 066145074b8..de60694d359 100644 --- a/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs +++ b/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs @@ -78,9 +78,6 @@ async fn create_certificate_with_buffered_signatures() { cycle!(tester, "signing"); comment!("register signers"); - // Note: removing this registration makes the epoch_service unable to build its - // next_protocol_multi_signer because the list of next_signers is empty. - // Todo: Is this a bug ? tester .register_signers(&fixture.signers_fixture()) .await diff --git a/mithril-aggregator/tests/test_extensions/aggregator_observer.rs b/mithril-aggregator/tests/test_extensions/aggregator_observer.rs index 8965064f969..b633f43286b 100644 --- a/mithril-aggregator/tests/test_extensions/aggregator_observer.rs +++ b/mithril-aggregator/tests/test_extensions/aggregator_observer.rs @@ -62,10 +62,6 @@ impl AggregatorObserver { /// Compute the full [SignedEntityType] for the given discriminant based on the current /// [TimePoint]. - /// - /// Note: It computes what would be the signed entity type if the aggregator was working on this - /// discriminant now. - /// Consequently, it doesn't mean that an open message is available for this type. pub async fn build_current_signed_entity_type( &self, discriminant: SignedEntityTypeDiscriminants, diff --git a/mithril-common/src/entities/single_signatures.rs b/mithril-common/src/entities/single_signatures.rs index 16e6d2d965c..d800083bb90 100644 --- a/mithril-common/src/entities/single_signatures.rs +++ b/mithril-common/src/entities/single_signatures.rs @@ -67,7 +67,7 @@ impl SingleSignatures { /// Create a fake [SingleSignatures] with valid cryptographic data for testing purposes. // TODO: this method is slow due to the fixture creation, we should either make // the fixture faster or find a faster alternative. - pub fn fake, T2: Into>(party_id: T1, message: T2) -> Self { + pub fn fake, TMessage: Into>(party_id: TPartyId, message: TMessage) -> Self { use crate::entities::{ProtocolParameters}; use crate::test_utils::{MithrilFixtureBuilder, StakeDistributionGenerationMethod}; diff --git a/openapi.yaml b/openapi.yaml index 0e6d2c44d50..ae35e9d431b 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -568,7 +568,7 @@ paths: "201": description: signatures registration succeeded "202": - description: signatures registration buffered for later processing + description: signatures registration received and queued for later processing "400": description: signatures registration bad request content: From 90f7c20a5e143fdbf528d290115876de56f95987 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:27:06 +0200 Subject: [PATCH 41/43] style: naming & comments adjustements from pr reviews --- .../delete_buffered_single_signature.rs | 4 ++-- .../get_buffered_single_signature.rs | 6 ++---- .../buffered_single_signature_repository.rs | 16 ++++++++-------- .../src/dependency_injection/builder.rs | 2 +- .../src/http_server/routes/signatures_routes.rs | 2 +- .../src/services/certifier/interface.rs | 6 +++--- mithril-common/src/entities/single_signatures.rs | 4 ++-- 7 files changed, 19 insertions(+), 21 deletions(-) diff --git a/mithril-aggregator/src/database/query/buffered_single_signature/delete_buffered_single_signature.rs b/mithril-aggregator/src/database/query/buffered_single_signature/delete_buffered_single_signature.rs index 6344ea03d22..f578f4814b6 100644 --- a/mithril-aggregator/src/database/query/buffered_single_signature/delete_buffered_single_signature.rs +++ b/mithril-aggregator/src/database/query/buffered_single_signature/delete_buffered_single_signature.rs @@ -12,7 +12,7 @@ pub struct DeleteBufferedSingleSignatureQuery { impl DeleteBufferedSingleSignatureQuery { pub fn by_discriminant_and_party_ids( - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + signed_entity_type_discriminant: SignedEntityTypeDiscriminants, party_ids: Vec, ) -> Self { let ids_values = party_ids.into_iter().map(Value::String).collect(); @@ -21,7 +21,7 @@ impl DeleteBufferedSingleSignatureQuery { condition: WhereCondition::new( "signed_entity_type_id = ?*", vec![Value::Integer( - signed_entity_type_discriminants.index() as i64 + signed_entity_type_discriminant.index() as i64 )], ) .and_where(WhereCondition::where_in("party_id", ids_values)), diff --git a/mithril-aggregator/src/database/query/buffered_single_signature/get_buffered_single_signature.rs b/mithril-aggregator/src/database/query/buffered_single_signature/get_buffered_single_signature.rs index 02518f8f1a3..7365e715a3f 100644 --- a/mithril-aggregator/src/database/query/buffered_single_signature/get_buffered_single_signature.rs +++ b/mithril-aggregator/src/database/query/buffered_single_signature/get_buffered_single_signature.rs @@ -18,14 +18,12 @@ impl GetBufferedSingleSignatureQuery { } } - pub fn by_discriminant( - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, - ) -> Self { + pub fn by_discriminant(signed_entity_type_discriminant: SignedEntityTypeDiscriminants) -> Self { Self { condition: WhereCondition::new( "signed_entity_type_id = ?*", vec![Value::Integer( - signed_entity_type_discriminants.index() as i64 + signed_entity_type_discriminant.index() as i64 )], ), } diff --git a/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs b/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs index 8ce9dac196c..e4265f1ab41 100644 --- a/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs +++ b/mithril-aggregator/src/database/repository/buffered_single_signature_repository.rs @@ -34,7 +34,7 @@ impl BufferedSingleSignatureRepository { fn get_by_discriminant( &self, - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + signed_entity_type_discriminant: SignedEntityTypeDiscriminants, ) -> StdResult> where T: TryFrom, @@ -43,7 +43,7 @@ impl BufferedSingleSignatureRepository { let records: Vec = self.connection .fetch_collect(GetBufferedSingleSignatureQuery::by_discriminant( - signed_entity_type_discriminants, + signed_entity_type_discriminant, ))?; let mut entities: Vec = Vec::with_capacity(records.len()); @@ -59,12 +59,12 @@ impl BufferedSingleSignatureRepository { impl BufferedSingleSignatureStore for BufferedSingleSignatureRepository { async fn buffer_signature( &self, - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + signed_entity_type_discriminant: SignedEntityTypeDiscriminants, signature: &SingleSignatures, ) -> StdResult<()> { let record = BufferedSingleSignatureRecord::try_from_single_signatures( signature, - signed_entity_type_discriminants, + signed_entity_type_discriminant, ) .with_context(|| "Failed to convert SingleSignatures to BufferedSingleSignatureRecord")?; @@ -78,20 +78,20 @@ impl BufferedSingleSignatureStore for BufferedSingleSignatureRepository { async fn get_buffered_signatures( &self, - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + signed_entity_type_discriminant: SignedEntityTypeDiscriminants, ) -> StdResult> { - self.get_by_discriminant(signed_entity_type_discriminants) + self.get_by_discriminant(signed_entity_type_discriminant) } async fn remove_buffered_signatures( &self, - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + signed_entity_type_discriminant: SignedEntityTypeDiscriminants, single_signatures: Vec, ) -> StdResult<()> { let signatures_party_ids = single_signatures.into_iter().map(|s| s.party_id).collect(); self.connection.fetch_first( DeleteBufferedSingleSignatureQuery::by_discriminant_and_party_ids( - signed_entity_type_discriminants, + signed_entity_type_discriminant, signatures_party_ids, ), )?; diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index 4c79c8a17cd..a5afeae4ef1 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -1277,7 +1277,7 @@ impl DependenciesBuilder { Ok(Arc::new(authenticator)) } - /// [MithrilSignerRegisterer] service + /// [SingleSignatureAuthenticator] service pub async fn get_single_signature_authenticator( &mut self, ) -> Result> { diff --git a/mithril-aggregator/src/http_server/routes/signatures_routes.rs b/mithril-aggregator/src/http_server/routes/signatures_routes.rs index f60d6f9f1d7..029f2f758af 100644 --- a/mithril-aggregator/src/http_server/routes/signatures_routes.rs +++ b/mithril-aggregator/src/http_server/routes/signatures_routes.rs @@ -168,7 +168,7 @@ mod tests { } #[tokio::test] - async fn test_register_signatures_return_404_if_authentication_fail() { + async fn test_register_signatures_return_400_if_authentication_fail() { let mut mock_certifier_service = MockCertifierService::new(); mock_certifier_service .expect_register_single_signature() diff --git a/mithril-aggregator/src/services/certifier/interface.rs b/mithril-aggregator/src/services/certifier/interface.rs index e59c1489573..043dc0dd158 100644 --- a/mithril-aggregator/src/services/certifier/interface.rs +++ b/mithril-aggregator/src/services/certifier/interface.rs @@ -139,20 +139,20 @@ pub trait BufferedSingleSignatureStore: Sync + Send { /// Buffer a single signature for later use. async fn buffer_signature( &self, - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + signed_entity_type_discriminant: SignedEntityTypeDiscriminants, signature: &SingleSignatures, ) -> StdResult<()>; /// Get the buffered single signatures for the given signed entity discriminant. async fn get_buffered_signatures( &self, - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + signed_entity_type_discriminant: SignedEntityTypeDiscriminants, ) -> StdResult>; /// Remove the given single signatures from the buffer. async fn remove_buffered_signatures( &self, - signed_entity_type_discriminants: SignedEntityTypeDiscriminants, + signed_entity_type_discriminant: SignedEntityTypeDiscriminants, single_signatures: Vec, ) -> StdResult<()>; } diff --git a/mithril-common/src/entities/single_signatures.rs b/mithril-common/src/entities/single_signatures.rs index d800083bb90..a136604b5dd 100644 --- a/mithril-common/src/entities/single_signatures.rs +++ b/mithril-common/src/entities/single_signatures.rs @@ -29,9 +29,9 @@ pub struct SingleSignatures { /// Status of the authentication of the signer that emitted the signature #[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] pub enum SingleSignatureAuthenticationStatus { - /// We could authenticate the signer that emitted the signature + /// The signer that emitted the signature is authenticated Authenticated, - /// We could not authenticate the signer that emitted the signature + /// The signer that emitted the signature is not authenticated #[default] Unauthenticated, } From f62785c6590418be1c75f5a6ef9669ac1fb61fd7 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Thu, 19 Sep 2024 12:31:41 +0200 Subject: [PATCH 42/43] chore: bump crates & openapi versions * Mithril-aggregator from `0.5.63` to `0.5.64` * Mithril-signer from `0.2.183` to `0.2.184` * Mithril-common from `0.4.51` to `0.4.52` * OpenApi from `0.1.29` to `0.1.30` --- Cargo.lock | 6 +++--- mithril-aggregator/Cargo.toml | 2 +- mithril-common/Cargo.toml | 2 +- mithril-signer/Cargo.toml | 2 +- openapi.yaml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ec3c56289e..272a3ac21fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3403,7 +3403,7 @@ dependencies = [ [[package]] name = "mithril-aggregator" -version = "0.5.63" +version = "0.5.64" dependencies = [ "anyhow", "async-trait", @@ -3559,7 +3559,7 @@ dependencies = [ [[package]] name = "mithril-common" -version = "0.4.51" +version = "0.4.52" dependencies = [ "anyhow", "async-trait", @@ -3704,7 +3704,7 @@ dependencies = [ [[package]] name = "mithril-signer" -version = "0.2.183" +version = "0.2.184" dependencies = [ "anyhow", "async-trait", diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml index ac7e9395526..36489257b7a 100644 --- a/mithril-aggregator/Cargo.toml +++ b/mithril-aggregator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-aggregator" -version = "0.5.63" +version = "0.5.64" description = "A Mithril Aggregator server" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index 3863cfe6358..0116e694bcf 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-common" -version = "0.4.51" +version = "0.4.52" description = "Common types, interfaces, and utilities for Mithril nodes." authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-signer/Cargo.toml b/mithril-signer/Cargo.toml index 98d255f744e..6c43d149a72 100644 --- a/mithril-signer/Cargo.toml +++ b/mithril-signer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-signer" -version = "0.2.183" +version = "0.2.184" description = "A Mithril Signer" authors = { workspace = true } edition = { workspace = true } diff --git a/openapi.yaml b/openapi.yaml index ae35e9d431b..9f96bd7080d 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.29 + version: 0.1.30 title: Mithril Aggregator Server description: | The REST API provided by a Mithril Aggregator Node in a Mithril network. From 5b578c7bfdbef6f546d986956d8232b4adc69832 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:59:30 +0200 Subject: [PATCH 43/43] docs: update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbc13622a95..948ac003f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ As a minor extension, we have adopted a slightly different versioning convention - Optimizations of the state machine used by the signer to create individual signatures. +- Support for buffering of incoming single signatures by the aggregator if it can not aggregate them yet + - Crates versions: | Crate | Version |