From 0e607335a22c56f16661f12ce9bc00af1dcd76ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Wed, 15 Jul 2026 16:56:39 +0200 Subject: [PATCH 01/14] feature(aggregator): init new import/export commands for protocol configuration --- mithril-aggregator/src/commands/mod.rs | 1 + .../protocol_configuration_command.rs | 85 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 mithril-aggregator/src/commands/protocol_configuration_command.rs diff --git a/mithril-aggregator/src/commands/mod.rs b/mithril-aggregator/src/commands/mod.rs index 66694801ad8..9452017dfcb 100644 --- a/mithril-aggregator/src/commands/mod.rs +++ b/mithril-aggregator/src/commands/mod.rs @@ -2,6 +2,7 @@ mod config_association; mod database_command; mod era_command; mod genesis_command; +mod protocol_configuration_command; mod serve_command; mod tools_command; diff --git a/mithril-aggregator/src/commands/protocol_configuration_command.rs b/mithril-aggregator/src/commands/protocol_configuration_command.rs new file mode 100644 index 00000000000..feb5e1abb55 --- /dev/null +++ b/mithril-aggregator/src/commands/protocol_configuration_command.rs @@ -0,0 +1,85 @@ +use std::path::PathBuf; + +use clap::{Parser, Subcommand}; +use config::{ConfigBuilder, builder::DefaultState}; +use mithril_common::StdResult; +use slog::Logger; + +pub struct ProtocolConfigurationParametersConfiguration {} + +#[derive(Parser, Debug, Clone)] +pub struct ProtocolConfigurationCommand { + /// commands + #[clap(subcommand)] + pub protocol_configuration_sub_command: ProtocolConfigurationSubCommand, +} + +impl ProtocolConfigurationCommand { + pub async fn execute( + &self, + root_logger: Logger, + config_builder: ConfigBuilder, + ) -> StdResult<()> { + self.protocol_configuration_sub_command + .execute(root_logger, config_builder) + .await + } +} + +#[derive(Debug, Clone, Subcommand)] +pub enum ProtocolConfigurationSubCommand { + /// Protocol configuration export command. + Export(ExportProtocolConfigurationSubCommand), + + /// Protocol configuration import command. + Import(ImportProtocolConfigurationSubCommand), +} + +impl ProtocolConfigurationSubCommand { + pub async fn execute( + &self, + root_logger: Logger, + config_builder: ConfigBuilder, + ) -> StdResult<()> { + match self { + Self::Export(cmd) => cmd.execute(root_logger, config_builder).await, + Self::Import(cmd) => cmd.execute(root_logger, config_builder).await, + } + } +} + +/// Protocol configuration export command +#[derive(Parser, Debug, Clone)] +pub struct ExportProtocolConfigurationSubCommand { + /// Target path + #[clap(long)] + target_path: PathBuf, +} + +impl ExportProtocolConfigurationSubCommand { + pub async fn execute( + &self, + root_logger: Logger, + config_builder: ConfigBuilder, + ) -> StdResult<()> { + Ok(()) + } +} + +/// Protocol configuration import command +#[derive(Parser, Debug, Clone)] +pub struct ImportProtocolConfigurationSubCommand { + /// Import path + #[clap(long, value_parser)] + pub import_path: PathBuf, +} + +impl ImportProtocolConfigurationSubCommand { + pub async fn execute( + &self, + root_logger: Logger, + config_builder: ConfigBuilder, + ) -> StdResult<()> { + Ok(()) + } +} From 5495a4030aefe5148663da2c5da901be7ca25d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Wed, 15 Jul 2026 18:30:07 +0200 Subject: [PATCH 02/14] feature(aggregator): init protocol configuration verifier for generate datum command --- mithril-aggregator/src/commands/mod.rs | 7 + .../protocol_configuration_command.rs | 157 +++++++++++++++++- mithril-aggregator/src/test/double/dummies.rs | 29 +++- ...human_readable_protocol_configuration.json | 43 +++++ 4 files changed, 227 insertions(+), 9 deletions(-) create mode 100644 mithril-aggregator/tests/human_readable_protocol_configuration.json diff --git a/mithril-aggregator/src/commands/mod.rs b/mithril-aggregator/src/commands/mod.rs index 9452017dfcb..4b666df6d6c 100644 --- a/mithril-aggregator/src/commands/mod.rs +++ b/mithril-aggregator/src/commands/mod.rs @@ -15,6 +15,8 @@ use mithril_doc::{Documenter, GenerateDocCommands, StructDoc}; use slog::{Level, Logger, debug}; use std::{collections::HashMap, path::PathBuf}; +pub use protocol_configuration_command::*; + use crate::{DefaultConfiguration, extract_all}; /// Main command selector @@ -25,6 +27,7 @@ pub enum MainCommand { Serve(serve_command::ServeCommand), Tools(tools_command::ToolsCommand), Database(database_command::DatabaseCommand), + ProtocolConfiguration(protocol_configuration_command::ProtocolConfigurationCommand), #[clap(alias("doc"), hide(true))] GenerateDoc(GenerateDocCommands), } @@ -49,6 +52,7 @@ impl MainCommand { Self::Serve(cmd) => cmd.execute(root_logger, config_builder).await, Self::Tools(cmd) => cmd.execute(root_logger, config_builder).await, Self::Database(cmd) => cmd.execute(root_logger, config_builder).await, + Self::ProtocolConfiguration(cmd) => cmd.execute(root_logger, config_builder).await, Self::GenerateDoc(cmd) => { let commands_configs = Self::extract_config(Self::format_crate_name_to_config_key()); @@ -68,6 +72,8 @@ impl MainCommand { Genesis = { genesis_command::GenesisCommand }, Serve = { serve_command::ServeCommand }, Tools = { tools_command::ToolsCommand }, + ProtocolConfiguration = + { protocol_configuration_command::ProtocolConfigurationCommand }, GenerateDoc = {}, ) } @@ -83,6 +89,7 @@ impl MainCommand { MainCommand::Era(_) => CommandType::CommandLine, MainCommand::Tools(_) => CommandType::CommandLine, MainCommand::Database(_) => CommandType::CommandLine, + MainCommand::ProtocolConfiguration(_) => CommandType::CommandLine, MainCommand::GenerateDoc(_) => CommandType::CommandLine, } } diff --git a/mithril-aggregator/src/commands/protocol_configuration_command.rs b/mithril-aggregator/src/commands/protocol_configuration_command.rs index feb5e1abb55..a60245b5595 100644 --- a/mithril-aggregator/src/commands/protocol_configuration_command.rs +++ b/mithril-aggregator/src/commands/protocol_configuration_command.rs @@ -1,12 +1,49 @@ -use std::path::PathBuf; - use clap::{Parser, Subcommand}; use config::{ConfigBuilder, builder::DefaultState}; -use mithril_common::StdResult; +use serde::{Deserialize, Serialize}; use slog::Logger; +use std::collections::{BTreeSet, HashMap}; +use std::fs; +use std::path::PathBuf; + +use mithril_common::StdResult; +use mithril_common::entities::{ + CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, Epoch, + ProtocolParameters, SignedEntityTypeDiscriminants, +}; +use mithril_doc::StructDoc; + +use crate::extract_all; pub struct ProtocolConfigurationParametersConfiguration {} +#[derive(Serialize, Deserialize)] +pub struct HumanReadableProtocolConfiguration { + pub epoch: Epoch, + pub protocol_parameters: ProtocolParameters, + pub cardano_transaction_signing_config: Option, + pub cardano_blocks_transactions_signing_config: Option, + pub enabled_signed_entity_types: BTreeSet, +} + +impl HumanReadableProtocolConfiguration { + pub fn new( + epoch: Epoch, + protocol_parameters: ProtocolParameters, + cardano_transaction_signing_config: Option, + cardano_blocks_transactions_signing_config: Option, + enabled_signed_entity_types: BTreeSet, + ) -> Self { + HumanReadableProtocolConfiguration { + epoch, + protocol_parameters, + cardano_transaction_signing_config, + cardano_blocks_transactions_signing_config, + enabled_signed_entity_types, + } + } +} + #[derive(Parser, Debug, Clone)] pub struct ProtocolConfigurationCommand { /// commands @@ -24,15 +61,24 @@ impl ProtocolConfigurationCommand { .execute(root_logger, config_builder) .await } + + pub fn extract_config(command_path: String) -> HashMap { + extract_all!( + command_path, + ProtocolConfigurationSubCommand, + ExportMarkers = { ExportProtocolConfigurationSubCommand }, + ImportMarkers = { ImportProtocolConfigurationSubCommand }, + ) + } } #[derive(Debug, Clone, Subcommand)] pub enum ProtocolConfigurationSubCommand { /// Protocol configuration export command. - Export(ExportProtocolConfigurationSubCommand), + ExportMarkers(ExportProtocolConfigurationSubCommand), /// Protocol configuration import command. - Import(ImportProtocolConfigurationSubCommand), + ImportMarkers(ImportProtocolConfigurationSubCommand), } impl ProtocolConfigurationSubCommand { @@ -42,8 +88,8 @@ impl ProtocolConfigurationSubCommand { config_builder: ConfigBuilder, ) -> StdResult<()> { match self { - Self::Export(cmd) => cmd.execute(root_logger, config_builder).await, - Self::Import(cmd) => cmd.execute(root_logger, config_builder).await, + Self::ExportMarkers(cmd) => cmd.execute(root_logger, config_builder).await, + Self::ImportMarkers(cmd) => cmd.execute(root_logger, config_builder).await, } } } @@ -64,6 +110,10 @@ impl ExportProtocolConfigurationSubCommand { ) -> StdResult<()> { Ok(()) } + + pub fn extract_config(_parent: String) -> HashMap { + HashMap::new() + } } /// Protocol configuration import command @@ -71,7 +121,7 @@ impl ExportProtocolConfigurationSubCommand { pub struct ImportProtocolConfigurationSubCommand { /// Import path #[clap(long, value_parser)] - pub import_path: PathBuf, + pub path: PathBuf, } impl ImportProtocolConfigurationSubCommand { @@ -80,6 +130,97 @@ impl ImportProtocolConfigurationSubCommand { root_logger: Logger, config_builder: ConfigBuilder, ) -> StdResult<()> { + //1 - we need to read the protocol configuration from the file + let json_protocol_configurations = fs::read_to_string(&self.path); + + //2 - we need to parse the json into a protocol configuration using serde_json + let protocol_configurations: Vec = + serde_json::from_str(&json_protocol_configurations?)?; + + //3 - Verify protocol config consistency + println!("Verifying protocol configuration consistency..."); + match Self::verify_protocol_configurations(protocol_configurations) { + Ok(_) => Ok(()), + Err(e) => Err(anyhow::anyhow!( + "Protocol configuration is not consistent: {}", + e + )), + } + + //4 - Cbor conversion + + //5 - check size < 10kb + //6 - Generate Tx datum + } + + pub fn verify_protocol_configurations( + configurations: Vec, + ) -> StdResult<()> { + //TODO verify non zero protocol parameters (other non zero attributes ?) + for config in configurations { + if config.protocol_parameters.k == 0 + || config.protocol_parameters.m == 0 + || config.protocol_parameters.phi_f == 0.0 + { + return Err(anyhow::anyhow!( + "Protocol parameters must be non-zero: {:?}", + config.protocol_parameters + )); + } + } + //TODO verify epoch consistency (no epoch smaller than latest export ?) + //TODO verify that if CardanoBlocksSigningConfig is filled that CardanoBlocksTransactions is in available signed entity types Ok(()) } + + pub fn extract_config(_parent: String) -> HashMap { + HashMap::new() + } +} + +#[cfg(test)] +mod tests { + use mithril_common::{entities::ProtocolParameters, test::double::Dummy}; + + use super::*; + + #[test] + fn test_verify_protocol_configurations_should_throw_error_with_protocol_parameter_at_zero() { + let protocol_parameters_list_to_test = [ + ProtocolParameters::new(0, 1, 0.123), + ProtocolParameters::new(1, 0, 0.123), + ProtocolParameters::new(1, 1, 0.0), + ]; + + for protocol_parameters in protocol_parameters_list_to_test { + let configurations = vec![HumanReadableProtocolConfiguration { + protocol_parameters: protocol_parameters.clone(), + ..Dummy::dummy() + }]; + + let result = ImportProtocolConfigurationSubCommand::verify_protocol_configurations( + configurations, + ); + + assert_eq!( + result.unwrap_err().to_string(), + format!( + "Protocol parameters must be non-zero: {:?}", + protocol_parameters + ) + ); + } + } + + #[test] + fn import_subcommand_parses_flag() { + ImportProtocolConfigurationSubCommand::try_parse_from([ + "import-markers", + "--path", + "tests/human_readable_protocol_configuration.json", + ]) + .expect("CLI parse should succeed"); + // assert_eq!(cmd.mithril_era, Some(SupportedEra::Lagrange)); + //TODO when available check that a tx datum file is written + } } diff --git a/mithril-aggregator/src/test/double/dummies.rs b/mithril-aggregator/src/test/double/dummies.rs index 73161f6c45e..2ca6384579d 100644 --- a/mithril-aggregator/src/test/double/dummies.rs +++ b/mithril-aggregator/src/test/double/dummies.rs @@ -1,7 +1,17 @@ +use std::collections::BTreeSet; + use chrono::Utc; use uuid::Uuid; -use mithril_common::test::double::{Dummy, fake_data}; +use mithril_common::{ + entities::{ + CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, Epoch, + ProtocolParameters, SignedEntityTypeDiscriminants, + }, + test::double::{Dummy, fake_data}, +}; + +use crate::commands::HumanReadableProtocolConfiguration; mod record { use mithril_common::entities::{ProtocolMessage, SignedEntityType}; @@ -101,3 +111,20 @@ mod entities { } } } + +impl Dummy for HumanReadableProtocolConfiguration { + fn dummy() -> Self { + HumanReadableProtocolConfiguration::new( + Epoch(42), + ProtocolParameters::new(10, 20, 0.123), + Some(CardanoTransactionsSigningConfig::dummy()), + Some(CardanoBlocksTransactionsSigningConfig::dummy()), + BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, + ]), + ) + } +} diff --git a/mithril-aggregator/tests/human_readable_protocol_configuration.json b/mithril-aggregator/tests/human_readable_protocol_configuration.json new file mode 100644 index 00000000000..f51a88685b8 --- /dev/null +++ b/mithril-aggregator/tests/human_readable_protocol_configuration.json @@ -0,0 +1,43 @@ +[ + { + "epoch": 42, + "protocol_parameters": { + "k": 9, + "m": 77, + "phi_f": 0.5 + }, + "cardano_transactions_signing_config": { + "security_parameter": 100, + "step": 10 + }, + "enabled_signed_entity_types": + [ + "MithrilStakeDistribution", + "CardanoDatabase", + "CardanoTransactions" + ] + }, + { + "epoch": 43, + "protocol_parameters": { + "k": 10, + "m": 88, + "phi_f": 0.7 + }, + "cardano_transactions_signing_config": { + "security_parameter": 100, + "step": 10 + }, + "cardano_blocks_transactions_signing_config": { + "security_parameter": 150, + "step": 20 + }, + "enabled_signed_entity_types": + [ + "MithrilStakeDistribution", + "CardanoDatabase", + "CardanoTransactions", + "CardanoBlocksTransactions" + ] + } +] \ No newline at end of file From 6da0ad4cd08a0e2ada1f8d63353c7b03eab0eeac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Fri, 17 Jul 2026 18:17:42 +0200 Subject: [PATCH 03/14] feature(aggregator, protocol-config, common): implementing CBOR conversion, signature and generating tx datum for protocol configuration command --- Cargo.lock | 2 + internal/mithril-protocol-config/Cargo.toml | 4 +- .../src/adapters/cardano_chain.rs | 84 ++++++++ .../src/adapters/mod.rs | 5 + internal/mithril-protocol-config/src/lib.rs | 4 + internal/mithril-protocol-config/src/model.rs | 6 +- .../src/protocol_configuration_reader.rs | 183 ++++++++++++++++++ .../src/test/double/dummies.rs | 25 ++- .../protocol_configuration_command.rs | 96 +++++++-- mithril-aggregator/src/tools/mod.rs | 2 + .../src/tools/protocol_configuration.rs | 150 ++++++++++++++ .../src/crypto_helper/ed25519_alias.rs | 9 + mithril-common/src/entities/type_alias.rs | 3 + 13 files changed, 547 insertions(+), 26 deletions(-) create mode 100644 internal/mithril-protocol-config/src/adapters/cardano_chain.rs create mode 100644 internal/mithril-protocol-config/src/adapters/mod.rs create mode 100644 internal/mithril-protocol-config/src/protocol_configuration_reader.rs create mode 100644 mithril-aggregator/src/tools/protocol_configuration.rs diff --git a/Cargo.lock b/Cargo.lock index 875670d0ab9..3c42c57db0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4600,10 +4600,12 @@ dependencies = [ "httpmock", "mithril-aggregator-client", "mithril-common", + "serde", "serde_json", "slog", "slog-async", "slog-term", + "thiserror 2.0.18", "tokio", ] diff --git a/internal/mithril-protocol-config/Cargo.toml b/internal/mithril-protocol-config/Cargo.toml index d987c9f3437..f5af85c6104 100644 --- a/internal/mithril-protocol-config/Cargo.toml +++ b/internal/mithril-protocol-config/Cargo.toml @@ -14,11 +14,13 @@ anyhow = { workspace = true } async-trait = { workspace = true } mithril-aggregator-client = { path = "../mithril-aggregator-client" } mithril-common = { path = "../../mithril-common" } +serde = { workspace = true } +serde_json = { workspace = true } slog = { workspace = true } +thiserror = { workspace = true } tokio = { workspace = true } [dev-dependencies] httpmock = "0.8.3" -serde_json = { workspace = true } slog-async = { workspace = true } slog-term = { workspace = true } diff --git a/internal/mithril-protocol-config/src/adapters/cardano_chain.rs b/internal/mithril-protocol-config/src/adapters/cardano_chain.rs new file mode 100644 index 00000000000..5a47dc16f43 --- /dev/null +++ b/internal/mithril-protocol-config/src/adapters/cardano_chain.rs @@ -0,0 +1,84 @@ +use anyhow::Context; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use mithril_common::crypto_helper::{ + ProtocolConfigurationMarkersSigner, ProtocolConfigurationMarkersVerifierSignature, + key_encode_hex, +}; +use mithril_common::{StdError, StdResult}; + +use crate::ProtocolConfigurationMarker; + +/// [ProtocolConfigurationMarkersPayload] related errors. +#[derive(Debug, Error)] +pub enum ProtocolConfigurationMarkersPayloadError { + /// Error raised when the message serialization fails + #[error("could not serialize message")] + SerializeMessage(#[source] StdError), + + /// Error raised when the signature deserialization fails + #[error("could not deserialize signature")] + DeserializeSignature(#[source] StdError), + + /// Error raised when the signature is missing + #[error("could not verify signature: signature is missing")] + MissingSignature, + + /// Error raised when the signature is invalid + #[error("could not verify signature")] + VerifySignature(#[source] StdError), + + /// Error raised when the signing the markers + #[error("could not create signature")] + CreateSignature(#[source] StdError), +} + +/// Protocol Configuration markers payload +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ProtocolConfigurationMarkersPayload { + /// List of protocol configuration markers + pub markers: Vec, + + /// Protocol Configuration markers signature + pub signature: Option, +} + +//TODO a SignedProtocolConfigurationMarkersPayload with non optional signature + +impl ProtocolConfigurationMarkersPayload { + /// Instanciate a new ProtocolConfigurationMarkersPayload with markers + pub fn new(protocol_configuration_markers: Vec) -> Self { + Self { + markers: protocol_configuration_markers, + signature: None, + } + } + + fn message_to_bytes(&self) -> Result, ProtocolConfigurationMarkersPayloadError> { + serde_json::to_vec(&self.markers) + .map_err(|e| ProtocolConfigurationMarkersPayloadError::SerializeMessage(e.into())) + } + + /// Encode this payload to a json hex string + pub fn to_json_hex(&self) -> StdResult { + key_encode_hex(self) + .with_context(|| "protocol configuration markers payload could not be json hex encoded") + } + + /// Sign an protocol configuration markers payload + pub fn sign( + self, + signer: &ProtocolConfigurationMarkersSigner, + ) -> Result { + let signature = + signer.sign(&self.message_to_bytes().map_err(|e| { + ProtocolConfigurationMarkersPayloadError::CreateSignature(e.into()) + })?); + + Ok(Self { + markers: self.markers, + signature: Some(signature), + }) + } +} diff --git a/internal/mithril-protocol-config/src/adapters/mod.rs b/internal/mithril-protocol-config/src/adapters/mod.rs new file mode 100644 index 00000000000..e9f987cfb60 --- /dev/null +++ b/internal/mithril-protocol-config/src/adapters/mod.rs @@ -0,0 +1,5 @@ +//! Module dedicated to ProtocolConfigurationReaderAdapter implementations. + +mod cardano_chain; + +pub use cardano_chain::ProtocolConfigurationMarkersPayload as ProtocolConfigurationMarkersPayloadCardanoChain; diff --git a/internal/mithril-protocol-config/src/lib.rs b/internal/mithril-protocol-config/src/lib.rs index b03e1691ad0..b892b0db47c 100644 --- a/internal/mithril-protocol-config/src/lib.rs +++ b/internal/mithril-protocol-config/src/lib.rs @@ -1,7 +1,11 @@ #![warn(missing_docs)] //! This crate provides mechanisms to read and check the configuration parameters of a Mithril network. +pub mod adapters; pub mod http; pub mod interface; pub mod model; +mod protocol_configuration_reader; pub mod test; + +pub use protocol_configuration_reader::*; diff --git a/internal/mithril-protocol-config/src/model.rs b/internal/mithril-protocol-config/src/model.rs index 6c898fb4e9a..f9326687b22 100644 --- a/internal/mithril-protocol-config/src/model.rs +++ b/internal/mithril-protocol-config/src/model.rs @@ -1,5 +1,6 @@ //! Model definitions for Mithril Protocol Configuration. +use serde::{Deserialize, Serialize}; use std::collections::BTreeSet; use mithril_common::{ @@ -10,7 +11,7 @@ use mithril_common::{ messages::{ProtocolConfigurationMessage, SignedEntityTypeDiscriminantsMessage}, }; -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] /// Custom configuration for the signed entity types pub struct SignedEntityTypeConfiguration { @@ -38,7 +39,7 @@ pub struct MithrilNetworkConfiguration { } //A epoch configuration -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] /// A network configuration available for an epoch pub struct MithrilNetworkConfigurationForEpoch { /// Cryptographic protocol parameters (`k`, `m` and `phi_f`) @@ -66,7 +67,6 @@ impl From for MithrilNetworkConfigurationForEpoch } } } - #[cfg(test)] mod tests { use mithril_common::messages::{ diff --git a/internal/mithril-protocol-config/src/protocol_configuration_reader.rs b/internal/mithril-protocol-config/src/protocol_configuration_reader.rs new file mode 100644 index 00000000000..f426bec3c25 --- /dev/null +++ b/internal/mithril-protocol-config/src/protocol_configuration_reader.rs @@ -0,0 +1,183 @@ +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeSet, HashMap}; +use std::sync::Arc; +use thiserror::Error; + +use mithril_common::crypto_helper::{CodecParseError, SerDeShelleyFileFormat}; +use mithril_common::entities::{ + CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, Epoch, + ProtocolParameters, SignedEntityTypeDiscriminants, +}; +use mithril_common::{StdError, StdResult}; + +/// The cbor representation of a MithrilNetworkConfigurationForEpoch +pub type CborProtocolConfigurationForEpoch = String; + +/// Value object that represents a tag of Protocol Configuration. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ProtocolConfigurationMarker { + /// Epoch + pub epoch: Epoch, + + /// Protocol parameters + pub configuration: CborProtocolConfigurationForEpoch, +} + +impl ProtocolConfigurationMarker { + /// instantiate a new [ProtocolConfigurationMarker]. + pub fn new(epoch: Epoch, protocol_configuration: CborProtocolConfigurationForEpoch) -> Self { + ProtocolConfigurationMarker { + epoch, + configuration: protocol_configuration, + } + } +} + +//A epoch configuration used by the reader and for the CBOR representation in the ProtocolConfigurationMarker +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] +/// A network configuration available for an epoch +pub struct ProtocolConfigurationForEpoch { + /// Cryptographic protocol parameters (`k`, `m` and `phi_f`) + pub protocol_parameters: ProtocolParameters, + + /// List of available types of certifications + pub enabled_signed_entity_types: BTreeSet, + + //TODO do we want a sub struct like signed_entity_types_config in model ? + /// Signing configuration for Cardano transactions + pub cardano_transactions: Option, + + /// Signing configuration for Cardano blocks and transactions + pub cardano_blocks_transactions: Option, +} + +impl ProtocolConfigurationForEpoch { + /// Serialize the ProtocolConfigurationForEpoch to a CBOR hex representation + pub fn to_cbor(&self) -> Result { + self.to_cbor_hex() + } + + /// Deserialize the ProtocolConfigurationForEpoch from a CBOR hex representation + pub fn from_cbor(cbor: CborProtocolConfigurationForEpoch) -> Result { + Self::from_cbor_hex(&cbor) + } +} + +impl SerDeShelleyFileFormat for ProtocolConfigurationForEpoch { + const TYPE: &'static str = "ProtocolConfigurationForEpoch"; + const DESCRIPTION: &'static str = ""; +} + +/// Adapters are responsible of technically reading the information of +/// [ProtocolConfigurationMarker]s from a backend. +#[async_trait] +pub trait ProtocolConfigurationReaderAdapter: Sync + Send { + /// Read protocol configuration markers from the underlying adapter. + async fn read(&self) -> StdResult>; +} + +/// The ProtocolConfigurationReader is responsible of giving the current Protocol Configurations +/// It uses an [ProtocolConfigurationReaderAdapter] to read data from a backend. +pub struct ProtocolConfigurationReader { + adapter: Arc, +} + +/// Error type when [ProtocolConfigurationReader] fails to return a [todo]. +#[derive(Debug, Error)] +pub enum ProtocolConfigurationReaderError { + /// Underlying adapter fails to return data. + #[error("Adapter Error message: «{message}»")] + AdapterFailure { + /// context message + message: String, + + /// nested underlying adapter error + #[source] + error: StdError, + }, +} + +impl ProtocolConfigurationReader { + /// Instantiate a new [ProtocolConfigurationReader] with an [ProtocolConfigurationReaderAdapter]. + pub fn new(adapter: Arc) -> Self { + Self { adapter } + } + + /// Read protocol configuration markers from the underlying adapter. + pub async fn read_mithril_network_configurations( + &self, + ) -> Result, ProtocolConfigurationReaderError> + { + let markers = self.adapter.read().await.map_err(|e| { + ProtocolConfigurationReaderError::AdapterFailure { + message: "Failed to read protocol configuration markers from adapter".to_string(), + error: e, + } + })?; + + let mut mithril_network_configurations = HashMap::new(); + for marker in markers { + let configuration = ProtocolConfigurationForEpoch::from_cbor(marker.configuration) + .map_err(|e| ProtocolConfigurationReaderError::AdapterFailure { + message: format!( + "Failed to parse protocol configuration for epoch {}", + marker.epoch + ), + error: e.into(), + })?; + mithril_network_configurations.insert(marker.epoch, configuration); + } + Ok(mithril_network_configurations) + } +} + +#[cfg(test)] +mod tests { + use mithril_common::{ + entities::{BlockNumber, BlockNumberOffset}, + test::double::Dummy, + }; + + use super::*; + + #[test] + fn to_cbor_from_cbor_conversion() { + let mithril_network_configuration_for_epoch = ProtocolConfigurationForEpoch::dummy(); + let cbor = mithril_network_configuration_for_epoch.to_cbor().unwrap(); + let mithril_network_configuration_for_epoch_from_cbor = + ProtocolConfigurationForEpoch::from_cbor(cbor.clone()).unwrap(); + assert_eq!( + mithril_network_configuration_for_epoch, + mithril_network_configuration_for_epoch_from_cbor + ); + } + + #[test] + fn cbor_golden_test() { + let expected_cbor = "a47370726f746f636f6c5f706172616d6574657273a3616b01616d02657068695f66fb4002666666666666781b656e61626c65645f7369676e65645f656e746974795f747970657384781843617264616e6f5374616b65446973747269627574696f6e6f43617264616e6f44617461626173657343617264616e6f5472616e73616374696f6e73781943617264616e6f426c6f636b735472616e73616374696f6e737463617264616e6f5f7472616e73616374696f6e73a27273656375726974795f706172616d657465720a647374657014781b63617264616e6f5f626c6f636b735f7472616e73616374696f6e73a27273656375726974795f706172616d65746572181e64737465701828"; + let mithril_network_configuration_for_epoch = ProtocolConfigurationForEpoch { + protocol_parameters: ProtocolParameters { + k: 1, + m: 2, + phi_f: 2.3, + }, + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + ]), + cardano_transactions: Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumberOffset(10), + step: BlockNumber(20), + }), + cardano_blocks_transactions: Some(CardanoBlocksTransactionsSigningConfig { + security_parameter: BlockNumberOffset(30), + step: BlockNumber(40), + }), + }; + let cbor = mithril_network_configuration_for_epoch.to_cbor().unwrap(); + assert_eq!(&cbor, expected_cbor); + } +} diff --git a/internal/mithril-protocol-config/src/test/double/dummies.rs b/internal/mithril-protocol-config/src/test/double/dummies.rs index 20932af4c4a..89d1fb74913 100644 --- a/internal/mithril-protocol-config/src/test/double/dummies.rs +++ b/internal/mithril-protocol-config/src/test/double/dummies.rs @@ -8,8 +8,12 @@ use mithril_common::{ test::double::{Dummy, fake_data}, }; -use crate::model::{ - MithrilNetworkConfiguration, MithrilNetworkConfigurationForEpoch, SignedEntityTypeConfiguration, +use crate::{ + ProtocolConfigurationForEpoch, + model::{ + MithrilNetworkConfiguration, MithrilNetworkConfigurationForEpoch, + SignedEntityTypeConfiguration, + }, }; impl Dummy for MithrilNetworkConfiguration { @@ -42,6 +46,23 @@ impl Dummy for MithrilNetworkConfigurationForEpoch { } } +impl Dummy for ProtocolConfigurationForEpoch { + /// Return a dummy for [ProtocolConfigurationForEpoch] (test-only). + fn dummy() -> Self { + Self { + protocol_parameters: fake_data::protocol_parameters(), + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + ]), + cardano_transactions: Some(CardanoTransactionsSigningConfig::dummy()), + cardano_blocks_transactions: Some(CardanoBlocksTransactionsSigningConfig::dummy()), + } + } +} + impl Dummy for SignedEntityTypeConfiguration { /// Return a dummy [SignedEntityTypeConfiguration] (test-only). fn dummy() -> Self { diff --git a/mithril-aggregator/src/commands/protocol_configuration_command.rs b/mithril-aggregator/src/commands/protocol_configuration_command.rs index a60245b5595..da84306f96e 100644 --- a/mithril-aggregator/src/commands/protocol_configuration_command.rs +++ b/mithril-aggregator/src/commands/protocol_configuration_command.rs @@ -1,23 +1,30 @@ +use anyhow::Context; use clap::{Parser, Subcommand}; use config::{ConfigBuilder, builder::DefaultState}; +use mithril_common::crypto_helper::{ + ProtocolConfigurationMarkersSigner, ProtocolConfigurationMarkersVerifierSecretKey, +}; use serde::{Deserialize, Serialize}; use slog::Logger; use std::collections::{BTreeSet, HashMap}; -use std::fs; +use std::fs::{self, File}; +use std::io::Write; use std::path::PathBuf; use mithril_common::StdResult; use mithril_common::entities::{ CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, Epoch, - ProtocolParameters, SignedEntityTypeDiscriminants, + HexEncodedProtocolConfigurationMarkersSecretKey, ProtocolParameters, + SignedEntityTypeDiscriminants, }; use mithril_doc::StructDoc; use crate::extract_all; +use crate::tools::ProtocolConfigurationTools; pub struct ProtocolConfigurationParametersConfiguration {} -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct HumanReadableProtocolConfiguration { pub epoch: Epoch, pub protocol_parameters: ProtocolParameters, @@ -119,9 +126,17 @@ impl ExportProtocolConfigurationSubCommand { /// Protocol configuration import command #[derive(Parser, Debug, Clone)] pub struct ImportProtocolConfigurationSubCommand { - /// Import path + /// Import path of the human readable configurations + #[clap(long, value_parser)] + pub import_path: PathBuf, + + /// target path of the tx datum file #[clap(long, value_parser)] - pub path: PathBuf, + pub target_path: PathBuf, + + /// Protocol Configuration Markers Secret Key + #[clap(long, env = "PROTOCOL_CONFIGURATION_MARKERS_SECRET_KEY")] + protocol_configuration_markers_secret_key: HexEncodedProtocolConfigurationMarkersSecretKey, } impl ImportProtocolConfigurationSubCommand { @@ -131,26 +146,60 @@ impl ImportProtocolConfigurationSubCommand { config_builder: ConfigBuilder, ) -> StdResult<()> { //1 - we need to read the protocol configuration from the file - let json_protocol_configurations = fs::read_to_string(&self.path); + println!( + "Reading file content {}", + &self.import_path.to_string_lossy() + ); + let json_protocol_configurations = fs::read_to_string(&self.import_path); //2 - we need to parse the json into a protocol configuration using serde_json + println!("Json parsing ..."); let protocol_configurations: Vec = serde_json::from_str(&json_protocol_configurations?)?; - //3 - Verify protocol config consistency + //3 - Verify protocol config consistency, TODO could be move in ProtocolConfigurationTools ? println!("Verifying protocol configuration consistency..."); - match Self::verify_protocol_configurations(protocol_configurations) { - Ok(_) => Ok(()), - Err(e) => Err(anyhow::anyhow!( - "Protocol configuration is not consistent: {}", - e - )), - } + Self::verify_protocol_configurations(protocol_configurations.clone())?; //return a VerifiedProtocolConfigurations ? + + //3.2 Check epoch consistency on chain ? + + //4 - Generate Tx datum + println!("Generating Tx datum ..."); + let protocol_configuration_markers_signer = + Self::get_markers_signer(self.protocol_configuration_markers_secret_key.clone())?; - //4 - Cbor conversion + let tools = ProtocolConfigurationTools::new(); + let tx_datum = tools.generate_tx_datum( + protocol_configurations, + &protocol_configuration_markers_signer, + )?; + + //5 - TODO: check size < 10kb + + //6 - Write datum file + println!("Generating Tx datum output file..."); + let mut target_file = File::create(&self.target_path)?; + target_file.write_all(tx_datum.as_bytes())?; + + println!( + "Sucessfuly write Tx datum file at {}", + &self.target_path.to_string_lossy() + ); + + Ok(()) + } - //5 - check size < 10kb - //6 - Generate Tx datum + fn get_markers_signer( + secret_key: HexEncodedProtocolConfigurationMarkersSecretKey, + ) -> StdResult { + let markers_secret_key = + ProtocolConfigurationMarkersVerifierSecretKey::from_json_hex(&secret_key) + .with_context( + || "json hex decode of protocol configuration markers secret key failure", + )?; + Ok(ProtocolConfigurationMarkersSigner::from_secret_key( + markers_secret_key, + )) } pub fn verify_protocol_configurations( @@ -214,13 +263,20 @@ mod tests { #[test] fn import_subcommand_parses_flag() { + let signer_secret_key = ProtocolConfigurationMarkersSigner::create_deterministic_signer() + .secret_key() + .to_json_hex() + .expect("create_deterministic_signer for secret key should not fail"); + println!("Signer secret key: {}", signer_secret_key); ImportProtocolConfigurationSubCommand::try_parse_from([ "import-markers", - "--path", + "--import-path", "tests/human_readable_protocol_configuration.json", + "--target-path", + "/tests/protocol_configuration_tx_datum", + "--protocol-configuration-markers-secret-key", + &signer_secret_key, ]) .expect("CLI parse should succeed"); - // assert_eq!(cmd.mithril_era, Some(SupportedEra::Lagrange)); - //TODO when available check that a tx datum file is written } } diff --git a/mithril-aggregator/src/tools/mod.rs b/mithril-aggregator/src/tools/mod.rs index 71ebc698ffd..eb363796d0c 100644 --- a/mithril-aggregator/src/tools/mod.rs +++ b/mithril-aggregator/src/tools/mod.rs @@ -3,6 +3,7 @@ mod era; pub mod file_archiver; pub mod file_size; mod genesis; +mod protocol_configuration; pub mod signer_importer; mod single_signature_authenticator; pub mod url_sanitizer; @@ -13,6 +14,7 @@ pub use era::EraTools; #[cfg(feature = "future_snark")] pub use genesis::GenesisSignedPayload; pub use genesis::GenesisTools; +pub use protocol_configuration::ProtocolConfigurationTools; pub use single_signature_authenticator::*; pub use vacuum_tracker::VacuumTracker; diff --git a/mithril-aggregator/src/tools/protocol_configuration.rs b/mithril-aggregator/src/tools/protocol_configuration.rs new file mode 100644 index 00000000000..ba1e1f83a62 --- /dev/null +++ b/mithril-aggregator/src/tools/protocol_configuration.rs @@ -0,0 +1,150 @@ +use mithril_cardano_node_chain::entities::{TxDatumBuilder, TxDatumFieldValue}; +use mithril_common::{StdResult, crypto_helper::ProtocolConfigurationMarkersSigner}; +use mithril_protocol_config::{ + ProtocolConfigurationForEpoch, ProtocolConfigurationMarker, + adapters::ProtocolConfigurationMarkersPayloadCardanoChain, +}; + +use crate::commands::HumanReadableProtocolConfiguration; + +type ProtocolConfigurationToolsResult = StdResult; +pub struct ProtocolConfigurationTools {} + +impl ProtocolConfigurationTools { + pub fn new() -> Self { + Self {} + } + + /// Generate TxDatum for Protocol Configuration + pub fn generate_tx_datum( + &self, + configurations: Vec, // TODO Add a type VerifiedProtocolConfiguration ? + protocol_configuration_markers_signer: &ProtocolConfigurationMarkersSigner, + ) -> ProtocolConfigurationToolsResult { + let mut markers: Vec = Vec::new(); + for configuration in configurations { + let protocol_configuration_for_epoch: ProtocolConfigurationForEpoch = + configuration.clone().into(); + let marker: ProtocolConfigurationMarker = ProtocolConfigurationMarker::new( + configuration.epoch, + protocol_configuration_for_epoch.to_cbor()?, + ); + markers.push(marker); + } + let markers_payload = ProtocolConfigurationMarkersPayloadCardanoChain::new(markers) + .sign(protocol_configuration_markers_signer)?; + + //TODO add a type signedPayload to ensure both marker and signature is here + + let tx_datum = TxDatumBuilder::new() + .add_field(TxDatumFieldValue::Bytes(markers_payload.to_json_hex()?)) + .build()?; + Ok(tx_datum.0) + } +} + +impl From for ProtocolConfigurationForEpoch { + fn from(config: HumanReadableProtocolConfiguration) -> Self { + ProtocolConfigurationForEpoch { + protocol_parameters: config.protocol_parameters, + enabled_signed_entity_types: config.enabled_signed_entity_types, + cardano_transactions: config.cardano_transaction_signing_config, + cardano_blocks_transactions: config.cardano_blocks_transactions_signing_config, + } + } +} + +#[cfg(test)] +mod tests { + use mithril_common::entities::{ + BlockNumber, BlockNumberOffset, CardanoBlocksTransactionsSigningConfig, + CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, SignedEntityTypeDiscriminants, + }; + use std::collections::BTreeSet; + + use super::*; + + fn build_tools() -> ProtocolConfigurationTools { + ProtocolConfigurationTools::new() + } + + #[test] + fn test_from_human_readable_protocol_configuration() { + let human_readable_conf = HumanReadableProtocolConfiguration { + epoch: Epoch(42), + protocol_parameters: ProtocolParameters { + k: 9, + m: 77, + phi_f: 0.5, + }, + enabled_signed_entity_types: BTreeSet::from_iter(vec![ + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + cardano_transaction_signing_config: Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumberOffset(100), + step: BlockNumber(10), + }), + cardano_blocks_transactions_signing_config: Some( + CardanoBlocksTransactionsSigningConfig { + security_parameter: BlockNumberOffset(150), + step: BlockNumber(20), + }, + ), + }; + + let expected_protocol_configuration_for_epoch = ProtocolConfigurationForEpoch { + protocol_parameters: ProtocolParameters { + k: 9, + m: 77, + phi_f: 0.5, + }, + enabled_signed_entity_types: BTreeSet::from_iter(vec![ + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + cardano_transactions: Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumberOffset(100), + step: BlockNumber(10), + }), + cardano_blocks_transactions: Some(CardanoBlocksTransactionsSigningConfig { + security_parameter: BlockNumberOffset(150), + step: BlockNumber(20), + }), + }; + + assert_eq!( + ProtocolConfigurationForEpoch::from(human_readable_conf), + expected_protocol_configuration_for_epoch + ); + } + + #[test] + fn generate_tx_datum_ok() { + let configurations = vec![HumanReadableProtocolConfiguration { + epoch: Epoch(42), + protocol_parameters: ProtocolParameters { + k: 9, + m: 77, + phi_f: 0.5, + }, + enabled_signed_entity_types: BTreeSet::from_iter(vec![ + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + cardano_transaction_signing_config: Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumberOffset(100), + step: BlockNumber(10), + }), + cardano_blocks_transactions_signing_config: None, + }]; + let signer = ProtocolConfigurationMarkersSigner::create_deterministic_signer(); + let tools = build_tools(); + tools + .generate_tx_datum(configurations, &signer) + .expect("generate_tx_datum should not fail"); + } +} diff --git a/mithril-common/src/crypto_helper/ed25519_alias.rs b/mithril-common/src/crypto_helper/ed25519_alias.rs index 73daf34b3c0..9d3694a3258 100644 --- a/mithril-common/src/crypto_helper/ed25519_alias.rs +++ b/mithril-common/src/crypto_helper/ed25519_alias.rs @@ -15,6 +15,15 @@ pub mod era { pub type EraMarkersVerifier = Ed25519Verifier; /// [EraMarkersSigner] and [EraMarkersVerifier] related errors. pub type EraMarkersVerifierError = Ed25519VerifierError; + + /// Protocol Configuration markers verifier secret key + pub type ProtocolConfigurationMarkersVerifierSecretKey = Ed25519SecretKey; + /// Protocol Configuration markers verifier verification key + pub type ProtocolConfigurationMarkersVerifierVerificationKey = Ed25519VerificationKey; + /// Protocol Configuration markers verifier signature + pub type ProtocolConfigurationMarkersVerifierSignature = Ed25519Signature; + /// A cryptographic signer that is responsible for signing the ProtocolConfigurationMarkers + pub type ProtocolConfigurationMarkersSigner = Ed25519Signer; } pub mod manifest { diff --git a/mithril-common/src/entities/type_alias.rs b/mithril-common/src/entities/type_alias.rs index 2c4b7b59610..80f211b2fe1 100644 --- a/mithril-common/src/entities/type_alias.rs +++ b/mithril-common/src/entities/type_alias.rs @@ -63,6 +63,9 @@ pub type HexEncodedDigest = HexEncodedKey; /// Hex encoded Era Markers Secret Key pub type HexEncodedEraMarkersSecretKey = HexEncodedKey; +/// Hex encoded Protocol Configuration Markers Secret Key +pub type HexEncodedProtocolConfigurationMarkersSecretKey = HexEncodedKey; + /// Number of SPOs pub type TotalSPOs = u32; From c2f9d33e76dd56c234f48dea8dafecb635eec99b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Tue, 21 Jul 2026 11:45:18 +0200 Subject: [PATCH 04/14] feature(protocol-config, aggregator): introduce a Signed payload struct to avoid missing signing when generating protocol config tx datum --- .../src/adapters/cardano_chain.rs | 46 ++++++++++++------- .../src/adapters/mod.rs | 1 + .../src/tools/protocol_configuration.rs | 6 ++- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/internal/mithril-protocol-config/src/adapters/cardano_chain.rs b/internal/mithril-protocol-config/src/adapters/cardano_chain.rs index 5a47dc16f43..3ab8f302a51 100644 --- a/internal/mithril-protocol-config/src/adapters/cardano_chain.rs +++ b/internal/mithril-protocol-config/src/adapters/cardano_chain.rs @@ -39,20 +39,39 @@ pub enum ProtocolConfigurationMarkersPayloadError { pub struct ProtocolConfigurationMarkersPayload { /// List of protocol configuration markers pub markers: Vec, +} + +/// Signed Protocol Configuration markers payload +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SignedProtocolConfigurationMarkersPayload { + /// List of protocol configuration markers + pub markers: Vec, /// Protocol Configuration markers signature - pub signature: Option, + pub signature: ProtocolConfigurationMarkersVerifierSignature, } -//TODO a SignedProtocolConfigurationMarkersPayload with non optional signature +impl SignedProtocolConfigurationMarkersPayload { + /// Instanciate a new ProtocolConfigurationMarkersPayload with markers + pub fn new( + markers: Vec, + signature: ProtocolConfigurationMarkersVerifierSignature, + ) -> Self { + Self { markers, signature } + } + + /// Encode this payload to a json hex string + pub fn to_json_hex(&self) -> StdResult { + key_encode_hex(self).with_context( + || "SignedProtocolConfigurationMarkersPayload could not be json hex encoded", + ) + } +} impl ProtocolConfigurationMarkersPayload { /// Instanciate a new ProtocolConfigurationMarkersPayload with markers - pub fn new(protocol_configuration_markers: Vec) -> Self { - Self { - markers: protocol_configuration_markers, - signature: None, - } + pub fn new(markers: Vec) -> Self { + Self { markers } } fn message_to_bytes(&self) -> Result, ProtocolConfigurationMarkersPayloadError> { @@ -60,25 +79,20 @@ impl ProtocolConfigurationMarkersPayload { .map_err(|e| ProtocolConfigurationMarkersPayloadError::SerializeMessage(e.into())) } - /// Encode this payload to a json hex string - pub fn to_json_hex(&self) -> StdResult { - key_encode_hex(self) - .with_context(|| "protocol configuration markers payload could not be json hex encoded") - } - /// Sign an protocol configuration markers payload pub fn sign( self, signer: &ProtocolConfigurationMarkersSigner, - ) -> Result { + ) -> Result + { let signature = signer.sign(&self.message_to_bytes().map_err(|e| { ProtocolConfigurationMarkersPayloadError::CreateSignature(e.into()) })?); - Ok(Self { + Ok(SignedProtocolConfigurationMarkersPayload { markers: self.markers, - signature: Some(signature), + signature, }) } } diff --git a/internal/mithril-protocol-config/src/adapters/mod.rs b/internal/mithril-protocol-config/src/adapters/mod.rs index e9f987cfb60..ba2ecc5828f 100644 --- a/internal/mithril-protocol-config/src/adapters/mod.rs +++ b/internal/mithril-protocol-config/src/adapters/mod.rs @@ -3,3 +3,4 @@ mod cardano_chain; pub use cardano_chain::ProtocolConfigurationMarkersPayload as ProtocolConfigurationMarkersPayloadCardanoChain; +pub use cardano_chain::SignedProtocolConfigurationMarkersPayload as SignedProtocolConfigurationMarkersPayloadCardanoChain; diff --git a/mithril-aggregator/src/tools/protocol_configuration.rs b/mithril-aggregator/src/tools/protocol_configuration.rs index ba1e1f83a62..865f6593b27 100644 --- a/mithril-aggregator/src/tools/protocol_configuration.rs +++ b/mithril-aggregator/src/tools/protocol_configuration.rs @@ -31,13 +31,15 @@ impl ProtocolConfigurationTools { ); markers.push(marker); } - let markers_payload = ProtocolConfigurationMarkersPayloadCardanoChain::new(markers) + let signed_markers_payload = ProtocolConfigurationMarkersPayloadCardanoChain::new(markers) .sign(protocol_configuration_markers_signer)?; //TODO add a type signedPayload to ensure both marker and signature is here let tx_datum = TxDatumBuilder::new() - .add_field(TxDatumFieldValue::Bytes(markers_payload.to_json_hex()?)) + .add_field(TxDatumFieldValue::Bytes( + signed_markers_payload.to_json_hex()?, + )) .build()?; Ok(tx_datum.0) } From 088dc50ef490977667ca9550ac9071a8dbb26e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Tue, 21 Jul 2026 15:04:39 +0200 Subject: [PATCH 05/14] feature(protocol-config): implement a dummy protocol configuration adapter for tests --- .../src/adapters/cardano_chain.rs | 2 +- .../src/protocol_configuration_reader.rs | 79 +++++++++++++++- .../src/test/double/dummy_adatper.rs | 93 +++++++++++++++++++ .../src/test/double/mod.rs | 2 + 4 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 internal/mithril-protocol-config/src/test/double/dummy_adatper.rs diff --git a/internal/mithril-protocol-config/src/adapters/cardano_chain.rs b/internal/mithril-protocol-config/src/adapters/cardano_chain.rs index 3ab8f302a51..fdc0584c465 100644 --- a/internal/mithril-protocol-config/src/adapters/cardano_chain.rs +++ b/internal/mithril-protocol-config/src/adapters/cardano_chain.rs @@ -52,7 +52,7 @@ pub struct SignedProtocolConfigurationMarkersPayload { } impl SignedProtocolConfigurationMarkersPayload { - /// Instanciate a new ProtocolConfigurationMarkersPayload with markers + /// Instanciate a new SignedProtocolConfigurationMarkersPayload with markers and signature pub fn new( markers: Vec, signature: ProtocolConfigurationMarkersVerifierSignature, diff --git a/internal/mithril-protocol-config/src/protocol_configuration_reader.rs b/internal/mithril-protocol-config/src/protocol_configuration_reader.rs index f426bec3c25..ea9a8dc034d 100644 --- a/internal/mithril-protocol-config/src/protocol_configuration_reader.rs +++ b/internal/mithril-protocol-config/src/protocol_configuration_reader.rs @@ -105,7 +105,7 @@ impl ProtocolConfigurationReader { } /// Read protocol configuration markers from the underlying adapter. - pub async fn read_mithril_network_configurations( + pub async fn read_mithril_protocol_configurations( &self, ) -> Result, ProtocolConfigurationReaderError> { @@ -116,7 +116,7 @@ impl ProtocolConfigurationReader { } })?; - let mut mithril_network_configurations = HashMap::new(); + let mut protocol_configurations = HashMap::new(); for marker in markers { let configuration = ProtocolConfigurationForEpoch::from_cbor(marker.configuration) .map_err(|e| ProtocolConfigurationReaderError::AdapterFailure { @@ -126,9 +126,9 @@ impl ProtocolConfigurationReader { ), error: e.into(), })?; - mithril_network_configurations.insert(marker.epoch, configuration); + protocol_configurations.insert(marker.epoch, configuration); } - Ok(mithril_network_configurations) + Ok(protocol_configurations) } } @@ -139,6 +139,8 @@ mod tests { test::double::Dummy, }; + use crate::test::double::ProtocolConfigurationReaderDummyAdapter; + use super::*; #[test] @@ -180,4 +182,73 @@ mod tests { let cbor = mithril_network_configuration_for_epoch.to_cbor().unwrap(); assert_eq!(&cbor, expected_cbor); } + + fn get_basic_marker_sample() -> Vec { + vec![ProtocolConfigurationMarker { + epoch: Epoch(42), + configuration: ProtocolConfigurationForEpoch { + protocol_parameters: ProtocolParameters { + k: 1, + m: 2, + phi_f: 2.3, + }, + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + ]), + cardano_transactions: Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumberOffset(10), + step: BlockNumber(20), + }), + cardano_blocks_transactions: Some(CardanoBlocksTransactionsSigningConfig { + security_parameter: BlockNumberOffset(30), + step: BlockNumber(40), + }), + } + .to_cbor() + .expect("shoud not fail"), + }] + } + + #[tokio::test] + async fn read_mithril_protocol_configurations() { + let markers: Vec = get_basic_marker_sample(); + let adapter = ProtocolConfigurationReaderDummyAdapter::default(); + adapter.set_markers(markers); + + let reader = ProtocolConfigurationReader::new(Arc::new(adapter)); + let token = reader.read_mithril_protocol_configurations().await.unwrap(); + assert!(!token.contains_key(&Epoch(41))); + assert!(token.contains_key(&Epoch(42))); + assert!(!token.contains_key(&Epoch(43))); + + let (_, configuration) = token.get_key_value(&Epoch(42)).expect("should exist"); + + assert_eq!( + configuration, + &ProtocolConfigurationForEpoch { + protocol_parameters: ProtocolParameters { + k: 1, + m: 2, + phi_f: 2.3, + }, + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + ]), + cardano_transactions: Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumberOffset(10), + step: BlockNumber(20), + }), + cardano_blocks_transactions: Some(CardanoBlocksTransactionsSigningConfig { + security_parameter: BlockNumberOffset(30), + step: BlockNumber(40), + }), + } + ) + } } diff --git a/internal/mithril-protocol-config/src/test/double/dummy_adatper.rs b/internal/mithril-protocol-config/src/test/double/dummy_adatper.rs new file mode 100644 index 00000000000..29bdf9457ed --- /dev/null +++ b/internal/mithril-protocol-config/src/test/double/dummy_adatper.rs @@ -0,0 +1,93 @@ +use std::sync::RwLock; + +use async_trait::async_trait; +use mithril_common::StdResult; + +use crate::{ProtocolConfigurationMarker, ProtocolConfigurationReaderAdapter}; + +/// Dummy adapter is intended to be used in a test environment (end to end test) +/// to simulate retreiving protocol configurations +#[derive(Default)] +pub struct DummyAdapter { + markers: RwLock>, +} + +impl DummyAdapter { + /// Create a new instance directly from markers + pub fn from_markers(markers: Vec) -> Self { + let myself = Self::default(); + myself.set_markers(markers); + + myself + } + + /// Tells what markers should be sent back by the adapter. + pub fn set_markers(&self, markers: Vec) { + let mut my_markers = self.markers.write().unwrap(); + *my_markers = markers; + } +} + +#[async_trait] +impl ProtocolConfigurationReaderAdapter for DummyAdapter { + async fn read(&self) -> StdResult> { + let markers = self.markers.read().unwrap(); + + Ok((*markers.clone()).to_vec()) + } +} + +#[cfg(test)] +mod tests { + use mithril_common::entities::Epoch; + use mithril_common::test::double::Dummy; + + use crate::ProtocolConfigurationForEpoch; + + use super::*; + + #[tokio::test] + async fn empty_dummy_adapter() { + let adapter = DummyAdapter::default(); + + assert!( + adapter + .read() + .await + .expect("dummy adapter shall not fail reading") + .is_empty() + ); + } + + #[tokio::test] + async fn dummy_adapter_output() { + let markers = vec![ + ProtocolConfigurationMarker::new( + Epoch(1), + ProtocolConfigurationForEpoch::dummy() + .to_cbor() + .expect("should not fail"), + ), + ProtocolConfigurationMarker::new( + Epoch(2), + ProtocolConfigurationForEpoch::dummy() + .to_cbor() + .expect("should not fail"), + ), + ProtocolConfigurationMarker::new( + Epoch(3), + ProtocolConfigurationForEpoch::dummy() + .to_cbor() + .expect("should not fail"), + ), + ]; + + let adapter = DummyAdapter::default(); + adapter.set_markers(markers.clone()); + + assert_eq!( + markers, + adapter.read().await.expect("dummy adapter shall not fail reading") + ); + } +} diff --git a/internal/mithril-protocol-config/src/test/double/mod.rs b/internal/mithril-protocol-config/src/test/double/mod.rs index b4dee94f746..b4eeb5da541 100644 --- a/internal/mithril-protocol-config/src/test/double/mod.rs +++ b/internal/mithril-protocol-config/src/test/double/mod.rs @@ -5,5 +5,7 @@ pub mod configuration_provider; mod configuration_provider_with_markers; mod dummies; +mod dummy_adatper; pub use configuration_provider_with_markers::*; +pub use dummy_adatper::DummyAdapter as ProtocolConfigurationReaderDummyAdapter; From e53559fde1f6305c92c1ebb403ea2b98a4b3395e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Tue, 21 Jul 2026 18:12:13 +0200 Subject: [PATCH 06/14] feature(protocol-config): init protocol configuration builder, and a empty read impl for cardano chain adapter --- internal/mithril-protocol-config/Cargo.toml | 1 + .../src/adapters/builder.rs | 160 ++++++++++++++++++ .../src/adapters/cardano_chain.rs | 38 ++++- .../src/adapters/mod.rs | 12 +- 4 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 internal/mithril-protocol-config/src/adapters/builder.rs diff --git a/internal/mithril-protocol-config/Cargo.toml b/internal/mithril-protocol-config/Cargo.toml index f5af85c6104..59007478d34 100644 --- a/internal/mithril-protocol-config/Cargo.toml +++ b/internal/mithril-protocol-config/Cargo.toml @@ -13,6 +13,7 @@ include = ["**/*.rs", "Cargo.toml", "README.md", ".gitignore"] anyhow = { workspace = true } async-trait = { workspace = true } mithril-aggregator-client = { path = "../mithril-aggregator-client" } +mithril-cardano-node-chain = { path = "../cardano-node/mithril-cardano-node-chain" } mithril-common = { path = "../../mithril-common" } serde = { workspace = true } serde_json = { workspace = true } diff --git a/internal/mithril-protocol-config/src/adapters/builder.rs b/internal/mithril-protocol-config/src/adapters/builder.rs new file mode 100644 index 00000000000..f695b699464 --- /dev/null +++ b/internal/mithril-protocol-config/src/adapters/builder.rs @@ -0,0 +1,160 @@ +use serde::{Deserialize, Serialize}; +use std::{fmt::Display, sync::Arc}; +use thiserror::Error; + +use mithril_cardano_node_chain::chain_observer::ChainObserver; +use mithril_cardano_node_chain::entities::ChainAddress; +use mithril_common::StdError; +use mithril_common::crypto_helper::ProtocolConfigurationMarkersVerifierVerificationKey; + +use crate::ProtocolConfigurationReaderAdapter; +use crate::adapters::ProtocolConfigurationReaderCardanoChainAdapter; + +/// Type of protocol configuration reader adapters available +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum AdapterType { + /// Cardano chain adapter. + #[serde(rename = "cardano-chain")] + CardanoChain, + // /// File adapter. + // File, + // /// Dummy adapter. + // Dummy, + // /// Bootstrap adapter. + // Bootstrap, + // /// Bootstrap latest adapter. + // #[serde(rename = "bootstrap-latest")] + // BootstrapLatest, +} + +impl Display for AdapterType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::CardanoChain => write!(f, "cardano chain"), + // Self::Bootstrap => write!(f, "bootstrap"), + // Self::Dummy => write!(f, "dummy"), + // Self::File => write!(f, "file"), + // Self::BootstrapLatest => write!(f, "bootstrap latest"), + } + } +} + +/// Error type for protocol configuration reader adapter builder service. +#[derive(Error, Debug)] +pub enum AdapterBuilderError { + /// Missing parameters error. + #[error("protocol configuration reader adapter parameters are missing")] + MissingParameters(), + + /// Parameters parse error. + #[error("protocol configuration reader adapter parameters parse error")] + ParseParameters(#[source] serde_json::Error), + + /// Parameters decode error. + #[error("protocol configuration reader adapter parameters decode error")] + Decode(#[source] StdError), +} + +/// Protocol configuration adapter builder +pub struct AdapterBuilder { + adapter_type: AdapterType, + adapter_params: Option, +} + +impl AdapterBuilder { + /// Protocol configuration reader adapter builder factory + pub fn new(adapter_type: &AdapterType, adapter_params: &Option) -> Self { + Self { + adapter_type: adapter_type.to_owned(), + adapter_params: adapter_params.to_owned(), + } + } + + /// Create protocol configuration reader adapter from configuration settings. + pub fn build( + &self, + chain_observer: Arc, + ) -> Result, AdapterBuilderError> { + match self.adapter_type { + AdapterType::CardanoChain => { + #[derive(Deserialize)] + struct CardanoChainAdapterConfig { + address: ChainAddress, + verification_key: ProtocolConfigurationMarkersVerifierVerificationKey, + } + + let adapter_config: CardanoChainAdapterConfig = serde_json::from_str( + self.adapter_params + .as_ref() + .ok_or_else(AdapterBuilderError::MissingParameters)?, + ) + .map_err(AdapterBuilderError::ParseParameters)?; + + Ok(Arc::new( + ProtocolConfigurationReaderCardanoChainAdapter::new( + adapter_config.address, + chain_observer, + adapter_config.verification_key, + ), + )) + } // AdapterType::File => { + // #[derive(Deserialize)] + // struct EraReaderFileAdapterConfig { + // markers_file: PathBuf, + // } + + // let adapter_config: EraReaderFileAdapterConfig = serde_json::from_str( + // self.adapter_params + // .as_ref() + // .ok_or_else(AdapterBuilderError::MissingParameters)?, + // ) + // .map_err(AdapterBuilderError::ParseParameters)?; + // let file_adapter = EraReaderFileAdapter::new(adapter_config.markers_file); + + // Ok(Arc::new(file_adapter)) + // } + // AdapterType::Dummy => { + // #[derive(Deserialize)] + // struct EraReaderDummyAdapterConfig { + // markers: Vec, + // } + + // let adapter_config: EraReaderDummyAdapterConfig = serde_json::from_str( + // self.adapter_params + // .as_ref() + // .ok_or_else(AdapterBuilderError::MissingParameters)?, + // ) + // .map_err(AdapterBuilderError::ParseParameters)?; + // let dummy_adapter = EraReaderDummyAdapter::default(); + // dummy_adapter.set_markers(adapter_config.markers); + + // Ok(Arc::new(dummy_adapter)) + // } + // AdapterType::Bootstrap => Ok(Arc::new(EraReaderBootstrapAdapter)), + // AdapterType::BootstrapLatest => Ok(Arc::new(EraReaderBootstrapLatestAdapter)), + } + } +} + +#[cfg(test)] +mod test { + use mithril_cardano_node_chain::test::double::FakeChainObserver; + + use super::*; + + const GOLDEN_ADAPTER_PARAMS: &str = r#"{ + "address":"addr_test1qrv5xfwh043mlc3vk5d97s4nmhxu7cmleyssvhx37gkfyejfe8d38v3vsfgetjafgrsdc49krug8wf04h5rmtengtejqlxrksk", + "verification_key":"5b35352c3232382c3134342c38372c3133382c3133362c34382c382c31342c3138372c38352c3134382c39372c3233322c3235352c3232392c33382c3234342c3234372c3230342c3139382c31332c33312c3232322c32352c3136342c35322c3130322c39312c3132302c3230382c3134375d" + }"#; + + #[test] + fn golden_test_for_cardano_chain() { + AdapterBuilder::new( + &AdapterType::CardanoChain, + &Some(GOLDEN_ADAPTER_PARAMS.to_owned()), + ) + .build(Arc::new(FakeChainObserver::new(None))) + .expect("building an cardano chain era reader with golden params should not fail"); + } +} diff --git a/internal/mithril-protocol-config/src/adapters/cardano_chain.rs b/internal/mithril-protocol-config/src/adapters/cardano_chain.rs index fdc0584c465..e67fecb483f 100644 --- a/internal/mithril-protocol-config/src/adapters/cardano_chain.rs +++ b/internal/mithril-protocol-config/src/adapters/cardano_chain.rs @@ -1,14 +1,18 @@ use anyhow::Context; +use async_trait::async_trait; use serde::{Deserialize, Serialize}; +use std::sync::Arc; use thiserror::Error; +use mithril_cardano_node_chain::chain_observer::ChainObserver; +use mithril_cardano_node_chain::entities::ChainAddress; use mithril_common::crypto_helper::{ ProtocolConfigurationMarkersSigner, ProtocolConfigurationMarkersVerifierSignature, - key_encode_hex, + ProtocolConfigurationMarkersVerifierVerificationKey, key_encode_hex, }; use mithril_common::{StdError, StdResult}; -use crate::ProtocolConfigurationMarker; +use crate::{ProtocolConfigurationMarker, ProtocolConfigurationReaderAdapter}; /// [ProtocolConfigurationMarkersPayload] related errors. #[derive(Debug, Error)] @@ -96,3 +100,33 @@ impl ProtocolConfigurationMarkersPayload { }) } } + +/// Cardano Chain adapter retrieves protocol configuration markers on chain +pub struct CardanoChainAdapter { + address: ChainAddress, + chain_observer: Arc, + verification_key: ProtocolConfigurationMarkersVerifierVerificationKey, +} + +impl CardanoChainAdapter { + /// CardanoChainAdapter factory + pub fn new( + address: ChainAddress, + chain_observer: Arc, + verification_key: ProtocolConfigurationMarkersVerifierVerificationKey, + ) -> Self { + Self { + address, + chain_observer, + verification_key, + } + } +} + +#[async_trait] +impl ProtocolConfigurationReaderAdapter for CardanoChainAdapter { + async fn read(&self) -> StdResult> { + //TODO to implement + Ok(Vec::new()) + } +} diff --git a/internal/mithril-protocol-config/src/adapters/mod.rs b/internal/mithril-protocol-config/src/adapters/mod.rs index ba2ecc5828f..f453bc5a150 100644 --- a/internal/mithril-protocol-config/src/adapters/mod.rs +++ b/internal/mithril-protocol-config/src/adapters/mod.rs @@ -1,6 +1,14 @@ //! Module dedicated to ProtocolConfigurationReaderAdapter implementations. +mod builder; mod cardano_chain; -pub use cardano_chain::ProtocolConfigurationMarkersPayload as ProtocolConfigurationMarkersPayloadCardanoChain; -pub use cardano_chain::SignedProtocolConfigurationMarkersPayload as SignedProtocolConfigurationMarkersPayloadCardanoChain; +pub use builder::{ + AdapterBuilder as ProtocolConfigurationReaderAdapterBuilder, + AdapterType as ProtocolConfigurationReaderAdapterType, +}; +pub use cardano_chain::{ + CardanoChainAdapter as ProtocolConfigurationReaderCardanoChainAdapter, + ProtocolConfigurationMarkersPayload as ProtocolConfigurationMarkersPayloadCardanoChain, + SignedProtocolConfigurationMarkersPayload as SignedProtocolConfigurationMarkersPayloadCardanoChain, +}; From 5a8049ffda6bf5e245a51d2180a67aca212d0b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Wed, 22 Jul 2026 17:11:10 +0200 Subject: [PATCH 07/14] feature(aggregator): implement dependency injection for ProtocolConfigurationReader --- Cargo.lock | 1 + .../protocol_configuration_command.rs | 130 +++++++++++++++--- mithril-aggregator/src/configuration.rs | 11 ++ .../src/dependency_injection/builder/mod.rs | 32 ++++- .../builder/support/compatibility.rs | 35 +++++ .../dependency_injection/containers/mod.rs | 2 + .../containers/protocol_configuration.rs | 22 +++ .../dependency_injection/containers/serve.rs | 4 + .../src/tools/protocol_configuration.rs | 89 ++++++++++-- 9 files changed, 295 insertions(+), 31 deletions(-) create mode 100644 mithril-aggregator/src/dependency_injection/containers/protocol_configuration.rs diff --git a/Cargo.lock b/Cargo.lock index 3c42c57db0c..3d6a2af5833 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4599,6 +4599,7 @@ dependencies = [ "async-trait", "httpmock", "mithril-aggregator-client", + "mithril-cardano-node-chain", "mithril-common", "serde", "serde_json", diff --git a/mithril-aggregator/src/commands/protocol_configuration_command.rs b/mithril-aggregator/src/commands/protocol_configuration_command.rs index da84306f96e..4a89784ba15 100644 --- a/mithril-aggregator/src/commands/protocol_configuration_command.rs +++ b/mithril-aggregator/src/commands/protocol_configuration_command.rs @@ -1,28 +1,68 @@ use anyhow::Context; use clap::{Parser, Subcommand}; -use config::{ConfigBuilder, builder::DefaultState}; -use mithril_common::crypto_helper::{ - ProtocolConfigurationMarkersSigner, ProtocolConfigurationMarkersVerifierSecretKey, -}; +use config::{ConfigBuilder, Map, Value, builder::DefaultState}; use serde::{Deserialize, Serialize}; -use slog::Logger; -use std::collections::{BTreeSet, HashMap}; -use std::fs::{self, File}; -use std::io::Write; -use std::path::PathBuf; +use slog::{Logger, debug}; +use std::{ + collections::{BTreeSet, HashMap}, + fs::{self, File}, + io::Write, + path::PathBuf, + sync::Arc, +}; +use mithril_cardano_node_chain::chain_observer::ChainObserverType; use mithril_common::StdResult; +use mithril_common::crypto_helper::{ + ProtocolConfigurationMarkersSigner, ProtocolConfigurationMarkersVerifierSecretKey, +}; use mithril_common::entities::{ CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, Epoch, HexEncodedProtocolConfigurationMarkersSecretKey, ProtocolParameters, SignedEntityTypeDiscriminants, }; -use mithril_doc::StructDoc; +use mithril_doc::{Documenter, StructDoc}; + +use crate::{ConfigurationSource, ExecutionEnvironment, extract_all}; +use crate::{dependency_injection::DependenciesBuilder, tools::ProtocolConfigurationTools}; -use crate::extract_all; -use crate::tools::ProtocolConfigurationTools; +#[derive(Debug, Clone, Deserialize, Documenter)] +pub struct ProtocolConfigurationParametersConfiguration { + /// Cardano Network Magic number + /// + /// useful for TestNet & DevNet + #[example = "`1097911063` or `42`"] + pub network_magic: Option, -pub struct ProtocolConfigurationParametersConfiguration {} + /// Cardano network + #[example = "`mainnet` or `preprod` or `devnet`"] + network: String, + + /// Cardano chain observer type + pub chain_observer_type: ChainObserverType, +} + +impl ConfigurationSource for ProtocolConfigurationParametersConfiguration { + fn environment(&self) -> ExecutionEnvironment { + ExecutionEnvironment::Production + } + + fn network_magic(&self) -> Option { + self.network_magic + } + + fn network(&self) -> String { + self.network.clone() + } + + fn chain_observer_type(&self) -> ChainObserverType { + self.chain_observer_type.clone() + } + + fn store_retention_limit(&self) -> Option { + None + } +} #[derive(Serialize, Deserialize, Clone)] pub struct HumanReadableProtocolConfiguration { @@ -53,6 +93,15 @@ impl HumanReadableProtocolConfiguration { #[derive(Parser, Debug, Clone)] pub struct ProtocolConfigurationCommand { + /// Protocol configuration reader adapter type + #[clap(long, env = "PROTOCOL_CONFIGURATION_READER_ADAPTER_TYPE")] + pub protocol_configuration_reader_adapter_type: String, + + /// Protocol configation reader adapter parameters + /// example {"address":"your-address","verification_key":"your-verification-key"} + #[clap(long, env = "PROTOCOL_CONFIGURATION_READER_ADAPTER_PARAMS")] + pub protocol_configuration_reader_adapter_params: String, + /// commands #[clap(subcommand)] pub protocol_configuration_sub_command: ProtocolConfigurationSubCommand, @@ -145,30 +194,52 @@ impl ImportProtocolConfigurationSubCommand { root_logger: Logger, config_builder: ConfigBuilder, ) -> StdResult<()> { - //1 - we need to read the protocol configuration from the file + // 0 conf & dependencies + let config: ProtocolConfigurationParametersConfiguration = config_builder + .build() + .with_context(|| "configuration build error")? + .try_deserialize() + .with_context(|| "configuration deserialize error")?; + debug!(root_logger, "EXPORT PROTOCOL CONFIGURATION command"; "config" => format!("{config:?}")); + + let mut dependencies_builder = + DependenciesBuilder::new(root_logger.clone(), Arc::new(config.clone())); + + let dependencies = dependencies_builder + .create_protocol_configuration_container() + .await + .with_context( + || "Dependencies Builder can not create protocol configuration command dependencies container", + )?; + + //1 - Read the protocol configurations from the file println!( "Reading file content {}", &self.import_path.to_string_lossy() ); let json_protocol_configurations = fs::read_to_string(&self.import_path); - //2 - we need to parse the json into a protocol configuration using serde_json + //2 - Parse the json into a protocol configuration list using serde_json println!("Json parsing ..."); let protocol_configurations: Vec = serde_json::from_str(&json_protocol_configurations?)?; //3 - Verify protocol config consistency, TODO could be move in ProtocolConfigurationTools ? println!("Verifying protocol configuration consistency..."); - Self::verify_protocol_configurations(protocol_configurations.clone())?; //return a VerifiedProtocolConfigurations ? + Self::verify_protocol_configurations(&protocol_configurations)?; //return a VerifiedProtocolConfigurations ? - //3.2 Check epoch consistency on chain ? + //3.2 Check epoch consistency on chain + + let tools = ProtocolConfigurationTools::from_dependencies(dependencies) + .await + .with_context(|| "protocol-configuration-tools: initialization error")?; + // tools.verify_configuration_against_production(&protocol_configurations); //4 - Generate Tx datum println!("Generating Tx datum ..."); let protocol_configuration_markers_signer = Self::get_markers_signer(self.protocol_configuration_markers_secret_key.clone())?; - let tools = ProtocolConfigurationTools::new(); let tx_datum = tools.generate_tx_datum( protocol_configurations, &protocol_configuration_markers_signer, @@ -203,7 +274,7 @@ impl ImportProtocolConfigurationSubCommand { } pub fn verify_protocol_configurations( - configurations: Vec, + configurations: &Vec, ) -> StdResult<()> { //TODO verify non zero protocol parameters (other non zero attributes ?) for config in configurations { @@ -225,6 +296,23 @@ impl ImportProtocolConfigurationSubCommand { pub fn extract_config(_parent: String) -> HashMap { HashMap::new() } + + // to delete, moved to dep injection + // /// Create era reader adapter from configuration settings. + // fn build_protocol_configuration_reader_adapter( + // chain_observer: Arc, + // adapter_type: ProtocolConfigurationReaderAdapterType, + // adapter_params: Option, + // ) -> StdResult> { + // ProtocolConfigurationReaderAdapterBuilder::new(&adapter_type, &adapter_params) + // .build(chain_observer) + // .with_context(|| { + // format!( + // "Configuration: can not create protocol configuration reader for adapter '{}'.", + // adapter_type + // ) + // }) + // } } #[cfg(test)] @@ -248,7 +336,7 @@ mod tests { }]; let result = ImportProtocolConfigurationSubCommand::verify_protocol_configurations( - configurations, + &configurations, ); assert_eq!( @@ -267,7 +355,7 @@ mod tests { .secret_key() .to_json_hex() .expect("create_deterministic_signer for secret key should not fail"); - println!("Signer secret key: {}", signer_secret_key); + ImportProtocolConfigurationSubCommand::try_parse_from([ "import-markers", "--import-path", diff --git a/mithril-aggregator/src/configuration.rs b/mithril-aggregator/src/configuration.rs index 167508be0f9..bf5fb81f3d6 100644 --- a/mithril-aggregator/src/configuration.rs +++ b/mithril-aggregator/src/configuration.rs @@ -4,6 +4,7 @@ use std::str::FromStr; use anyhow::Context; use config::{ConfigError, Map, Source, Value, ValueKind}; +use mithril_protocol_config::adapters::ProtocolConfigurationReaderAdapterType; use semver::Version; use serde::Deserialize; @@ -196,6 +197,16 @@ pub trait ConfigurationSource { panic!("era_reader_adapter_params is not implemented."); } + /// Protocol configuration reader adapter type + fn protocol_configuration_reader_adapter_type(&self) -> ProtocolConfigurationReaderAdapterType { + panic!("protocol_configuration_reader_adapter_type is not implemented."); + } + + /// Protocol configuration reader adapter parameters + fn protocol_configuration_reader_adapter_params(&self) -> Option { + panic!("protocol_configuration_reader_adapter_params is not implemented."); + } + /// Configuration of the ancillary files signer /// /// **IMPORTANT**: The cryptographic scheme used is ED25519 diff --git a/mithril-aggregator/src/dependency_injection/builder/mod.rs b/mithril-aggregator/src/dependency_injection/builder/mod.rs index b20a56844b9..435d8a6329c 100644 --- a/mithril-aggregator/src/dependency_injection/builder/mod.rs +++ b/mithril-aggregator/src/dependency_injection/builder/mod.rs @@ -34,7 +34,10 @@ use mithril_common::{ }; use mithril_era::{EraChecker, EraReader, EraReaderAdapter}; use mithril_persistence::sqlite::{SqliteConnection, SqliteConnectionPool}; -use mithril_protocol_config::interface::MithrilNetworkConfigurationProvider; +use mithril_protocol_config::{ + ProtocolConfigurationReader, ProtocolConfigurationReaderAdapter, + interface::MithrilNetworkConfigurationProvider, +}; use mithril_signed_entity_lock::SignedEntityTypeLock; use mithril_ticker::TickerService; @@ -49,6 +52,7 @@ use crate::{ AggregatorCardanoChainDataRepository, CertificateRepository, EpochSettingsStore, OpenMessageRepository, SignedEntityStorer, SignerStore, StakePoolStore, }, + dependency_injection::ProtocolConfigurationCommandDependenciesContainer, event_store::{EventMessage, TransmitterService}, file_uploaders::FileUploader, http_server::routes::router::{self, RouterConfig, RouterState}, @@ -221,6 +225,12 @@ pub struct DependenciesBuilder { /// Era reader service pub era_reader: Option>, + /// Adapter for [ProtocolConfigurationReader] + pub protocol_configuration_reader_adapter: Option>, + + ///Protocol configuration reader service + pub protocol_configuration_reader: Option>, + /// Event Transmitter Service pub event_transmitter: Option>>, @@ -337,6 +347,8 @@ impl DependenciesBuilder { era_reader_adapter: None, era_checker: None, era_reader: None, + protocol_configuration_reader_adapter: None, + protocol_configuration_reader: None, event_transmitter: None, event_transmitter_channel: (None, None), api_version_provider: None, @@ -398,6 +410,7 @@ impl DependenciesBuilder { signer_registration_round_opener: self.get_signer_registration_round_opener().await?, era_checker: self.get_era_checker().await?, era_reader: self.get_era_reader().await?, + protocol_configuration_reader: self.get_protocol_configuration_reader().await?, event_transmitter: self.get_event_transmitter().await?, api_version_provider: self.get_api_version_provider().await?, stake_distribution_service: self.get_stake_distribution_service().await?, @@ -497,6 +510,23 @@ impl DependenciesBuilder { Ok(dependencies) } + /// Create dependencies for protocol configuration commands + pub async fn create_protocol_configuration_container( + &mut self, + ) -> Result { + let network = self.configuration.get_network().with_context( + || "Dependencies Builder can not get Cardano network while building protocol configuration container", + )?; + let dependencies = ProtocolConfigurationCommandDependenciesContainer { + network, + chain_observer: self.get_chain_observer().await?, + protocol_configuration_reader: self.get_protocol_configuration_reader().await?, + logger: self.root_logger(), + }; + + Ok(dependencies) + } + /// Create dependencies for database command pub async fn create_database_command_container( &mut self, diff --git a/mithril-aggregator/src/dependency_injection/builder/support/compatibility.rs b/mithril-aggregator/src/dependency_injection/builder/support/compatibility.rs index 9c25a9ae75b..e0c9e3a0e8e 100644 --- a/mithril-aggregator/src/dependency_injection/builder/support/compatibility.rs +++ b/mithril-aggregator/src/dependency_injection/builder/support/compatibility.rs @@ -4,6 +4,9 @@ use mithril_common::api_version::APIVersionProvider; use mithril_common::entities::{Epoch, SupportedEra}; use mithril_era::adapters::{EraReaderAdapterBuilder, EraReaderDummyAdapter}; use mithril_era::{EraChecker, EraMarker, EraReader, EraReaderAdapter}; +use mithril_protocol_config::adapters::ProtocolConfigurationReaderAdapterBuilder; +use mithril_protocol_config::test::double::ProtocolConfigurationReaderDummyAdapter; +use mithril_protocol_config::{ProtocolConfigurationReader, ProtocolConfigurationReaderAdapter}; use crate::ExecutionEnvironment; use crate::dependency_injection::{DependenciesBuilder, DependenciesBuilderError, Result}; @@ -82,4 +85,36 @@ impl DependenciesBuilder { pub async fn get_era_checker(&mut self) -> Result> { get_dependency!(self.era_checker) } + + async fn build_protocol_configuration_reader( + &mut self, + ) -> Result> { + let protocol_configuration_adapter: Arc = + match self.configuration.environment() { + ExecutionEnvironment::Production => ProtocolConfigurationReaderAdapterBuilder::new( + &self.configuration.protocol_configuration_reader_adapter_type(), + &self.configuration.protocol_configuration_reader_adapter_params(), + ) + .build(self.get_chain_observer().await?) + .map_err(|e| DependenciesBuilderError::Initialization { + message: "Could not build ProtocolConfigurationReader as dependency." + .to_string(), + error: Some(e.into()), + })?, + _ => Arc::new(ProtocolConfigurationReaderDummyAdapter::from_markers( + vec![], //TODO + )), + }; + + Ok(Arc::new(ProtocolConfigurationReader::new( + protocol_configuration_adapter, + ))) + } + + /// [ProtocolConfigurationReader] service + pub async fn get_protocol_configuration_reader( + &mut self, + ) -> Result> { + get_dependency!(self.protocol_configuration_reader) + } } diff --git a/mithril-aggregator/src/dependency_injection/containers/mod.rs b/mithril-aggregator/src/dependency_injection/containers/mod.rs index e8109eec3a4..e64b8b7ab07 100644 --- a/mithril-aggregator/src/dependency_injection/containers/mod.rs +++ b/mithril-aggregator/src/dependency_injection/containers/mod.rs @@ -1,7 +1,9 @@ mod genesis; +mod protocol_configuration; mod serve; pub use genesis::GenesisCommandDependenciesContainer; +pub use protocol_configuration::ProtocolConfigurationCommandDependenciesContainer; pub use serve::*; use std::sync::Arc; diff --git a/mithril-aggregator/src/dependency_injection/containers/protocol_configuration.rs b/mithril-aggregator/src/dependency_injection/containers/protocol_configuration.rs new file mode 100644 index 00000000000..c5116eaff5d --- /dev/null +++ b/mithril-aggregator/src/dependency_injection/containers/protocol_configuration.rs @@ -0,0 +1,22 @@ +use std::sync::Arc; + +use mithril_protocol_config::ProtocolConfigurationReader; +use slog::Logger; + +use mithril_cardano_node_chain::chain_observer::ChainObserver; +use mithril_common::CardanoNetwork; + +/// Dependencies container for the protocol configuration commands +pub struct ProtocolConfigurationCommandDependenciesContainer { + /// Cardano network + pub network: CardanoNetwork, + + /// Chain observer + pub chain_observer: Arc, + + /// Protocol configuration reader + pub protocol_configuration_reader: Arc, + + /// Logger. + pub logger: Logger, +} diff --git a/mithril-aggregator/src/dependency_injection/containers/serve.rs b/mithril-aggregator/src/dependency_injection/containers/serve.rs index 98511d0b744..ffcbfc78b3d 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::ProtocolConfigurationReader; use slog::Logger; use std::sync::Arc; use tokio::sync::RwLock; @@ -68,6 +69,9 @@ pub struct ServeCommandDependenciesContainer { /// Era reader service pub(crate) era_reader: Arc, + /// Protocol configuration service + pub(crate) protocol_configuration_reader: Arc, + /// Event Transmitter Service pub(crate) event_transmitter: Arc>, diff --git a/mithril-aggregator/src/tools/protocol_configuration.rs b/mithril-aggregator/src/tools/protocol_configuration.rs index 865f6593b27..101b3a0d659 100644 --- a/mithril-aggregator/src/tools/protocol_configuration.rs +++ b/mithril-aggregator/src/tools/protocol_configuration.rs @@ -1,24 +1,90 @@ +use std::collections::HashMap; + +use anyhow::Context; use mithril_cardano_node_chain::entities::{TxDatumBuilder, TxDatumFieldValue}; -use mithril_common::{StdResult, crypto_helper::ProtocolConfigurationMarkersSigner}; +use mithril_common::{ + CardanoNetwork, StdResult, crypto_helper::ProtocolConfigurationMarkersSigner, entities::Epoch, +}; use mithril_protocol_config::{ ProtocolConfigurationForEpoch, ProtocolConfigurationMarker, adapters::ProtocolConfigurationMarkersPayloadCardanoChain, }; +use slog::Logger; -use crate::commands::HumanReadableProtocolConfiguration; +use crate::{ + commands::HumanReadableProtocolConfiguration, + dependency_injection::ProtocolConfigurationCommandDependenciesContainer, +}; type ProtocolConfigurationToolsResult = StdResult; -pub struct ProtocolConfigurationTools {} + +/// Configuration for the protocol configuration tools. +pub struct ProtocolConfigurationToolsConfiguration { + /// Cardano network. + pub network: CardanoNetwork, + + /// Current epoch. + pub epoch: Epoch, + + //On chain configurations by Epoch. + pub on_chain_configurations: HashMap, +} + +pub struct ProtocolConfigurationTools { + configuration: ProtocolConfigurationToolsConfiguration, + + logger: Logger, +} impl ProtocolConfigurationTools { - pub fn new() -> Self { - Self {} + pub fn new(configuration: ProtocolConfigurationToolsConfiguration, logger: Logger) -> Self { + Self { + configuration, + logger, + } } + pub async fn from_dependencies( + dependencies: ProtocolConfigurationCommandDependenciesContainer, + ) -> StdResult { + let epoch = dependencies + .chain_observer + .get_current_epoch() + .await? + .with_context(|| "Chain observer can not retrieve current epoch")?; + + let on_chain_configurations = dependencies + .protocol_configuration_reader + .read_mithril_protocol_configurations() + .await?; + + let configuration = ProtocolConfigurationToolsConfiguration { + network: dependencies.network, + epoch, + on_chain_configurations, + }; + + Ok(Self::new(configuration, dependencies.logger)) + } + + // /// Verify if configuration have greater Epoch (with a offset) than configuration on chain + // pub async fn verify_configuration_against_production( + // &self, + // configurations: Vec, + // ) -> StdResult<()> { + // let production_configurations = self.adapter.read_mithril_protocol_configurations().await?; + // production_configurations + // .keys() + // .collect::>() + // .contains(&Epoch(1)); + + // Ok(()) + // } + /// Generate TxDatum for Protocol Configuration pub fn generate_tx_datum( &self, - configurations: Vec, // TODO Add a type VerifiedProtocolConfiguration ? + configurations: Vec, protocol_configuration_markers_signer: &ProtocolConfigurationMarkersSigner, ) -> ProtocolConfigurationToolsResult { let mut markers: Vec = Vec::new(); @@ -34,8 +100,6 @@ impl ProtocolConfigurationTools { let signed_markers_payload = ProtocolConfigurationMarkersPayloadCardanoChain::new(markers) .sign(protocol_configuration_markers_signer)?; - //TODO add a type signedPayload to ensure both marker and signature is here - let tx_datum = TxDatumBuilder::new() .add_field(TxDatumFieldValue::Bytes( signed_markers_payload.to_json_hex()?, @@ -64,10 +128,17 @@ mod tests { }; use std::collections::BTreeSet; + use crate::test::TestLogger; + use super::*; fn build_tools() -> ProtocolConfigurationTools { - ProtocolConfigurationTools::new() + let configuration = ProtocolConfigurationToolsConfiguration { + network: CardanoNetwork::TestNet(42), + epoch: Epoch(30), + on_chain_configurations: HashMap::new(), + }; + ProtocolConfigurationTools::new(configuration, TestLogger::stdout()) } #[test] From e81c3c5eb6fafcbde066111f98f0c8340d2141d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Thu, 23 Jul 2026 17:30:19 +0200 Subject: [PATCH 08/14] feature(protocol-config): implement a ConfigurationComputerFromMarkers with fallback logic for ProtocolConfigurationReader --- .../src/configuration_computer.rs | 116 ++++++++++++++++++ internal/mithril-protocol-config/src/lib.rs | 1 + 2 files changed, 117 insertions(+) create mode 100644 internal/mithril-protocol-config/src/configuration_computer.rs diff --git a/internal/mithril-protocol-config/src/configuration_computer.rs b/internal/mithril-protocol-config/src/configuration_computer.rs new file mode 100644 index 00000000000..b434a21de41 --- /dev/null +++ b/internal/mithril-protocol-config/src/configuration_computer.rs @@ -0,0 +1,116 @@ +//! //! Model definitions for ProtocolConfigurationReader. + +use std::collections::BTreeMap; + +use mithril_common::entities::Epoch; + +use crate::ProtocolConfigurationForEpoch; + +/// [ConfigurationComputerFromMarkers] containing markers by epoch +#[derive(PartialEq, Clone, Debug)] +pub struct ConfigurationComputerFromMarkers { + markers: BTreeMap, +} + +impl ConfigurationComputerFromMarkers { + /// Create a new [ConfigurationComputerFromMarkers] with the given markers. + pub fn new(markers: BTreeMap) -> Self { + Self { markers } + } + + /// retrieve configuration for given Epoch or fallback to last known configuration + pub fn get_network_configuration(&self, epoch: Epoch) -> Option { + self.markers + .range(..=epoch) + .next_back() + .map(|(_, marker)| marker.clone()) + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use mithril_common::{ + entities::{ + CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, Epoch, + ProtocolParameters, SignedEntityTypeDiscriminants, + }, + test::double::Dummy, + }; + + use crate::ProtocolConfigurationForEpoch; + + use super::*; + + fn fake_config_for_epoch(epoch: Epoch) -> ProtocolConfigurationForEpoch { + ProtocolConfigurationForEpoch { + protocol_parameters: ProtocolParameters::new(*epoch, *epoch, 0.1), + enabled_signed_entity_types: SignedEntityTypeDiscriminants::all(), + cardano_transactions: Some(CardanoTransactionsSigningConfig::dummy()), + cardano_blocks_transactions: Some(CardanoBlocksTransactionsSigningConfig::dummy()), + } + } + + #[derive(Debug)] + struct TestCase { + requested_epoch: Epoch, + expected_conf_epoch: Epoch, + } + + macro_rules! test_case { + ( + requested: $requested_epoch:expr, + expected: $expected_conf_epoch:expr + ) => { + TestCase { + requested_epoch: Epoch($requested_epoch), + expected_conf_epoch: Epoch($expected_conf_epoch), + } + }; + } + + #[test] + fn test_get_network_configuration_must_fallback_to_last_known_configuration_if_epoch_not_found() + { + let markers = BTreeMap::from([ + (Epoch(2), fake_config_for_epoch(Epoch(2))), + (Epoch(6), fake_config_for_epoch(Epoch(6))), + (Epoch(10), fake_config_for_epoch(Epoch(10))), + ]); + + fn test_cases() -> Vec { + vec![ + test_case!(requested: 3, expected: 2 ), + test_case!(requested: 5, expected: 2 ), + test_case!(requested: 6, expected: 6 ), + test_case!(requested: 7, expected: 6 ), + test_case!(requested: 9, expected: 6 ), + test_case!(requested: 10, expected: 10), + test_case!(requested: 11, expected: 10), + test_case!(requested: 12, expected: 10), + ] + } + + let configurations = ConfigurationComputerFromMarkers::new(markers); + + for test_case in test_cases() { + assert_eq!( + configurations.get_network_configuration(test_case.requested_epoch), + Some(fake_config_for_epoch(test_case.expected_conf_epoch)) + ); + } + } + + #[test] + fn test_get_network_configuration_return_none_if_no_fallback_conf_is_available() { + let markers = BTreeMap::from([ + (Epoch(6), fake_config_for_epoch(Epoch(6))), + (Epoch(10), fake_config_for_epoch(Epoch(10))), + ]); + + let configurations = ConfigurationComputerFromMarkers::new(markers); + + assert_eq!(configurations.get_network_configuration(Epoch(4)), None); + } +} diff --git a/internal/mithril-protocol-config/src/lib.rs b/internal/mithril-protocol-config/src/lib.rs index b892b0db47c..10b61b314d2 100644 --- a/internal/mithril-protocol-config/src/lib.rs +++ b/internal/mithril-protocol-config/src/lib.rs @@ -2,6 +2,7 @@ //! This crate provides mechanisms to read and check the configuration parameters of a Mithril network. pub mod adapters; +pub mod configuration_computer; pub mod http; pub mod interface; pub mod model; From f414c82874ec1c9dee8f047c41213f12bf08ec73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Thu, 23 Jul 2026 17:49:19 +0200 Subject: [PATCH 09/14] feature(protocol-config): use ConfigurationComputerFromMarkers as return type for ProtocolConfigurationReader --- .../src/configuration_computer.rs | 3 +- .../src/protocol_configuration_reader.rs | 29 ++++++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/internal/mithril-protocol-config/src/configuration_computer.rs b/internal/mithril-protocol-config/src/configuration_computer.rs index b434a21de41..7f0181c33b6 100644 --- a/internal/mithril-protocol-config/src/configuration_computer.rs +++ b/internal/mithril-protocol-config/src/configuration_computer.rs @@ -9,7 +9,8 @@ use crate::ProtocolConfigurationForEpoch; /// [ConfigurationComputerFromMarkers] containing markers by epoch #[derive(PartialEq, Clone, Debug)] pub struct ConfigurationComputerFromMarkers { - markers: BTreeMap, + /// BTreeMap assotiation of ProtocolConfigurationForEpoch to a coresponding Epoch + pub markers: BTreeMap, } impl ConfigurationComputerFromMarkers { diff --git a/internal/mithril-protocol-config/src/protocol_configuration_reader.rs b/internal/mithril-protocol-config/src/protocol_configuration_reader.rs index ea9a8dc034d..910b9bd8c41 100644 --- a/internal/mithril-protocol-config/src/protocol_configuration_reader.rs +++ b/internal/mithril-protocol-config/src/protocol_configuration_reader.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeSet, HashMap}; +use std::collections::{BTreeMap, BTreeSet}; use std::sync::Arc; use thiserror::Error; @@ -11,6 +11,8 @@ use mithril_common::entities::{ }; use mithril_common::{StdError, StdResult}; +use crate::configuration_computer::ConfigurationComputerFromMarkers; + /// The cbor representation of a MithrilNetworkConfigurationForEpoch pub type CborProtocolConfigurationForEpoch = String; @@ -107,8 +109,7 @@ impl ProtocolConfigurationReader { /// Read protocol configuration markers from the underlying adapter. pub async fn read_mithril_protocol_configurations( &self, - ) -> Result, ProtocolConfigurationReaderError> - { + ) -> Result { let markers = self.adapter.read().await.map_err(|e| { ProtocolConfigurationReaderError::AdapterFailure { message: "Failed to read protocol configuration markers from adapter".to_string(), @@ -116,7 +117,7 @@ impl ProtocolConfigurationReader { } })?; - let mut protocol_configurations = HashMap::new(); + let mut decoded_markers = BTreeMap::new(); for marker in markers { let configuration = ProtocolConfigurationForEpoch::from_cbor(marker.configuration) .map_err(|e| ProtocolConfigurationReaderError::AdapterFailure { @@ -126,9 +127,9 @@ impl ProtocolConfigurationReader { ), error: e.into(), })?; - protocol_configurations.insert(marker.epoch, configuration); + decoded_markers.insert(marker.epoch, configuration); } - Ok(protocol_configurations) + Ok(ConfigurationComputerFromMarkers::new(decoded_markers)) } } @@ -214,17 +215,19 @@ mod tests { #[tokio::test] async fn read_mithril_protocol_configurations() { - let markers: Vec = get_basic_marker_sample(); + let cbor_markers: Vec = get_basic_marker_sample(); let adapter = ProtocolConfigurationReaderDummyAdapter::default(); - adapter.set_markers(markers); + adapter.set_markers(cbor_markers); let reader = ProtocolConfigurationReader::new(Arc::new(adapter)); - let token = reader.read_mithril_protocol_configurations().await.unwrap(); - assert!(!token.contains_key(&Epoch(41))); - assert!(token.contains_key(&Epoch(42))); - assert!(!token.contains_key(&Epoch(43))); + let configurations = reader.read_mithril_protocol_configurations().await.unwrap(); + let markers = configurations.markers; + + assert!(!markers.contains_key(&Epoch(41))); + assert!(markers.contains_key(&Epoch(42))); + assert!(!markers.contains_key(&Epoch(43))); - let (_, configuration) = token.get_key_value(&Epoch(42)).expect("should exist"); + let (_, configuration) = markers.get_key_value(&Epoch(42)).expect("should exist"); assert_eq!( configuration, From 6093a473efff50e1a8e5cf7eb00eb529f7aab92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Fri, 24 Jul 2026 18:25:07 +0200 Subject: [PATCH 10/14] feature(aggregator): WIP: implementing verification between user import and on chain configurations --- .../src/tools/protocol_configuration.rs | 374 ++++++++++++++++-- 1 file changed, 352 insertions(+), 22 deletions(-) diff --git a/mithril-aggregator/src/tools/protocol_configuration.rs b/mithril-aggregator/src/tools/protocol_configuration.rs index 101b3a0d659..e847d11a70a 100644 --- a/mithril-aggregator/src/tools/protocol_configuration.rs +++ b/mithril-aggregator/src/tools/protocol_configuration.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::BTreeMap; use anyhow::Context; use mithril_cardano_node_chain::entities::{TxDatumBuilder, TxDatumFieldValue}; @@ -8,14 +8,24 @@ use mithril_common::{ use mithril_protocol_config::{ ProtocolConfigurationForEpoch, ProtocolConfigurationMarker, adapters::ProtocolConfigurationMarkersPayloadCardanoChain, + configuration_computer::ConfigurationComputerFromMarkers, }; -use slog::Logger; +use slog::{Logger, info, warn}; +use thiserror::Error; use crate::{ commands::HumanReadableProtocolConfiguration, dependency_injection::ProtocolConfigurationCommandDependenciesContainer, }; +#[derive(Debug, Error)] +pub enum ProtocolConfigurationVerifierError { + #[error("Configuration to import for {0:?}, is not the same has configuration on chain")] + NotSameConfigurationForEpoch(Epoch), +} + +const EPOCH_OFFSET: u64 = 3; + type ProtocolConfigurationToolsResult = StdResult; /// Configuration for the protocol configuration tools. @@ -27,7 +37,7 @@ pub struct ProtocolConfigurationToolsConfiguration { pub epoch: Epoch, //On chain configurations by Epoch. - pub on_chain_configurations: HashMap, + pub on_chain_configurations: ConfigurationComputerFromMarkers, } pub struct ProtocolConfigurationTools { @@ -67,19 +77,42 @@ impl ProtocolConfigurationTools { Ok(Self::new(configuration, dependencies.logger)) } - // /// Verify if configuration have greater Epoch (with a offset) than configuration on chain - // pub async fn verify_configuration_against_production( - // &self, - // configurations: Vec, - // ) -> StdResult<()> { - // let production_configurations = self.adapter.read_mithril_protocol_configurations().await?; - // production_configurations - // .keys() - // .collect::>() - // .contains(&Epoch(1)); + /// Verify if configuration to import share same windows as on chain configuration for current epoch + pub fn verify_configurations_against_chain( + &self, + configurations_to_import: Vec, + ) -> Result<(), ProtocolConfigurationVerifierError> { + let current_epoch = self.configuration.epoch; + info!(&self.logger, "Current epoch is {}", current_epoch); + + let markers_from_chain = self.configuration.on_chain_configurations.clone(); + let markers_to_import = to_configuration_computer_from_markers(configurations_to_import); - // Ok(()) - // } + let epoch_range_to_verify = (current_epoch.0 - EPOCH_OFFSET)..=current_epoch.0; + info!( + &self.logger, + "Verifying configurations for epoch range [{:?}]", epoch_range_to_verify + ); + + for epoch in epoch_range_to_verify.map(Epoch) { + let marker_to_import = markers_to_import.get_network_configuration(epoch); + let marker_on_chain = markers_from_chain.get_network_configuration(epoch); + if marker_on_chain.is_some() { + if marker_to_import != marker_on_chain { + return Err( + ProtocolConfigurationVerifierError::NotSameConfigurationForEpoch(epoch), + ); + } + } else { + warn!( + &self.logger, + "No configuration found on chain for {}, this is normal behavior if there is no protocol configuration marker on chain", + epoch + ); + } + } + Ok(()) + } /// Generate TxDatum for Protocol Configuration pub fn generate_tx_datum( @@ -120,27 +153,55 @@ impl From for ProtocolConfigurationForEpoch } } +fn to_configuration_computer_from_markers( + configs: Vec, +) -> ConfigurationComputerFromMarkers { + let mut markers = BTreeMap::new(); + + for config in configs { + markers.insert(config.epoch, ProtocolConfigurationForEpoch::from(config)); + } + ConfigurationComputerFromMarkers::new(markers) +} + #[cfg(test)] mod tests { - use mithril_common::entities::{ - BlockNumber, BlockNumberOffset, CardanoBlocksTransactionsSigningConfig, - CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, SignedEntityTypeDiscriminants, + use mithril_common::{ + entities::{ + BlockNumber, BlockNumberOffset, CardanoBlocksTransactionsSigningConfig, + CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, + SignedEntityTypeDiscriminants, + }, + test::double::Dummy, }; - use std::collections::BTreeSet; + use std::collections::{BTreeMap, BTreeSet}; use crate::test::TestLogger; use super::*; - fn build_tools() -> ProtocolConfigurationTools { + fn build_tools_dummy() -> ProtocolConfigurationTools { let configuration = ProtocolConfigurationToolsConfiguration { network: CardanoNetwork::TestNet(42), epoch: Epoch(30), - on_chain_configurations: HashMap::new(), + on_chain_configurations: ConfigurationComputerFromMarkers::new(BTreeMap::new()), }; ProtocolConfigurationTools::new(configuration, TestLogger::stdout()) } + fn build_tools( + current_epoch: Epoch, + on_chain_configurations: ConfigurationComputerFromMarkers, + logger: Logger, + ) -> ProtocolConfigurationTools { + let configuration = ProtocolConfigurationToolsConfiguration { + network: CardanoNetwork::TestNet(42), + epoch: current_epoch, + on_chain_configurations, + }; + ProtocolConfigurationTools::new(configuration, logger) + } + #[test] fn test_from_human_readable_protocol_configuration() { let human_readable_conf = HumanReadableProtocolConfiguration { @@ -215,9 +276,278 @@ mod tests { cardano_blocks_transactions_signing_config: None, }]; let signer = ProtocolConfigurationMarkersSigner::create_deterministic_signer(); - let tools = build_tools(); + let tools = build_tools_dummy(); tools .generate_tx_datum(configurations, &signer) .expect("generate_tx_datum should not fail"); } + + mod verify_configurations_against_chain { + use super::*; + + /// instanciate a unique ProtocolConfigurationForEpoch based on char + fn fake_configuration(conf: char) -> ProtocolConfigurationForEpoch { + ProtocolConfigurationForEpoch { + protocol_parameters: ProtocolParameters { + k: conf as u64, + m: conf as u64, + phi_f: 1.2, + }, + cardano_transactions: Some(CardanoTransactionsSigningConfig::dummy()), + cardano_blocks_transactions: Some(CardanoBlocksTransactionsSigningConfig::dummy()), + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + ]), + } + } + + /// Instanciate a HumanReadableProtocolConfiguration at epoch with a unique char configuration + fn fake_configuration_to_import( + epoch: Epoch, + conf: char, + ) -> HumanReadableProtocolConfiguration { + HumanReadableProtocolConfiguration { + epoch, + protocol_parameters: ProtocolParameters { + k: conf as u64, + m: conf as u64, + phi_f: 1.2, + }, + cardano_transaction_signing_config: Some(CardanoTransactionsSigningConfig::dummy()), + cardano_blocks_transactions_signing_config: Some( + CardanoBlocksTransactionsSigningConfig::dummy(), + ), + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, + SignedEntityTypeDiscriminants::CardanoStakeDistribution, + ]), + } + } + + fn build_on_chain_markers( + configurations: Vec<(Epoch, char)>, + ) -> ConfigurationComputerFromMarkers { + let mut on_chain_markers = BTreeMap::new(); + for conf in configurations { + on_chain_markers.insert(conf.0, fake_configuration(conf.1)); + } + ConfigurationComputerFromMarkers::new(on_chain_markers) + } + + fn build_configurations_to_import( + configurations: Vec<(Epoch, char)>, + ) -> Vec { + configurations + .iter() + .map(|conf| fake_configuration_to_import(conf.0, conf.1)) + .collect() + } + + #[test] + fn ok_with_only_one_same_epoch_conf_in_offset_window() { + let (logger, log_inspector) = TestLogger::memory(); + + let current_epoch = Epoch(47); + let mut on_chain_markers = BTreeMap::new(); + on_chain_markers.insert(Epoch(38), fake_configuration('A')); //conf outside offset window + on_chain_markers.insert(Epoch(44), fake_configuration('B')); //conf inside offset window + let on_chain_configurations = ConfigurationComputerFromMarkers::new(on_chain_markers); + + let configurations_to_import = + build_configurations_to_import(vec![(Epoch(44), 'B'), (Epoch(56), 'Z')]); + + let tools = build_tools(current_epoch, on_chain_configurations, logger); + assert!( + tools + .verify_configurations_against_chain(configurations_to_import) + .is_ok() + ); + + assert!( + log_inspector.contains_log("Verifying configurations for epoch range [44..=47]") + ) + } + + #[test] + fn ok_with_only_one_same_epoch_conf_outside_offset_window_with_fallback() { + let current_epoch = Epoch(47); + let mut on_chain_markers = BTreeMap::new(); + on_chain_markers.insert(Epoch(31), fake_configuration('A')); //conf outside offset window + on_chain_markers.insert(Epoch(38), fake_configuration('B')); //conf outside offset window + let on_chain_configurations = ConfigurationComputerFromMarkers::new(on_chain_markers); + + let configurations_to_import = vec![ + fake_configuration_to_import(Epoch(38), 'B'), + fake_configuration_to_import(Epoch(56), 'Z'), + ]; + + let tools = build_tools(current_epoch, on_chain_configurations, TestLogger::stdout()); + + assert!( + tools + .verify_configurations_against_chain(configurations_to_import) + .is_ok() + ); + } + + #[test] + fn ok_with_only_one_same_conf_at_different_epoch() { + let current_epoch = Epoch(47); + let mut on_chain_markers = BTreeMap::new(); + on_chain_markers.insert(Epoch(31), fake_configuration('A')); //conf outside offset window + on_chain_markers.insert(Epoch(38), fake_configuration('B')); //conf outside offset window + let on_chain_configurations = ConfigurationComputerFromMarkers::new(on_chain_markers); + + let configurations_to_import = vec![ + fake_configuration_to_import(Epoch(40), 'B'), + fake_configuration_to_import(Epoch(56), 'Z'), + ]; + + let tools = build_tools(current_epoch, on_chain_configurations, TestLogger::stdout()); + + assert!( + tools + .verify_configurations_against_chain(configurations_to_import) + .is_ok() + ); + } + + #[test] + fn ko_because_last_known_on_chain_configuration_b_for_offset_window_is_not_repeated() { + let current_epoch = Epoch(47); + let mut on_chain_markers = BTreeMap::new(); + on_chain_markers.insert(Epoch(31), fake_configuration('A')); //conf outside offset window + on_chain_markers.insert(Epoch(38), fake_configuration('B')); //conf outside offset window + let on_chain_configurations = ConfigurationComputerFromMarkers::new(on_chain_markers); + + let configurations_to_import = vec![ + fake_configuration_to_import(Epoch(40), 'C'), + fake_configuration_to_import(Epoch(56), 'Z'), + ]; + + let tools = build_tools(current_epoch, on_chain_configurations, TestLogger::stdout()); + let result = tools.verify_configurations_against_chain(configurations_to_import); + + assert!(matches!( + result.unwrap_err(), + ProtocolConfigurationVerifierError::NotSameConfigurationForEpoch(Epoch(44)) + )); + } + + #[test] + fn full_offset_window_have_to_be_repeated_if_it_have_different_configuration() { + let current_epoch = Epoch(47); + let mut on_chain_markers = BTreeMap::new(); + on_chain_markers.insert(Epoch(43), fake_configuration('A')); //conf outside offset window + on_chain_markers.insert(Epoch(44), fake_configuration('B')); //conf inside offset window + on_chain_markers.insert(Epoch(45), fake_configuration('C')); //conf inside offset window + on_chain_markers.insert(Epoch(46), fake_configuration('D')); //conf inside offset window + on_chain_markers.insert(Epoch(47), fake_configuration('E')); //conf inside offset window + let on_chain_configurations = ConfigurationComputerFromMarkers::new(on_chain_markers); + + let configurations_to_import = vec![ + fake_configuration_to_import(Epoch(44), 'B'), + fake_configuration_to_import(Epoch(45), 'C'), + fake_configuration_to_import(Epoch(46), 'D'), + fake_configuration_to_import(Epoch(47), 'E'), + fake_configuration_to_import(Epoch(53), 'Z'), + ]; + + let tools = build_tools( + current_epoch, + on_chain_configurations.clone(), + TestLogger::stdout(), + ); + assert!( + tools + .verify_configurations_against_chain(configurations_to_import) + .is_ok() + ); + + //It fail if one of epoch/conf from offset window is not repeated + let bad_configurations_to_import = vec![ + fake_configuration_to_import(Epoch(44), 'B'), + fake_configuration_to_import(Epoch(45), 'X'), + fake_configuration_to_import(Epoch(46), 'D'), + fake_configuration_to_import(Epoch(47), 'E'), + fake_configuration_to_import(Epoch(53), 'Z'), + ]; + + let tools = build_tools(current_epoch, on_chain_configurations, TestLogger::stdout()); + let result = tools.verify_configurations_against_chain(bad_configurations_to_import); + + assert!(matches!( + result.unwrap_err(), + ProtocolConfigurationVerifierError::NotSameConfigurationForEpoch(Epoch(45)) + )); + } + + #[test] + fn window_to_repeat_dont_have_to_be_exactly_at_same_epoch_as_long_as_it_can_fallback_to_same_configuration() + { + let current_epoch = Epoch(47); + let on_chain_configurations = + build_on_chain_markers(vec![(Epoch(30), 'A'), (Epoch(44), 'B'), (Epoch(47), 'B')]); + + let configurations_to_import = vec![ + fake_configuration_to_import(Epoch(32), 'A'), + fake_configuration_to_import(Epoch(40), 'B'), + fake_configuration_to_import(Epoch(53), 'Z'), + ]; + + let tools = build_tools( + current_epoch, + on_chain_configurations.clone(), + TestLogger::stdout(), + ); + assert!( + tools + .verify_configurations_against_chain(configurations_to_import) + .is_ok() + ); + } + + #[test] + fn verification_with_no_markers_on_chain_should_be_ok() { + let current_epoch = Epoch(47); + let on_chain_configurations = ConfigurationComputerFromMarkers::new(BTreeMap::new()); + + let configurations_to_import = vec![fake_configuration_to_import(Epoch(53), 'Z')]; + + let tools = build_tools( + current_epoch, + on_chain_configurations.clone(), + TestLogger::stdout(), + ); + assert!( + tools + .verify_configurations_against_chain(configurations_to_import) + .is_ok() + ); + } + + #[test] + fn verification_with_no_markers_to_import_should_be_ko() { + let current_epoch = Epoch(47); + let on_chain_configurations = + build_on_chain_markers(vec![(Epoch(30), 'A'), (Epoch(44), 'B')]); + + let tools = build_tools( + current_epoch, + on_chain_configurations.clone(), + TestLogger::stdout(), + ); + let result = tools.verify_configurations_against_chain(vec![]); + + assert!(matches!( + result.unwrap_err(), + ProtocolConfigurationVerifierError::NotSameConfigurationForEpoch(Epoch(44)) + )); + } + } } From 7c71d4e634a921d98fb3f4ce844ac4d570e820cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Mon, 27 Jul 2026 16:37:24 +0200 Subject: [PATCH 11/14] feature(protocol-config, aggregator): use ciborium encoding/decoding for protocol configutation markers --- Cargo.lock | 2 + internal/mithril-protocol-config/Cargo.toml | 2 + .../src/protocol_configuration_reader.rs | 59 ++++++++++++++----- .../src/test/double/dummy_adatper.rs | 6 +- .../src/tools/protocol_configuration.rs | 2 +- 5 files changed, 51 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d6a2af5833..6848ada5e0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4597,6 +4597,8 @@ version = "0.1.11" dependencies = [ "anyhow", "async-trait", + "ciborium", + "hex", "httpmock", "mithril-aggregator-client", "mithril-cardano-node-chain", diff --git a/internal/mithril-protocol-config/Cargo.toml b/internal/mithril-protocol-config/Cargo.toml index 59007478d34..5a471a9b942 100644 --- a/internal/mithril-protocol-config/Cargo.toml +++ b/internal/mithril-protocol-config/Cargo.toml @@ -12,6 +12,8 @@ include = ["**/*.rs", "Cargo.toml", "README.md", ".gitignore"] [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } +ciborium = "0.2.2" +hex = { workspace = true } mithril-aggregator-client = { path = "../mithril-aggregator-client" } mithril-cardano-node-chain = { path = "../cardano-node/mithril-cardano-node-chain" } mithril-common = { path = "../../mithril-common" } diff --git a/internal/mithril-protocol-config/src/protocol_configuration_reader.rs b/internal/mithril-protocol-config/src/protocol_configuration_reader.rs index 910b9bd8c41..3f2f963495d 100644 --- a/internal/mithril-protocol-config/src/protocol_configuration_reader.rs +++ b/internal/mithril-protocol-config/src/protocol_configuration_reader.rs @@ -1,10 +1,11 @@ +use anyhow::Context; use async_trait::async_trait; +use hex::FromHex; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; use std::sync::Arc; use thiserror::Error; -use mithril_common::crypto_helper::{CodecParseError, SerDeShelleyFileFormat}; use mithril_common::entities::{ CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, SignedEntityTypeDiscriminants, @@ -13,6 +14,11 @@ use mithril_common::{StdError, StdResult}; use crate::configuration_computer::ConfigurationComputerFromMarkers; +/// Parse error +#[derive(Error, Debug)] +#[error("Codec parse error")] +pub struct ProtocolConfigurationParseError(#[source] StdError); + /// The cbor representation of a MithrilNetworkConfigurationForEpoch pub type CborProtocolConfigurationForEpoch = String; @@ -55,20 +61,41 @@ pub struct ProtocolConfigurationForEpoch { } impl ProtocolConfigurationForEpoch { - /// Serialize the ProtocolConfigurationForEpoch to a CBOR hex representation - pub fn to_cbor(&self) -> Result { - self.to_cbor_hex() + /// Serialize the structure to a CBOR bytes representation. + fn to_cbor_bytes(&self) -> Result, ProtocolConfigurationParseError> { + let mut cursor = std::io::Cursor::new(Vec::new()); + ciborium::ser::into_writer(&self, &mut cursor) + .with_context(|| "ProtocolConfigurationForEpoch can not serialize data to cbor") + .map_err(ProtocolConfigurationParseError)?; + + Ok(cursor.into_inner()) } - /// Deserialize the ProtocolConfigurationForEpoch from a CBOR hex representation - pub fn from_cbor(cbor: CborProtocolConfigurationForEpoch) -> Result { - Self::from_cbor_hex(&cbor) + /// Serialize the structure to a CBOR hex representation. + pub fn to_cbor_hex(&self) -> Result { + Ok(hex::encode(self.to_cbor_bytes()?)) } -} -impl SerDeShelleyFileFormat for ProtocolConfigurationForEpoch { - const TYPE: &'static str = "ProtocolConfigurationForEpoch"; - const DESCRIPTION: &'static str = ""; + /// Deserialize a type `T: Serialize + DeserializeOwned` from CBOR bytes representation. + fn from_cbor_bytes(bytes: &[u8]) -> Result { + let mut cursor = std::io::Cursor::new(&bytes); + let a: Self = ciborium::de::from_reader(&mut cursor) + .with_context(|| "ProtocolConfigurationForEpoch can not unserialize cbor data") + .map_err(ProtocolConfigurationParseError)?; + + Ok(a) + } + + /// Deserialize a type `T: Serialize + DeserializeOwned` from CBOR hex representation. + pub fn from_cbor_hex(hex: &str) -> Result { + let hex_vector = Vec::from_hex(hex) + .with_context(|| "ProtocolConfigurationForEpoch can not unserialize hex data") + .map_err(ProtocolConfigurationParseError)?; + + Self::from_cbor_bytes(&hex_vector) + .with_context(|| "ProtocolConfigurationForEpoch can not unserialize cbor data") + .map_err(ProtocolConfigurationParseError) + } } /// Adapters are responsible of technically reading the information of @@ -119,7 +146,7 @@ impl ProtocolConfigurationReader { let mut decoded_markers = BTreeMap::new(); for marker in markers { - let configuration = ProtocolConfigurationForEpoch::from_cbor(marker.configuration) + let configuration = ProtocolConfigurationForEpoch::from_cbor_hex(&marker.configuration) .map_err(|e| ProtocolConfigurationReaderError::AdapterFailure { message: format!( "Failed to parse protocol configuration for epoch {}", @@ -147,9 +174,9 @@ mod tests { #[test] fn to_cbor_from_cbor_conversion() { let mithril_network_configuration_for_epoch = ProtocolConfigurationForEpoch::dummy(); - let cbor = mithril_network_configuration_for_epoch.to_cbor().unwrap(); + let cbor = mithril_network_configuration_for_epoch.to_cbor_hex().unwrap(); let mithril_network_configuration_for_epoch_from_cbor = - ProtocolConfigurationForEpoch::from_cbor(cbor.clone()).unwrap(); + ProtocolConfigurationForEpoch::from_cbor_hex(&cbor).unwrap(); assert_eq!( mithril_network_configuration_for_epoch, mithril_network_configuration_for_epoch_from_cbor @@ -180,7 +207,7 @@ mod tests { step: BlockNumber(40), }), }; - let cbor = mithril_network_configuration_for_epoch.to_cbor().unwrap(); + let cbor = mithril_network_configuration_for_epoch.to_cbor_hex().unwrap(); assert_eq!(&cbor, expected_cbor); } @@ -208,7 +235,7 @@ mod tests { step: BlockNumber(40), }), } - .to_cbor() + .to_cbor_hex() .expect("shoud not fail"), }] } diff --git a/internal/mithril-protocol-config/src/test/double/dummy_adatper.rs b/internal/mithril-protocol-config/src/test/double/dummy_adatper.rs index 29bdf9457ed..6818e8df4af 100644 --- a/internal/mithril-protocol-config/src/test/double/dummy_adatper.rs +++ b/internal/mithril-protocol-config/src/test/double/dummy_adatper.rs @@ -65,19 +65,19 @@ mod tests { ProtocolConfigurationMarker::new( Epoch(1), ProtocolConfigurationForEpoch::dummy() - .to_cbor() + .to_cbor_hex() .expect("should not fail"), ), ProtocolConfigurationMarker::new( Epoch(2), ProtocolConfigurationForEpoch::dummy() - .to_cbor() + .to_cbor_hex() .expect("should not fail"), ), ProtocolConfigurationMarker::new( Epoch(3), ProtocolConfigurationForEpoch::dummy() - .to_cbor() + .to_cbor_hex() .expect("should not fail"), ), ]; diff --git a/mithril-aggregator/src/tools/protocol_configuration.rs b/mithril-aggregator/src/tools/protocol_configuration.rs index e847d11a70a..31976bfec3d 100644 --- a/mithril-aggregator/src/tools/protocol_configuration.rs +++ b/mithril-aggregator/src/tools/protocol_configuration.rs @@ -126,7 +126,7 @@ impl ProtocolConfigurationTools { configuration.clone().into(); let marker: ProtocolConfigurationMarker = ProtocolConfigurationMarker::new( configuration.epoch, - protocol_configuration_for_epoch.to_cbor()?, + protocol_configuration_for_epoch.to_cbor_hex()?, ); markers.push(marker); } From 08590728ba09015e0438e0560f633ff608425c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Mon, 27 Jul 2026 18:08:04 +0200 Subject: [PATCH 12/14] feature(aggregator): check enabled signed entity types and configuration consistency for protocol-configuration import command --- .../protocol_configuration_command.rs | 137 ++++++++++++------ 1 file changed, 93 insertions(+), 44 deletions(-) diff --git a/mithril-aggregator/src/commands/protocol_configuration_command.rs b/mithril-aggregator/src/commands/protocol_configuration_command.rs index 4a89784ba15..fb05bfd84e9 100644 --- a/mithril-aggregator/src/commands/protocol_configuration_command.rs +++ b/mithril-aggregator/src/commands/protocol_configuration_command.rs @@ -10,6 +10,7 @@ use std::{ path::PathBuf, sync::Arc, }; +use thiserror::Error; use mithril_cardano_node_chain::chain_observer::ChainObserverType; use mithril_common::StdResult; @@ -26,6 +27,15 @@ use mithril_doc::{Documenter, StructDoc}; use crate::{ConfigurationSource, ExecutionEnvironment, extract_all}; use crate::{dependency_injection::DependenciesBuilder, tools::ProtocolConfigurationTools}; +#[derive(Debug, Error)] +pub enum UserConfImportVerificationError { + #[error("Protocol parameters must be non-zero: {0:?}")] + ZeroValueProtocolParameters(ProtocolParameters), + + #[error("enabled_signed_entity_types contains '{0:?}' without any associed configuration")] + EnabledSignedEntityTypeWithoutConfiguration(SignedEntityTypeDiscriminants), +} + #[derive(Debug, Clone, Deserialize, Documenter)] pub struct ProtocolConfigurationParametersConfiguration { /// Cardano Network Magic number @@ -275,44 +285,49 @@ impl ImportProtocolConfigurationSubCommand { pub fn verify_protocol_configurations( configurations: &Vec, - ) -> StdResult<()> { + ) -> Result<(), UserConfImportVerificationError> { //TODO verify non zero protocol parameters (other non zero attributes ?) for config in configurations { if config.protocol_parameters.k == 0 || config.protocol_parameters.m == 0 || config.protocol_parameters.phi_f == 0.0 { - return Err(anyhow::anyhow!( - "Protocol parameters must be non-zero: {:?}", - config.protocol_parameters - )); + return Err( + UserConfImportVerificationError::ZeroValueProtocolParameters( + config.protocol_parameters.clone(), + ), + ); + } + if config + .enabled_signed_entity_types + .contains(&SignedEntityTypeDiscriminants::CardanoTransactions) + && config.cardano_transaction_signing_config.is_none() + { + return Err( + UserConfImportVerificationError::EnabledSignedEntityTypeWithoutConfiguration( + SignedEntityTypeDiscriminants::CardanoTransactions, + ), + ); + } + if config + .enabled_signed_entity_types + .contains(&SignedEntityTypeDiscriminants::CardanoBlocksTransactions) + && config.cardano_blocks_transactions_signing_config.is_none() + { + return Err( + UserConfImportVerificationError::EnabledSignedEntityTypeWithoutConfiguration( + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + ), + ); } } - //TODO verify epoch consistency (no epoch smaller than latest export ?) - //TODO verify that if CardanoBlocksSigningConfig is filled that CardanoBlocksTransactions is in available signed entity types + Ok(()) } pub fn extract_config(_parent: String) -> HashMap { HashMap::new() } - - // to delete, moved to dep injection - // /// Create era reader adapter from configuration settings. - // fn build_protocol_configuration_reader_adapter( - // chain_observer: Arc, - // adapter_type: ProtocolConfigurationReaderAdapterType, - // adapter_params: Option, - // ) -> StdResult> { - // ProtocolConfigurationReaderAdapterBuilder::new(&adapter_type, &adapter_params) - // .build(chain_observer) - // .with_context(|| { - // format!( - // "Configuration: can not create protocol configuration reader for adapter '{}'.", - // adapter_type - // ) - // }) - // } } #[cfg(test)] @@ -321,31 +336,65 @@ mod tests { use super::*; - #[test] - fn test_verify_protocol_configurations_should_throw_error_with_protocol_parameter_at_zero() { - let protocol_parameters_list_to_test = [ - ProtocolParameters::new(0, 1, 0.123), - ProtocolParameters::new(1, 0, 0.123), - ProtocolParameters::new(1, 1, 0.0), - ]; - - for protocol_parameters in protocol_parameters_list_to_test { + mod verify_protocol_configurations { + + use super::*; + + #[test] + fn should_throw_error_with_protocol_parameter_at_zero() { + let protocol_parameters_list_to_test = [ + ProtocolParameters::new(0, 1, 0.123), + ProtocolParameters::new(1, 0, 0.123), + ProtocolParameters::new(1, 1, 0.0), + ]; + + for protocol_parameters in protocol_parameters_list_to_test { + let configurations = vec![HumanReadableProtocolConfiguration { + protocol_parameters: protocol_parameters.clone(), + ..Dummy::dummy() + }]; + + ImportProtocolConfigurationSubCommand::verify_protocol_configurations( + &configurations, + ) + .expect_err( + &format!( + "Protocol parameters must be non-zero: {:?}", + protocol_parameters + ) + .to_string(), + ); + } + } + + #[test] + fn shoud_throw_error_if_enabled_entity_types_contains_cardano_transactions_without_configuration() + { let configurations = vec![HumanReadableProtocolConfiguration { - protocol_parameters: protocol_parameters.clone(), + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + cardano_transaction_signing_config: None, ..Dummy::dummy() }]; - let result = ImportProtocolConfigurationSubCommand::verify_protocol_configurations( - &configurations, - ); + ImportProtocolConfigurationSubCommand::verify_protocol_configurations(&configurations) + .expect_err("enabled_signed_entity_types contains 'CardanoTransactions' without any associed configuration"); + } - assert_eq!( - result.unwrap_err().to_string(), - format!( - "Protocol parameters must be non-zero: {:?}", - protocol_parameters - ) - ); + #[test] + fn shoud_throw_error_if_enabled_entity_types_contains_cardano_blocks_transactions_without_configuration() + { + let configurations = vec![HumanReadableProtocolConfiguration { + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoBlocksTransactions, + ]), + cardano_blocks_transactions_signing_config: None, + ..Dummy::dummy() + }]; + + ImportProtocolConfigurationSubCommand::verify_protocol_configurations(&configurations) + .expect_err("enabled_signed_entity_types contains 'CardanoBlocksTransactions' without any associed configuration"); } } From 70d124fa240d5a152852f165e47d3ed40dae5e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Mon, 27 Jul 2026 18:34:12 +0200 Subject: [PATCH 13/14] WIP: checking that datum of protocol configuration does not exceed 10 KB --- .../src/tools/protocol_configuration.rs | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/mithril-aggregator/src/tools/protocol_configuration.rs b/mithril-aggregator/src/tools/protocol_configuration.rs index 31976bfec3d..f030c8b401e 100644 --- a/mithril-aggregator/src/tools/protocol_configuration.rs +++ b/mithril-aggregator/src/tools/protocol_configuration.rs @@ -18,13 +18,17 @@ use crate::{ dependency_injection::ProtocolConfigurationCommandDependenciesContainer, }; +const EPOCH_OFFSET: u64 = 3; +const DATUM_MAX_SIZE_KB: usize = 10; + #[derive(Debug, Error)] pub enum ProtocolConfigurationVerifierError { #[error("Configuration to import for {0:?}, is not the same has configuration on chain")] NotSameConfigurationForEpoch(Epoch), -} -const EPOCH_OFFSET: u64 = 3; + #[error("Size of datum is {0:?} KB (Maximum authorized size is {DATUM_MAX_SIZE_KB} KB")] + DatumMaxSizeExceeded(f64), +} type ProtocolConfigurationToolsResult = StdResult; @@ -140,6 +144,24 @@ impl ProtocolConfigurationTools { .build()?; Ok(tx_datum.0) } + + fn verify_tx_datum_size( + &self, + datum: String, + ) -> Result<(), ProtocolConfigurationVerifierError> { + let size_bytes = datum.len(); + let size_kb = size_bytes as f64 / 1024.0; + + println!("Datum size: {:.2} KB", size_kb); + + if size_bytes > DATUM_MAX_SIZE_KB * 1024 { + return Err(ProtocolConfigurationVerifierError::DatumMaxSizeExceeded( + size_kb, + )); + } + + Ok(()) + } } impl From for ProtocolConfigurationForEpoch { @@ -277,9 +299,34 @@ mod tests { }]; let signer = ProtocolConfigurationMarkersSigner::create_deterministic_signer(); let tools = build_tools_dummy(); - tools + assert!(tools.generate_tx_datum(configurations, &signer).is_ok()); + } + + #[test] + fn verify_tx_datum_size_is_ok_with_datum_under_10_kb() { + let tools = build_tools_dummy(); + assert!(tools.verify_tx_datum_size("tx datum under 10 kb".to_string()).is_ok()); + } + + #[test] + fn verify_tx_datum_size_is_ok_with_datum_from_dummy_configuration() { + let configurations = vec![ + HumanReadableProtocolConfiguration { + epoch: Epoch(42), + ..Dummy::dummy() + }, + HumanReadableProtocolConfiguration { + epoch: Epoch(53), + ..Dummy::dummy() + }, + ]; + let signer = ProtocolConfigurationMarkersSigner::create_deterministic_signer(); + let tools = build_tools_dummy(); + let datum = tools .generate_tx_datum(configurations, &signer) .expect("generate_tx_datum should not fail"); + + assert!(tools.verify_tx_datum_size(datum).is_ok()); } mod verify_configurations_against_chain { From 5504bb76311c10cd11f7e731b91257a3babcd1c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Tue, 28 Jul 2026 17:28:45 +0200 Subject: [PATCH 14/14] feature(aggretor, protocol-config): cleanning builder adapter mecanism, and rework protocol-configuration command parameters --- .../src/adapters/builder.rs | 160 ------------------ .../src/adapters/mod.rs | 5 - .../protocol_configuration_command.rs | 42 ++--- mithril-aggregator/src/configuration.rs | 32 +++- .../builder/support/compatibility.rs | 20 +-- .../src/tools/protocol_configuration.rs | 8 +- 6 files changed, 54 insertions(+), 213 deletions(-) delete mode 100644 internal/mithril-protocol-config/src/adapters/builder.rs diff --git a/internal/mithril-protocol-config/src/adapters/builder.rs b/internal/mithril-protocol-config/src/adapters/builder.rs deleted file mode 100644 index f695b699464..00000000000 --- a/internal/mithril-protocol-config/src/adapters/builder.rs +++ /dev/null @@ -1,160 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::{fmt::Display, sync::Arc}; -use thiserror::Error; - -use mithril_cardano_node_chain::chain_observer::ChainObserver; -use mithril_cardano_node_chain::entities::ChainAddress; -use mithril_common::StdError; -use mithril_common::crypto_helper::ProtocolConfigurationMarkersVerifierVerificationKey; - -use crate::ProtocolConfigurationReaderAdapter; -use crate::adapters::ProtocolConfigurationReaderCardanoChainAdapter; - -/// Type of protocol configuration reader adapters available -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "lowercase")] -pub enum AdapterType { - /// Cardano chain adapter. - #[serde(rename = "cardano-chain")] - CardanoChain, - // /// File adapter. - // File, - // /// Dummy adapter. - // Dummy, - // /// Bootstrap adapter. - // Bootstrap, - // /// Bootstrap latest adapter. - // #[serde(rename = "bootstrap-latest")] - // BootstrapLatest, -} - -impl Display for AdapterType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::CardanoChain => write!(f, "cardano chain"), - // Self::Bootstrap => write!(f, "bootstrap"), - // Self::Dummy => write!(f, "dummy"), - // Self::File => write!(f, "file"), - // Self::BootstrapLatest => write!(f, "bootstrap latest"), - } - } -} - -/// Error type for protocol configuration reader adapter builder service. -#[derive(Error, Debug)] -pub enum AdapterBuilderError { - /// Missing parameters error. - #[error("protocol configuration reader adapter parameters are missing")] - MissingParameters(), - - /// Parameters parse error. - #[error("protocol configuration reader adapter parameters parse error")] - ParseParameters(#[source] serde_json::Error), - - /// Parameters decode error. - #[error("protocol configuration reader adapter parameters decode error")] - Decode(#[source] StdError), -} - -/// Protocol configuration adapter builder -pub struct AdapterBuilder { - adapter_type: AdapterType, - adapter_params: Option, -} - -impl AdapterBuilder { - /// Protocol configuration reader adapter builder factory - pub fn new(adapter_type: &AdapterType, adapter_params: &Option) -> Self { - Self { - adapter_type: adapter_type.to_owned(), - adapter_params: adapter_params.to_owned(), - } - } - - /// Create protocol configuration reader adapter from configuration settings. - pub fn build( - &self, - chain_observer: Arc, - ) -> Result, AdapterBuilderError> { - match self.adapter_type { - AdapterType::CardanoChain => { - #[derive(Deserialize)] - struct CardanoChainAdapterConfig { - address: ChainAddress, - verification_key: ProtocolConfigurationMarkersVerifierVerificationKey, - } - - let adapter_config: CardanoChainAdapterConfig = serde_json::from_str( - self.adapter_params - .as_ref() - .ok_or_else(AdapterBuilderError::MissingParameters)?, - ) - .map_err(AdapterBuilderError::ParseParameters)?; - - Ok(Arc::new( - ProtocolConfigurationReaderCardanoChainAdapter::new( - adapter_config.address, - chain_observer, - adapter_config.verification_key, - ), - )) - } // AdapterType::File => { - // #[derive(Deserialize)] - // struct EraReaderFileAdapterConfig { - // markers_file: PathBuf, - // } - - // let adapter_config: EraReaderFileAdapterConfig = serde_json::from_str( - // self.adapter_params - // .as_ref() - // .ok_or_else(AdapterBuilderError::MissingParameters)?, - // ) - // .map_err(AdapterBuilderError::ParseParameters)?; - // let file_adapter = EraReaderFileAdapter::new(adapter_config.markers_file); - - // Ok(Arc::new(file_adapter)) - // } - // AdapterType::Dummy => { - // #[derive(Deserialize)] - // struct EraReaderDummyAdapterConfig { - // markers: Vec, - // } - - // let adapter_config: EraReaderDummyAdapterConfig = serde_json::from_str( - // self.adapter_params - // .as_ref() - // .ok_or_else(AdapterBuilderError::MissingParameters)?, - // ) - // .map_err(AdapterBuilderError::ParseParameters)?; - // let dummy_adapter = EraReaderDummyAdapter::default(); - // dummy_adapter.set_markers(adapter_config.markers); - - // Ok(Arc::new(dummy_adapter)) - // } - // AdapterType::Bootstrap => Ok(Arc::new(EraReaderBootstrapAdapter)), - // AdapterType::BootstrapLatest => Ok(Arc::new(EraReaderBootstrapLatestAdapter)), - } - } -} - -#[cfg(test)] -mod test { - use mithril_cardano_node_chain::test::double::FakeChainObserver; - - use super::*; - - const GOLDEN_ADAPTER_PARAMS: &str = r#"{ - "address":"addr_test1qrv5xfwh043mlc3vk5d97s4nmhxu7cmleyssvhx37gkfyejfe8d38v3vsfgetjafgrsdc49krug8wf04h5rmtengtejqlxrksk", - "verification_key":"5b35352c3232382c3134342c38372c3133382c3133362c34382c382c31342c3138372c38352c3134382c39372c3233322c3235352c3232392c33382c3234342c3234372c3230342c3139382c31332c33312c3232322c32352c3136342c35322c3130322c39312c3132302c3230382c3134375d" - }"#; - - #[test] - fn golden_test_for_cardano_chain() { - AdapterBuilder::new( - &AdapterType::CardanoChain, - &Some(GOLDEN_ADAPTER_PARAMS.to_owned()), - ) - .build(Arc::new(FakeChainObserver::new(None))) - .expect("building an cardano chain era reader with golden params should not fail"); - } -} diff --git a/internal/mithril-protocol-config/src/adapters/mod.rs b/internal/mithril-protocol-config/src/adapters/mod.rs index f453bc5a150..d05ffd38aee 100644 --- a/internal/mithril-protocol-config/src/adapters/mod.rs +++ b/internal/mithril-protocol-config/src/adapters/mod.rs @@ -1,12 +1,7 @@ //! Module dedicated to ProtocolConfigurationReaderAdapter implementations. -mod builder; mod cardano_chain; -pub use builder::{ - AdapterBuilder as ProtocolConfigurationReaderAdapterBuilder, - AdapterType as ProtocolConfigurationReaderAdapterType, -}; pub use cardano_chain::{ CardanoChainAdapter as ProtocolConfigurationReaderCardanoChainAdapter, ProtocolConfigurationMarkersPayload as ProtocolConfigurationMarkersPayloadCardanoChain, diff --git a/mithril-aggregator/src/commands/protocol_configuration_command.rs b/mithril-aggregator/src/commands/protocol_configuration_command.rs index fb05bfd84e9..24a850adcf5 100644 --- a/mithril-aggregator/src/commands/protocol_configuration_command.rs +++ b/mithril-aggregator/src/commands/protocol_configuration_command.rs @@ -13,6 +13,7 @@ use std::{ use thiserror::Error; use mithril_cardano_node_chain::chain_observer::ChainObserverType; +use mithril_cli_helper::serde_deserialization; use mithril_common::StdResult; use mithril_common::crypto_helper::{ ProtocolConfigurationMarkersSigner, ProtocolConfigurationMarkersVerifierSecretKey, @@ -24,7 +25,10 @@ use mithril_common::entities::{ }; use mithril_doc::{Documenter, StructDoc}; -use crate::{ConfigurationSource, ExecutionEnvironment, extract_all}; +use crate::{ + ConfigurationSource, ExecutionEnvironment, + configuration::ProtocolConfigurationReaderParameters, extract_all, +}; use crate::{dependency_injection::DependenciesBuilder, tools::ProtocolConfigurationTools}; #[derive(Debug, Error)] @@ -38,11 +42,9 @@ pub enum UserConfImportVerificationError { #[derive(Debug, Clone, Deserialize, Documenter)] pub struct ProtocolConfigurationParametersConfiguration { - /// Cardano Network Magic number - /// - /// useful for TestNet & DevNet - #[example = "`1097911063` or `42`"] - pub network_magic: Option, + /// Path of the socket opened by the Cardano node + #[example = "`/ipc/node.socket`"] + pub cardano_node_socket_path: PathBuf, /// Cardano network #[example = "`mainnet` or `preprod` or `devnet`"] @@ -50,6 +52,13 @@ pub struct ProtocolConfigurationParametersConfiguration { /// Cardano chain observer type pub chain_observer_type: ChainObserverType, + + /// Protocol configuration Reader Adapter Parameters + #[example = "\ + `{ \"address\": \"address\", \"verification_key\": \"key\" }`\ + "] + #[serde(deserialize_with = "serde_deserialization::string_or_struct")] + pub protocol_configuration_reader_adapter_params: ProtocolConfigurationReaderParameters, } impl ConfigurationSource for ProtocolConfigurationParametersConfiguration { @@ -57,8 +66,8 @@ impl ConfigurationSource for ProtocolConfigurationParametersConfiguration { ExecutionEnvironment::Production } - fn network_magic(&self) -> Option { - self.network_magic + fn cardano_node_socket_path(&self) -> PathBuf { + self.cardano_node_socket_path.clone() } fn network(&self) -> String { @@ -69,8 +78,8 @@ impl ConfigurationSource for ProtocolConfigurationParametersConfiguration { self.chain_observer_type.clone() } - fn store_retention_limit(&self) -> Option { - None + fn protocol_configuration_reader_parameters(&self) -> ProtocolConfigurationReaderParameters { + self.protocol_configuration_reader_adapter_params.clone() } } @@ -103,15 +112,6 @@ impl HumanReadableProtocolConfiguration { #[derive(Parser, Debug, Clone)] pub struct ProtocolConfigurationCommand { - /// Protocol configuration reader adapter type - #[clap(long, env = "PROTOCOL_CONFIGURATION_READER_ADAPTER_TYPE")] - pub protocol_configuration_reader_adapter_type: String, - - /// Protocol configation reader adapter parameters - /// example {"address":"your-address","verification_key":"your-verification-key"} - #[clap(long, env = "PROTOCOL_CONFIGURATION_READER_ADAPTER_PARAMS")] - pub protocol_configuration_reader_adapter_params: String, - /// commands #[clap(subcommand)] pub protocol_configuration_sub_command: ProtocolConfigurationSubCommand, @@ -243,7 +243,7 @@ impl ImportProtocolConfigurationSubCommand { let tools = ProtocolConfigurationTools::from_dependencies(dependencies) .await .with_context(|| "protocol-configuration-tools: initialization error")?; - // tools.verify_configuration_against_production(&protocol_configurations); + //tools.verify_configurations_against_chain(protocol_configurations.clone()); //4 - Generate Tx datum println!("Generating Tx datum ..."); @@ -408,7 +408,7 @@ mod tests { ImportProtocolConfigurationSubCommand::try_parse_from([ "import-markers", "--import-path", - "tests/human_readable_protocol_configuration.json", + "tests/human_readable_protocol_configuration_toto.json", "--target-path", "/tests/protocol_configuration_tx_datum", "--protocol-configuration-markers-secret-key", diff --git a/mithril-aggregator/src/configuration.rs b/mithril-aggregator/src/configuration.rs index bf5fb81f3d6..200c42f0a99 100644 --- a/mithril-aggregator/src/configuration.rs +++ b/mithril-aggregator/src/configuration.rs @@ -4,13 +4,14 @@ use std::str::FromStr; use anyhow::Context; use config::{ConfigError, Map, Source, Value, ValueKind}; -use mithril_protocol_config::adapters::ProtocolConfigurationReaderAdapterType; use semver::Version; use serde::Deserialize; use mithril_cardano_node_chain::chain_observer::ChainObserverType; use mithril_cli_helper::{register_config_value, serde_deserialization}; -use mithril_common::crypto_helper::{GenesisEd25519Signer, ManifestSigner}; +use mithril_common::crypto_helper::{ + GenesisEd25519Signer, ManifestSigner, ProtocolConfigurationMarkersVerifierVerificationKey, +}; use mithril_common::entities::{ BlockNumber, BlockNumberOffset, CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, CompressionAlgorithm, ConfigSecret, @@ -197,14 +198,9 @@ pub trait ConfigurationSource { panic!("era_reader_adapter_params is not implemented."); } - /// Protocol configuration reader adapter type - fn protocol_configuration_reader_adapter_type(&self) -> ProtocolConfigurationReaderAdapterType { - panic!("protocol_configuration_reader_adapter_type is not implemented."); - } - /// Protocol configuration reader adapter parameters - fn protocol_configuration_reader_adapter_params(&self) -> Option { - panic!("protocol_configuration_reader_adapter_params is not implemented."); + fn protocol_configuration_reader_parameters(&self) -> ProtocolConfigurationReaderParameters { + panic!("protocol_configuration_reader_parameters is not implemented."); } /// Configuration of the ancillary files signer @@ -783,6 +779,24 @@ impl FromStr for AncillaryFilesSignerConfig { } } +/// Configuration of the protocol Configuration Reader +#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] +pub struct ProtocolConfigurationReaderParameters { + /// Address + pub address: String, + + /// Verification key + pub verification_key: ProtocolConfigurationMarkersVerifierVerificationKey, +} + +impl FromStr for ProtocolConfigurationReaderParameters { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } +} + impl ServeCommandConfiguration { /// Create a sample configuration mainly for tests pub fn new_sample(tmp_path: PathBuf) -> Self { diff --git a/mithril-aggregator/src/dependency_injection/builder/support/compatibility.rs b/mithril-aggregator/src/dependency_injection/builder/support/compatibility.rs index e0c9e3a0e8e..c823f89b296 100644 --- a/mithril-aggregator/src/dependency_injection/builder/support/compatibility.rs +++ b/mithril-aggregator/src/dependency_injection/builder/support/compatibility.rs @@ -4,7 +4,7 @@ use mithril_common::api_version::APIVersionProvider; use mithril_common::entities::{Epoch, SupportedEra}; use mithril_era::adapters::{EraReaderAdapterBuilder, EraReaderDummyAdapter}; use mithril_era::{EraChecker, EraMarker, EraReader, EraReaderAdapter}; -use mithril_protocol_config::adapters::ProtocolConfigurationReaderAdapterBuilder; +use mithril_protocol_config::adapters::ProtocolConfigurationReaderCardanoChainAdapter; use mithril_protocol_config::test::double::ProtocolConfigurationReaderDummyAdapter; use mithril_protocol_config::{ProtocolConfigurationReader, ProtocolConfigurationReaderAdapter}; @@ -91,16 +91,14 @@ impl DependenciesBuilder { ) -> Result> { let protocol_configuration_adapter: Arc = match self.configuration.environment() { - ExecutionEnvironment::Production => ProtocolConfigurationReaderAdapterBuilder::new( - &self.configuration.protocol_configuration_reader_adapter_type(), - &self.configuration.protocol_configuration_reader_adapter_params(), - ) - .build(self.get_chain_observer().await?) - .map_err(|e| DependenciesBuilderError::Initialization { - message: "Could not build ProtocolConfigurationReader as dependency." - .to_string(), - error: Some(e.into()), - })?, + ExecutionEnvironment::Production => { + let parameters = self.configuration.protocol_configuration_reader_parameters(); + Arc::new(ProtocolConfigurationReaderCardanoChainAdapter::new( + parameters.address, + self.get_chain_observer().await?, + parameters.verification_key, + )) + } _ => Arc::new(ProtocolConfigurationReaderDummyAdapter::from_markers( vec![], //TODO )), diff --git a/mithril-aggregator/src/tools/protocol_configuration.rs b/mithril-aggregator/src/tools/protocol_configuration.rs index f030c8b401e..e544930424f 100644 --- a/mithril-aggregator/src/tools/protocol_configuration.rs +++ b/mithril-aggregator/src/tools/protocol_configuration.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; use anyhow::Context; use mithril_cardano_node_chain::entities::{TxDatumBuilder, TxDatumFieldValue}; use mithril_common::{ - CardanoNetwork, StdResult, crypto_helper::ProtocolConfigurationMarkersSigner, entities::Epoch, + StdResult, crypto_helper::ProtocolConfigurationMarkersSigner, entities::Epoch, }; use mithril_protocol_config::{ ProtocolConfigurationForEpoch, ProtocolConfigurationMarker, @@ -34,9 +34,6 @@ type ProtocolConfigurationToolsResult = StdResult; /// Configuration for the protocol configuration tools. pub struct ProtocolConfigurationToolsConfiguration { - /// Cardano network. - pub network: CardanoNetwork, - /// Current epoch. pub epoch: Epoch, @@ -73,7 +70,6 @@ impl ProtocolConfigurationTools { .await?; let configuration = ProtocolConfigurationToolsConfiguration { - network: dependencies.network, epoch, on_chain_configurations, }; @@ -204,7 +200,6 @@ mod tests { fn build_tools_dummy() -> ProtocolConfigurationTools { let configuration = ProtocolConfigurationToolsConfiguration { - network: CardanoNetwork::TestNet(42), epoch: Epoch(30), on_chain_configurations: ConfigurationComputerFromMarkers::new(BTreeMap::new()), }; @@ -217,7 +212,6 @@ mod tests { logger: Logger, ) -> ProtocolConfigurationTools { let configuration = ProtocolConfigurationToolsConfiguration { - network: CardanoNetwork::TestNet(42), epoch: current_epoch, on_chain_configurations, };