From a3bf45e61d78c167f2cb3a373cd385ce4e4fd592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Tue, 7 Oct 2025 18:06:15 +0200 Subject: [PATCH 01/18] feature(aggregator): WIP: add protocol-config dependency and start a local configuration implementation --- Cargo.lock | 1 + mithril-aggregator/Cargo.toml | 1 + .../enablers/mithril_network_configuration.rs | 32 +++++++++++++++++++ .../builder/enablers/mod.rs | 1 + 4 files changed, 35 insertions(+) create mode 100644 mithril-aggregator/src/dependency_injection/builder/enablers/mithril_network_configuration.rs diff --git a/Cargo.lock b/Cargo.lock index 44ef9f6de80..b47c8220e45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3621,6 +3621,7 @@ dependencies = [ "mithril-era", "mithril-metric", "mithril-persistence", + "mithril-protocol-config", "mithril-resource-pool", "mithril-signed-entity-lock", "mithril-signed-entity-preloader", diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml index 8390d0136fb..89a470ad16b 100644 --- a/mithril-aggregator/Cargo.toml +++ b/mithril-aggregator/Cargo.toml @@ -36,6 +36,7 @@ mithril-doc = { path = "../internal/mithril-doc" } mithril-era = { path = "../internal/mithril-era" } mithril-metric = { path = "../internal/mithril-metric" } mithril-persistence = { path = "../internal/mithril-persistence" } +mithril-protocol-config = { path = "../internal/mithril-protocol-config" } mithril-resource-pool = { path = "../internal/mithril-resource-pool" } mithril-signed-entity-lock = { path = "../internal/signed-entity/mithril-signed-entity-lock" } mithril-signed-entity-preloader = { path = "../internal/signed-entity/mithril-signed-entity-preloader" } diff --git a/mithril-aggregator/src/dependency_injection/builder/enablers/mithril_network_configuration.rs b/mithril-aggregator/src/dependency_injection/builder/enablers/mithril_network_configuration.rs new file mode 100644 index 00000000000..09506f27098 --- /dev/null +++ b/mithril-aggregator/src/dependency_injection/builder/enablers/mithril_network_configuration.rs @@ -0,0 +1,32 @@ +use std::collections::BTreeSet; + +use crate::entities::AggregatorEpochSettings; +use async_trait::async_trait; +use mithril_common::{StdResult, entities::SignedEntityTypeDiscriminants, test::double::Dummy}; +use mithril_protocol_config::{ + interface::MithrilNetworkConfigurationProvider, model::MithrilNetworkConfiguration, +}; + +pub struct LocalMithrilNetworkConfigurationProvider { + epoch_settings: AggregatorEpochSettings, + allowed_discriminants: BTreeSet, +} + +impl LocalMithrilNetworkConfigurationProvider { + pub fn new( + epoch_settings: AggregatorEpochSettings, + allowed_discriminants: BTreeSet, + ) -> Self { + Self { + epoch_settings, + allowed_discriminants, + } + } +} + +#[async_trait] +impl MithrilNetworkConfigurationProvider for LocalMithrilNetworkConfigurationProvider { + async fn get(&self) -> StdResult { + Ok(MithrilNetworkConfiguration::dummy()) + } +} diff --git a/mithril-aggregator/src/dependency_injection/builder/enablers/mod.rs b/mithril-aggregator/src/dependency_injection/builder/enablers/mod.rs index ce6ea560874..542bd3a817b 100644 --- a/mithril-aggregator/src/dependency_injection/builder/enablers/mod.rs +++ b/mithril-aggregator/src/dependency_injection/builder/enablers/mod.rs @@ -3,4 +3,5 @@ mod cardano_node; mod epoch; mod misc; +mod mithril_network_configuration; mod ticker; From 6f1d085cbc0c9b040788cbbdd470fb257150ef52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Fri, 10 Oct 2025 17:03:18 +0200 Subject: [PATCH 02/18] feat(aggregator): implement local configuration for mithril network configuration and adapt epoch service --- .../builder/enablers/epoch.rs | 41 ++- .../enablers/mithril_network_configuration.rs | 32 --- .../builder/enablers/mod.rs | 1 - .../src/dependency_injection/builder/mod.rs | 9 + .../dependency_injection/containers/serve.rs | 4 + .../src/entities/aggregator_epoch_settings.rs | 28 ++ .../src/services/epoch_service.rs | 200 ++++++++------ mithril-aggregator/src/services/mod.rs | 2 + .../network_configuration_provider.rs | 248 ++++++++++++++++++ 9 files changed, 447 insertions(+), 118 deletions(-) delete mode 100644 mithril-aggregator/src/dependency_injection/builder/enablers/mithril_network_configuration.rs create mode 100644 mithril-aggregator/src/services/network_configuration_provider.rs diff --git a/mithril-aggregator/src/dependency_injection/builder/enablers/epoch.rs b/mithril-aggregator/src/dependency_injection/builder/enablers/epoch.rs index edc0cf5eed8..1ec8b30c9e0 100644 --- a/mithril-aggregator/src/dependency_injection/builder/enablers/epoch.rs +++ b/mithril-aggregator/src/dependency_injection/builder/enablers/epoch.rs @@ -1,9 +1,16 @@ use std::sync::Arc; use tokio::sync::RwLock; +use mithril_protocol_config::{ + http::HttpMithrilNetworkConfigurationProvider, interface::MithrilNetworkConfigurationProvider, +}; + use crate::dependency_injection::{DependenciesBuilder, EpochServiceWrapper, Result}; use crate::get_dependency; -use crate::services::{EpochServiceDependencies, MithrilEpochService}; +use crate::services::{ + EpochServiceDependencies, LocalMithrilNetworkConfigurationProvider, MithrilEpochService, +}; + impl DependenciesBuilder { async fn build_epoch_service(&mut self) -> Result { let verification_key_store = self.get_verification_key_store().await?; @@ -11,14 +18,13 @@ impl DependenciesBuilder { let chain_observer = self.get_chain_observer().await?; let era_checker = self.get_era_checker().await?; let stake_store = self.get_stake_store().await?; - let epoch_settings = self.configuration.get_epoch_settings_configuration(); let allowed_discriminants = self .configuration .compute_allowed_signed_entity_types_discriminants()?; let epoch_service = Arc::new(RwLock::new(MithrilEpochService::new( - epoch_settings, EpochServiceDependencies::new( + self.get_mithril_network_configuration_provider().await?, epoch_settings_storer, verification_key_store, chain_observer, @@ -36,4 +42,33 @@ impl DependenciesBuilder { pub async fn get_epoch_service(&mut self) -> Result { get_dependency!(self.epoch_service) } + + async fn build_mithril_network_configuration_provider( + &mut self, + ) -> Result> { + let network_configuration_provider: Arc = + if self.configuration.is_follower_aggregator() { + Arc::new(HttpMithrilNetworkConfigurationProvider::new( + self.get_leader_aggregator_client().await?, + )) + } else { + Arc::new(LocalMithrilNetworkConfigurationProvider::new( + self.configuration.get_epoch_settings_configuration(), + self.configuration + .compute_allowed_signed_entity_types_discriminants()?, + self.get_epoch_settings_store().await?, + )) + }; + + //TODO handle discrepency here + + Ok(network_configuration_provider) + } + + /// [MithrilNetworkConfigurationProvider][mithril_protocol_config::interface::MithrilNetworkConfigurationProvider] service + pub async fn get_mithril_network_configuration_provider( + &mut self, + ) -> Result> { + get_dependency!(self.mithril_network_configuration_provider) + } } diff --git a/mithril-aggregator/src/dependency_injection/builder/enablers/mithril_network_configuration.rs b/mithril-aggregator/src/dependency_injection/builder/enablers/mithril_network_configuration.rs deleted file mode 100644 index 09506f27098..00000000000 --- a/mithril-aggregator/src/dependency_injection/builder/enablers/mithril_network_configuration.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::collections::BTreeSet; - -use crate::entities::AggregatorEpochSettings; -use async_trait::async_trait; -use mithril_common::{StdResult, entities::SignedEntityTypeDiscriminants, test::double::Dummy}; -use mithril_protocol_config::{ - interface::MithrilNetworkConfigurationProvider, model::MithrilNetworkConfiguration, -}; - -pub struct LocalMithrilNetworkConfigurationProvider { - epoch_settings: AggregatorEpochSettings, - allowed_discriminants: BTreeSet, -} - -impl LocalMithrilNetworkConfigurationProvider { - pub fn new( - epoch_settings: AggregatorEpochSettings, - allowed_discriminants: BTreeSet, - ) -> Self { - Self { - epoch_settings, - allowed_discriminants, - } - } -} - -#[async_trait] -impl MithrilNetworkConfigurationProvider for LocalMithrilNetworkConfigurationProvider { - async fn get(&self) -> StdResult { - Ok(MithrilNetworkConfiguration::dummy()) - } -} diff --git a/mithril-aggregator/src/dependency_injection/builder/enablers/mod.rs b/mithril-aggregator/src/dependency_injection/builder/enablers/mod.rs index 542bd3a817b..ce6ea560874 100644 --- a/mithril-aggregator/src/dependency_injection/builder/enablers/mod.rs +++ b/mithril-aggregator/src/dependency_injection/builder/enablers/mod.rs @@ -3,5 +3,4 @@ mod cardano_node; mod epoch; mod misc; -mod mithril_network_configuration; mod ticker; diff --git a/mithril-aggregator/src/dependency_injection/builder/mod.rs b/mithril-aggregator/src/dependency_injection/builder/mod.rs index 5e84c8a541a..d27a350a9a6 100644 --- a/mithril-aggregator/src/dependency_injection/builder/mod.rs +++ b/mithril-aggregator/src/dependency_injection/builder/mod.rs @@ -36,6 +36,7 @@ use mithril_persistence::{ database::repository::CardanoTransactionRepository, sqlite::{SqliteConnection, SqliteConnectionPool}, }; +use mithril_protocol_config::interface::MithrilNetworkConfigurationProvider; use mithril_signed_entity_lock::SignedEntityTypeLock; use mithril_ticker::TickerService; @@ -254,6 +255,10 @@ pub struct DependenciesBuilder { /// Epoch service. pub epoch_service: Option, + /// Mithril network configuration provider + pub mithril_network_configuration_provider: + Option>, + /// Signed Entity storer pub signed_entity_storer: Option>, @@ -339,6 +344,7 @@ impl DependenciesBuilder { signed_entity_service: None, certifier_service: None, epoch_service: None, + mithril_network_configuration_provider: None, signed_entity_storer: None, message_service: None, prover_service: None, @@ -395,6 +401,9 @@ impl DependenciesBuilder { signed_entity_service: self.get_signed_entity_service().await?, certifier_service: self.get_certifier_service().await?, epoch_service: self.get_epoch_service().await?, + mithril_network_configuration_provider: self + .get_mithril_network_configuration_provider() + .await?, ticker_service: self.get_ticker_service().await?, signed_entity_storer: self.get_signed_entity_storer().await?, signer_getter: self.get_signer_store().await?, diff --git a/mithril-aggregator/src/dependency_injection/containers/serve.rs b/mithril-aggregator/src/dependency_injection/containers/serve.rs index b37d783aad5..56a00376380 100644 --- a/mithril-aggregator/src/dependency_injection/containers/serve.rs +++ b/mithril-aggregator/src/dependency_injection/containers/serve.rs @@ -1,3 +1,4 @@ +use mithril_protocol_config::interface::MithrilNetworkConfigurationProvider; use slog::Logger; use std::sync::Arc; use tokio::sync::RwLock; @@ -100,6 +101,9 @@ pub struct ServeCommandDependenciesContainer { /// Epoch service pub(crate) epoch_service: EpochServiceWrapper, + /// Mithril network configuration provider + pub(crate) mithril_network_configuration_provider: Arc, + /// Ticker Service pub(crate) ticker_service: Arc, diff --git a/mithril-aggregator/src/entities/aggregator_epoch_settings.rs b/mithril-aggregator/src/entities/aggregator_epoch_settings.rs index 014392f689c..80b8188d88c 100644 --- a/mithril-aggregator/src/entities/aggregator_epoch_settings.rs +++ b/mithril-aggregator/src/entities/aggregator_epoch_settings.rs @@ -9,3 +9,31 @@ pub struct AggregatorEpochSettings { /// Cardano transactions signing configuration pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig, } + +#[cfg(test)] +mod test_utils { + use std::collections::BTreeSet; + + use mithril_common::entities::SignedEntityTypeDiscriminants; + use mithril_protocol_config::model::{ + MithrilNetworkConfigurationForEpoch, SignedEntityTypeConfiguration, + }; + + use super::*; + + impl AggregatorEpochSettings { + /// Convert this epoch setting into a [MithrilNetworkConfigurationForEpoch] + pub fn into_network_configuration_for_epoch( + self, + enabled_signed_entity_types: BTreeSet, + ) -> MithrilNetworkConfigurationForEpoch { + MithrilNetworkConfigurationForEpoch { + protocol_parameters: self.protocol_parameters, + enabled_signed_entity_types, + signed_entity_types_config: SignedEntityTypeConfiguration { + cardano_transactions: Some(self.cardano_transactions_signing_config), + }, + } + } + } +} diff --git a/mithril-aggregator/src/services/epoch_service.rs b/mithril-aggregator/src/services/epoch_service.rs index 9aa85a5d863..667e231ea99 100644 --- a/mithril-aggregator/src/services/epoch_service.rs +++ b/mithril-aggregator/src/services/epoch_service.rs @@ -1,5 +1,7 @@ use anyhow::{Context, anyhow}; use async_trait::async_trait; +use mithril_protocol_config::interface::MithrilNetworkConfigurationProvider; +use mithril_protocol_config::model::MithrilNetworkConfiguration; use slog::{Logger, debug}; use std::collections::BTreeSet; use std::sync::Arc; @@ -151,6 +153,7 @@ struct ComputedEpochData { /// Dependencies required by the [MithrilEpochService]. pub struct EpochServiceDependencies { + mithril_network_configuration_provider: Arc, epoch_settings_storer: Arc, verification_key_store: Arc, chain_observer: Arc, @@ -161,6 +164,7 @@ pub struct EpochServiceDependencies { impl EpochServiceDependencies { /// Create a new instance of [EpochServiceDependencies]. pub fn new( + mithril_network_configuration_provider: Arc, epoch_settings_storer: Arc, verification_key_store: Arc, chain_observer: Arc, @@ -168,6 +172,7 @@ impl EpochServiceDependencies { stake_store: Arc, ) -> Self { Self { + mithril_network_configuration_provider, epoch_settings_storer, verification_key_store, chain_observer, @@ -179,10 +184,9 @@ impl EpochServiceDependencies { /// Implementation of the [epoch service][EpochService]. pub struct MithrilEpochService { - /// Epoch settings that will be inserted when inform_epoch is called - future_epoch_settings: AggregatorEpochSettings, epoch_data: Option, computed_epoch_data: Option, + mithril_network_configuration_provider: Arc, epoch_settings_storer: Arc, verification_key_store: Arc, chain_observer: Arc, @@ -195,15 +199,15 @@ pub struct MithrilEpochService { impl MithrilEpochService { /// Create a new service instance pub fn new( - future_epoch_settings: AggregatorEpochSettings, dependencies: EpochServiceDependencies, allowed_discriminants: BTreeSet, logger: Logger, ) -> Self { Self { - future_epoch_settings, epoch_data: None, computed_epoch_data: None, + mithril_network_configuration_provider: dependencies + .mithril_network_configuration_provider, epoch_settings_storer: dependencies.epoch_settings_storer, verification_key_store: dependencies.verification_key_store, chain_observer: dependencies.chain_observer, @@ -248,33 +252,15 @@ impl MithrilEpochService { Ok(signers) } - async fn get_epoch_settings( + async fn insert_epoch_settings( &self, - epoch: Epoch, - name: &str, - ) -> StdResult { - let epoch_settings = self - .epoch_settings_storer - .get_epoch_settings(epoch) - .await - .with_context(|| format!("Epoch service failed to obtain {name}"))? - .ok_or(EpochServiceError::UnavailableData(epoch, name.to_string()))?; - - Ok(epoch_settings) - } - - async fn insert_future_epoch_settings(&self, actual_epoch: Epoch) -> StdResult<()> { - let recording_epoch = actual_epoch.offset_to_epoch_settings_recording_epoch(); - - debug!( - self.logger, "Inserting epoch settings in epoch {recording_epoch}"; - "epoch_settings" => ?self.future_epoch_settings - ); - + recording_epoch: Epoch, + epoch_settings: &AggregatorEpochSettings, + ) -> StdResult<()> { self.epoch_settings_storer .save_epoch_settings( recording_epoch, - self.future_epoch_settings.clone(), + epoch_settings.clone() ) .await .with_context(|| format!("Epoch service failed to insert future_epoch_settings to epoch {recording_epoch}")) @@ -308,21 +294,59 @@ impl EpochService for MithrilEpochService { format!("EpochService could not compute signer retrieval epoch from epoch: {epoch}") })?; let next_signer_retrieval_epoch = epoch.offset_to_next_signer_retrieval_epoch(); + let signer_registration_epoch = epoch.offset_to_recording_epoch(); - let current_epoch_settings = self - .get_epoch_settings(signer_retrieval_epoch, "current epoch settings") + let network_configuration = self + .mithril_network_configuration_provider + .get_network_configuration(epoch) .await?; - let next_epoch_settings = self - .get_epoch_settings(next_signer_retrieval_epoch, "next epoch settings") - .await?; + let current_epoch_settings = AggregatorEpochSettings { + protocol_parameters: network_configuration + .configuration_for_aggregation + .protocol_parameters, + cardano_transactions_signing_config: network_configuration + .configuration_for_aggregation + .signed_entity_types_config + .cardano_transactions + .ok_or(anyhow!( + "Missing cardano transactions signing config for aggregation epoch {:?}", + epoch + ))?, + }; - let signer_registration_epoch_settings = self - .get_epoch_settings( - next_signer_retrieval_epoch.next(), - "signer registration epoch settings", - ) - .await?; + let next_epoch_settings = AggregatorEpochSettings { + protocol_parameters: network_configuration + .configuration_for_next_aggregation + .protocol_parameters, + cardano_transactions_signing_config: network_configuration + .configuration_for_next_aggregation + .signed_entity_types_config + .cardano_transactions + .ok_or(anyhow!( + "Missing cardano transactions signing config for next aggregation epoch {:?}", + epoch + ))?, + }; + + let signer_registration_epoch_settings = AggregatorEpochSettings { + protocol_parameters: network_configuration + .configuration_for_registration + .protocol_parameters, + cardano_transactions_signing_config: network_configuration + .configuration_for_registration + .signed_entity_types_config + .cardano_transactions + .ok_or(anyhow!( + "Missing cardano transactions signing config for registration epoch {:?}", + epoch + ))?, + }; + self.insert_epoch_settings( + signer_registration_epoch, + &signer_registration_epoch_settings, + ) + .await?; let current_signers_with_stake = self.get_signers_with_stake_at_epoch(signer_retrieval_epoch).await?; @@ -368,12 +392,7 @@ impl EpochService for MithrilEpochService { async fn update_epoch_settings(&mut self) -> StdResult<()> { debug!(self.logger, ">> update_epoch_settings"); - - let data = self.unwrap_data().with_context( - || "can't update epoch settings if inform_epoch has not been called first", - )?; - - self.insert_future_epoch_settings(data.epoch).await + todo!("remove") } async fn update_next_signers_with_stake(&mut self) -> StdResult<()> { @@ -823,6 +842,10 @@ mod tests { builder::{MithrilFixture, MithrilFixtureBuilder, StakeDistributionGenerationMethod}, double::{Dummy, fake_data}, }; + use mithril_protocol_config::{ + model::{MithrilNetworkConfigurationForEpoch, SignedEntityTypeConfiguration}, + test::double::configuration_provider::FakeMithrilNetworkConfigurationProvider, + }; use crate::store::{FakeEpochSettingsStorer, MockVerificationKeyStorer}; use crate::test::TestLogger; @@ -909,8 +932,6 @@ mod tests { } struct EpochServiceBuilder { - cardano_transactions_signing_config: CardanoTransactionsSigningConfig, - future_protocol_parameters: ProtocolParameters, allowed_discriminants: BTreeSet, cardano_era: CardanoEra, mithril_era: SupportedEra, @@ -927,8 +948,6 @@ mod tests { impl EpochServiceBuilder { fn new(epoch: Epoch, epoch_fixture: MithrilFixture) -> Self { Self { - cardano_transactions_signing_config: CardanoTransactionsSigningConfig::dummy(), - future_protocol_parameters: epoch_fixture.protocol_parameters(), allowed_discriminants: BTreeSet::new(), cardano_era: String::new(), mithril_era: SupportedEra::dummy(), @@ -958,18 +977,6 @@ mod tests { let next_signer_retrieval_epoch = self.current_epoch.offset_to_next_signer_retrieval_epoch(); - let epoch_settings_storer = FakeEpochSettingsStorer::new(vec![ - (signer_retrieval_epoch, self.stored_current_epoch_settings), - ( - next_signer_retrieval_epoch, - self.stored_next_epoch_settings.clone(), - ), - ( - next_signer_retrieval_epoch.next(), - self.stored_signer_registration_epoch_settings.clone(), - ), - ]); - let verification_key_store = { let mut store = MockVerificationKeyStorer::new(); let signers_with_stake = self.signers_with_stake.clone(); @@ -1009,13 +1016,28 @@ mod tests { stake_store }; + let configuration_for_aggregation = self + .stored_current_epoch_settings + .into_network_configuration_for_epoch(self.allowed_discriminants.clone()); + + let configuration_for_next_aggregation = self + .stored_next_epoch_settings + .into_network_configuration_for_epoch(self.allowed_discriminants.clone()); + + let configuration_for_registration = self + .stored_signer_registration_epoch_settings + .into_network_configuration_for_epoch(self.allowed_discriminants.clone()); + + let network_configuration_provider = FakeMithrilNetworkConfigurationProvider::new( + configuration_for_aggregation, + configuration_for_next_aggregation, + configuration_for_registration, + ); + MithrilEpochService::new( - AggregatorEpochSettings { - protocol_parameters: self.future_protocol_parameters, - cardano_transactions_signing_config: self.cardano_transactions_signing_config, - }, EpochServiceDependencies::new( - Arc::new(epoch_settings_storer), + Arc::new(network_configuration_provider), + Arc::new(FakeEpochSettingsStorer::new(Vec::new())), Arc::new(verification_key_store), Arc::new(chain_observer), Arc::new(era_checker), @@ -1243,41 +1265,55 @@ mod tests { } #[tokio::test] - async fn update_epoch_settings_insert_future_epoch_settings_in_the_store() { - let future_protocol_parameters = ProtocolParameters::new(6, 89, 0.124); + async fn inform_epoch_insert_registration_epoch_settings_in_the_store() { + let expected_epoch_settings = AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(6, 89, 0.124), + cardano_transactions_signing_config: CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(10), + step: BlockNumber(15), + }, + }; + let epoch = Epoch(4); - let mut service = EpochServiceBuilder { - future_protocol_parameters: future_protocol_parameters.clone(), + let mut service = MithrilEpochService { + mithril_network_configuration_provider: Arc::new( + FakeMithrilNetworkConfigurationProvider::new( + MithrilNetworkConfigurationForEpoch::dummy(), + MithrilNetworkConfigurationForEpoch::dummy(), + MithrilNetworkConfigurationForEpoch { + protocol_parameters: expected_epoch_settings.protocol_parameters.clone(), + signed_entity_types_config: SignedEntityTypeConfiguration { + cardano_transactions: Some( + expected_epoch_settings.cardano_transactions_signing_config.clone(), + ), + }, + ..Dummy::dummy() + }, + ), + ), ..EpochServiceBuilder::new(epoch, MithrilFixtureBuilder::default().build()) - } - .build() - .await; + .build() + .await + }; service .inform_epoch(epoch) .await .expect("inform_epoch should not fail"); - service - .update_epoch_settings() - .await - .expect("update_epoch_settings should not fail"); let inserted_epoch_settings = service .epoch_settings_storer - .get_epoch_settings(epoch.offset_to_epoch_settings_recording_epoch()) + .get_epoch_settings(epoch.offset_to_recording_epoch()) .await .unwrap_or_else(|_| { panic!( "epoch settings should have been inserted for epoch {}", - epoch.offset_to_epoch_settings_recording_epoch() + epoch.offset_to_recording_epoch() ) }) .unwrap(); - assert_eq!( - inserted_epoch_settings.protocol_parameters, - future_protocol_parameters - ); + assert_eq!(inserted_epoch_settings, expected_epoch_settings); } #[tokio::test] diff --git a/mithril-aggregator/src/services/mod.rs b/mithril-aggregator/src/services/mod.rs index 26c053b63e4..9ae55f26bc7 100644 --- a/mithril-aggregator/src/services/mod.rs +++ b/mithril-aggregator/src/services/mod.rs @@ -15,6 +15,7 @@ mod certificate_chain_synchronizer; mod certifier; mod epoch_service; mod message; +mod network_configuration_provider; mod prover; mod signable_builder; mod signature_consumer; @@ -31,6 +32,7 @@ pub use certificate_chain_synchronizer::*; pub use certifier::*; pub use epoch_service::*; pub use message::*; +pub use network_configuration_provider::*; pub use prover::*; pub use signable_builder::*; pub use signature_consumer::*; diff --git a/mithril-aggregator/src/services/network_configuration_provider.rs b/mithril-aggregator/src/services/network_configuration_provider.rs new file mode 100644 index 00000000000..c837647095e --- /dev/null +++ b/mithril-aggregator/src/services/network_configuration_provider.rs @@ -0,0 +1,248 @@ +use anyhow::Context; +use async_trait::async_trait; +use std::collections::BTreeSet; +use std::sync::Arc; + +use mithril_common::StdResult; +use mithril_common::entities::{Epoch, SignedEntityTypeDiscriminants}; +use mithril_protocol_config::interface::MithrilNetworkConfigurationProvider; +use mithril_protocol_config::model::{ + MithrilNetworkConfiguration, MithrilNetworkConfigurationForEpoch, SignedEntityTypeConfiguration, +}; + +use crate::EpochSettingsStorer; +use crate::entities::AggregatorEpochSettings; + +/// Read network configuration from the database epoch_settings or, if no records is available for +/// an epoch, fallback to the provided configuration value +pub struct LocalMithrilNetworkConfigurationProvider { + local_configuration_epoch_settings: AggregatorEpochSettings, + allowed_discriminants: BTreeSet, + epoch_settings_store: Arc, +} + +impl LocalMithrilNetworkConfigurationProvider { + /// Instantiate a new `LocalMithrilNetworkConfigurationProvider` + pub fn new( + local_configuration_epoch_settings: AggregatorEpochSettings, + allowed_discriminants: BTreeSet, + epoch_settings_store: Arc, + ) -> Self { + Self { + local_configuration_epoch_settings, + allowed_discriminants, + epoch_settings_store, + } + } + + /// Get epoch configuration from store or fallback to local configuration if it does not exists + async fn get_stored_configuration_or_fallback( + &self, + epoch: Epoch, + ) -> StdResult { + let epoch_settings = self.epoch_settings_store.get_epoch_settings(epoch).await?.unwrap_or( + AggregatorEpochSettings { + protocol_parameters: self + .local_configuration_epoch_settings + .protocol_parameters + .clone(), + cardano_transactions_signing_config: self + .local_configuration_epoch_settings + .cardano_transactions_signing_config + .clone(), + }, + ); + + Ok(MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: self.allowed_discriminants.clone(), + protocol_parameters: epoch_settings.protocol_parameters, + signed_entity_types_config: SignedEntityTypeConfiguration { + cardano_transactions: Some(epoch_settings.cardano_transactions_signing_config), + }, + }) + } +} + +#[async_trait] +impl MithrilNetworkConfigurationProvider for LocalMithrilNetworkConfigurationProvider { + async fn get_network_configuration( + &self, + epoch: Epoch, + ) -> StdResult { + let aggregation_epoch = + epoch.offset_to_signer_retrieval_epoch().with_context(|| { + format!("MithrilNetworkConfigurationProvider could not compute aggregation epoch from epoch: {epoch}") + })?; + let next_aggregation_epoch = epoch.offset_to_next_signer_retrieval_epoch(); + let registration_epoch = epoch.offset_to_next_signer_retrieval_epoch().next(); + + let configuration_for_aggregation = + self.get_stored_configuration_or_fallback(aggregation_epoch).await?; + let configuration_for_next_aggregation = self + .get_stored_configuration_or_fallback(next_aggregation_epoch) + .await?; + let configuration_for_registration = + self.get_stored_configuration_or_fallback(registration_epoch).await?; + + let config = MithrilNetworkConfiguration { + epoch, + configuration_for_aggregation, + configuration_for_next_aggregation, + configuration_for_registration, + }; + Ok(config) + } +} + +#[cfg(test)] +mod tests { + use mithril_common::{ + entities::{BlockNumber, CardanoTransactionsSigningConfig, ProtocolParameters}, + test::double::Dummy, + }; + + use crate::store::FakeEpochSettingsStorer; + + use super::*; + + #[tokio::test] + async fn get_stored_configuration_with_stored_value_returns_them() { + let local_configuration_epoch_settings = AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(2000, 200, 0.2), + ..Dummy::dummy() + }; + let stored_epoch_settings = AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(1000, 100, 0.1), + ..Dummy::dummy() + }; + + let local_provider = LocalMithrilNetworkConfigurationProvider::new( + local_configuration_epoch_settings, + SignedEntityTypeDiscriminants::all(), + Arc::new(FakeEpochSettingsStorer::new(vec![( + Epoch(42), + stored_epoch_settings.clone(), + )])), + ); + + let network_configuration = local_provider + .get_stored_configuration_or_fallback(Epoch(42)) + .await + .unwrap(); + + assert_eq!( + stored_epoch_settings.protocol_parameters, + network_configuration.protocol_parameters + ) + } + + #[tokio::test] + async fn get_stored_configuration_without_stored_value_fallback_to_configuration_value() { + let local_configuration_epoch_settings = AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(2000, 200, 0.2), + ..Dummy::dummy() + }; + + let local_provider = LocalMithrilNetworkConfigurationProvider::new( + local_configuration_epoch_settings.clone(), + SignedEntityTypeDiscriminants::all(), + Arc::new(FakeEpochSettingsStorer::new(vec![])), + ); + + let network_configuration = local_provider + .get_stored_configuration_or_fallback(Epoch(42)) + .await + .unwrap(); + + assert_eq!( + local_configuration_epoch_settings.protocol_parameters, + network_configuration.protocol_parameters + ) + } + + #[tokio::test] + async fn test_get_network_configuration_retrieve_configurations_for_aggregation_next_aggregation_and_registration() + { + let local_configuration_epoch_settings = AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(3000, 300, 0.3), + cardano_transactions_signing_config: CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(3), + step: BlockNumber(30), + }, + }; + + // Nothing stored at 44, should fallback to configuration + let local_provider = LocalMithrilNetworkConfigurationProvider::new( + local_configuration_epoch_settings, + SignedEntityTypeDiscriminants::all(), + Arc::new(FakeEpochSettingsStorer::new(vec![ + ( + Epoch(42), + AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(1000, 100, 0.1), + cardano_transactions_signing_config: CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(1), + step: BlockNumber(10), + }, + }, + ), + ( + Epoch(43), + AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(2000, 200, 0.2), + cardano_transactions_signing_config: CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(2), + step: BlockNumber(20), + }, + }, + ), + ])), + ); + + let configuration = local_provider.get_network_configuration(Epoch(43)).await.unwrap(); + + assert_eq!(Epoch(43), configuration.epoch); + + assert_eq!( + MithrilNetworkConfigurationForEpoch { + protocol_parameters: ProtocolParameters::new(1000, 100, 0.1), + enabled_signed_entity_types: SignedEntityTypeDiscriminants::all(), + signed_entity_types_config: SignedEntityTypeConfiguration { + cardano_transactions: Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(1), + step: BlockNumber(10), + }), + }, + }, + configuration.configuration_for_aggregation + ); + + assert_eq!( + MithrilNetworkConfigurationForEpoch { + protocol_parameters: ProtocolParameters::new(2000, 200, 0.2), + enabled_signed_entity_types: SignedEntityTypeDiscriminants::all(), + signed_entity_types_config: SignedEntityTypeConfiguration { + cardano_transactions: Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(2), + step: BlockNumber(20), + }), + }, + }, + configuration.configuration_for_next_aggregation + ); + + assert_eq!( + MithrilNetworkConfigurationForEpoch { + protocol_parameters: ProtocolParameters::new(3000, 300, 0.3), + enabled_signed_entity_types: SignedEntityTypeDiscriminants::all(), + signed_entity_types_config: SignedEntityTypeConfiguration { + cardano_transactions: Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(3), + step: BlockNumber(30), + }), + }, + }, + configuration.configuration_for_registration + ); + } +} From 7c0f6b5df9e853880cd2e7736582d2592ec81472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Mon, 3 Nov 2025 18:23:19 +0100 Subject: [PATCH 03/18] chore(aggregator): remove update_epoch_settings from epoch_service, cleanup dependency injection --- .../src/dependency_injection/builder/mod.rs | 3 - .../dependency_injection/containers/serve.rs | 4 -- mithril-aggregator/src/runtime/runner.rs | 55 ------------------- .../src/runtime/state_machine.rs | 4 -- .../src/services/epoch_service.rs | 23 -------- .../services/signer_registration/follower.rs | 2 +- .../leader_aggregator_http_server.rs | 22 +++++++- 7 files changed, 22 insertions(+), 91 deletions(-) diff --git a/mithril-aggregator/src/dependency_injection/builder/mod.rs b/mithril-aggregator/src/dependency_injection/builder/mod.rs index d27a350a9a6..ac1649c4c89 100644 --- a/mithril-aggregator/src/dependency_injection/builder/mod.rs +++ b/mithril-aggregator/src/dependency_injection/builder/mod.rs @@ -401,9 +401,6 @@ impl DependenciesBuilder { signed_entity_service: self.get_signed_entity_service().await?, certifier_service: self.get_certifier_service().await?, epoch_service: self.get_epoch_service().await?, - mithril_network_configuration_provider: self - .get_mithril_network_configuration_provider() - .await?, ticker_service: self.get_ticker_service().await?, signed_entity_storer: self.get_signed_entity_storer().await?, signer_getter: self.get_signer_store().await?, diff --git a/mithril-aggregator/src/dependency_injection/containers/serve.rs b/mithril-aggregator/src/dependency_injection/containers/serve.rs index 56a00376380..b37d783aad5 100644 --- a/mithril-aggregator/src/dependency_injection/containers/serve.rs +++ b/mithril-aggregator/src/dependency_injection/containers/serve.rs @@ -1,4 +1,3 @@ -use mithril_protocol_config::interface::MithrilNetworkConfigurationProvider; use slog::Logger; use std::sync::Arc; use tokio::sync::RwLock; @@ -101,9 +100,6 @@ pub struct ServeCommandDependenciesContainer { /// Epoch service pub(crate) epoch_service: EpochServiceWrapper, - /// Mithril network configuration provider - pub(crate) mithril_network_configuration_provider: Arc, - /// Ticker Service pub(crate) ticker_service: Arc, diff --git a/mithril-aggregator/src/runtime/runner.rs b/mithril-aggregator/src/runtime/runner.rs index e9de8698a9a..758f9ffaf9e 100644 --- a/mithril-aggregator/src/runtime/runner.rs +++ b/mithril-aggregator/src/runtime/runner.rs @@ -78,9 +78,6 @@ pub trait AggregatorRunnerTrait: Sync + Send { force_sync: bool, ) -> StdResult<()>; - /// Ask the EpochService to update the epoch settings. - async fn update_epoch_settings(&self) -> StdResult<()>; - /// Compute the protocol message async fn compute_protocol_message( &self, @@ -298,16 +295,6 @@ impl AggregatorRunnerTrait for AggregatorRunner { .map_err(|e| e.into()) } - async fn update_epoch_settings(&self) -> StdResult<()> { - debug!(self.logger, ">> update_epoch_settings"); - self.dependencies - .epoch_service - .write() - .await - .update_epoch_settings() - .await - } - async fn compute_protocol_message( &self, signed_entity_type: &SignedEntityType, @@ -908,48 +895,6 @@ pub mod tests { runner.upkeep(Epoch(5)).await.unwrap(); } - #[tokio::test] - async fn test_update_epoch_settings() { - let mut mock_certifier_service = MockCertifierService::new(); - mock_certifier_service - .expect_inform_epoch() - .returning(|_| Ok(())) - .times(1); - - let config = ServeCommandConfiguration::new_sample(temp_dir!()); - let mut deps = DependenciesBuilder::new_with_stdout_logger(Arc::new(config.clone())) - .build_serve_dependencies_container() - .await - .unwrap(); - deps.certifier_service = Arc::new(mock_certifier_service); - let epoch_settings_storer = deps.epoch_settings_storer.clone(); - let current_epoch = deps.ticker_service.get_current_epoch().await.unwrap(); - let insert_epoch = current_epoch.offset_to_epoch_settings_recording_epoch(); - - let runner = build_runner_with_fixture_data(deps).await; - runner.inform_new_epoch(current_epoch).await.unwrap(); - runner - .update_epoch_settings() - .await - .expect("update_epoch_settings should not fail"); - - let saved_epoch_settings = epoch_settings_storer - .get_epoch_settings(insert_epoch) - .await - .unwrap() - .unwrap_or_else(|| panic!("should have epoch settings for epoch {insert_epoch}",)); - - assert_eq!( - AggregatorEpochSettings { - protocol_parameters: config.protocol_parameters.clone(), - cardano_transactions_signing_config: config - .cardano_transactions_signing_config - .clone(), - }, - saved_epoch_settings - ); - } - #[tokio::test] async fn test_precompute_epoch_data() { let mut deps = initialize_dependencies!().await; diff --git a/mithril-aggregator/src/runtime/state_machine.rs b/mithril-aggregator/src/runtime/state_machine.rs index fc613f3f5c8..c9e286fdc0c 100644 --- a/mithril-aggregator/src/runtime/state_machine.rs +++ b/mithril-aggregator/src/runtime/state_machine.rs @@ -268,7 +268,6 @@ impl AggregatorRuntime { self.runner.inform_new_epoch(new_time_point.epoch).await?; self.runner.upkeep(new_time_point.epoch).await?; self.runner.open_signer_registration_round(&new_time_point).await?; - self.runner.update_epoch_settings().await?; if self.config.is_follower { self.runner .synchronize_follower_aggregator_signer_registration() @@ -447,7 +446,6 @@ mod tests { .with(predicate::eq(TimePoint::dummy().epoch)) .once() .returning(|_| Ok(())); - runner.expect_update_epoch_settings().once().returning(|| Ok(())); runner.expect_precompute_epoch_data().once().returning(|| Ok(())); runner .expect_upkeep() @@ -510,7 +508,6 @@ mod tests { .with(predicate::eq(TimePoint::dummy().epoch)) .once() .returning(|_| Ok(())); - runner.expect_update_epoch_settings().once().returning(|| Ok(())); runner.expect_precompute_epoch_data().once().returning(|| Ok(())); runner .expect_upkeep() @@ -936,7 +933,6 @@ mod tests { .with(predicate::eq(new_time_point_clone.clone().epoch)) .once() .returning(|_| Ok(())); - runner.expect_update_epoch_settings().once().returning(|| Ok(())); runner.expect_precompute_epoch_data().once().returning(|| Ok(())); runner .expect_upkeep() diff --git a/mithril-aggregator/src/services/epoch_service.rs b/mithril-aggregator/src/services/epoch_service.rs index 667e231ea99..0268b01137c 100644 --- a/mithril-aggregator/src/services/epoch_service.rs +++ b/mithril-aggregator/src/services/epoch_service.rs @@ -1,7 +1,6 @@ use anyhow::{Context, anyhow}; use async_trait::async_trait; use mithril_protocol_config::interface::MithrilNetworkConfigurationProvider; -use mithril_protocol_config::model::MithrilNetworkConfiguration; use slog::{Logger, debug}; use std::collections::BTreeSet; use std::sync::Arc; @@ -46,11 +45,6 @@ pub trait EpochService: Sync + Send { /// internal state for the new epoch. async fn inform_epoch(&mut self, epoch: Epoch) -> StdResult<()>; - /// Insert future epoch settings in the store based on this service current epoch (epoch offset +2). - /// - /// Note: must be called after `inform_epoch`. - async fn update_epoch_settings(&mut self) -> StdResult<()>; - /// Update the next signers with stake for the next epoch. async fn update_next_signers_with_stake(&mut self) -> StdResult<()>; @@ -390,11 +384,6 @@ impl EpochService for MithrilEpochService { Ok(()) } - async fn update_epoch_settings(&mut self) -> StdResult<()> { - debug!(self.logger, ">> update_epoch_settings"); - todo!("remove") - } - async fn update_next_signers_with_stake(&mut self) -> StdResult<()> { debug!(self.logger, ">> update_next_signers_with_stake"); @@ -542,7 +531,6 @@ pub(crate) struct FakeEpochService { epoch_data: Option, computed_epoch_data: Option, inform_epoch_error: bool, - update_epoch_settings_error: bool, precompute_epoch_data_error: bool, update_next_signers_with_stake_error: bool, } @@ -631,7 +619,6 @@ impl FakeEpochServiceBuilder { next_protocol_multi_signer, }), inform_epoch_error: false, - update_epoch_settings_error: false, precompute_epoch_data_error: false, update_next_signers_with_stake_error: false, } @@ -678,7 +665,6 @@ impl FakeEpochService { epoch_data: None, computed_epoch_data: None, inform_epoch_error: false, - update_epoch_settings_error: false, precompute_epoch_data_error: false, update_next_signers_with_stake_error: false, } @@ -687,12 +673,10 @@ impl FakeEpochService { pub fn toggle_errors( &mut self, inform_epoch: bool, - update_protocol_parameters: bool, precompute_epoch: bool, update_next_signers_with_stake: bool, ) { self.inform_epoch_error = inform_epoch; - self.update_epoch_settings_error = update_protocol_parameters; self.precompute_epoch_data_error = precompute_epoch; self.update_next_signers_with_stake_error = update_next_signers_with_stake; } @@ -720,13 +704,6 @@ impl EpochService for FakeEpochService { Ok(()) } - async fn update_epoch_settings(&mut self) -> StdResult<()> { - if self.update_epoch_settings_error { - anyhow::bail!("update_epoch_settings fake error"); - } - Ok(()) - } - async fn precompute_epoch_data(&mut self) -> StdResult<()> { if self.precompute_epoch_data_error { anyhow::bail!("precompute_epoch_data fake error"); diff --git a/mithril-aggregator/src/services/signer_registration/follower.rs b/mithril-aggregator/src/services/signer_registration/follower.rs index 68465ff4de3..5c472ea6ffa 100644 --- a/mithril-aggregator/src/services/signer_registration/follower.rs +++ b/mithril-aggregator/src/services/signer_registration/follower.rs @@ -456,7 +456,7 @@ mod tests { let signer_registration_follower = MithrilSignerRegistrationFollowerBuilder::default() .with_epoch_service({ let mut epoch_service = FakeEpochService::without_data(); - epoch_service.toggle_errors(false, false, false, true); + epoch_service.toggle_errors(false, false, true); epoch_service }) diff --git a/mithril-aggregator/tests/test_extensions/leader_aggregator_http_server.rs b/mithril-aggregator/tests/test_extensions/leader_aggregator_http_server.rs index 72a387aa284..cedcbac27e2 100644 --- a/mithril-aggregator/tests/test_extensions/leader_aggregator_http_server.rs +++ b/mithril-aggregator/tests/test_extensions/leader_aggregator_http_server.rs @@ -11,7 +11,7 @@ use axum_test::TestServer; use reqwest::Url; use mithril_aggregator::services::MessageService; -use mithril_common::entities::SignedEntityTypeDiscriminants; +use mithril_common::entities::{Epoch, SignedEntityTypeDiscriminants}; use mithril_common::logging::LoggerExtensions; use mithril_common::{StdError, StdResult}; @@ -33,6 +33,10 @@ impl LeaderAggregatorHttpServer { .route("/certificates", get(certificates_list)) .route("/certificate/genesis", get(certificate_last_genesis)) .route("/certificate/{hash}", get(certificate_by_hash)) + .route( + "/protocol-configuration/{epoch}", + get(protocol_configuration_by_epoch), + ) .with_state(state); let server = TestServer::builder().http_transport().build(router)?; @@ -98,3 +102,19 @@ async fn certificate_by_hash( Err(err) => internal_server_error(err).into_response(), } } + +async fn protocol_configuration_by_epoch( + Path(epoch): Path, + state: State, +) -> Response { + slog::debug!(state.logger, "/protocol-configuration/{epoch}"); + match state + .message_service + .get_protocol_configuration_message(Epoch(epoch), SignedEntityTypeDiscriminants::all()) + .await + { + Ok(Some(message)) => (StatusCode::OK, Json(message)).into_response(), + Ok(None) => StatusCode::NOT_FOUND.into_response(), + Err(err) => internal_server_error(err).into_response(), + } +} From 758c48c2b0aed7ba16444f8b4e9a01650399a237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Mon, 3 Nov 2025 19:12:53 +0100 Subject: [PATCH 04/18] refactor(aggregator): do not override existing epoch settings on save --- .../insert_or_ignore_epoch_settings.rs | 116 ++++++++++++++++++ .../src/database/query/epoch_settings/mod.rs | 4 +- .../epoch_settings/update_epoch_settings.rs | 103 ---------------- .../src/database/record/epoch_settings.rs | 2 +- .../repository/epoch_settings_store.rs | 45 ++++++- .../src/database/test_helper.rs | 25 ++-- 6 files changed, 170 insertions(+), 125 deletions(-) create mode 100644 mithril-aggregator/src/database/query/epoch_settings/insert_or_ignore_epoch_settings.rs delete mode 100644 mithril-aggregator/src/database/query/epoch_settings/update_epoch_settings.rs diff --git a/mithril-aggregator/src/database/query/epoch_settings/insert_or_ignore_epoch_settings.rs b/mithril-aggregator/src/database/query/epoch_settings/insert_or_ignore_epoch_settings.rs new file mode 100644 index 00000000000..8bac1b12804 --- /dev/null +++ b/mithril-aggregator/src/database/query/epoch_settings/insert_or_ignore_epoch_settings.rs @@ -0,0 +1,116 @@ +use sqlite::Value; + +use mithril_persistence::sqlite::{Query, SourceAlias, SqLiteEntity, WhereCondition}; + +use crate::database::record::EpochSettingsRecord; + +/// Query to update [EpochSettingsRecord] in the sqlite database +pub struct InsertOrIgnoreEpochSettingsQuery { + condition: WhereCondition, +} + +impl InsertOrIgnoreEpochSettingsQuery { + pub fn one(epoch_settings: EpochSettingsRecord) -> Self { + Self { + condition: WhereCondition::new( + "(epoch_setting_id, protocol_parameters, cardano_transactions_signing_config) values (?1, ?2, ?3)", + vec![ + Value::Integer(*epoch_settings.epoch_settings_id as i64), + Value::String( + serde_json::to_string(&epoch_settings.protocol_parameters).unwrap(), + ), + Value::String( + serde_json::to_string(&epoch_settings.cardano_transactions_signing_config) + .unwrap(), + ), + ], + ), + } + } +} + +impl Query for InsertOrIgnoreEpochSettingsQuery { + type Entity = EpochSettingsRecord; + + 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(&[("{:epoch_setting:}", "epoch_setting")])); + + format!("insert or ignore into epoch_setting {condition} returning {projection}") + } +} + +#[cfg(test)] +mod tests { + use mithril_common::entities::{BlockNumber, CardanoTransactionsSigningConfig, Epoch}; + use mithril_common::test::double::fake_data; + use mithril_persistence::sqlite::ConnectionExtensions; + + use crate::database::query::GetEpochSettingsQuery; + use crate::database::test_helper::main_db_connection; + + use super::*; + + #[test] + fn test_insert_epoch_setting_in_empty_db() { + let connection = main_db_connection().unwrap(); + + let expected_epoch_settings = EpochSettingsRecord { + epoch_settings_id: Epoch(3), + protocol_parameters: fake_data::protocol_parameters(), + cardano_transactions_signing_config: CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(24), + step: BlockNumber(62), + }, + }; + let record = connection + .fetch_first(InsertOrIgnoreEpochSettingsQuery::one( + expected_epoch_settings.clone(), + )) + .unwrap(); + + assert_eq!(Some(expected_epoch_settings), record); + } + + #[test] + fn test_cant_replace_existing_value() { + let connection = main_db_connection().unwrap(); + + let expected_epoch_settings = EpochSettingsRecord { + epoch_settings_id: Epoch(3), + protocol_parameters: fake_data::protocol_parameters(), + cardano_transactions_signing_config: CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(24), + step: BlockNumber(62), + }, + }; + let record = connection + .fetch_first(InsertOrIgnoreEpochSettingsQuery::one( + expected_epoch_settings.clone(), + )) + .unwrap(); + assert!(record.is_some()); + + let record = connection + .fetch_first(InsertOrIgnoreEpochSettingsQuery::one(EpochSettingsRecord { + cardano_transactions_signing_config: CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(134), + step: BlockNumber(872), + }, + ..expected_epoch_settings.clone() + })) + .unwrap(); + assert!(record.is_none()); + + let record_in_db = connection + .fetch_first(GetEpochSettingsQuery::by_epoch(Epoch(3)).unwrap()) + .unwrap(); + assert_eq!(Some(expected_epoch_settings), record_in_db); + } +} diff --git a/mithril-aggregator/src/database/query/epoch_settings/mod.rs b/mithril-aggregator/src/database/query/epoch_settings/mod.rs index 8ff7fb12e7d..e45403ee09d 100644 --- a/mithril-aggregator/src/database/query/epoch_settings/mod.rs +++ b/mithril-aggregator/src/database/query/epoch_settings/mod.rs @@ -1,7 +1,7 @@ mod delete_epoch_settings; mod get_epoch_settings; -mod update_epoch_settings; +mod insert_or_ignore_epoch_settings; pub use delete_epoch_settings::*; pub use get_epoch_settings::*; -pub use update_epoch_settings::*; +pub use insert_or_ignore_epoch_settings::*; diff --git a/mithril-aggregator/src/database/query/epoch_settings/update_epoch_settings.rs b/mithril-aggregator/src/database/query/epoch_settings/update_epoch_settings.rs deleted file mode 100644 index d21c1312059..00000000000 --- a/mithril-aggregator/src/database/query/epoch_settings/update_epoch_settings.rs +++ /dev/null @@ -1,103 +0,0 @@ -use sqlite::Value; - -use mithril_common::entities::Epoch; -use mithril_persistence::sqlite::{Query, SourceAlias, SqLiteEntity, WhereCondition}; - -use crate::database::record::EpochSettingsRecord; -use crate::entities::AggregatorEpochSettings; - -/// Query to update [EpochSettingsRecord] in the sqlite database -pub struct UpdateEpochSettingsQuery { - condition: WhereCondition, -} - -impl UpdateEpochSettingsQuery { - pub fn one(epoch: Epoch, epoch_settings: AggregatorEpochSettings) -> Self { - let epoch_settings_id: i64 = epoch.try_into().unwrap(); - - Self { - condition: WhereCondition::new( - "(epoch_setting_id, protocol_parameters, cardano_transactions_signing_config) values (?1, ?2, ?3)", - vec![ - Value::Integer(epoch_settings_id), - Value::String( - serde_json::to_string(&epoch_settings.protocol_parameters).unwrap(), - ), - Value::String( - serde_json::to_string(&epoch_settings.cardano_transactions_signing_config) - .unwrap(), - ), - ], - ), - } - } -} - -impl Query for UpdateEpochSettingsQuery { - type Entity = EpochSettingsRecord; - - 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(&[("{:epoch_setting:}", "epoch_setting")])); - - format!("insert or replace into epoch_setting {condition} returning {projection}") - } -} - -#[cfg(test)] -mod tests { - use mithril_common::entities::{BlockNumber, CardanoTransactionsSigningConfig}; - use mithril_common::test::double::fake_data; - use mithril_persistence::sqlite::ConnectionExtensions; - - use crate::database::query::GetEpochSettingsQuery; - use crate::database::test_helper::{insert_epoch_settings, main_db_connection}; - - use super::*; - - #[test] - fn test_update_epoch_settings() { - let connection = main_db_connection().unwrap(); - insert_epoch_settings(&connection, &[*Epoch(3)]).unwrap(); - - let epoch_settings_send_to_update = AggregatorEpochSettings { - protocol_parameters: fake_data::protocol_parameters(), - cardano_transactions_signing_config: CardanoTransactionsSigningConfig { - security_parameter: BlockNumber(24), - step: BlockNumber(62), - }, - }; - let record_returned_by_update_query = connection - .fetch_first(UpdateEpochSettingsQuery::one( - Epoch(3), - epoch_settings_send_to_update.clone(), - )) - .unwrap() - .unwrap(); - - assert_eq!(Epoch(3), record_returned_by_update_query.epoch_settings_id); - assert_eq!( - epoch_settings_send_to_update.protocol_parameters, - record_returned_by_update_query.protocol_parameters - ); - assert_eq!( - epoch_settings_send_to_update.cardano_transactions_signing_config, - record_returned_by_update_query.cardano_transactions_signing_config - ); - - let mut cursor = connection - .fetch(GetEpochSettingsQuery::by_epoch(Epoch(3)).unwrap()) - .unwrap(); - let epoch_settings_record = - cursor.next().expect("Should have an epoch settings for epoch 3."); - - assert_eq!(record_returned_by_update_query, epoch_settings_record); - assert_eq!(0, cursor.count()); - } -} diff --git a/mithril-aggregator/src/database/record/epoch_settings.rs b/mithril-aggregator/src/database/record/epoch_settings.rs index 21cc605742a..bf9b271e2ea 100644 --- a/mithril-aggregator/src/database/record/epoch_settings.rs +++ b/mithril-aggregator/src/database/record/epoch_settings.rs @@ -4,7 +4,7 @@ use mithril_persistence::sqlite::{HydrationError, Projection, SqLiteEntity}; use crate::entities::AggregatorEpochSettings; /// Settings for an epoch, including the protocol parameters. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct EpochSettingsRecord { /// Epoch settings id, i.e. the epoch number. pub epoch_settings_id: Epoch, diff --git a/mithril-aggregator/src/database/repository/epoch_settings_store.rs b/mithril-aggregator/src/database/repository/epoch_settings_store.rs index 8949657149a..28defe049e9 100644 --- a/mithril-aggregator/src/database/repository/epoch_settings_store.rs +++ b/mithril-aggregator/src/database/repository/epoch_settings_store.rs @@ -8,8 +8,9 @@ use mithril_common::entities::{Epoch, ProtocolParameters}; use mithril_persistence::sqlite::{ConnectionExtensions, SqliteConnection}; use crate::database::query::{ - DeleteEpochSettingsQuery, GetEpochSettingsQuery, UpdateEpochSettingsQuery, + DeleteEpochSettingsQuery, GetEpochSettingsQuery, InsertOrIgnoreEpochSettingsQuery, }; +use crate::database::record::EpochSettingsRecord; use crate::entities::AggregatorEpochSettings; use crate::services::EpochPruningTask; use crate::{EpochSettingsStorer, ProtocolParametersRetriever}; @@ -50,13 +51,17 @@ impl EpochSettingsStorer for EpochSettingsStore { epoch: Epoch, epoch_settings: AggregatorEpochSettings, ) -> StdResult> { + let record_to_insert = EpochSettingsRecord { + epoch_settings_id: epoch, + cardano_transactions_signing_config: epoch_settings.cardano_transactions_signing_config, + protocol_parameters: epoch_settings.protocol_parameters, + }; let epoch_settings_record = self .connection - .fetch_first(UpdateEpochSettingsQuery::one(epoch, epoch_settings)) - .with_context(|| format!("persist epoch settings failure for epoch {epoch:?}"))? - .unwrap_or_else(|| panic!("No entity returned by the persister, epoch = {epoch:?}")); + .fetch_first(InsertOrIgnoreEpochSettingsQuery::one(record_to_insert)) + .with_context(|| format!("persist epoch settings failure for epoch {epoch:?}"))?; - Ok(Some(epoch_settings_record.into())) + Ok(epoch_settings_record.map(Into::into)) } async fn get_epoch_settings(&self, epoch: Epoch) -> StdResult> { @@ -171,4 +176,34 @@ mod tests { assert_eq!(None, epoch_settings); } } + + #[tokio::test] + async fn save_epoch_settings_does_not_replace_existing_value_in_database() { + let connection = main_db_connection().unwrap(); + + let store = EpochSettingsStore::new(Arc::new(connection), None); + let expected_epoch_settings = AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(1, 1, 0.5), + ..Dummy::dummy() + }; + + store + .save_epoch_settings(Epoch(2), expected_epoch_settings.clone()) + .await + .expect("saving epoch settings should not fails"); + + store + .save_epoch_settings( + Epoch(2), + AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(2, 2, 1.5), + ..Dummy::dummy() + }, + ) + .await + .expect("saving epoch settings should not fails"); + + let epoch_settings = store.get_epoch_settings(Epoch(2)).await.unwrap().unwrap(); + assert_eq!(expected_epoch_settings, epoch_settings); + } } diff --git a/mithril-aggregator/src/database/test_helper.rs b/mithril-aggregator/src/database/test_helper.rs index 73992afc682..cdb19aa0de7 100644 --- a/mithril-aggregator/src/database/test_helper.rs +++ b/mithril-aggregator/src/database/test_helper.rs @@ -12,16 +12,15 @@ use mithril_persistence::sqlite::{ }; use crate::database::query::{ - ImportSignerRecordQuery, InsertCertificateRecordQuery, + ImportSignerRecordQuery, InsertCertificateRecordQuery, InsertOrIgnoreEpochSettingsQuery, InsertOrReplaceBufferedSingleSignatureRecordQuery, InsertOrReplaceSignerRegistrationRecordQuery, InsertOrReplaceStakePoolQuery, - InsertSignedEntityRecordQuery, UpdateEpochSettingsQuery, UpdateSingleSignatureRecordQuery, + InsertSignedEntityRecordQuery, UpdateSingleSignatureRecordQuery, }; use crate::database::record::{ - BufferedSingleSignatureRecord, CertificateRecord, SignedEntityRecord, SignerRecord, - SignerRegistrationRecord, SingleSignatureRecord, + BufferedSingleSignatureRecord, CertificateRecord, EpochSettingsRecord, SignedEntityRecord, + SignerRecord, SignerRegistrationRecord, SingleSignatureRecord, }; -use crate::entities::AggregatorEpochSettings; /// In-memory sqlite database without foreign key support with migrations applied pub fn main_db_connection() -> StdResult { @@ -206,16 +205,14 @@ pub fn insert_epoch_settings( let query = { // leverage the expanded parameter from this query which is unit // tested on its own above. - let (sql_values, _) = UpdateEpochSettingsQuery::one( - Epoch(1), - AggregatorEpochSettings { - protocol_parameters: ProtocolParameters::new(1, 2, 1.0), - cardano_transactions_signing_config: CardanoTransactionsSigningConfig { - security_parameter: BlockNumber(0), - step: BlockNumber(0), - }, + let (sql_values, _) = InsertOrIgnoreEpochSettingsQuery::one(EpochSettingsRecord { + epoch_settings_id: Epoch(1), + protocol_parameters: ProtocolParameters::new(1, 2, 1.0), + cardano_transactions_signing_config: CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(0), + step: BlockNumber(0), }, - ) + }) .filters() .expand(); From 53bfc51b85406ffc857d5e8bce3cd9acf34fe16e Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:45:37 +0100 Subject: [PATCH 05/18] feat(aggregator): add `preload_security_parameter` config parameter --- mithril-aggregator/src/configuration.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/mithril-aggregator/src/configuration.rs b/mithril-aggregator/src/configuration.rs index fc3d2400899..e29eb430838 100644 --- a/mithril-aggregator/src/configuration.rs +++ b/mithril-aggregator/src/configuration.rs @@ -255,6 +255,11 @@ pub trait ConfigurationSource { panic!("cardano_transactions_signing_config is not implemented."); } + /// Blocks offset, from the tip of the chain, to exclude during the cardano transactions preload + fn preload_security_parameter(&self) -> BlockNumber { + panic!("preload_security_parameter is not implemented."); + } + /// Maximum number of transactions hashes allowed by request to the prover of the Cardano transactions fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize { panic!("cardano_transactions_prover_max_hashes_allowed_by_request is not implemented."); @@ -558,6 +563,10 @@ pub struct ServeCommandConfiguration { #[example = "`{ security_parameter: 3000, step: 120 }`"] pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig, + /// Blocks offset, from the tip of the chain, to exclude during the cardano transactions preload + /// `[default: 2160]`. + pub preload_security_parameter: BlockNumber, + /// Maximum number of transactions hashes allowed by request to the prover of the Cardano transactions pub cardano_transactions_prover_max_hashes_allowed_by_request: usize, @@ -714,6 +723,7 @@ impl ServeCommandConfiguration { security_parameter: BlockNumber(120), step: BlockNumber(15), }, + preload_security_parameter: BlockNumber(30), cardano_transactions_prover_max_hashes_allowed_by_request: 100, cardano_transactions_block_streamer_max_roll_forwards_per_poll: 1000, enable_metrics_server: true, @@ -881,6 +891,10 @@ impl ConfigurationSource for ServeCommandConfiguration { self.cardano_transactions_signing_config.clone() } + fn preload_security_parameter(&self) -> BlockNumber { + self.preload_security_parameter + } + fn cardano_transactions_prover_max_hashes_allowed_by_request(&self) -> usize { self.cardano_transactions_prover_max_hashes_allowed_by_request } @@ -981,6 +995,9 @@ pub struct DefaultConfiguration { /// Cardano transactions signing configuration pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig, + /// Blocks offset, from the tip of the chain, to exclude during the cardano transactions preload + pub preload_security_parameter: u64, + /// Maximum number of transactions hashes allowed by request to the prover of the Cardano transactions pub cardano_transactions_prover_max_hashes_allowed_by_request: u32, @@ -1026,6 +1043,7 @@ impl Default for DefaultConfiguration { security_parameter: BlockNumber(3000), step: BlockNumber(120), }, + preload_security_parameter: 2160, cardano_transactions_prover_max_hashes_allowed_by_request: 100, cardano_transactions_block_streamer_max_roll_forwards_per_poll: 10000, enable_metrics_server: "false".to_string(), @@ -1104,6 +1122,7 @@ impl Source for DefaultConfiguration { &namespace, myself.persist_usage_report_interval_in_seconds ); + register_config_value!(result, &namespace, myself.preload_security_parameter); register_config_value!( result, &namespace, @@ -1111,7 +1130,7 @@ impl Source for DefaultConfiguration { |v: CardanoTransactionsSigningConfig| HashMap::from([ ( "security_parameter".to_string(), - ValueKind::from(*v.security_parameter,), + ValueKind::from(*v.security_parameter), ), ("step".to_string(), ValueKind::from(*v.step),) ]) From dcd103aed05457b081c605f21dfd8403f10269de Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 10 Nov 2025 13:03:11 +0100 Subject: [PATCH 06/18] doc(website): add `preload_security_parameter` to aggregator doc --- docs/website/root/manual/develop/nodes/mithril-aggregator.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/website/root/manual/develop/nodes/mithril-aggregator.md b/docs/website/root/manual/develop/nodes/mithril-aggregator.md index 7ef2126b6ac..243fd1d7f22 100644 --- a/docs/website/root/manual/develop/nodes/mithril-aggregator.md +++ b/docs/website/root/manual/develop/nodes/mithril-aggregator.md @@ -537,6 +537,7 @@ Here is a list of the available parameters for the serve command: | `cardano_transactions_prover_max_hashes_allowed_by_request` | `--cardano-transactions-prover-max-hashes-allowed-by-request` | - | `CARDANO_TRANSACTIONS_PROVER_MAX_HASHES_ALLOWED_BY_REQUEST` | Maximum number of transactions hashes allowed by request to the prover of the Cardano transactions | `100` | `100` | - | | `cardano_transactions_block_streamer_max_roll_forwards_per_poll` | `--cardano-transactions-block-streamer-max-roll-forwards-per-poll` | - | `CARDANO_TRANSACTIONS_BLOCK_STREAMER_MAX_ROLL_FORWARDS_PER_POLL` | Maximum number of roll forwards during a poll of the block streamer when importing transactions | `1000` | `1000` | - | | `cardano_transactions_signing_config` | `--cardano-transactions-signing-config` | - | `CARDANO_TRANSACTIONS_SIGNING_CONFIG` | Cardano transactions signing configuration | `{ "security_parameter": 3000, "step": 120 }` | `{ "security_parameter": 3000, "step": 120 }` | - | +| `preload_security_parameter` | - | - | `PRELOAD_SECURITY_PARAMETER` | Blocks offset, from the tip of the chain, to exclude during the cardano transactions preload
`[default: 2160]`. | `2160` | - | :heavy_check_mark: | | `enable_metrics_server` | `--enable-metrics-server` | - | `ENABLE_METRICS_SERVER` | Enable metrics HTTP server (Prometheus endpoint on /metrics) | `false` | - | - | | `metrics_server_ip` | `--metrics-server-ip` | - | `METRICS_SERVER_IP` | Metrics HTTP server IP | `0.0.0.0` | - | - | | `metrics_server_port` | `--metrics-server-port` | - | `METRICS_SERVER_PORT` | Metrics HTTP server listening port | `9090` | - | - | From 961e88023fcd9794bc8aa21edd5537339e43ca78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Tue, 4 Nov 2025 18:46:43 +0100 Subject: [PATCH 07/18] feat(aggregator): make protocol_parameters and cardano_transaction_signing_config optionnal (mandatory for leader) --- mithril-aggregator/src/configuration.rs | 40 +++++++++++-------- .../builder/enablers/cardano_node.rs | 4 +- .../builder/enablers/epoch.rs | 3 +- .../builder/support/stores.rs | 4 +- .../services/certifier/certifier_service.rs | 2 +- ...ardano_stake_distribution_verify_stakes.rs | 2 +- mithril-aggregator/tests/certificate_chain.rs | 2 +- .../tests/create_certificate.rs | 6 +-- .../tests/create_certificate_follower.rs | 2 +- ...te_certificate_with_buffered_signatures.rs | 6 +-- mithril-aggregator/tests/era_checker.rs | 2 +- .../tests/genesis_to_signing.rs | 2 +- .../tests/open_message_expiration.rs | 2 +- .../tests/open_message_newer_exists.rs | 2 +- .../tests/prove_transactions.rs | 6 +-- .../tests/test_extensions/runtime_tester.rs | 2 +- 16 files changed, 48 insertions(+), 39 deletions(-) diff --git a/mithril-aggregator/src/configuration.rs b/mithril-aggregator/src/configuration.rs index e29eb430838..ff72b93810b 100644 --- a/mithril-aggregator/src/configuration.rs +++ b/mithril-aggregator/src/configuration.rs @@ -109,7 +109,7 @@ pub trait ConfigurationSource { } /// Protocol parameters - fn protocol_parameters(&self) -> ProtocolParameters { + fn protocol_parameters(&self) -> Option { panic!("protocol_parameters is not implemented."); } @@ -251,7 +251,7 @@ pub trait ConfigurationSource { } /// Cardano transactions signing configuration - fn cardano_transactions_signing_config(&self) -> CardanoTransactionsSigningConfig { + fn cardano_transactions_signing_config(&self) -> Option { panic!("cardano_transactions_signing_config is not implemented."); } @@ -378,12 +378,20 @@ pub trait ConfigurationSource { } } - /// Infer the [AggregatorEpochSettings] from the configuration. - fn get_epoch_settings_configuration(&self) -> AggregatorEpochSettings { - AggregatorEpochSettings { - protocol_parameters: self.protocol_parameters(), - cardano_transactions_signing_config: self.cardano_transactions_signing_config(), - } + /// `leader aggregator only` Infer the [AggregatorEpochSettings] from the configuration. + fn get_leader_aggregator_epoch_settings_configuration( + &self, + ) -> StdResult { + Ok(AggregatorEpochSettings { + protocol_parameters: self.protocol_parameters().with_context( + || "Configuration `protocol_parameter` is mandatory for a Leader Aggregator", + )?, + cardano_transactions_signing_config: self + .cardano_transactions_signing_config() + .with_context( + || "Configuration `cardano_transactions_signing_config` is mandatory for a Leader Aggregator", + )?, + }) } /// Check if the aggregator is running in follower mode. @@ -458,7 +466,7 @@ pub struct ServeCommandConfiguration { /// Protocol parameters #[example = "`{ k: 5, m: 100, phi_f: 0.65 }`"] - pub protocol_parameters: ProtocolParameters, + pub protocol_parameters: Option, /// Type of snapshot uploader to use #[example = "`gcp` or `local`"] @@ -561,7 +569,7 @@ pub struct ServeCommandConfiguration { /// Cardano transactions signing configuration #[example = "`{ security_parameter: 3000, step: 120 }`"] - pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig, + pub cardano_transactions_signing_config: Option, /// Blocks offset, from the tip of the chain, to exclude during the cardano transactions preload /// `[default: 2160]`. @@ -681,11 +689,11 @@ impl ServeCommandConfiguration { network_magic: Some(42), dmq_network_magic: Some(3141592), chain_observer_type: ChainObserverType::Fake, - protocol_parameters: ProtocolParameters { + protocol_parameters: Some(ProtocolParameters { k: 5, m: 100, phi_f: 0.95, - }, + }), snapshot_uploader_type: SnapshotUploaderType::Local, snapshot_bucket_name: None, snapshot_use_cdn_domain: false, @@ -719,10 +727,10 @@ impl ServeCommandConfiguration { allow_unparsable_block: false, cardano_transactions_prover_cache_pool_size: 3, cardano_transactions_database_connection_pool_size: 5, - cardano_transactions_signing_config: CardanoTransactionsSigningConfig { + cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig { security_parameter: BlockNumber(120), step: BlockNumber(15), - }, + }), preload_security_parameter: BlockNumber(30), cardano_transactions_prover_max_hashes_allowed_by_request: 100, cardano_transactions_block_streamer_max_roll_forwards_per_poll: 1000, @@ -783,7 +791,7 @@ impl ConfigurationSource for ServeCommandConfiguration { self.chain_observer_type.clone() } - fn protocol_parameters(&self) -> ProtocolParameters { + fn protocol_parameters(&self) -> Option { self.protocol_parameters.clone() } @@ -887,7 +895,7 @@ impl ConfigurationSource for ServeCommandConfiguration { self.cardano_transactions_database_connection_pool_size } - fn cardano_transactions_signing_config(&self) -> CardanoTransactionsSigningConfig { + fn cardano_transactions_signing_config(&self) -> Option { self.cardano_transactions_signing_config.clone() } diff --git a/mithril-aggregator/src/dependency_injection/builder/enablers/cardano_node.rs b/mithril-aggregator/src/dependency_injection/builder/enablers/cardano_node.rs index 90eeab09ab6..074c70c5909 100644 --- a/mithril-aggregator/src/dependency_injection/builder/enablers/cardano_node.rs +++ b/mithril-aggregator/src/dependency_injection/builder/enablers/cardano_node.rs @@ -134,9 +134,7 @@ impl DependenciesBuilder { let cardano_transactions_preloader = CardanoTransactionsPreloader::new( self.get_signed_entity_type_lock().await?, self.get_transactions_importer().await?, - self.configuration - .cardano_transactions_signing_config() - .security_parameter, + self.configuration.preload_security_parameter(), self.get_chain_observer().await?, self.root_logger(), Arc::new(CardanoTransactionsPreloaderActivation::new(activation)), diff --git a/mithril-aggregator/src/dependency_injection/builder/enablers/epoch.rs b/mithril-aggregator/src/dependency_injection/builder/enablers/epoch.rs index 1ec8b30c9e0..e16a109c6fa 100644 --- a/mithril-aggregator/src/dependency_injection/builder/enablers/epoch.rs +++ b/mithril-aggregator/src/dependency_injection/builder/enablers/epoch.rs @@ -53,7 +53,8 @@ impl DependenciesBuilder { )) } else { Arc::new(LocalMithrilNetworkConfigurationProvider::new( - self.configuration.get_epoch_settings_configuration(), + self.configuration + .get_leader_aggregator_epoch_settings_configuration()?, self.configuration .compute_allowed_signed_entity_types_discriminants()?, self.get_epoch_settings_store().await?, diff --git a/mithril-aggregator/src/dependency_injection/builder/support/stores.rs b/mithril-aggregator/src/dependency_injection/builder/support/stores.rs index c178b6435c7..d2113380cad 100644 --- a/mithril-aggregator/src/dependency_injection/builder/support/stores.rs +++ b/mithril-aggregator/src/dependency_injection/builder/support/stores.rs @@ -115,7 +115,9 @@ impl DependenciesBuilder { error: Some(e.into()), })?; - let epoch_settings_configuration = self.configuration.get_epoch_settings_configuration(); + let epoch_settings_configuration = self + .configuration + .get_leader_aggregator_epoch_settings_configuration()?; debug!( logger, "Handle discrepancies at startup of epoch settings store, will record epoch settings from the configuration for epoch {retrieval_epoch}"; diff --git a/mithril-aggregator/src/services/certifier/certifier_service.rs b/mithril-aggregator/src/services/certifier/certifier_service.rs index 574867822d5..a4a2777d83f 100644 --- a/mithril-aggregator/src/services/certifier/certifier_service.rs +++ b/mithril-aggregator/src/services/certifier/certifier_service.rs @@ -481,7 +481,7 @@ mod tests { dependency_manager .init_state_from_fixture( fixture, - &cardano_transactions_signing_config, + &cardano_transactions_signing_config.unwrap(), epochs_with_signers, ) .await; diff --git a/mithril-aggregator/tests/cardano_stake_distribution_verify_stakes.rs b/mithril-aggregator/tests/cardano_stake_distribution_verify_stakes.rs index 2dbe303d143..c7fb7eab01b 100644 --- a/mithril-aggregator/tests/cardano_stake_distribution_verify_stakes.rs +++ b/mithril-aggregator/tests/cardano_stake_distribution_verify_stakes.rs @@ -22,7 +22,7 @@ async fn cardano_stake_distribution_verify_stakes() { phi_f: 0.95, }; let configuration = ServeCommandConfiguration { - protocol_parameters: protocol_parameters.clone(), + protocol_parameters: Some(protocol_parameters.clone()), signed_entity_types: Some( SignedEntityTypeDiscriminants::CardanoStakeDistribution.to_string(), ), diff --git a/mithril-aggregator/tests/certificate_chain.rs b/mithril-aggregator/tests/certificate_chain.rs index dcf7ab2a62f..6e0ff498be1 100644 --- a/mithril-aggregator/tests/certificate_chain.rs +++ b/mithril-aggregator/tests/certificate_chain.rs @@ -22,7 +22,7 @@ async fn certificate_chain() { phi_f: 0.95, }; let configuration = ServeCommandConfiguration { - protocol_parameters: protocol_parameters.clone(), + protocol_parameters: Some(protocol_parameters.clone()), data_stores_directory: get_test_dir("certificate_chain"), signed_entity_types: Some(SignedEntityTypeDiscriminants::CardanoDatabase.to_string()), ..ServeCommandConfiguration::new_sample(temp_dir!()) diff --git a/mithril-aggregator/tests/create_certificate.rs b/mithril-aggregator/tests/create_certificate.rs index b6a34970664..b983feb8e73 100644 --- a/mithril-aggregator/tests/create_certificate.rs +++ b/mithril-aggregator/tests/create_certificate.rs @@ -22,7 +22,7 @@ async fn create_certificate() { phi_f: 0.95, }; let configuration = ServeCommandConfiguration { - protocol_parameters: protocol_parameters.clone(), + protocol_parameters: Some(protocol_parameters.clone()), signed_entity_types: Some( [ SignedEntityTypeDiscriminants::CardanoTransactions.to_string(), @@ -31,10 +31,10 @@ async fn create_certificate() { .join(","), ), data_stores_directory: get_test_dir("create_certificate"), - cardano_transactions_signing_config: CardanoTransactionsSigningConfig { + cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig { security_parameter: BlockNumber(0), step: BlockNumber(30), - }, + }), ..ServeCommandConfiguration::new_sample(temp_dir!()) }; let mut tester = RuntimeTester::build( diff --git a/mithril-aggregator/tests/create_certificate_follower.rs b/mithril-aggregator/tests/create_certificate_follower.rs index 371fe067b06..9f75c791a51 100644 --- a/mithril-aggregator/tests/create_certificate_follower.rs +++ b/mithril-aggregator/tests/create_certificate_follower.rs @@ -98,7 +98,7 @@ async fn create_certificate_follower() { }, }; let leader_configuration = ServeCommandConfiguration { - protocol_parameters: protocol_parameters.clone(), + protocol_parameters: Some(protocol_parameters.clone()), data_stores_directory: get_test_dir("create_certificate_leader"), signed_entity_types: Some( SignedEntityTypeDiscriminants::CardanoStakeDistribution.to_string(), diff --git a/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs b/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs index 073b1e5b587..f46d497920f 100644 --- a/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs +++ b/mithril-aggregator/tests/create_certificate_with_buffered_signatures.rs @@ -22,13 +22,13 @@ async fn create_certificate_with_buffered_signatures() { phi_f: 0.95, }; let configuration = ServeCommandConfiguration { - protocol_parameters: protocol_parameters.clone(), + protocol_parameters: Some(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 { + cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig { security_parameter: BlockNumber(0), step: BlockNumber(30), - }, + }), ..ServeCommandConfiguration::new_sample(temp_dir!()) }; let mut tester = RuntimeTester::build( diff --git a/mithril-aggregator/tests/era_checker.rs b/mithril-aggregator/tests/era_checker.rs index 36202f14da9..04fbfb2173f 100644 --- a/mithril-aggregator/tests/era_checker.rs +++ b/mithril-aggregator/tests/era_checker.rs @@ -22,7 +22,7 @@ async fn testing_eras() { phi_f: 0.95, }; let configuration = ServeCommandConfiguration { - protocol_parameters: protocol_parameters.clone(), + protocol_parameters: Some(protocol_parameters.clone()), data_stores_directory: get_test_dir("testing_eras"), ..ServeCommandConfiguration::new_sample(temp_dir!()) }; diff --git a/mithril-aggregator/tests/genesis_to_signing.rs b/mithril-aggregator/tests/genesis_to_signing.rs index e47dba18f5a..6ee5ed3d2e5 100644 --- a/mithril-aggregator/tests/genesis_to_signing.rs +++ b/mithril-aggregator/tests/genesis_to_signing.rs @@ -16,7 +16,7 @@ async fn genesis_to_signing() { phi_f: 0.65, }; let configuration = ServeCommandConfiguration { - protocol_parameters: protocol_parameters.clone(), + protocol_parameters: Some(protocol_parameters.clone()), data_stores_directory: get_test_dir("genesis_to_signing"), ..ServeCommandConfiguration::new_sample(temp_dir!()) }; diff --git a/mithril-aggregator/tests/open_message_expiration.rs b/mithril-aggregator/tests/open_message_expiration.rs index 4e321f2b158..7442aedc556 100644 --- a/mithril-aggregator/tests/open_message_expiration.rs +++ b/mithril-aggregator/tests/open_message_expiration.rs @@ -23,7 +23,7 @@ async fn open_message_expiration() { phi_f: 0.95, }; let configuration = ServeCommandConfiguration { - protocol_parameters: protocol_parameters.clone(), + protocol_parameters: Some(protocol_parameters.clone()), data_stores_directory: get_test_dir("open_message_expiration"), signed_entity_types: Some(SignedEntityTypeDiscriminants::CardanoDatabase.to_string()), ..ServeCommandConfiguration::new_sample(temp_dir!()) diff --git a/mithril-aggregator/tests/open_message_newer_exists.rs b/mithril-aggregator/tests/open_message_newer_exists.rs index f3850e3201a..4f1253576cd 100644 --- a/mithril-aggregator/tests/open_message_newer_exists.rs +++ b/mithril-aggregator/tests/open_message_newer_exists.rs @@ -21,7 +21,7 @@ async fn open_message_newer_exists() { phi_f: 0.95, }; let configuration = ServeCommandConfiguration { - protocol_parameters: protocol_parameters.clone(), + protocol_parameters: Some(protocol_parameters.clone()), data_stores_directory: get_test_dir("open_message_newer_exists"), signed_entity_types: Some(SignedEntityTypeDiscriminants::CardanoDatabase.to_string()), ..ServeCommandConfiguration::new_sample(temp_dir!()) diff --git a/mithril-aggregator/tests/prove_transactions.rs b/mithril-aggregator/tests/prove_transactions.rs index 43868d0fd92..3e6fa861a2c 100644 --- a/mithril-aggregator/tests/prove_transactions.rs +++ b/mithril-aggregator/tests/prove_transactions.rs @@ -23,13 +23,13 @@ async fn prove_transactions() { phi_f: 0.95, }; let configuration = ServeCommandConfiguration { - protocol_parameters: protocol_parameters.clone(), + protocol_parameters: Some(protocol_parameters.clone()), signed_entity_types: Some(SignedEntityTypeDiscriminants::CardanoTransactions.to_string()), data_stores_directory: get_test_dir("prove_transactions"), - cardano_transactions_signing_config: CardanoTransactionsSigningConfig { + cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig { security_parameter: BlockNumber(0), step: BlockNumber(30), - }, + }), ..ServeCommandConfiguration::new_sample(temp_dir!()) }; let mut tester = RuntimeTester::build( diff --git a/mithril-aggregator/tests/test_extensions/runtime_tester.rs b/mithril-aggregator/tests/test_extensions/runtime_tester.rs index 2adb9d8d70b..6c7e91cab13 100644 --- a/mithril-aggregator/tests/test_extensions/runtime_tester.rs +++ b/mithril-aggregator/tests/test_extensions/runtime_tester.rs @@ -160,7 +160,7 @@ impl RuntimeTester { let global_logger = slog_scope::set_global_logger(logger.clone()); let network = configuration.network.clone(); let cardano_transactions_signing_config = - configuration.cardano_transactions_signing_config.clone(); + configuration.cardano_transactions_signing_config.clone().unwrap(); let snapshot_uploader = Arc::new(DumbUploader::default()); let immutable_file_observer = Arc::new(DumbImmutableFileObserver::new()); immutable_file_observer From 83d9a1895c392e3168e0683b1ef2ca73d5390aa9 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Tue, 4 Nov 2025 23:13:04 +0100 Subject: [PATCH 08/18] test(aggregator): simplify & rework serve deps container test tooling Rework `init_state_from_fixture` to not save epoch_settings and works with the fixed window of three epoch (aggregate/next aggregate/signer registration), epoch settings should already exists, most of the time they will be inserted by the handle discrepancies system --- .../src/dependency_injection/builder/mod.rs | 1 - .../dependency_injection/containers/serve.rs | 136 ++++++---------- mithril-aggregator/src/runtime/runner.rs | 39 ++--- .../services/certifier/certifier_service.rs | 154 +++++++----------- .../tests/test_extensions/runtime_tester.rs | 19 +-- 5 files changed, 121 insertions(+), 228 deletions(-) diff --git a/mithril-aggregator/src/dependency_injection/builder/mod.rs b/mithril-aggregator/src/dependency_injection/builder/mod.rs index ac1649c4c89..ba6836347a3 100644 --- a/mithril-aggregator/src/dependency_injection/builder/mod.rs +++ b/mithril-aggregator/src/dependency_injection/builder/mod.rs @@ -386,7 +386,6 @@ impl DependenciesBuilder { certificate_repository: self.get_certificate_repository().await?, verification_key_store: self.get_verification_key_store().await?, epoch_settings_storer: self.get_epoch_settings_store().await?, - chain_observer: self.get_chain_observer().await?, certificate_chain_synchronizer: self.get_certificate_chain_synchronizer().await?, signer_registerer: self.get_signer_registerer().await?, signer_synchronizer: self.get_signer_synchronizer().await?, diff --git a/mithril-aggregator/src/dependency_injection/containers/serve.rs b/mithril-aggregator/src/dependency_injection/containers/serve.rs index b37d783aad5..3ea00fa6868 100644 --- a/mithril-aggregator/src/dependency_injection/containers/serve.rs +++ b/mithril-aggregator/src/dependency_injection/containers/serve.rs @@ -2,13 +2,9 @@ use slog::Logger; use std::sync::Arc; use tokio::sync::RwLock; -use mithril_cardano_node_chain::chain_observer::ChainObserver; use mithril_common::{ api_version::APIVersionProvider, - entities::{ - CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, SignerWithStake, - StakeDistribution, - }, + entities::{Epoch, SignerWithStake, StakeDistribution}, signable_builder::SignableBuilderService, test::builder::MithrilFixture, }; @@ -24,7 +20,6 @@ use crate::{ database::repository::{ CertificateRepository, SignedEntityStorer, SignerGetter, StakePoolStore, }, - entities::AggregatorEpochSettings, event_store::{EventMessage, TransmitterService}, services::{ CertificateChainSynchronizer, CertifierService, EpochService, MessageService, @@ -55,9 +50,6 @@ pub struct ServeCommandDependenciesContainer { /// Epoch settings storer. pub epoch_settings_storer: Arc, - /// Chain observer service. - pub(crate) chain_observer: Arc, - /// Certificate chain synchronizer service pub(crate) certificate_chain_synchronizer: Arc, @@ -132,94 +124,64 @@ pub struct ServeCommandDependenciesContainer { impl ServeCommandDependenciesContainer { /// `TEST METHOD ONLY` /// - /// Get the first two epochs that will be used by a newly started aggregator - pub async fn get_genesis_epochs(&self) -> (Epoch, Epoch) { - let current_epoch = self - .chain_observer - .get_current_epoch() - .await - .expect("get_current_epoch should not fail") - .expect("an epoch should've been set to the chain observer"); - let work_epoch = current_epoch - .offset_to_signer_retrieval_epoch() - .expect("epoch.offset_by SIGNER_EPOCH_RETRIEVAL_OFFSET should not fail"); - let epoch_to_sign = current_epoch.offset_to_next_signer_retrieval_epoch(); - - (work_epoch, epoch_to_sign) - } - - /// `TEST METHOD ONLY` - /// - /// Fill the stores of a [DependencyManager] in a way to simulate an aggregator state + /// Fill the stores of this container in a way to simulate an aggregator state /// using the data from a precomputed fixture. + /// + /// Data will be inserted in the given `next_aggregation_epoch`, the current aggregation epoch + /// (`next_aggregation_epoch - 1`), and the signer registration epoch (`next_aggregation_epoch + 1`). + /// + /// Note: `epoch_settings` store must have data for the inserted epochs, this should be done + /// automatically when building the [ServeCommandDependenciesContainer] by `handle_discrepancies_at_startup` pub async fn init_state_from_fixture( &self, fixture: &MithrilFixture, - cardano_transactions_signing_config: &CardanoTransactionsSigningConfig, - target_epochs: &[Epoch], + next_aggregation_epoch: Epoch, ) { - for epoch in target_epochs { - self.epoch_settings_storer - .save_epoch_settings( - *epoch, - AggregatorEpochSettings { - protocol_parameters: fixture.protocol_parameters(), - cardano_transactions_signing_config: cardano_transactions_signing_config - .clone(), - }, - ) - .await - .expect("save_epoch_settings should not fail"); - self.fill_verification_key_store(*epoch, &fixture.signers_with_stake()) - .await; - self.fill_stakes_store(*epoch, fixture.signers_with_stake()).await; - } + self.init_state_from_fixture_internal( + fixture, + [ + next_aggregation_epoch.offset_to_signer_retrieval_epoch().unwrap(), + next_aggregation_epoch, + next_aggregation_epoch.offset_to_recording_epoch(), + ], + ) + .await } /// `TEST METHOD ONLY` /// - /// Fill the stores of a [DependencyManager] in a way to simulate an aggregator genesis state. + /// Fill the stores of this container in a way to simulate an aggregator state ready to sign a + /// genesis certificate using the data from a precomputed fixture. + /// + /// Data will be inserted in the given `next_aggregation_epoch`, the current aggregation epoch + /// (`next_aggregation_epoch - 1`). /// - /// For the current and the next epoch: - /// * Fill the [VerificationKeyStorer] with the given signers keys. - /// * Fill the [StakeStore] with the given signers stakes. - /// * Fill the [ProtocolParametersStore] with the given parameters. - pub async fn prepare_for_genesis( + /// Note: `epoch_settings` store must have data for the inserted epochs, this should be done + /// automatically when building the [ServeCommandDependenciesContainer] by `handle_discrepancies_at_startup` + pub async fn init_state_from_fixture_for_genesis( &self, - genesis_signers: Vec, - second_epoch_signers: Vec, - genesis_protocol_parameters: &ProtocolParameters, - cardano_transactions_signing_config: &CardanoTransactionsSigningConfig, + fixture: &MithrilFixture, + next_aggregation_epoch: Epoch, ) { - self.init_epoch_settings_storer(&AggregatorEpochSettings { - protocol_parameters: genesis_protocol_parameters.clone(), - cardano_transactions_signing_config: cardano_transactions_signing_config.clone(), - }) - .await; - - let (work_epoch, epoch_to_sign) = self.get_genesis_epochs().await; - for (epoch, signers) in - [(work_epoch, genesis_signers), (epoch_to_sign, second_epoch_signers)] - { - self.fill_verification_key_store(epoch, &signers).await; - self.fill_stakes_store(epoch, signers).await; - } + self.init_state_from_fixture_internal( + fixture, + [ + next_aggregation_epoch.offset_to_signer_retrieval_epoch().unwrap(), + next_aggregation_epoch, + ], + ) + .await } - /// `TEST METHOD ONLY` - /// - /// Fill up to the first three epochs of the [EpochSettingsStorer] with the given value. - pub async fn init_epoch_settings_storer(&self, epoch_settings: &AggregatorEpochSettings) { - let (work_epoch, epoch_to_sign) = self.get_genesis_epochs().await; - let mut epochs_to_save = Vec::new(); - epochs_to_save.push(work_epoch); - epochs_to_save.push(epoch_to_sign); - epochs_to_save.push(epoch_to_sign.next()); - for epoch in epochs_to_save { - self.epoch_settings_storer - .save_epoch_settings(epoch, epoch_settings.clone()) - .await - .expect("save_epoch_settings should not fail"); + async fn init_state_from_fixture_internal( + &self, + fixture: &MithrilFixture, + epochs_to_fill: [Epoch; N], + ) { + for epoch in epochs_to_fill { + self.fill_verification_key_store(epoch, &fixture.signers_with_stake()) + .await; + self.fill_stakes_store(epoch, fixture.signers_with_stake()).await; } } @@ -250,13 +212,11 @@ impl ServeCommandDependenciesContainer { #[cfg(test)] pub(crate) mod tests { + use std::path::PathBuf; - use std::{path::PathBuf, sync::Arc}; + use crate::{ServeCommandConfiguration, dependency_injection::DependenciesBuilder}; - use crate::{ - ServeCommandConfiguration, ServeCommandDependenciesContainer, - dependency_injection::DependenciesBuilder, - }; + use super::*; /// Initialize dependency container with a unique temporary snapshot directory build from test path. /// This macro should used directly in a function test to be able to retrieve the function name. diff --git a/mithril-aggregator/src/runtime/runner.rs b/mithril-aggregator/src/runtime/runner.rs index 758f9ffaf9e..3d412abc124 100644 --- a/mithril-aggregator/src/runtime/runner.rs +++ b/mithril-aggregator/src/runtime/runner.rs @@ -530,9 +530,8 @@ pub mod tests { use mithril_common::{ StdResult, entities::{ - CardanoTransactionsSigningConfig, ChainPoint, Epoch, ProtocolMessage, - SignedEntityConfig, SignedEntityType, SignedEntityTypeDiscriminants, StakeDistribution, - TimePoint, + ChainPoint, Epoch, ProtocolMessage, SignedEntityConfig, SignedEntityType, + SignedEntityTypeDiscriminants, StakeDistribution, TimePoint, }, signable_builder::SignableBuilderService, temp_dir, @@ -549,12 +548,11 @@ pub mod tests { MithrilSignerRegistrationLeader, ServeCommandConfiguration, ServeCommandDependenciesContainer, SignerRegistrationRound, dependency_injection::DependenciesBuilder, - entities::{AggregatorEpochSettings, OpenMessage}, + entities::OpenMessage, initialize_dependencies, runtime::{AggregatorRunner, AggregatorRunnerTrait}, services::{ - FakeEpochService, FakeEpochServiceBuilder, MithrilStakeDistributionService, - MockCertifierService, MockUpkeepService, + FakeEpochService, FakeEpochServiceBuilder, MockCertifierService, MockUpkeepService, }, }; @@ -576,17 +574,8 @@ pub mod tests { deps: ServeCommandDependenciesContainer, ) -> AggregatorRunner { let fixture = MithrilFixtureBuilder::default().with_signers(5).build(); - let current_epoch = deps.chain_observer.get_current_epoch().await.unwrap().unwrap(); - deps.init_state_from_fixture( - &fixture, - &CardanoTransactionsSigningConfig::dummy(), - &[ - current_epoch.offset_to_signer_retrieval_epoch().unwrap(), - current_epoch, - current_epoch.next(), - ], - ) - .await; + let current_epoch = deps.ticker_service.get_current_epoch().await.unwrap(); + deps.init_state_from_fixture(&fixture, current_epoch).await; AggregatorRunner::new(Arc::new(deps)) } @@ -704,13 +693,11 @@ pub mod tests { async fn test_update_stake_distribution() { let chain_observer = Arc::new(FakeChainObserver::default()); let deps = { - let mut deps = initialize_dependencies!().await; - deps.chain_observer = chain_observer.clone(); - deps.stake_distribution_service = Arc::new(MithrilStakeDistributionService::new( - deps.stake_store.clone(), - chain_observer.clone(), - )); - Arc::new(deps) + let config = ServeCommandConfiguration::new_sample(temp_dir!()); + let mut builder = DependenciesBuilder::new_with_stdout_logger(Arc::new(config)); + builder.chain_observer = Some(chain_observer.clone()); + + Arc::new(builder.build_serve_dependencies_container().await.unwrap()) }; let runner = AggregatorRunner::new(deps.clone()); let time_point = runner.get_time_point_from_chain().await.unwrap(); @@ -865,7 +852,7 @@ pub mod tests { .returning(|_| Ok(())) .times(1); let mut deps = initialize_dependencies!().await; - let current_epoch = deps.chain_observer.get_current_epoch().await.unwrap().unwrap(); + let current_epoch = deps.ticker_service.get_current_epoch().await.unwrap(); deps.certifier_service = Arc::new(mock_certifier_service); deps.epoch_service = Arc::new(RwLock::new(FakeEpochService::from_fixture( @@ -898,7 +885,7 @@ pub mod tests { #[tokio::test] async fn test_precompute_epoch_data() { let mut deps = initialize_dependencies!().await; - let current_epoch = deps.chain_observer.get_current_epoch().await.unwrap().unwrap(); + let current_epoch = deps.ticker_service.get_current_epoch().await.unwrap(); deps.epoch_service = Arc::new(RwLock::new(FakeEpochService::from_fixture( current_epoch, diff --git a/mithril-aggregator/src/services/certifier/certifier_service.rs b/mithril-aggregator/src/services/certifier/certifier_service.rs index a4a2777d83f..725cee1abfb 100644 --- a/mithril-aggregator/src/services/certifier/certifier_service.rs +++ b/mithril-aggregator/src/services/certifier/certifier_service.rs @@ -409,22 +409,24 @@ impl CertifierService for MithrilCertifierService { #[cfg(test)] mod tests { + use chrono::{DateTime, Days}; use std::path::PathBuf; + use tokio::sync::RwLock; - use crate::{ - ServeCommandConfiguration, dependency_injection::DependenciesBuilder, - multi_signer::MockMultiSigner, services::FakeEpochService, test::TestLogger, - }; - use chrono::{DateTime, Days}; + use mithril_cardano_node_chain::test::double::FakeChainObserver; use mithril_common::{ - entities::{CardanoDbBeacon, ProtocolMessagePartKey}, + entities::{CardanoDbBeacon, ProtocolMessagePartKey, TimePoint}, temp_dir, test::{ builder::{MithrilFixture, MithrilFixtureBuilder}, - double::fake_data, + double::{Dummy, fake_data}, }, }; - use tokio::sync::RwLock; + + use crate::{ + ServeCommandConfiguration, dependency_injection::DependenciesBuilder, + multi_signer::MockMultiSigner, services::FakeEpochService, test::TestLogger, + }; use super::*; @@ -458,51 +460,30 @@ mod tests { } /// Note: If current_epoch is provided the [EpochService] will be automatically initialized - async fn setup_certifier_service_with_network( + async fn setup_certifier_service( snapshot_directory: PathBuf, - network: CardanoNetwork, fixture: &MithrilFixture, - epochs_with_signers: &[Epoch], - current_epoch: Option, + current_epoch: Epoch, ) -> MithrilCertifierService { let configuration = ServeCommandConfiguration::new_sample(snapshot_directory); - let cardano_transactions_signing_config = - configuration.cardano_transactions_signing_config.clone(); let mut dependency_builder = DependenciesBuilder::new_with_stdout_logger(Arc::new(configuration)); - if let Some(epoch) = current_epoch { - dependency_builder.epoch_service = Some(Arc::new(RwLock::new( - FakeEpochService::from_fixture(epoch, fixture), - ))); - } + dependency_builder.epoch_service = Some(Arc::new(RwLock::new( + FakeEpochService::from_fixture(current_epoch, fixture), + ))); + dependency_builder.chain_observer = + Some(Arc::new(FakeChainObserver::new(Some(TimePoint { + epoch: current_epoch, + ..Dummy::dummy() + })))); let dependency_manager = dependency_builder.build_serve_dependencies_container().await.unwrap(); dependency_manager - .init_state_from_fixture( - fixture, - &cardano_transactions_signing_config.unwrap(), - epochs_with_signers, - ) + .init_state_from_fixture(fixture, current_epoch) .await; - MithrilCertifierService::from_deps(network, dependency_builder).await - } - - async fn setup_certifier_service( - snapshot_directory: PathBuf, - fixture: &MithrilFixture, - epochs_with_signers: &[Epoch], - current_epoch: Option, - ) -> MithrilCertifierService { - setup_certifier_service_with_network( - snapshot_directory, - fake_data::network(), - fixture, - epochs_with_signers, - current_epoch, - ) - .await + MithrilCertifierService::from_deps(fake_data::network(), dependency_builder).await } #[tokio::test] @@ -511,10 +492,8 @@ mod tests { 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(temp_dir!(), &fixture, &epochs_with_signers, None).await; + let certifier_service = setup_certifier_service(temp_dir!(), &fixture, beacon.epoch).await; certifier_service .create_open_message(&signed_entity_type, &protocol_message) .await @@ -529,10 +508,8 @@ mod tests { let beacon = CardanoDbBeacon::new(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(temp_dir!(), &fixture, &epochs_with_signers, None).await; + let certifier_service = setup_certifier_service(temp_dir!(), &fixture, beacon.epoch).await; let mut open_message = certifier_service .open_message_repository .create_open_message(beacon.epoch, &signed_entity_type, &protocol_message) @@ -562,10 +539,8 @@ mod tests { let beacon = CardanoDbBeacon::new(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(temp_dir!(), &fixture, &epochs_with_signers, None).await; + let certifier_service = setup_certifier_service(temp_dir!(), &fixture, beacon.epoch).await; let mut open_message = certifier_service .open_message_repository .create_open_message(beacon.epoch, &signed_entity_type, &protocol_message) @@ -590,10 +565,8 @@ mod tests { let beacon = CardanoDbBeacon::new(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(temp_dir!(), &fixture, &epochs_with_signers, None).await; + let certifier_service = setup_certifier_service(temp_dir!(), &fixture, beacon.epoch).await; let mut open_message = certifier_service .open_message_repository .create_open_message(beacon.epoch, &signed_entity_type, &protocol_message) @@ -618,15 +591,8 @@ mod tests { let beacon = CardanoDbBeacon::new(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( - temp_dir!(), - &fixture, - &epochs_with_signers, - Some(beacon.epoch), - ) - .await; + let certifier_service = setup_certifier_service(temp_dir!(), &fixture, beacon.epoch).await; certifier_service .create_open_message(&signed_entity_type, &protocol_message) @@ -656,15 +622,8 @@ mod tests { let beacon = CardanoDbBeacon::new(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( - temp_dir!(), - &fixture, - &epochs_with_signers, - Some(beacon.epoch), - ) - .await; + let certifier_service = setup_certifier_service(temp_dir!(), &fixture, beacon.epoch).await; certifier_service .create_open_message(&signed_entity_type, &protocol_message) @@ -701,10 +660,8 @@ mod tests { let beacon = CardanoDbBeacon::new(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(temp_dir!(), &fixture, &epochs_with_signers, None).await; + let certifier_service = setup_certifier_service(temp_dir!(), &fixture, beacon.epoch).await; let mut open_message = certifier_service .open_message_repository .create_open_message(beacon.epoch, &signed_entity_type, &protocol_message) @@ -734,10 +691,8 @@ mod tests { let beacon = CardanoDbBeacon::new(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(temp_dir!(), &fixture, &epochs_with_signers, None).await; + let certifier_service = setup_certifier_service(temp_dir!(), &fixture, beacon.epoch).await; let mut open_message = certifier_service .open_message_repository .create_open_message(beacon.epoch, &signed_entity_type, &protocol_message) @@ -764,28 +719,20 @@ mod tests { #[tokio::test] async fn should_create_certificate_when_multi_signature_produced() { - let network = fake_data::network(); let beacon = CardanoDbBeacon::new(3, 1); let signed_entity_type = SignedEntityType::CardanoImmutableFilesFull(beacon.clone()); let mut protocol_message = ProtocolMessage::new(); protocol_message.set_message_part(ProtocolMessagePartKey::CurrentEpoch, "3".to_string()); - 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( - temp_dir!(), - network, - &fixture, - &epochs_with_signers, - Some(beacon.epoch), - ) - .await; + let certifier_service = setup_certifier_service(temp_dir!(), &fixture, beacon.epoch).await; certifier_service .create_open_message(&signed_entity_type, &protocol_message) .await .unwrap(); - let genesis_certificate = fixture.create_genesis_certificate(network, beacon.epoch - 1); + let genesis_certificate = + fixture.create_genesis_certificate(certifier_service.network, beacon.epoch - 1); certifier_service .certificate_repository .create_certificate(genesis_certificate) @@ -843,10 +790,8 @@ mod tests { async fn should_not_create_certificate_for_open_message_not_created() { let beacon = CardanoDbBeacon::new(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(temp_dir!(), &fixture, &epochs_with_signers, None).await; + let certifier_service = setup_certifier_service(temp_dir!(), &fixture, beacon.epoch).await; certifier_service .create_certificate(&signed_entity_type) .await @@ -859,19 +804,33 @@ mod tests { 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(temp_dir!(), &fixture, &epochs_with_signers, None).await; - certifier_service + let certifier_service = setup_certifier_service(temp_dir!(), &fixture, beacon.epoch).await; + let mut record = certifier_service .open_message_repository .create_open_message(epoch, &signed_entity_type, &protocol_message) .await .unwrap(); + record.is_certified = true; certifier_service + .open_message_repository + .update_open_message(&record) + .await + .unwrap(); + + let error = certifier_service .create_certificate(&signed_entity_type) .await .expect_err("create_certificate should fail"); + + if let Some(err) = error.downcast_ref::() { + assert!(matches!( + err, + CertifierServiceError::AlreadyCertified(signed_entity) if signed_entity == &signed_entity_type + ),); + } else { + panic!("Unexpected error {error:?}"); + } } #[tokio::test] @@ -883,10 +842,9 @@ mod tests { let beacon = CardanoDbBeacon::new(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(temp_dir!(), &fixture, &epochs_with_signers, None).await; + setup_certifier_service(temp_dir!(), &fixture, beacon.epoch).await; certifier_service.multi_signer = Arc::new(mock_multi_signer); certifier_service .create_open_message(&signed_entity_type, &protocol_message) @@ -902,10 +860,9 @@ mod tests { #[tokio::test] async fn test_epoch_gap_certificate_chain() { let builder = MithrilFixtureBuilder::default(); - let certifier_service = - setup_certifier_service(temp_dir!(), &builder.build(), &[], None).await; let certificate = fake_data::genesis_certificate("whatever"); let epoch = certificate.epoch + 2; + let certifier_service = setup_certifier_service(temp_dir!(), &builder.build(), epoch).await; certifier_service .certificate_repository .create_certificate(certificate) @@ -925,10 +882,9 @@ mod tests { #[tokio::test] async fn test_epoch_gap_certificate_chain_ok() { let builder = MithrilFixtureBuilder::default(); - let certifier_service = - setup_certifier_service(temp_dir!(), &builder.build(), &[], None).await; let certificate = fake_data::genesis_certificate("whatever"); let epoch = certificate.epoch + 1; + let certifier_service = setup_certifier_service(temp_dir!(), &builder.build(), epoch).await; certifier_service .certificate_repository .create_certificate(certificate) diff --git a/mithril-aggregator/tests/test_extensions/runtime_tester.rs b/mithril-aggregator/tests/test_extensions/runtime_tester.rs index 6c7e91cab13..49aa5ba52e4 100644 --- a/mithril-aggregator/tests/test_extensions/runtime_tester.rs +++ b/mithril-aggregator/tests/test_extensions/runtime_tester.rs @@ -24,10 +24,9 @@ use mithril_common::{ StdResult, crypto_helper::ProtocolGenesisSigner, entities::{ - BlockNumber, CardanoTransactionsSigningConfig, Certificate, CertificateSignature, - ChainPoint, Epoch, ImmutableFileNumber, SignedEntityType, SignedEntityTypeDiscriminants, - SingleSignatureAuthenticationStatus, SlotNumber, StakeDistribution, SupportedEra, - TimePoint, + BlockNumber, Certificate, CertificateSignature, ChainPoint, Epoch, ImmutableFileNumber, + SignedEntityType, SignedEntityTypeDiscriminants, SingleSignatureAuthenticationStatus, + SlotNumber, StakeDistribution, SupportedEra, TimePoint, }, test::{ builder::{ @@ -127,7 +126,6 @@ macro_rules! assert_metrics_eq { pub struct RuntimeTester { pub network: String, - pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig, pub snapshot_uploader: Arc, pub chain_observer: Arc, pub immutable_file_observer: Arc, @@ -159,8 +157,6 @@ impl RuntimeTester { let logger = build_logger(); let global_logger = slog_scope::set_global_logger(logger.clone()); let network = configuration.network.clone(); - let cardano_transactions_signing_config = - configuration.cardano_transactions_signing_config.clone().unwrap(); let snapshot_uploader = Arc::new(DumbUploader::default()); let immutable_file_observer = Arc::new(DumbImmutableFileObserver::new()); immutable_file_observer @@ -196,7 +192,6 @@ impl RuntimeTester { Self { network, - cardano_transactions_signing_config, snapshot_uploader, chain_observer, immutable_file_observer, @@ -228,13 +223,9 @@ impl RuntimeTester { self.chain_observer.set_signers(fixture.signers_with_stake()).await; // Init the stores needed for a genesis certificate - let genesis_epochs = self.dependencies.get_genesis_epochs().await; + let time_point = self.observer.current_time_point().await; self.dependencies - .init_state_from_fixture( - fixture, - &self.cardano_transactions_signing_config, - &[genesis_epochs.0, genesis_epochs.1], - ) + .init_state_from_fixture_for_genesis(fixture, time_point.epoch) .await; Ok(()) } From 3a497cddf2dcf85842a61feda32c227baa43d8c2 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:10:50 +0100 Subject: [PATCH 09/18] feat(aggregator): move & rework handle discrepancies - run it at the end of the serve dependency container build - retrieve and save data from the network configuration provider instead of the local node configuration - update follower integration test to check that local protocol parameter configuration is not read, instead the configuration is read through the network configuration provider from the leader --- .../builder/enablers/epoch.rs | 2 - .../src/dependency_injection/builder/mod.rs | 60 +++++++++++- .../builder/support/stores.rs | 46 +-------- .../src/store/epoch_settings_storer.rs | 96 ++++++++++++------- .../tests/create_certificate_follower.rs | 3 + 5 files changed, 124 insertions(+), 83 deletions(-) diff --git a/mithril-aggregator/src/dependency_injection/builder/enablers/epoch.rs b/mithril-aggregator/src/dependency_injection/builder/enablers/epoch.rs index e16a109c6fa..1f45c91158e 100644 --- a/mithril-aggregator/src/dependency_injection/builder/enablers/epoch.rs +++ b/mithril-aggregator/src/dependency_injection/builder/enablers/epoch.rs @@ -61,8 +61,6 @@ impl DependenciesBuilder { )) }; - //TODO handle discrepency here - Ok(network_configuration_provider) } diff --git a/mithril-aggregator/src/dependency_injection/builder/mod.rs b/mithril-aggregator/src/dependency_injection/builder/mod.rs index ba6836347a3..ef320d9130d 100644 --- a/mithril-aggregator/src/dependency_injection/builder/mod.rs +++ b/mithril-aggregator/src/dependency_injection/builder/mod.rs @@ -3,7 +3,7 @@ mod protocol; mod support; use anyhow::Context; -use slog::Logger; +use slog::{Logger, debug}; use std::{path::PathBuf, sync::Arc}; use tokio::{ sync::{ @@ -45,10 +45,11 @@ use super::{ GenesisCommandDependenciesContainer, Result, ToolsCommandDependenciesContainer, }; use crate::{ - AggregatorConfig, AggregatorRunner, AggregatorRuntime, ImmutableFileDigestMapper, - MetricsService, MithrilSignerRegistrationLeader, MultiSigner, ProtocolParametersRetriever, - ServeCommandDependenciesContainer, SignerRegisterer, SignerRegistrationRoundOpener, - SignerRegistrationVerifier, SingleSignatureAuthenticator, VerificationKeyStorer, + AggregatorConfig, AggregatorRunner, AggregatorRuntime, EpochSettingsStorer, + ImmutableFileDigestMapper, MetricsService, MithrilSignerRegistrationLeader, MultiSigner, + ProtocolParametersRetriever, ServeCommandDependenciesContainer, SignerRegisterer, + SignerRegistrationRoundOpener, SignerRegistrationVerifier, SingleSignatureAuthenticator, + VerificationKeyStorer, configuration::ConfigurationSource, database::repository::{ CertificateRepository, EpochSettingsStore, OpenMessageRepository, SignedEntityStorer, @@ -411,6 +412,8 @@ impl DependenciesBuilder { metrics_service: self.get_metrics_service().await?, }; + self.handle_discrepancies_at_startup().await?; + Ok(dependencies_manager) } @@ -527,6 +530,53 @@ impl DependenciesBuilder { Ok(dependencies) } + /// Look for discrepancies in stored data and fix them. + /// + /// Fix discrepancies for: + /// - epoch settings: ensure that the network configuration parameters for the three working epochs + /// window are stored in the database (see [mithril_protocol_config::model::MithrilNetworkConfiguration]) + pub async fn handle_discrepancies_at_startup(&mut self) -> Result<()> { + let logger = self.root_logger(); + let current_epoch = self + .get_chain_observer() + .await? + .get_current_epoch() + .await + .map_err(|e| DependenciesBuilderError::Initialization { + message: "cannot handle startup discrepancies: failed to retrieve current epoch." + .to_string(), + error: Some(e.into()), + })? + .ok_or(DependenciesBuilderError::Initialization { + message: "cannot handle startup discrepancies: no epoch returned.".to_string(), + error: None, + })?; + let network_configuration = self + .get_mithril_network_configuration_provider() + .await? + .get_network_configuration(current_epoch) + .await + .map_err(|e| DependenciesBuilderError::Initialization { + message: format!("cannot handle startup discrepancies: failed to retrieve network configuration for epoch {current_epoch}"), + error: Some(e), + })?; + let epoch_settings_store = self.get_epoch_settings_store().await?; + + debug!( + logger, + "Handle discrepancies at startup of epoch settings store, will record epoch settings from the configuration for epoch {current_epoch}"; + "network_configuration" => ?network_configuration, + ); + epoch_settings_store + .handle_discrepancies_at_startup(&network_configuration) + .await + .map_err(|e| DependenciesBuilderError::Initialization { + message: "can not create aggregator runner".to_string(), + error: Some(e), + })?; + Ok(()) + } + /// Remove the dependencies builder from memory to release Arc instances. pub async fn vanish(self) { self.drop_sqlite_connections().await; diff --git a/mithril-aggregator/src/dependency_injection/builder/support/stores.rs b/mithril-aggregator/src/dependency_injection/builder/support/stores.rs index d2113380cad..32e3ac6b3c7 100644 --- a/mithril-aggregator/src/dependency_injection/builder/support/stores.rs +++ b/mithril-aggregator/src/dependency_injection/builder/support/stores.rs @@ -1,5 +1,4 @@ use anyhow::Context; -use slog::debug; use std::sync::Arc; use std::time::Duration; @@ -11,11 +10,11 @@ use crate::database::repository::{ OpenMessageRepository, SignedEntityStore, SignedEntityStorer, SignerRegistrationStore, SignerStore, StakePoolStore, }; -use crate::dependency_injection::{DependenciesBuilder, DependenciesBuilderError, Result}; +use crate::dependency_injection::{DependenciesBuilder, Result}; use crate::get_dependency; use crate::{ - CExplorerSignerRetriever, EpochSettingsStorer, ImmutableFileDigestMapper, - ProtocolParametersRetriever, SignersImporter, VerificationKeyStorer, + CExplorerSignerRetriever, ImmutableFileDigestMapper, ProtocolParametersRetriever, + SignersImporter, VerificationKeyStorer, }; impl DependenciesBuilder { @@ -89,52 +88,15 @@ impl DependenciesBuilder { } async fn build_epoch_settings_store(&mut self) -> Result> { - let logger = self.root_logger(); let epoch_settings_store = EpochSettingsStore::new( self.get_sqlite_connection().await?, self.configuration.safe_epoch_retention_limit(), ); - let current_epoch = self - .get_chain_observer() - .await? - .get_current_epoch() - .await - .map_err(|e| DependenciesBuilderError::Initialization { - message: "cannot create aggregator runner: failed to retrieve current epoch." - .to_string(), - error: Some(e.into()), - })? - .ok_or(DependenciesBuilderError::Initialization { - message: "cannot build aggregator runner: no epoch returned.".to_string(), - error: None, - })?; - let retrieval_epoch = current_epoch - .offset_to_signer_retrieval_epoch() - .map_err(|e| DependenciesBuilderError::Initialization { - message: format!("cannot create aggregator runner: failed to offset current epoch '{current_epoch}' to signer retrieval epoch."), - error: Some(e.into()), - })?; - - let epoch_settings_configuration = self - .configuration - .get_leader_aggregator_epoch_settings_configuration()?; - debug!( - logger, - "Handle discrepancies at startup of epoch settings store, will record epoch settings from the configuration for epoch {retrieval_epoch}"; - "epoch_settings_configuration" => ?epoch_settings_configuration, - ); - epoch_settings_store - .handle_discrepancies_at_startup(retrieval_epoch, &epoch_settings_configuration) - .await - .map_err(|e| DependenciesBuilderError::Initialization { - message: "can not create aggregator runner".to_string(), - error: Some(e), - })?; Ok(Arc::new(epoch_settings_store)) } - /// Get a configured [EpochSettingsStorer]. + /// Get a configured [EpochSettingsStore]. pub async fn get_epoch_settings_store(&mut self) -> Result> { get_dependency!(self.epoch_settings_store) } diff --git a/mithril-aggregator/src/store/epoch_settings_storer.rs b/mithril-aggregator/src/store/epoch_settings_storer.rs index c8d93362bd2..f971d77c851 100644 --- a/mithril-aggregator/src/store/epoch_settings_storer.rs +++ b/mithril-aggregator/src/store/epoch_settings_storer.rs @@ -1,12 +1,13 @@ +use anyhow::anyhow; +use async_trait::async_trait; #[cfg(test)] use std::collections::HashMap; - -use async_trait::async_trait; -use mithril_common::StdResult; #[cfg(test)] use tokio::sync::RwLock; +use mithril_common::StdResult; use mithril_common::entities::{Epoch, ProtocolParameters}; +use mithril_protocol_config::model::MithrilNetworkConfiguration; use crate::{entities::AggregatorEpochSettings, services::EpochPruningTask}; @@ -43,14 +44,37 @@ pub trait EpochSettingsStorer: /// call and the epoch service call. async fn handle_discrepancies_at_startup( &self, - current_epoch: Epoch, - epoch_settings_configuration: &AggregatorEpochSettings, + network_configuration: &MithrilNetworkConfiguration, ) -> StdResult<()> { - for epoch_offset in 0..=3 { - let epoch = current_epoch + epoch_offset; + for (epoch, epoch_configuration) in [ + ( + network_configuration.epoch.offset_to_signer_retrieval_epoch()?, + &network_configuration.configuration_for_aggregation, + ), + ( + network_configuration.epoch, + &network_configuration.configuration_for_next_aggregation, + ), + ( + network_configuration.epoch.offset_to_recording_epoch(), + &network_configuration.configuration_for_registration, + ), + ] { if self.get_epoch_settings(epoch).await?.is_none() { - self.save_epoch_settings(epoch, epoch_settings_configuration.clone()) - .await?; + self.save_epoch_settings( + epoch, + AggregatorEpochSettings { + protocol_parameters: epoch_configuration.protocol_parameters.clone(), + cardano_transactions_signing_config: epoch_configuration + .signed_entity_types_config + .cardano_transactions + .clone() + .ok_or(anyhow!( + "missing cardano transactions signing config for epoch {epoch}" + ))?, + }, + ) + .await?; } } @@ -116,7 +140,8 @@ impl EpochPruningTask for FakeEpochSettingsStorer { #[cfg(test)] mod tests { - use mithril_common::entities::CardanoTransactionsSigningConfig; + use std::collections::BTreeSet; + use mithril_common::test::double::Dummy; use super::*; @@ -178,40 +203,43 @@ mod tests { #[tokio::test] async fn test_handle_discrepancies_at_startup_should_complete_at_least_four_epochs() { let epoch_settings = AggregatorEpochSettings::dummy(); - let epoch_settings_new = AggregatorEpochSettings { - protocol_parameters: ProtocolParameters { - k: epoch_settings.protocol_parameters.k + 1, - ..epoch_settings.protocol_parameters - }, - cardano_transactions_signing_config: CardanoTransactionsSigningConfig { - step: epoch_settings.cardano_transactions_signing_config.step + 1, - ..epoch_settings.cardano_transactions_signing_config - }, - }; - let epoch = Epoch(1); - let store = FakeEpochSettingsStorer::new(vec![ - (epoch, epoch_settings.clone()), - (epoch + 1, epoch_settings.clone()), - ]); + let mut aggregation_epoch_settings = epoch_settings.clone(); + aggregation_epoch_settings.protocol_parameters.k += 15; + let mut next_aggregation_epoch_settings = epoch_settings.clone(); + next_aggregation_epoch_settings.protocol_parameters.k += 26; + + let mut registration_epoch_settings = epoch_settings.clone(); + registration_epoch_settings.protocol_parameters.k += 37; + + let epoch = Epoch(5); + let store = FakeEpochSettingsStorer::new(vec![]); store - .handle_discrepancies_at_startup(epoch, &epoch_settings_new) + .handle_discrepancies_at_startup(&MithrilNetworkConfiguration { + epoch, + configuration_for_aggregation: aggregation_epoch_settings + .clone() + .into_network_configuration_for_epoch(BTreeSet::new()), + configuration_for_next_aggregation: next_aggregation_epoch_settings + .clone() + .into_network_configuration_for_epoch(BTreeSet::new()), + configuration_for_registration: registration_epoch_settings + .clone() + .into_network_configuration_for_epoch(BTreeSet::new()), + }) .await .unwrap(); + let epoch_settings_stored = store.get_epoch_settings(epoch - 1).await.unwrap(); + assert_eq!(Some(aggregation_epoch_settings), epoch_settings_stored); + let epoch_settings_stored = store.get_epoch_settings(epoch).await.unwrap(); - assert_eq!(Some(epoch_settings.clone()), epoch_settings_stored); + assert_eq!(Some(next_aggregation_epoch_settings), epoch_settings_stored); let epoch_settings_stored = store.get_epoch_settings(epoch + 1).await.unwrap(); - assert_eq!(Some(epoch_settings.clone()), epoch_settings_stored); + assert_eq!(Some(registration_epoch_settings), epoch_settings_stored); let epoch_settings_stored = store.get_epoch_settings(epoch + 2).await.unwrap(); - assert_eq!(Some(epoch_settings_new.clone()), epoch_settings_stored); - - let epoch_settings_stored = store.get_epoch_settings(epoch + 3).await.unwrap(); - assert_eq!(Some(epoch_settings_new.clone()), epoch_settings_stored); - - let epoch_settings_stored = store.get_epoch_settings(epoch + 4).await.unwrap(); assert!(epoch_settings_stored.is_none()); } } diff --git a/mithril-aggregator/tests/create_certificate_follower.rs b/mithril-aggregator/tests/create_certificate_follower.rs index 9f75c791a51..2fea0d9b18e 100644 --- a/mithril-aggregator/tests/create_certificate_follower.rs +++ b/mithril-aggregator/tests/create_certificate_follower.rs @@ -117,6 +117,9 @@ async fn create_certificate_follower() { "create_certificate_follower", ), leader_aggregator_endpoint: Some(leader_aggregator_http_server.url().to_string()), + // Follower must retrieve parameters from the network configuration (today through the leader) + // so this parameters should not be read + protocol_parameters: None, ..leader_configuration }; let mut follower_tester = RuntimeTester::build(start_time_point, follower_configuration).await; From bad67ff1f5e5df3ede5b63d883a13b433c00f3ff Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 5 Nov 2025 17:18:24 +0100 Subject: [PATCH 10/18] chore(aggregator): log when a dependency is built by the dep builder --- mithril-aggregator/src/dependency_injection/builder/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mithril-aggregator/src/dependency_injection/builder/mod.rs b/mithril-aggregator/src/dependency_injection/builder/mod.rs index ef320d9130d..17f54dc39dd 100644 --- a/mithril-aggregator/src/dependency_injection/builder/mod.rs +++ b/mithril-aggregator/src/dependency_injection/builder/mod.rs @@ -82,6 +82,7 @@ macro_rules! get_dependency { ( $self:ident.$attribute:ident = $builder:expr ) => {{ paste::paste! { if $self.$attribute.is_none() { + slog::debug!($self.root_logger(), "Building dependency {}", stringify!($attribute)); $self.$attribute = Some($builder); } From 229c9e70483d060c03263fb6cee2fbfd4595c266 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:09:44 +0100 Subject: [PATCH 11/18] fix(e2e): only update protocol parameters on leader aggregator Since now the follower read the network config from the leader, this means that the update of the protocol parameters is now a responsability of the leader only. This lead to flakiness because this step was restarting all aggregators, and sometimes the follower started before the leader and had a error when it executed its handle discrepencies because the leader http server was still down. --- mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs b/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs index 1a3aa4078a0..04baacad1d0 100644 --- a/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs +++ b/mithril-test-lab/mithril-end-to-end/src/end_to_end_spec.rs @@ -136,7 +136,9 @@ impl Spec { "epoch after which the protocol parameters will change".to_string(), ) .await?; - assertions::update_protocol_parameters(aggregator).await?; + if aggregator.is_first() { + assertions::update_protocol_parameters(aggregator).await?; + } // Wait 6 epochs after protocol parameters update, so that we make sure that we use new protocol parameters as well as new stake distribution a few times target_epoch += 6; From 78ce58b34ce42122a6d7a81f41797467ccda6f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Fri, 7 Nov 2025 15:13:04 +0100 Subject: [PATCH 12/18] feat(aggregator): improve logging when single signature authentication fail --- .../tools/single_signature_authenticator.rs | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/mithril-aggregator/src/tools/single_signature_authenticator.rs b/mithril-aggregator/src/tools/single_signature_authenticator.rs index cbec2451833..db49312dc12 100644 --- a/mithril-aggregator/src/tools/single_signature_authenticator.rs +++ b/mithril-aggregator/src/tools/single_signature_authenticator.rs @@ -40,29 +40,33 @@ impl SingleSignatureAuthenticator { ); true } - Err(_error) => { + Err(current_stake_distribution_error) => { // Signers may detect epoch changes before the aggregator and send // new signatures using the next epoch stake distribution - if self + match self .multi_signer .verify_single_signature_for_next_stake_distribution( 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 + Ok(_) => { + debug!( + self.logger, "Single signature party authenticated for next stake distribution"; + "party_id" => &single_signature.party_id, + ); + true + } + Err(next_stake_distribution_error) => { + debug!( + self.logger, "Single signature party not authenticated"; + "party_id" => &single_signature.party_id, + "current_stake_distribution_error" => ?current_stake_distribution_error, + "next_stake_distribution_error" => ?next_stake_distribution_error, + ); + false + } } } }; From b032862cb119d820be2ee27757e978264426eeb7 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Thu, 6 Nov 2025 11:26:49 +0100 Subject: [PATCH 13/18] feat(e2e): output logs in expandable group when running in github actions --- .../src/utils/formatting.rs | 37 +++++++++++++++++++ .../src/utils/mithril_command.rs | 26 +++++-------- .../mithril-end-to-end/src/utils/mod.rs | 6 +++ 3 files changed, 52 insertions(+), 17 deletions(-) create mode 100644 mithril-test-lab/mithril-end-to-end/src/utils/formatting.rs diff --git a/mithril-test-lab/mithril-end-to-end/src/utils/formatting.rs b/mithril-test-lab/mithril-end-to-end/src/utils/formatting.rs new file mode 100644 index 00000000000..9c069a98c4e --- /dev/null +++ b/mithril-test-lab/mithril-end-to-end/src/utils/formatting.rs @@ -0,0 +1,37 @@ +use crate::utils::is_running_in_github_actions; + +/// Handle printing a log group title and a separator. +/// +/// If running in GitHub actions, the logs produced between the creation of the group and its +/// drop will be wrapped within a `::group::` and `::endgroup::` github action command. +pub struct LogGroup; + +impl LogGroup { + pub fn new(name: &str, title: &str) -> Self { + let group_title = Self::group_title(name, title); + if is_running_in_github_actions() { + println!("::group::{group_title}"); + } + Self::print_header(&group_title); + + Self + } + + fn group_title(name: &str, title: &str) -> String { + format!("{} LOGS - {}:", name.to_uppercase(), title) + } + + fn print_header(title: &str) { + println!("{:-^100}", ""); + println!("{title:^30}",); + println!("{:-^100}", ""); + } +} + +impl Drop for LogGroup { + fn drop(&mut self) { + if is_running_in_github_actions() { + println!("::endgroup::"); + } + } +} diff --git a/mithril-test-lab/mithril-end-to-end/src/utils/mithril_command.rs b/mithril-test-lab/mithril-end-to-end/src/utils/mithril_command.rs index 1823a7d8b5c..bbae3b8df57 100644 --- a/mithril-test-lab/mithril-end-to-end/src/utils/mithril_command.rs +++ b/mithril-test-lab/mithril-end-to-end/src/utils/mithril_command.rs @@ -1,4 +1,4 @@ -use crate::utils::file_utils; +use crate::utils::{LogGroup, file_utils}; use anyhow::{Context, anyhow}; use mithril_common::StdResult; use slog_scope::info; @@ -146,7 +146,10 @@ impl MithrilCommand { )); } - self.print_header(name, &format!("LAST {number_of_line} LINES")); + let _log_group_guard = LogGroup::new( + name.unwrap_or(&self.name), + &format!("LAST {number_of_line} LINES"), + ); println!( "{}", @@ -172,7 +175,10 @@ impl MithrilCommand { )); } - self.print_header(name, &format!("LAST {number_of_error} ERROR(S)")); + let _log_group_guard = LogGroup::new( + name.unwrap_or(&self.name), + &format!("LAST {number_of_error} ERROR(S)"), + ); println!( "{}", @@ -183,18 +189,4 @@ impl MithrilCommand { Ok(()) } - - fn print_header(&self, name: Option<&str>, title: &str) { - let name = match name { - Some(n) => n, - None => &self.name, - }; - - println!("{:-^100}", ""); - println!( - "{:^30}", - format!("{} LOGS - {}:", name.to_uppercase(), title) - ); - println!("{:-^100}", ""); - } } diff --git a/mithril-test-lab/mithril-end-to-end/src/utils/mod.rs b/mithril-test-lab/mithril-end-to-end/src/utils/mod.rs index 90fabb0f39b..acdd6db8084 100644 --- a/mithril-test-lab/mithril-end-to-end/src/utils/mod.rs +++ b/mithril-test-lab/mithril-end-to-end/src/utils/mod.rs @@ -2,6 +2,12 @@ mod mithril_command; #[macro_use] mod spec_utils; mod file_utils; +mod formatting; +pub use formatting::*; pub use mithril_command::MithrilCommand; pub use spec_utils::AttemptResult; + +pub fn is_running_in_github_actions() -> bool { + std::env::var("GITHUB_ACTIONS").is_ok() +} From 6319f63e3c23bb4268cc788873056a000ffb4af6 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 10 Nov 2025 12:28:30 +0100 Subject: [PATCH 14/18] chore(e2e): disable log group in github action until we can figure out how to make the logs group work correctly when the e2e is retry by `nick-fields/retry` (currently the group before the retry are lost) --- mithril-test-lab/mithril-end-to-end/src/utils/formatting.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mithril-test-lab/mithril-end-to-end/src/utils/formatting.rs b/mithril-test-lab/mithril-end-to-end/src/utils/formatting.rs index 9c069a98c4e..8496984f729 100644 --- a/mithril-test-lab/mithril-end-to-end/src/utils/formatting.rs +++ b/mithril-test-lab/mithril-end-to-end/src/utils/formatting.rs @@ -10,7 +10,9 @@ impl LogGroup { pub fn new(name: &str, title: &str) -> Self { let group_title = Self::group_title(name, title); if is_running_in_github_actions() { - println!("::group::{group_title}"); + // Note: Disabled until we can figure out how to make the logs group work correctly when + // the e2e is retry by `nick-fields/retry` (currently the group before the retry are lost) + // println!("::group::{group_title}"); } Self::print_header(&group_title); @@ -31,7 +33,7 @@ impl LogGroup { impl Drop for LogGroup { fn drop(&mut self) { if is_running_in_github_actions() { - println!("::endgroup::"); + // println!("::endgroup::"); } } } From df320c5c896e40dd58cc86eced60964aa49ad0de Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:41:08 +0100 Subject: [PATCH 15/18] feat(aggregator): make epoch service allowed discriminants an intersection between local configuration and network configuration For leader aggregator this does not change anything right now since both value come from the aggregator configuration. For follower this allow them to use a subset of the signed entity types allowed in their leader. --- .../src/services/epoch_service.rs | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/mithril-aggregator/src/services/epoch_service.rs b/mithril-aggregator/src/services/epoch_service.rs index 0268b01137c..81c296c82bd 100644 --- a/mithril-aggregator/src/services/epoch_service.rs +++ b/mithril-aggregator/src/services/epoch_service.rs @@ -353,7 +353,15 @@ impl EpochService for MithrilEpochService { let total_next_stakes_signers = next_signers_with_stake.iter().map(|s| s.stake).sum(); let signed_entity_config = SignedEntityConfig { - allowed_discriminants: self.allowed_signed_entity_discriminants.clone(), + allowed_discriminants: self + .allowed_signed_entity_discriminants + .intersection( + &network_configuration + .configuration_for_aggregation + .enabled_signed_entity_types, + ) + .cloned() + .collect(), cardano_transactions_signing_config: current_epoch_settings .cardano_transactions_signing_config .clone(), @@ -1134,6 +1142,58 @@ mod tests { ); } + #[tokio::test] + async fn inform_epoch_compute_allowed_discriminants_from_intersection_of_aggregation_network_config_and_configured_discriminants() + { + let epoch = Epoch(5); + let allowed_discriminants = BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + SignedEntityTypeDiscriminants::CardanoImmutableFilesFull, + SignedEntityTypeDiscriminants::CardanoTransactions, + ]); + let enabled_discriminants = BTreeSet::from([ + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + SignedEntityTypeDiscriminants::CardanoTransactions, + ]); + + let mut service = MithrilEpochService { + mithril_network_configuration_provider: Arc::new( + FakeMithrilNetworkConfigurationProvider::new( + MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: enabled_discriminants, + ..Dummy::dummy() + }, + MithrilNetworkConfigurationForEpoch::dummy(), + MithrilNetworkConfigurationForEpoch::dummy(), + ), + ), + ..EpochServiceBuilder { + allowed_discriminants: allowed_discriminants.clone(), + ..EpochServiceBuilder::new(epoch, MithrilFixtureBuilder::default().build()) + } + .build() + .await + }; + + service + .inform_epoch(epoch) + .await + .expect("inform_epoch should not fail"); + + let signed_entity_config = service + .signed_entity_config() + .expect("extracting data from service should not fail"); + + assert_eq!( + BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + ]), + signed_entity_config.allowed_discriminants + ); + } + #[tokio::test] async fn compute_data_with_data_from_inform_epoch() { let current_epoch_fixture = MithrilFixtureBuilder::default().with_signers(3).build(); From b5d7048a6dba2ce78463b6afcf489290e2c384c9 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 12 Nov 2025 10:34:56 +0100 Subject: [PATCH 16/18] chore(openapi): prepare removal of `signer_registration_protocol` and `cardano_transactions_signing_config` from `EpochSettingsMessage` - mark both fields as deprecated - make `signer_registration_protocol` optional --- openapi.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openapi.yaml b/openapi.yaml index 07e5fe2c3fc..c5ecd9221aa 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -995,11 +995,11 @@ components: - epoch - current_signers - next_signers - - signer_registration_protocol properties: epoch: $ref: "#/components/schemas/Epoch" signer_registration_protocol: + deprecated: true $ref: "#/components/schemas/ProtocolParameters" current_signers: type: array @@ -1010,6 +1010,7 @@ components: items: $ref: "#/components/schemas/Signer" cardano_transactions_signing_config: + deprecated: true $ref: "#/components/schemas/CardanoTransactionsSigningConfig" examples: - { From c42e7a825d942cc06620444e9d4b7bc5e3187609 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 12 Nov 2025 11:08:50 +0100 Subject: [PATCH 17/18] chore: prepare removal of protocol params & ctx config from `EpochSettingsMessage` This ensures that nodes built with this patch won't fails when thoses fields will be removed. --- .../leader_aggregator_epoch_settings.rs | 10 +-- .../message_adapters/from_epoch_settings.rs | 2 - mithril-aggregator/src/services/message.rs | 6 +- mithril-aggregator/src/test/double/dummies.rs | 19 +----- mithril-common/src/messages/epoch_settings.rs | 65 ++++++++++++++++--- mithril-common/src/test/double/dummies.rs | 4 +- 6 files changed, 63 insertions(+), 43 deletions(-) diff --git a/mithril-aggregator/src/entities/leader_aggregator_epoch_settings.rs b/mithril-aggregator/src/entities/leader_aggregator_epoch_settings.rs index d40530fdc91..fe5b0f18899 100644 --- a/mithril-aggregator/src/entities/leader_aggregator_epoch_settings.rs +++ b/mithril-aggregator/src/entities/leader_aggregator_epoch_settings.rs @@ -1,6 +1,4 @@ -use mithril_common::entities::{ - CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, Signer, -}; +use mithril_common::entities::{Epoch, Signer}; /// LeaderAggregatorEpochSettings represents the settings of an epoch #[derive(Clone, Debug, PartialEq)] @@ -8,15 +6,9 @@ pub struct LeaderAggregatorEpochSettings { /// Current Epoch pub epoch: Epoch, - /// Registration protocol parameters - pub registration_protocol_parameters: ProtocolParameters, - /// Current Signers pub current_signers: Vec, /// Signers that will be able to sign on the next epoch pub next_signers: Vec, - - /// Cardano transactions signing configuration for the current epoch - pub cardano_transactions_signing_config: Option, } diff --git a/mithril-aggregator/src/message_adapters/from_epoch_settings.rs b/mithril-aggregator/src/message_adapters/from_epoch_settings.rs index fe2c0895e02..cd889a6ee92 100644 --- a/mithril-aggregator/src/message_adapters/from_epoch_settings.rs +++ b/mithril-aggregator/src/message_adapters/from_epoch_settings.rs @@ -16,12 +16,10 @@ impl TryFromMessageAdapter fn try_adapt(message: EpochSettingsMessage) -> StdResult { let epoch_settings = LeaderAggregatorEpochSettings { epoch: message.epoch, - registration_protocol_parameters: message.signer_registration_protocol_parameters, current_signers: SignerMessagePart::try_into_signers(message.current_signers) .with_context(|| "'FromMessageAdapter' can not convert the current signers")?, next_signers: SignerMessagePart::try_into_signers(message.next_signers) .with_context(|| "'FromMessageAdapter' can not convert the next signers")?, - cardano_transactions_signing_config: message.cardano_transactions_signing_config, }; Ok(epoch_settings) diff --git a/mithril-aggregator/src/services/message.rs b/mithril-aggregator/src/services/message.rs index 02b5a0c0cb6..a3474554fb4 100644 --- a/mithril-aggregator/src/services/message.rs +++ b/mithril-aggregator/src/services/message.rs @@ -185,7 +185,7 @@ impl MessageService for MithrilMessageService { let epoch_settings_message = EpochSettingsMessage { epoch, - signer_registration_protocol_parameters, + signer_registration_protocol_parameters: Some(signer_registration_protocol_parameters), current_signers: SignerMessagePart::from_signers(current_signers.to_vec()), next_signers: SignerMessagePart::from_signers(next_signers.to_vec()), cardano_transactions_signing_config, @@ -536,7 +536,7 @@ mod tests { assert_eq!(message.epoch, Epoch(4)); assert_eq!( message.signer_registration_protocol_parameters, - ProtocolParameters::new(5, 100, 0.65) + Some(ProtocolParameters::new(5, 100, 0.65)) ); assert_eq!(message.current_signers.len(), 3); assert_eq!(message.next_signers.len(), 3); @@ -620,7 +620,7 @@ mod tests { assert_eq!( message.signer_registration_protocol_parameters, - signer_registration_epoch_settings.protocol_parameters + Some(signer_registration_epoch_settings.protocol_parameters) ); } diff --git a/mithril-aggregator/src/test/double/dummies.rs b/mithril-aggregator/src/test/double/dummies.rs index e2646af9d1a..518251d82b9 100644 --- a/mithril-aggregator/src/test/double/dummies.rs +++ b/mithril-aggregator/src/test/double/dummies.rs @@ -67,28 +67,13 @@ mod entities { impl Dummy for LeaderAggregatorEpochSettings { /// Create a dummy `LeaderAggregatorEpochSettings` fn dummy() -> Self { - // Beacon let beacon = fake_data::beacon(); - - // Registration protocol parameters - let registration_protocol_parameters = fake_data::protocol_parameters(); - - // Signers let signers = fake_data::signers(5); - let current_signers = signers[1..3].to_vec(); - let next_signers = signers[2..5].to_vec(); - // Cardano transactions signing configuration - let cardano_transactions_signing_config = - Some(CardanoTransactionsSigningConfig::dummy()); - - // Signer Epoch settings LeaderAggregatorEpochSettings { epoch: beacon.epoch, - registration_protocol_parameters, - current_signers, - next_signers, - cardano_transactions_signing_config, + current_signers: signers[1..3].to_vec(), + next_signers: signers[2..5].to_vec(), } } } diff --git a/mithril-common/src/messages/epoch_settings.rs b/mithril-common/src/messages/epoch_settings.rs index a1e480046c3..dd72fc30288 100644 --- a/mithril-common/src/messages/epoch_settings.rs +++ b/mithril-common/src/messages/epoch_settings.rs @@ -9,8 +9,11 @@ pub struct EpochSettingsMessage { pub epoch: Epoch, /// Signer Registration Protocol parameters - #[serde(rename = "signer_registration_protocol")] - pub signer_registration_protocol_parameters: ProtocolParameters, + #[serde( + rename = "signer_registration_protocol", + skip_serializing_if = "Option::is_none" + )] + pub signer_registration_protocol_parameters: Option, /// Current Signers pub current_signers: Vec, @@ -25,7 +28,6 @@ pub struct EpochSettingsMessage { #[cfg(test)] mod tests { - use crate::entities::BlockNumber; use super::*; @@ -56,21 +58,27 @@ mod tests { #[derive(Debug, Serialize, Deserialize, PartialEq)] struct EpochSettingsMessageUntilV0_1_51 { pub epoch: Epoch, - #[serde(rename = "signer_registration_protocol")] pub signer_registration_protocol_parameters: ProtocolParameters, - pub current_signers: Vec, - pub next_signers: Vec, - #[serde(skip_serializing_if = "Option::is_none")] pub cardano_transactions_signing_config: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub next_cardano_transactions_signing_config: Option, } + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] + pub struct EpochSettingsMessageUntilV0_1_55 { + pub epoch: Epoch, + #[serde(rename = "signer_registration_protocol")] + pub signer_registration_protocol_parameters: ProtocolParameters, + pub current_signers: Vec, + pub next_signers: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub cardano_transactions_signing_config: Option, + } + fn golden_message_until_open_api_0_1_51() -> EpochSettingsMessageUntilV0_1_51 { EpochSettingsMessageUntilV0_1_51 { epoch: Epoch(10), @@ -101,8 +109,8 @@ mod tests { } } - fn golden_current_message() -> EpochSettingsMessage { - EpochSettingsMessage { + fn golden_message_until_open_api_0_1_55() -> EpochSettingsMessageUntilV0_1_55 { + EpochSettingsMessageUntilV0_1_55 { epoch: Epoch(10), signer_registration_protocol_parameters: ProtocolParameters { k: 500, @@ -130,6 +138,35 @@ mod tests { } } + fn golden_current_message() -> EpochSettingsMessage { + EpochSettingsMessage { + epoch: Epoch(10), + signer_registration_protocol_parameters: Some(ProtocolParameters { + k: 500, + m: 10000, + phi_f: 0.65, + }), + current_signers: vec![SignerMessagePart { + party_id: "123".to_string(), + verification_key: "key_123".to_string(), + verification_key_signature: Some("signature_123".to_string()), + operational_certificate: Some("certificate_123".to_string()), + kes_period: Some(12), + }], + next_signers: vec![SignerMessagePart { + party_id: "456".to_string(), + verification_key: "key_456".to_string(), + verification_key_signature: Some("signature_456".to_string()), + operational_certificate: Some("certificate_456".to_string()), + kes_period: Some(45), + }], + cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(70), + step: BlockNumber(20), + }), + } + } + #[test] fn test_current_json_deserialized_into_message_supported_until_open_api_0_1_51() { let json = CURRENT_JSON; @@ -138,6 +175,14 @@ mod tests { assert_eq!(golden_message_until_open_api_0_1_51(), message); } + #[test] + fn test_current_json_deserialized_into_message_supported_until_open_api_0_1_55() { + let json = CURRENT_JSON; + let message: EpochSettingsMessageUntilV0_1_55 = serde_json::from_str(json).unwrap(); + + assert_eq!(golden_message_until_open_api_0_1_55(), message); + } + #[test] fn test_current_json_deserialized_into_current_message() { let json = CURRENT_JSON; diff --git a/mithril-common/src/test/double/dummies.rs b/mithril-common/src/test/double/dummies.rs index 93878fedc73..3321b0f0913 100644 --- a/mithril-common/src/test/double/dummies.rs +++ b/mithril-common/src/test/double/dummies.rs @@ -408,11 +408,11 @@ mod messages { fn dummy() -> Self { Self { epoch: Epoch(10), - signer_registration_protocol_parameters: ProtocolParameters { + signer_registration_protocol_parameters: Some(ProtocolParameters { k: 5, m: 100, phi_f: 0.65, - }, + }), current_signers: [SignerMessagePart::dummy()].to_vec(), next_signers: [SignerMessagePart::dummy()].to_vec(), cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig::dummy()), From 3f771a3e17e11593edbf631513f6f08e5d234963 Mon Sep 17 00:00:00 2001 From: DJO <790521+Alenar@users.noreply.github.com> Date: Wed, 12 Nov 2025 11:20:58 +0100 Subject: [PATCH 18/18] chore(common): deprecate protocol params & ctx config fields in `EpochSettingsMessage` --- mithril-aggregator/src/services/message.rs | 2 ++ mithril-common/src/messages/epoch_settings.rs | 9 +++++++++ mithril-common/src/test/double/dummies.rs | 1 + 3 files changed, 12 insertions(+) diff --git a/mithril-aggregator/src/services/message.rs b/mithril-aggregator/src/services/message.rs index a3474554fb4..e5ec7cf46e0 100644 --- a/mithril-aggregator/src/services/message.rs +++ b/mithril-aggregator/src/services/message.rs @@ -183,6 +183,7 @@ impl MessageService for MithrilMessageService { .transpose()? .cloned(); + #[allow(deprecated)] let epoch_settings_message = EpochSettingsMessage { epoch, signer_registration_protocol_parameters: Some(signer_registration_protocol_parameters), @@ -509,6 +510,7 @@ mod tests { } } + #[allow(deprecated)] mod epoch_settings { use mithril_common::{ entities::{CardanoTransactionsSigningConfig, ProtocolParameters}, diff --git a/mithril-common/src/messages/epoch_settings.rs b/mithril-common/src/messages/epoch_settings.rs index dd72fc30288..bd81c31668c 100644 --- a/mithril-common/src/messages/epoch_settings.rs +++ b/mithril-common/src/messages/epoch_settings.rs @@ -9,6 +9,10 @@ pub struct EpochSettingsMessage { pub epoch: Epoch, /// Signer Registration Protocol parameters + #[deprecated( + since = "0.6.27", + note = "Will be removed soon. use `mithril_protocol_config` crate instead." + )] #[serde( rename = "signer_registration_protocol", skip_serializing_if = "Option::is_none" @@ -22,6 +26,10 @@ pub struct EpochSettingsMessage { pub next_signers: Vec, /// Cardano transactions signing configuration for the current epoch + #[deprecated( + since = "0.6.27", + note = "Will be removed soon. use `mithril_protocol_config` crate instead." + )] #[serde(skip_serializing_if = "Option::is_none")] pub cardano_transactions_signing_config: Option, } @@ -139,6 +147,7 @@ mod tests { } fn golden_current_message() -> EpochSettingsMessage { + #[allow(deprecated)] EpochSettingsMessage { epoch: Epoch(10), signer_registration_protocol_parameters: Some(ProtocolParameters { diff --git a/mithril-common/src/test/double/dummies.rs b/mithril-common/src/test/double/dummies.rs index 3321b0f0913..4c5c95d9165 100644 --- a/mithril-common/src/test/double/dummies.rs +++ b/mithril-common/src/test/double/dummies.rs @@ -406,6 +406,7 @@ mod messages { impl Dummy for EpochSettingsMessage { /// Return a dummy [EpochSettingsMessage] (test-only). fn dummy() -> Self { + #[allow(deprecated)] Self { epoch: Epoch(10), signer_registration_protocol_parameters: Some(ProtocolParameters {