From 2aff742ebfd7443ef67545b04c9163c00fa47a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Mon, 22 Sep 2025 18:10:40 +0200 Subject: [PATCH 01/18] feature(protocol-config): init a new crate, with http impl of a new trait to retrieve epoch setting --- Cargo.lock | 21 + Cargo.toml | 3 +- README.md | 2 + internal/mithril-protocol-config/Cargo.toml | 40 + internal/mithril-protocol-config/README.md | 5 + .../src/http/aggregator_client.rs | 720 ++++++++++++++++++ .../src/http/http_impl.rs | 59 ++ .../mithril-protocol-config/src/http/mod.rs | 2 + .../mithril-protocol-config/src/interface.rs | 11 + internal/mithril-protocol-config/src/lib.rs | 10 + internal/mithril-protocol-config/src/model.rs | 25 + 11 files changed, 897 insertions(+), 1 deletion(-) create mode 100644 internal/mithril-protocol-config/Cargo.toml create mode 100644 internal/mithril-protocol-config/README.md create mode 100644 internal/mithril-protocol-config/src/http/aggregator_client.rs create mode 100644 internal/mithril-protocol-config/src/http/http_impl.rs create mode 100644 internal/mithril-protocol-config/src/http/mod.rs create mode 100644 internal/mithril-protocol-config/src/interface.rs create mode 100644 internal/mithril-protocol-config/src/lib.rs create mode 100644 internal/mithril-protocol-config/src/model.rs diff --git a/Cargo.lock b/Cargo.lock index 6abe1e5a704..54dd975294c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4405,6 +4405,27 @@ dependencies = [ "tokio", ] +[[package]] +name = "mithril-protocol-config" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "http 1.3.1", + "httpmock", + "mithril-common", + "mockall", + "reqwest", + "semver", + "serde", + "serde_json", + "slog", + "slog-async", + "slog-term", + "thiserror 2.0.16", + "tokio", +] + [[package]] name = "mithril-relay" version = "0.1.52" diff --git a/Cargo.toml b/Cargo.toml index fabdd2909de..c3f0cc44659 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,8 @@ members = [ "internal/mithril-doc", "internal/mithril-doc-derive", "internal/mithril-era", - "internal/mithril-metric", + "internal/mithril-metric", + "internal/mithril-protocol-config", "internal/mithril-persistence", "internal/mithril-resource-pool", "internal/mithril-ticker", diff --git a/README.md b/README.md index 920bd1a3ec2..283cc97477f 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,8 @@ This repository consists of the following parts: - [**Mithril persistence**](./internal/mithril-persistence): the **persistence** library that is used by **Mithril network** nodes. + - [**Mithril protocol config**](./internal/mithril-protocol-config): mechanisms to read and check the **configuration parameters** of a **Mithril network**. + - [**Mithril resource pool**](./internal/mithril-resource-pool): a **resource pool** mechanism that is used by **Mithril network** nodes. - [**Mithril ticker**](./internal/mithril-ticker): a **ticker** mechanism that reads time information from the chain and is used by **Mithril network** nodes. diff --git a/internal/mithril-protocol-config/Cargo.toml b/internal/mithril-protocol-config/Cargo.toml new file mode 100644 index 00000000000..953be99b68a --- /dev/null +++ b/internal/mithril-protocol-config/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "mithril-protocol-config" +version = "0.1.0" +description = "Configuraton parameters of the mithril network" +authors = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +repository = { workspace = true } + +[dependencies] +anyhow = { workspace = true } +async-trait = { workspace = true } +mithril-common = { path = "../../mithril-common" } +reqwest = { workspace = true, features = [ + "default", + "stream", + "gzip", + "zstd", + "deflate", + "brotli" +] } +semver = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +slog = { workspace = true, features = [ + "max_level_trace", + "release_max_level_debug", +] } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } + + +[dev-dependencies] +slog-async = { workspace = true } +slog-term = { workspace = true } +#criterion = { version = "0.7.0", features = ["html_reports", "async_tokio"] } +http = "1.3.1" +httpmock = "0.7.0" +mockall = { workspace = true } \ No newline at end of file diff --git a/internal/mithril-protocol-config/README.md b/internal/mithril-protocol-config/README.md new file mode 100644 index 00000000000..b7ad44b1301 --- /dev/null +++ b/internal/mithril-protocol-config/README.md @@ -0,0 +1,5 @@ +# Mithril-protocol-config + +**This is a work in progress** 🛠 + +This crate provides mechanisms to read and check the configuration parameters of a Mithril network. diff --git a/internal/mithril-protocol-config/src/http/aggregator_client.rs b/internal/mithril-protocol-config/src/http/aggregator_client.rs new file mode 100644 index 00000000000..a3321c5c571 --- /dev/null +++ b/internal/mithril-protocol-config/src/http/aggregator_client.rs @@ -0,0 +1,720 @@ +use anyhow::anyhow; +use async_trait::async_trait; +use reqwest::header::{self, HeaderValue}; +use reqwest::{self, Client, Proxy, RequestBuilder, Response, StatusCode}; +use semver::Version; +use slog::{Logger, debug, error, warn}; +use std::{io, sync::Arc, time::Duration}; +use thiserror::Error; + +use mithril_common::{ + MITHRIL_API_VERSION_HEADER, MITHRIL_SIGNER_VERSION_HEADER, StdError, + api_version::APIVersionProvider, + entities::{ClientError, ServerError}, + logging::LoggerExtensions, + messages::EpochSettingsMessage, +}; + +const JSON_CONTENT_TYPE: HeaderValue = HeaderValue::from_static("application/json"); + +const API_VERSION_MISMATCH_WARNING_MESSAGE: &str = + "OpenAPI version may be incompatible, please update your Mithril node to the latest version."; + +/// Error structure for the Aggregator Client. +#[derive(Error, Debug)] +pub enum AggregatorClientError { + /// The aggregator host has returned a technical error. + #[error("remote server technical error")] + RemoteServerTechnical(#[source] StdError), + + /// The aggregator host responded it cannot fulfill our request. + #[error("remote server logical error")] + RemoteServerLogical(#[source] StdError), + + /// Could not reach aggregator. + #[error("remote server unreachable")] + RemoteServerUnreachable(#[source] StdError), + + /// Unhandled status code + #[error("unhandled status code: {0}, response text: {1}")] + UnhandledStatusCode(StatusCode, String), + + /// Could not parse response. + #[error("json parsing failed")] + JsonParseFailed(#[source] StdError), + + /// Mostly network errors. + #[error("Input/Output error")] + IOError(#[from] io::Error), + + /// HTTP client creation error + #[error("HTTP client creation failed")] + HTTPClientCreation(#[source] StdError), + + /// Proxy creation error + #[error("proxy creation failed")] + ProxyCreation(#[source] StdError), + + /// No signer registration round opened yet + #[error("a signer registration round is not opened yet, please try again later")] + RegistrationRoundNotYetOpened(#[source] StdError), +} + +impl AggregatorClientError { + /// Create an `AggregatorClientError` from a response. + /// + /// This method is meant to be used after handling domain-specific cases leaving only + /// 4xx or 5xx status codes. + /// Otherwise, it will return an `UnhandledStatusCode` error. + pub async fn from_response(response: Response) -> Self { + let error_code = response.status(); + + if error_code.is_client_error() { + let root_cause = Self::get_root_cause(response).await; + Self::RemoteServerLogical(anyhow!(root_cause)) + } else if error_code.is_server_error() { + let root_cause = Self::get_root_cause(response).await; + match error_code.as_u16() { + 550 => Self::RegistrationRoundNotYetOpened(anyhow!(root_cause)), + _ => Self::RemoteServerTechnical(anyhow!(root_cause)), + } + } else { + let response_text = response.text().await.unwrap_or_default(); + Self::UnhandledStatusCode(error_code, response_text) + } + } + + async fn get_root_cause(response: Response) -> String { + let error_code = response.status(); + let canonical_reason = error_code.canonical_reason().unwrap_or_default().to_lowercase(); + let is_json = response + .headers() + .get(header::CONTENT_TYPE) + .is_some_and(|ct| JSON_CONTENT_TYPE == ct); + + if is_json { + let json_value: serde_json::Value = response.json().await.unwrap_or_default(); + + if let Ok(client_error) = serde_json::from_value::(json_value.clone()) { + format!( + "{}: {}: {}", + canonical_reason, client_error.label, client_error.message + ) + } else if let Ok(server_error) = + serde_json::from_value::(json_value.clone()) + { + format!("{}: {}", canonical_reason, server_error.message) + } else if json_value.is_null() { + canonical_reason.to_string() + } else { + format!("{canonical_reason}: {json_value}") + } + } else { + let response_text = response.text().await.unwrap_or_default(); + format!("{canonical_reason}: {response_text}") + } + } +} + +/// Trait for mocking and testing a `AggregatorClient` +#[cfg_attr(test, mockall::automock)] +#[async_trait] +pub trait AggregatorClient: Sync + Send { + /// Retrieves epoch settings from the aggregator + async fn retrieve_epoch_settings( + &self, + ) -> Result, AggregatorClientError>; +} + +/// AggregatorHTTPClient is a http client for an aggregator +pub struct AggregatorHTTPClient { + aggregator_endpoint: String, + relay_endpoint: Option, + api_version_provider: Arc, + timeout_duration: Option, + logger: Logger, +} + +impl AggregatorHTTPClient { + /// AggregatorHTTPClient factory + pub fn new( + aggregator_endpoint: String, + relay_endpoint: Option, + api_version_provider: Arc, + timeout_duration: Option, + logger: Logger, + ) -> Self { + let logger = logger.new_with_component_name::(); + debug!(logger, "New AggregatorHTTPClient created"); + Self { + aggregator_endpoint, + relay_endpoint, + api_version_provider, + timeout_duration, + logger, + } + } + + fn prepare_http_client(&self) -> Result { + let client = match &self.relay_endpoint { + Some(relay_endpoint) => Client::builder() + .proxy( + Proxy::all(relay_endpoint) + .map_err(|e| AggregatorClientError::ProxyCreation(anyhow!(e)))?, + ) + .build() + .map_err(|e| AggregatorClientError::HTTPClientCreation(anyhow!(e)))?, + None => Client::new(), + }; + + Ok(client) + } + + /// Forge a client request adding protocol version in the headers. + pub fn prepare_request_builder(&self, request_builder: RequestBuilder) -> RequestBuilder { + let request_builder = request_builder + .header( + MITHRIL_API_VERSION_HEADER, + self.api_version_provider + .compute_current_version() + .unwrap() + .to_string(), + ) + .header(MITHRIL_SIGNER_VERSION_HEADER, env!("CARGO_PKG_VERSION")); + + if let Some(duration) = self.timeout_duration { + request_builder.timeout(duration) + } else { + request_builder + } + } + + /// Check API version mismatch and log a warning if the aggregator's version is more recent. + fn warn_if_api_version_mismatch(&self, response: &Response) { + let aggregator_version = response + .headers() + .get(MITHRIL_API_VERSION_HEADER) + .and_then(|v| v.to_str().ok()) + .and_then(|s| Version::parse(s).ok()); + + let signer_version = self.api_version_provider.compute_current_version(); + + match (aggregator_version, signer_version) { + (Some(aggregator), Ok(signer)) if signer < aggregator => { + warn!(self.logger, "{}", API_VERSION_MISMATCH_WARNING_MESSAGE; + "aggregator_version" => %aggregator, + "signer_version" => %signer, + ); + } + (Some(_), Err(error)) => { + error!( + self.logger, + "Failed to compute the current signer API version"; + "error" => error.to_string() + ); + } + _ => {} + } + } +} + +#[async_trait] +impl AggregatorClient for AggregatorHTTPClient { + async fn retrieve_epoch_settings( + &self, + ) -> Result, AggregatorClientError> { + debug!(self.logger, "Retrieve epoch settings"); + let url = format!("{}/epoch-settings", self.aggregator_endpoint); + let response = self + .prepare_request_builder(self.prepare_http_client()?.get(url.clone())) + .send() + .await; + + match response { + Ok(response) => match response.status() { + StatusCode::OK => { + self.warn_if_api_version_mismatch(&response); + match response.json::().await { + Ok(message) => Ok(Some(message)), + Err(err) => Err(AggregatorClientError::JsonParseFailed(anyhow!(err))), + } + } + _ => Err(AggregatorClientError::from_response(response).await), + }, + Err(err) => Err(AggregatorClientError::RemoteServerUnreachable(anyhow!(err))), + } + } +} + +#[cfg(test)] +pub(crate) mod dumb { + use mithril_common::test::double::Dummy; + use tokio::sync::RwLock; + + use super::*; + + /// This aggregator client is intended to be used by test services. + /// It actually does not communicate with an aggregator host but mimics this behavior. + /// It is driven by a Tester that controls the data it can return, and it can return its internal state for testing. + pub struct DumbAggregatorClient { + epoch_settings: RwLock>, + } + + // impl DumbAggregatorClient { + // /// Return the last signer that called with the `register` method. + // pub async fn get_last_registered_signer(&self) -> Option { + // self.last_registered_signer.read().await.clone() + // } + // } + + impl Default for DumbAggregatorClient { + fn default() -> Self { + Self { + epoch_settings: RwLock::new(Some(EpochSettingsMessage::dummy())), + } + } + } + + #[async_trait] + impl AggregatorClient for DumbAggregatorClient { + async fn retrieve_epoch_settings( + &self, + ) -> Result, AggregatorClientError> { + let epoch_settings = self.epoch_settings.read().await.clone(); + + Ok(epoch_settings) + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use http::response::Builder as HttpResponseBuilder; + use httpmock::prelude::*; + use semver::Version; + use serde_json::json; + + use mithril_common::test::{ + double::{Dummy, DummyApiVersionDiscriminantSource}, + logging::MemoryDrainForTestInspector, + }; + + use crate::test_tools::TestLogger; + + use super::*; + + fn setup_client>(server_url: U) -> AggregatorHTTPClient { + let discriminant_source = DummyApiVersionDiscriminantSource::new("dummy"); + let api_version_provider = APIVersionProvider::new(Arc::new(discriminant_source)); + + AggregatorHTTPClient::new( + server_url.into(), + None, + Arc::new(api_version_provider), + None, + TestLogger::stdout(), + ) + } + + fn setup_server_and_client() -> (MockServer, AggregatorHTTPClient) { + let server = MockServer::start(); + let aggregator_endpoint = server.url(""); + let client = setup_client(&aggregator_endpoint); + + (server, client) + } + + fn set_returning_500(server: &MockServer) { + server.mock(|_, then| { + then.status(500).body("an error occurred"); + }); + } + + fn set_unparsable_json(server: &MockServer) { + server.mock(|_, then| { + then.status(200).body("this is not a json"); + }); + } + + fn build_text_response>(status_code: StatusCode, body: T) -> Response { + HttpResponseBuilder::new() + .status(status_code) + .body(body.into()) + .unwrap() + .into() + } + + fn build_json_response(status_code: StatusCode, body: &T) -> Response { + HttpResponseBuilder::new() + .status(status_code) + .header(header::CONTENT_TYPE, JSON_CONTENT_TYPE) + .body(serde_json::to_string(&body).unwrap()) + .unwrap() + .into() + } + + macro_rules! assert_error_text_contains { + ($error: expr, $expect_contains: expr) => { + let error = &$error; + assert!( + error.contains($expect_contains), + "Expected error message to contain '{}'\ngot '{error:?}'", + $expect_contains, + ); + }; + } + + #[tokio::test] + async fn test_epoch_settings_ok_200() { + let (server, client) = setup_server_and_client(); + let epoch_settings_expected = EpochSettingsMessage::dummy(); + let _server_mock = server.mock(|when, then| { + when.path("/epoch-settings"); + then.status(200).body(json!(epoch_settings_expected).to_string()); + }); + + let epoch_settings = client.retrieve_epoch_settings().await; + epoch_settings.as_ref().expect("unexpected error"); + assert_eq!(epoch_settings_expected, epoch_settings.unwrap().unwrap()); + } + + #[tokio::test] + async fn test_epoch_settings_ko_500() { + let (server, client) = setup_server_and_client(); + let _server_mock = server.mock(|when, then| { + when.path("/epoch-settings"); + then.status(500).body("an error occurred"); + }); + + match client.retrieve_epoch_settings().await.unwrap_err() { + AggregatorClientError::RemoteServerTechnical(_) => (), + e => panic!("Expected Aggregator::RemoteServerTechnical error, got '{e:?}'."), + }; + } + + #[tokio::test] + async fn test_epoch_settings_timeout() { + let (server, mut client) = setup_server_and_client(); + client.timeout_duration = Some(Duration::from_millis(10)); + let _server_mock = server.mock(|when, then| { + when.path("/epoch-settings"); + then.delay(Duration::from_millis(100)); + }); + + let error = client + .retrieve_epoch_settings() + .await + .expect_err("retrieve_epoch_settings should fail"); + + assert!( + matches!(error, AggregatorClientError::RemoteServerUnreachable(_)), + "unexpected error type: {error:?}" + ); + } + + #[tokio::test] + async fn test_4xx_errors_are_handled_as_remote_server_logical() { + let response = build_text_response(StatusCode::BAD_REQUEST, "error text"); + let handled_error = AggregatorClientError::from_response(response).await; + + assert!( + matches!( + handled_error, + AggregatorClientError::RemoteServerLogical(..) + ), + "Expected error to be RemoteServerLogical\ngot '{handled_error:?}'", + ); + } + + #[tokio::test] + async fn test_5xx_errors_are_handled_as_remote_server_technical() { + let response = build_text_response(StatusCode::INTERNAL_SERVER_ERROR, "error text"); + let handled_error = AggregatorClientError::from_response(response).await; + + assert!( + matches!( + handled_error, + AggregatorClientError::RemoteServerTechnical(..) + ), + "Expected error to be RemoteServerLogical\ngot '{handled_error:?}'", + ); + } + + #[tokio::test] + async fn test_550_error_is_handled_as_registration_round_not_yet_opened() { + let response = build_text_response(StatusCode::from_u16(550).unwrap(), "Not yet available"); + let handled_error = AggregatorClientError::from_response(response).await; + + assert!( + matches!( + handled_error, + AggregatorClientError::RegistrationRoundNotYetOpened(..) + ), + "Expected error to be RegistrationRoundNotYetOpened\ngot '{handled_error:?}'", + ); + } + + #[tokio::test] + async fn test_non_4xx_or_5xx_errors_are_handled_as_unhandled_status_code_and_contains_response_text() + { + let response = build_text_response(StatusCode::OK, "ok text"); + let handled_error = AggregatorClientError::from_response(response).await; + + assert!( + matches!( + handled_error, + AggregatorClientError::UnhandledStatusCode(..) if format!("{handled_error:?}").contains("ok text") + ), + "Expected error to be UnhandledStatusCode with 'ok text' in error text\ngot '{handled_error:?}'", + ); + } + + #[tokio::test] + async fn test_root_cause_of_non_json_response_contains_response_plain_text() { + let error_text = "An error occurred; please try again later."; + let response = build_text_response(StatusCode::EXPECTATION_FAILED, error_text); + + assert_error_text_contains!( + AggregatorClientError::get_root_cause(response).await, + "expectation failed: An error occurred; please try again later." + ); + } + + #[tokio::test] + async fn test_root_cause_of_json_formatted_client_error_response_contains_error_label_and_message() + { + let client_error = ClientError::new("label", "message"); + let response = build_json_response(StatusCode::BAD_REQUEST, &client_error); + + assert_error_text_contains!( + AggregatorClientError::get_root_cause(response).await, + "bad request: label: message" + ); + } + + #[tokio::test] + async fn test_root_cause_of_json_formatted_server_error_response_contains_error_label_and_message() + { + let server_error = ServerError::new("message"); + let response = build_json_response(StatusCode::BAD_REQUEST, &server_error); + + assert_error_text_contains!( + AggregatorClientError::get_root_cause(response).await, + "bad request: message" + ); + } + + #[tokio::test] + async fn test_root_cause_of_unknown_formatted_json_response_contains_json_key_value_pairs() { + let response = build_json_response( + StatusCode::INTERNAL_SERVER_ERROR, + &json!({ "second": "unknown", "first": "foreign" }), + ); + + assert_error_text_contains!( + AggregatorClientError::get_root_cause(response).await, + r#"internal server error: {"first":"foreign","second":"unknown"}"# + ); + } + + #[tokio::test] + async fn test_root_cause_with_invalid_json_response_still_contains_response_status_name() { + let response = HttpResponseBuilder::new() + .status(StatusCode::BAD_REQUEST) + .header(header::CONTENT_TYPE, JSON_CONTENT_TYPE) + .body(r#"{"invalid":"unexpected dot", "key": "value".}"#) + .unwrap() + .into(); + + let root_cause = AggregatorClientError::get_root_cause(response).await; + + assert_error_text_contains!(root_cause, "bad request"); + assert!( + !root_cause.contains("bad request: "), + "Expected error message should not contain additional information \ngot '{root_cause:?}'" + ); + } + + mod warn_if_api_version_mismatch { + use mithril_common::test::api_version_extensions::ApiVersionProviderTestExtension; + + use super::*; + + fn version_provider_with_open_api_version>( + version: V, + ) -> APIVersionProvider { + let mut version_provider = version_provider_without_open_api_version(); + let mut open_api_versions = HashMap::new(); + open_api_versions.insert( + "openapi.yaml".to_string(), + Version::parse(&version.into()).unwrap(), + ); + version_provider.update_open_api_versions(open_api_versions); + + version_provider + } + + fn version_provider_without_open_api_version() -> APIVersionProvider { + let mut version_provider = + APIVersionProvider::new(Arc::new(DummyApiVersionDiscriminantSource::new("dummy"))); + version_provider.update_open_api_versions(HashMap::new()); + + version_provider + } + + fn build_fake_response_with_header, V: Into>( + key: K, + value: V, + ) -> Response { + HttpResponseBuilder::new() + .header(key.into(), value.into()) + .body("whatever") + .unwrap() + .into() + } + + fn assert_api_version_warning_logged, S: Into>( + log_inspector: &MemoryDrainForTestInspector, + aggregator_version: A, + signer_version: S, + ) { + assert!(log_inspector.contains_log(API_VERSION_MISMATCH_WARNING_MESSAGE)); + assert!( + log_inspector + .contains_log(&format!("aggregator_version={}", aggregator_version.into())) + ); + assert!( + log_inspector.contains_log(&format!("signer_version={}", signer_version.into())) + ); + } + + #[test] + fn test_logs_warning_when_aggregator_api_version_is_newer() { + let aggregator_version = "2.0.0"; + let signer_version = "1.0.0"; + let (logger, log_inspector) = TestLogger::memory(); + let version_provider = version_provider_with_open_api_version(signer_version); + let mut client = setup_client("whatever"); + client.api_version_provider = Arc::new(version_provider); + client.logger = logger; + let response = + build_fake_response_with_header(MITHRIL_API_VERSION_HEADER, aggregator_version); + + assert!( + Version::parse(aggregator_version).unwrap() + > Version::parse(signer_version).unwrap() + ); + + client.warn_if_api_version_mismatch(&response); + + assert_api_version_warning_logged(&log_inspector, aggregator_version, signer_version); + } + + #[test] + fn test_no_warning_logged_when_versions_match() { + let version = "1.0.0"; + let (logger, log_inspector) = TestLogger::memory(); + let version_provider = version_provider_with_open_api_version(version); + let mut client = setup_client("whatever"); + client.api_version_provider = Arc::new(version_provider); + client.logger = logger; + let response = build_fake_response_with_header(MITHRIL_API_VERSION_HEADER, version); + + client.warn_if_api_version_mismatch(&response); + + assert!(!log_inspector.contains_log(API_VERSION_MISMATCH_WARNING_MESSAGE)); + } + + #[test] + fn test_no_warning_logged_when_aggregator_api_version_is_older() { + let aggregator_version = "1.0.0"; + let signer_version = "2.0.0"; + let (logger, log_inspector) = TestLogger::memory(); + let version_provider = version_provider_with_open_api_version(signer_version); + let mut client = setup_client("whatever"); + client.api_version_provider = Arc::new(version_provider); + client.logger = logger; + let response = + build_fake_response_with_header(MITHRIL_API_VERSION_HEADER, aggregator_version); + + assert!( + Version::parse(aggregator_version).unwrap() + < Version::parse(signer_version).unwrap() + ); + + client.warn_if_api_version_mismatch(&response); + + assert!(!log_inspector.contains_log(API_VERSION_MISMATCH_WARNING_MESSAGE)); + } + + #[test] + fn test_does_not_log_or_fail_when_header_is_missing() { + let (logger, log_inspector) = TestLogger::memory(); + let mut client = setup_client("whatever"); + client.logger = logger; + let response = + build_fake_response_with_header("NotMithrilAPIVersionHeader", "whatever"); + + client.warn_if_api_version_mismatch(&response); + + assert!(!log_inspector.contains_log(API_VERSION_MISMATCH_WARNING_MESSAGE)); + } + + #[test] + fn test_does_not_log_or_fail_when_header_is_not_a_version() { + let (logger, log_inspector) = TestLogger::memory(); + let mut client = setup_client("whatever"); + client.logger = logger; + let response = + build_fake_response_with_header(MITHRIL_API_VERSION_HEADER, "not_a_version"); + + client.warn_if_api_version_mismatch(&response); + + assert!(!log_inspector.contains_log(API_VERSION_MISMATCH_WARNING_MESSAGE)); + } + + #[test] + fn test_logs_error_when_signer_version_cannot_be_computed() { + let (logger, log_inspector) = TestLogger::memory(); + let version_provider = version_provider_without_open_api_version(); + let mut client = setup_client("whatever"); + client.api_version_provider = Arc::new(version_provider); + client.logger = logger; + let response = build_fake_response_with_header(MITHRIL_API_VERSION_HEADER, "1.0.0"); + + client.warn_if_api_version_mismatch(&response); + + assert!(!log_inspector.contains_log(API_VERSION_MISMATCH_WARNING_MESSAGE)); + } + + #[tokio::test] + async fn test_epoch_settings_ok_200_log_warning_if_api_version_mismatch() { + let aggregator_version = "2.0.0"; + let signer_version = "1.0.0"; + let (server, mut client) = setup_server_and_client(); + let (logger, log_inspector) = TestLogger::memory(); + let version_provider = version_provider_with_open_api_version(signer_version); + client.api_version_provider = Arc::new(version_provider); + client.logger = logger; + + let epoch_settings_expected = EpochSettingsMessage::dummy(); + let _server_mock = server.mock(|when, then| { + when.path("/epoch-settings"); + then.status(200) + .header(MITHRIL_API_VERSION_HEADER, aggregator_version) + .body(json!(epoch_settings_expected).to_string()); + }); + + assert!( + Version::parse(aggregator_version).unwrap() + > Version::parse(signer_version).unwrap() + ); + + client.retrieve_epoch_settings().await.unwrap(); + + assert_api_version_warning_logged(&log_inspector, aggregator_version, signer_version); + } + } +} diff --git a/internal/mithril-protocol-config/src/http/http_impl.rs b/internal/mithril-protocol-config/src/http/http_impl.rs new file mode 100644 index 00000000000..28fff0363e6 --- /dev/null +++ b/internal/mithril-protocol-config/src/http/http_impl.rs @@ -0,0 +1,59 @@ +use anyhow::anyhow; +use std::{collections::BTreeSet, sync::Arc, time::Duration}; + +use crate::{ + HTTP_REQUEST_TIMEOUT_DURATION, + http::aggregator_client::{AggregatorClient, AggregatorHTTPClient}, + interface::MithrilNetworkConfigurationProvider, + model::MithrilNetworkConfiguration, +}; +use async_trait::async_trait; +use mithril_common::StdResult; +use mithril_common::api_version::APIVersionProvider; + +struct HttpMithrilNetworkConfigurationProvider { + aggregator_client: AggregatorHTTPClient, + logger: slog::Logger, +} + +impl HttpMithrilNetworkConfigurationProvider { + pub fn new( + aggregator_endpoint: String, + relay_endpoint: Option, + api_version_provider: Arc, + logger: slog::Logger, + ) -> Self { + let aggregator_client = AggregatorHTTPClient::new( + aggregator_endpoint.clone(), + relay_endpoint.clone(), + api_version_provider.clone(), + Some(Duration::from_millis(HTTP_REQUEST_TIMEOUT_DURATION)), + logger.clone(), + ); + + Self { + aggregator_client, + logger, + } + } +} + +#[async_trait] +impl MithrilNetworkConfigurationProvider for HttpMithrilNetworkConfigurationProvider { + async fn get(&self) -> StdResult { + let Some(epoch_settings) = self.aggregator_client.retrieve_epoch_settings().await? else { + return Err(anyhow!("Failed to retrieve epoch settings")); + }; + + let available_signed_entity_types = BTreeSet::new(); // To be implemented to be retrieve from /aggreagator-features from aggregator_client.rs + let signed_entity_types_config = vec![]; // To be implemented + + Ok(MithrilNetworkConfiguration { + epoch: epoch_settings.epoch, + signer_registration_protocol_parameters: epoch_settings + .signer_registration_protocol_parameters, + available_signed_entity_types, + signed_entity_types_config, + }) + } +} diff --git a/internal/mithril-protocol-config/src/http/mod.rs b/internal/mithril-protocol-config/src/http/mod.rs new file mode 100644 index 00000000000..b57c837658c --- /dev/null +++ b/internal/mithril-protocol-config/src/http/mod.rs @@ -0,0 +1,2 @@ +mod aggregator_client; +mod http_impl; diff --git a/internal/mithril-protocol-config/src/interface.rs b/internal/mithril-protocol-config/src/interface.rs new file mode 100644 index 00000000000..e6eb8f38eb9 --- /dev/null +++ b/internal/mithril-protocol-config/src/interface.rs @@ -0,0 +1,11 @@ +use async_trait::async_trait; +use mithril_common::StdResult; + +use crate::model::MithrilNetworkConfiguration; + +/// Trait to provide the current Mithril network configuration. +#[async_trait] +pub trait MithrilNetworkConfigurationProvider: Sync + Send { + /// Get the Mithril network configuration for the current epoch. + async fn get(&self) -> StdResult; +} diff --git a/internal/mithril-protocol-config/src/lib.rs b/internal/mithril-protocol-config/src/lib.rs new file mode 100644 index 00000000000..c689546f434 --- /dev/null +++ b/internal/mithril-protocol-config/src/lib.rs @@ -0,0 +1,10 @@ +/// HTTP request timeout duration in milliseconds +const HTTP_REQUEST_TIMEOUT_DURATION: u64 = 30000; +pub mod http; +pub mod interface; +pub mod model; + +#[cfg(test)] +pub(crate) mod test_tools { + mithril_common::define_test_logger!(); +} diff --git a/internal/mithril-protocol-config/src/model.rs b/internal/mithril-protocol-config/src/model.rs new file mode 100644 index 00000000000..f588dd03e64 --- /dev/null +++ b/internal/mithril-protocol-config/src/model.rs @@ -0,0 +1,25 @@ +use std::collections::BTreeSet; + +use mithril_common::entities::{ + CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, SignedEntityTypeDiscriminants, +}; + +pub enum SignedEntityTypeConfiguration { + /// Cardano Transactions + CardanoTransactions(CardanoTransactionsSigningConfig), +} + +/// A Mithril network configuration +pub struct MithrilNetworkConfiguration { + /// Epoch + pub epoch: Epoch, + + /// Cryptographic protocol parameters (`k`, `m` and `phi_f`) + pub signer_registration_protocol_parameters: ProtocolParameters, + + /// List of available types of certifications (`CardanoDatabase`, `CardanoTransactions`, `CardanoStakeDistribution`, ...) + pub available_signed_entity_types: BTreeSet, + + /// Custom configurations for signed entity types (e.g. `cardano_transactions_signing_config` for `CardanoTransactions`) + pub signed_entity_types_config: Vec, //or HashMap +} From af9629ba0cd849a20534a70dad8b90d85a263200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Fri, 26 Sep 2025 11:16:36 +0200 Subject: [PATCH 02/18] feature(protocol-config): add and update makefiles --- Makefile | 1 + internal/mithril-protocol-config/Makefile | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 internal/mithril-protocol-config/Makefile diff --git a/Makefile b/Makefile index 3057609c4fc..a379c8e96a7 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ COMPONENTS = mithril-aggregator mithril-client mithril-client-cli mithril-client internal/mithril-build-script internal/mithril-cli-helper internal/mithril-doc \ internal/mithril-dmq \ internal/mithril-doc-derive internal/mithril-era internal/mithril-metric internal/mithril-persistence \ + internal/mithril-protocol-config \ internal/mithril-resource-pool internal/mithril-ticker \ internal/cardano-node/mithril-cardano-node-chain internal/cardano-node/mithril-cardano-node-internal-database \ internal/signed-entity/mithril-signed-entity-lock internal/signed-entity/mithril-signed-entity-preloader \ diff --git a/internal/mithril-protocol-config/Makefile b/internal/mithril-protocol-config/Makefile new file mode 100644 index 00000000000..d66d6d9fefc --- /dev/null +++ b/internal/mithril-protocol-config/Makefile @@ -0,0 +1,19 @@ +.PHONY: all build test check doc + +CARGO = cargo + +all: test build + +build: + ${CARGO} build --release + +test: + ${CARGO} test + +check: + ${CARGO} check --release --all-features --all-targets + ${CARGO} clippy --release --all-features --all-targets + ${CARGO} fmt --check + +doc: + ${CARGO} doc --no-deps --open From 3a920af97ffec6827e1ded746525ed26f3a164b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Fri, 26 Sep 2025 11:18:47 +0200 Subject: [PATCH 03/18] feature(protocol-config): rename http to http_client --- .../src/{http => http_client}/aggregator_client.rs | 0 .../src/{http => http_client}/http_impl.rs | 2 +- .../mithril-protocol-config/src/{http => http_client}/mod.rs | 0 internal/mithril-protocol-config/src/lib.rs | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename internal/mithril-protocol-config/src/{http => http_client}/aggregator_client.rs (100%) rename internal/mithril-protocol-config/src/{http => http_client}/http_impl.rs (96%) rename internal/mithril-protocol-config/src/{http => http_client}/mod.rs (100%) diff --git a/internal/mithril-protocol-config/src/http/aggregator_client.rs b/internal/mithril-protocol-config/src/http_client/aggregator_client.rs similarity index 100% rename from internal/mithril-protocol-config/src/http/aggregator_client.rs rename to internal/mithril-protocol-config/src/http_client/aggregator_client.rs diff --git a/internal/mithril-protocol-config/src/http/http_impl.rs b/internal/mithril-protocol-config/src/http_client/http_impl.rs similarity index 96% rename from internal/mithril-protocol-config/src/http/http_impl.rs rename to internal/mithril-protocol-config/src/http_client/http_impl.rs index 28fff0363e6..67573a0ea19 100644 --- a/internal/mithril-protocol-config/src/http/http_impl.rs +++ b/internal/mithril-protocol-config/src/http_client/http_impl.rs @@ -3,7 +3,7 @@ use std::{collections::BTreeSet, sync::Arc, time::Duration}; use crate::{ HTTP_REQUEST_TIMEOUT_DURATION, - http::aggregator_client::{AggregatorClient, AggregatorHTTPClient}, + http_client::aggregator_client::{AggregatorClient, AggregatorHTTPClient}, interface::MithrilNetworkConfigurationProvider, model::MithrilNetworkConfiguration, }; diff --git a/internal/mithril-protocol-config/src/http/mod.rs b/internal/mithril-protocol-config/src/http_client/mod.rs similarity index 100% rename from internal/mithril-protocol-config/src/http/mod.rs rename to internal/mithril-protocol-config/src/http_client/mod.rs diff --git a/internal/mithril-protocol-config/src/lib.rs b/internal/mithril-protocol-config/src/lib.rs index c689546f434..208fb3030b1 100644 --- a/internal/mithril-protocol-config/src/lib.rs +++ b/internal/mithril-protocol-config/src/lib.rs @@ -1,6 +1,6 @@ /// HTTP request timeout duration in milliseconds const HTTP_REQUEST_TIMEOUT_DURATION: u64 = 30000; -pub mod http; +pub mod http_client; pub mod interface; pub mod model; From 8e6fa48910ceeec1526b307a26938deced65a795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Fri, 26 Sep 2025 11:21:43 +0200 Subject: [PATCH 04/18] feature(protocol-config): switch to hashmap for signed_entity_types_config --- .../mithril-protocol-config/src/http_client/http_impl.rs | 8 ++++++-- internal/mithril-protocol-config/src/model.rs | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/internal/mithril-protocol-config/src/http_client/http_impl.rs b/internal/mithril-protocol-config/src/http_client/http_impl.rs index 67573a0ea19..cf6ba074fad 100644 --- a/internal/mithril-protocol-config/src/http_client/http_impl.rs +++ b/internal/mithril-protocol-config/src/http_client/http_impl.rs @@ -1,5 +1,9 @@ use anyhow::anyhow; -use std::{collections::BTreeSet, sync::Arc, time::Duration}; +use std::{ + collections::{BTreeSet, HashMap}, + sync::Arc, + time::Duration, +}; use crate::{ HTTP_REQUEST_TIMEOUT_DURATION, @@ -46,7 +50,7 @@ impl MithrilNetworkConfigurationProvider for HttpMithrilNetworkConfigurationProv }; let available_signed_entity_types = BTreeSet::new(); // To be implemented to be retrieve from /aggreagator-features from aggregator_client.rs - let signed_entity_types_config = vec![]; // To be implemented + let signed_entity_types_config = HashMap::new(); // To be implemented Ok(MithrilNetworkConfiguration { epoch: epoch_settings.epoch, diff --git a/internal/mithril-protocol-config/src/model.rs b/internal/mithril-protocol-config/src/model.rs index f588dd03e64..1033d082b28 100644 --- a/internal/mithril-protocol-config/src/model.rs +++ b/internal/mithril-protocol-config/src/model.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::collections::{BTreeSet, HashMap}; use mithril_common::entities::{ CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, SignedEntityTypeDiscriminants, @@ -21,5 +21,6 @@ pub struct MithrilNetworkConfiguration { pub available_signed_entity_types: BTreeSet, /// Custom configurations for signed entity types (e.g. `cardano_transactions_signing_config` for `CardanoTransactions`) - pub signed_entity_types_config: Vec, //or HashMap + pub signed_entity_types_config: + HashMap, } From f0c506e448143b9f79a79c7c650de408b66227a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Fri, 26 Sep 2025 12:05:53 +0200 Subject: [PATCH 05/18] feature(protocol-config): put back aggregator_features route call --- .../src/http_client/aggregator_client.rs | 209 +++++++++++------- 1 file changed, 133 insertions(+), 76 deletions(-) diff --git a/internal/mithril-protocol-config/src/http_client/aggregator_client.rs b/internal/mithril-protocol-config/src/http_client/aggregator_client.rs index a3321c5c571..31f30f8f605 100644 --- a/internal/mithril-protocol-config/src/http_client/aggregator_client.rs +++ b/internal/mithril-protocol-config/src/http_client/aggregator_client.rs @@ -12,7 +12,7 @@ use mithril_common::{ api_version::APIVersionProvider, entities::{ClientError, ServerError}, logging::LoggerExtensions, - messages::EpochSettingsMessage, + messages::{AggregatorFeaturesMessage, EpochSettingsMessage}, }; const JSON_CONTENT_TYPE: HeaderValue = HeaderValue::from_static("application/json"); @@ -124,6 +124,11 @@ pub trait AggregatorClient: Sync + Send { async fn retrieve_epoch_settings( &self, ) -> Result, AggregatorClientError>; + + /// Retrieves aggregator features message from the aggregator + async fn retrieve_aggregator_features( + &self, + ) -> Result; } /// AggregatorHTTPClient is a http client for an aggregator @@ -244,45 +249,30 @@ impl AggregatorClient for AggregatorHTTPClient { Err(err) => Err(AggregatorClientError::RemoteServerUnreachable(anyhow!(err))), } } -} - -#[cfg(test)] -pub(crate) mod dumb { - use mithril_common::test::double::Dummy; - use tokio::sync::RwLock; - - use super::*; - - /// This aggregator client is intended to be used by test services. - /// It actually does not communicate with an aggregator host but mimics this behavior. - /// It is driven by a Tester that controls the data it can return, and it can return its internal state for testing. - pub struct DumbAggregatorClient { - epoch_settings: RwLock>, - } - // impl DumbAggregatorClient { - // /// Return the last signer that called with the `register` method. - // pub async fn get_last_registered_signer(&self) -> Option { - // self.last_registered_signer.read().await.clone() - // } - // } - - impl Default for DumbAggregatorClient { - fn default() -> Self { - Self { - epoch_settings: RwLock::new(Some(EpochSettingsMessage::dummy())), - } - } - } + async fn retrieve_aggregator_features( + &self, + ) -> Result { + debug!(self.logger, "Retrieve aggregator features message"); + let url = format!("{}/", self.aggregator_endpoint); + let response = self + .prepare_request_builder(self.prepare_http_client()?.get(url.clone())) + .send() + .await; - #[async_trait] - impl AggregatorClient for DumbAggregatorClient { - async fn retrieve_epoch_settings( - &self, - ) -> Result, AggregatorClientError> { - let epoch_settings = self.epoch_settings.read().await.clone(); + match response { + Ok(response) => match response.status() { + StatusCode::OK => { + self.warn_if_api_version_mismatch(&response); - Ok(epoch_settings) + Ok(response + .json::() + .await + .map_err(|e| AggregatorClientError::JsonParseFailed(anyhow!(e)))?) + } + _ => Err(AggregatorClientError::from_response(response).await), + }, + Err(err) => Err(AggregatorClientError::RemoteServerUnreachable(anyhow!(err))), } } } @@ -305,6 +295,17 @@ mod tests { use super::*; + macro_rules! assert_is_error { + ($error:expr, $error_type:pat) => { + assert!( + matches!($error, $error_type), + "Expected {} error, got '{:?}'.", + stringify!($error_type), + $error + ); + }; + } + fn setup_client>(server_url: U) -> AggregatorHTTPClient { let discriminant_source = DummyApiVersionDiscriminantSource::new("dummy"); let api_version_provider = APIVersionProvider::new(Arc::new(discriminant_source)); @@ -366,52 +367,108 @@ mod tests { }; } - #[tokio::test] - async fn test_epoch_settings_ok_200() { - let (server, client) = setup_server_and_client(); - let epoch_settings_expected = EpochSettingsMessage::dummy(); - let _server_mock = server.mock(|when, then| { - when.path("/epoch-settings"); - then.status(200).body(json!(epoch_settings_expected).to_string()); - }); + mod epoch_settings { + use super::*; - let epoch_settings = client.retrieve_epoch_settings().await; - epoch_settings.as_ref().expect("unexpected error"); - assert_eq!(epoch_settings_expected, epoch_settings.unwrap().unwrap()); - } + #[tokio::test] + async fn test_epoch_settings_ok_200() { + let (server, client) = setup_server_and_client(); + let epoch_settings_expected = EpochSettingsMessage::dummy(); + let _server_mock = server.mock(|when, then| { + when.path("/epoch-settings"); + then.status(200).body(json!(epoch_settings_expected).to_string()); + }); - #[tokio::test] - async fn test_epoch_settings_ko_500() { - let (server, client) = setup_server_and_client(); - let _server_mock = server.mock(|when, then| { - when.path("/epoch-settings"); - then.status(500).body("an error occurred"); - }); + let epoch_settings = client.retrieve_epoch_settings().await; + epoch_settings.as_ref().expect("unexpected error"); + assert_eq!(epoch_settings_expected, epoch_settings.unwrap().unwrap()); + } - match client.retrieve_epoch_settings().await.unwrap_err() { - AggregatorClientError::RemoteServerTechnical(_) => (), - e => panic!("Expected Aggregator::RemoteServerTechnical error, got '{e:?}'."), - }; + #[tokio::test] + async fn test_epoch_settings_ko_500() { + let (server, client) = setup_server_and_client(); + let _server_mock = server.mock(|when, then| { + when.path("/epoch-settings"); + then.status(500).body("an error occurred"); + }); + + match client.retrieve_epoch_settings().await.unwrap_err() { + AggregatorClientError::RemoteServerTechnical(_) => (), + e => panic!("Expected Aggregator::RemoteServerTechnical error, got '{e:?}'."), + }; + } + + #[tokio::test] + async fn test_epoch_settings_timeout() { + let (server, mut client) = setup_server_and_client(); + client.timeout_duration = Some(Duration::from_millis(10)); + let _server_mock = server.mock(|when, then| { + when.path("/epoch-settings"); + then.delay(Duration::from_millis(100)); + }); + + let error = client + .retrieve_epoch_settings() + .await + .expect_err("retrieve_epoch_settings should fail"); + + assert!( + matches!(error, AggregatorClientError::RemoteServerUnreachable(_)), + "unexpected error type: {error:?}" + ); + } } - #[tokio::test] - async fn test_epoch_settings_timeout() { - let (server, mut client) = setup_server_and_client(); - client.timeout_duration = Some(Duration::from_millis(10)); - let _server_mock = server.mock(|when, then| { - when.path("/epoch-settings"); - then.delay(Duration::from_millis(100)); - }); + mod aggregator_features { + use super::*; + + #[tokio::test] + async fn test_aggregator_features_ok_200() { + let (server, client) = setup_server_and_client(); + let message_expected = AggregatorFeaturesMessage::dummy(); + let _server_mock = server.mock(|when, then| { + when.path("/"); + then.status(200).body(json!(message_expected).to_string()); + }); - let error = client - .retrieve_epoch_settings() - .await - .expect_err("retrieve_epoch_settings should fail"); + let message = client.retrieve_aggregator_features().await.unwrap(); - assert!( - matches!(error, AggregatorClientError::RemoteServerUnreachable(_)), - "unexpected error type: {error:?}" - ); + assert_eq!(message_expected, message); + } + + #[tokio::test] + async fn test_aggregator_features_ko_500() { + let (server, client) = setup_server_and_client(); + set_returning_500(&server); + + let error = client.retrieve_aggregator_features().await.unwrap_err(); + + assert_is_error!(error, AggregatorClientError::RemoteServerTechnical(_)); + } + + #[tokio::test] + async fn test_aggregator_features_ko_json_serialization() { + let (server, client) = setup_server_and_client(); + set_unparsable_json(&server); + + let error = client.retrieve_aggregator_features().await.unwrap_err(); + + assert_is_error!(error, AggregatorClientError::JsonParseFailed(_)); + } + + #[tokio::test] + async fn test_aggregator_features_timeout() { + let (server, mut client) = setup_server_and_client(); + client.timeout_duration = Some(Duration::from_millis(10)); + let _server_mock = server.mock(|when, then| { + when.path("/"); + then.delay(Duration::from_millis(100)); + }); + + let error = client.retrieve_aggregator_features().await.unwrap_err(); + + assert_is_error!(error, AggregatorClientError::RemoteServerUnreachable(_)); + } } #[tokio::test] From e3bc7858c12297a891206c29e2a4a559650a94d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Fri, 26 Sep 2025 17:58:01 +0200 Subject: [PATCH 06/18] feature(common): make SignedEntitTypesDiscriminant Hashable to be used in HashMap --- mithril-common/src/entities/signed_entity_type.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mithril-common/src/entities/signed_entity_type.rs b/mithril-common/src/entities/signed_entity_type.rs index 397c8c0057e..62a0a385999 100644 --- a/mithril-common/src/entities/signed_entity_type.rs +++ b/mithril-common/src/entities/signed_entity_type.rs @@ -49,6 +49,7 @@ const ENTITY_TYPE_CARDANO_DATABASE: usize = 4; PartialOrd, Ord, EnumIter, + Hash, ))] pub enum SignedEntityType { /// Mithril stake distribution From dacf327a5f16a07ef5f1058acd2777670508ca8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Fri, 26 Sep 2025 17:59:50 +0200 Subject: [PATCH 07/18] feature(protocol-config): build MithrilNetworkConfiguration result with available signe entity type and config --- .../src/http_client/http_impl.rs | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/internal/mithril-protocol-config/src/http_client/http_impl.rs b/internal/mithril-protocol-config/src/http_client/http_impl.rs index cf6ba074fad..b4bc509e1b4 100644 --- a/internal/mithril-protocol-config/src/http_client/http_impl.rs +++ b/internal/mithril-protocol-config/src/http_client/http_impl.rs @@ -1,19 +1,15 @@ use anyhow::anyhow; -use std::{ - collections::{BTreeSet, HashMap}, - sync::Arc, - time::Duration, -}; +use std::{collections::HashMap, sync::Arc, time::Duration}; use crate::{ HTTP_REQUEST_TIMEOUT_DURATION, http_client::aggregator_client::{AggregatorClient, AggregatorHTTPClient}, interface::MithrilNetworkConfigurationProvider, - model::MithrilNetworkConfiguration, + model::{MithrilNetworkConfiguration, SignedEntityTypeConfiguration}, }; use async_trait::async_trait; -use mithril_common::StdResult; use mithril_common::api_version::APIVersionProvider; +use mithril_common::{StdResult, entities::SignedEntityTypeDiscriminants}; struct HttpMithrilNetworkConfigurationProvider { aggregator_client: AggregatorHTTPClient, @@ -49,8 +45,18 @@ impl MithrilNetworkConfigurationProvider for HttpMithrilNetworkConfigurationProv return Err(anyhow!("Failed to retrieve epoch settings")); }; - let available_signed_entity_types = BTreeSet::new(); // To be implemented to be retrieve from /aggreagator-features from aggregator_client.rs - let signed_entity_types_config = HashMap::new(); // To be implemented + let aggregator_features = self.aggregator_client.retrieve_aggregator_features().await?; + let available_signed_entity_types = aggregator_features.capabilities.signed_entity_types; + + let mut signed_entity_types_config = HashMap::new(); + signed_entity_types_config.insert( + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeConfiguration::CardanoTransactions( + epoch_settings.cardano_transactions_signing_config.ok_or_else(|| { + anyhow!("Cardano transactions signing config is missing in epoch settings") + })?, + ), + ); Ok(MithrilNetworkConfiguration { epoch: epoch_settings.epoch, From 159de72f02e24b0581b8c82366b4eda91203923c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Tue, 30 Sep 2025 14:54:30 +0200 Subject: [PATCH 08/18] feature(protocol-config): add test double on MithrilNetworkConfigurationProvider --- internal/mithril-protocol-config/src/lib.rs | 1 + internal/mithril-protocol-config/src/model.rs | 2 + .../src/test/double/fake_data.rs | 24 +++++++ .../mithril_network_configuration_provider.rs | 68 +++++++++++++++++++ .../src/test/double/mod.rs | 2 + .../mithril-protocol-config/src/test/mod.rs | 1 + 6 files changed, 98 insertions(+) create mode 100644 internal/mithril-protocol-config/src/test/double/fake_data.rs create mode 100644 internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs create mode 100644 internal/mithril-protocol-config/src/test/double/mod.rs create mode 100644 internal/mithril-protocol-config/src/test/mod.rs diff --git a/internal/mithril-protocol-config/src/lib.rs b/internal/mithril-protocol-config/src/lib.rs index 208fb3030b1..d6f7f25acf7 100644 --- a/internal/mithril-protocol-config/src/lib.rs +++ b/internal/mithril-protocol-config/src/lib.rs @@ -3,6 +3,7 @@ const HTTP_REQUEST_TIMEOUT_DURATION: u64 = 30000; pub mod http_client; pub mod interface; pub mod model; +pub mod test; #[cfg(test)] pub(crate) mod test_tools { diff --git a/internal/mithril-protocol-config/src/model.rs b/internal/mithril-protocol-config/src/model.rs index 1033d082b28..86a5db6323d 100644 --- a/internal/mithril-protocol-config/src/model.rs +++ b/internal/mithril-protocol-config/src/model.rs @@ -4,12 +4,14 @@ use mithril_common::entities::{ CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, SignedEntityTypeDiscriminants, }; +#[derive(PartialEq, Clone, Debug)] pub enum SignedEntityTypeConfiguration { /// Cardano Transactions CardanoTransactions(CardanoTransactionsSigningConfig), } /// A Mithril network configuration +#[derive(PartialEq, Clone, Debug)] pub struct MithrilNetworkConfiguration { /// Epoch pub epoch: Epoch, diff --git a/internal/mithril-protocol-config/src/test/double/fake_data.rs b/internal/mithril-protocol-config/src/test/double/fake_data.rs new file mode 100644 index 00000000000..9b276bf761e --- /dev/null +++ b/internal/mithril-protocol-config/src/test/double/fake_data.rs @@ -0,0 +1,24 @@ +use std::collections::{BTreeSet, HashMap}; + +use mithril_common::entities::{Epoch, ProtocolParameters, SignedEntityTypeDiscriminants}; + +use crate::model::{MithrilNetworkConfiguration, SignedEntityTypeConfiguration}; + +pub fn mithril_network_configuration( + epoch: u64, + k: u64, + m: u64, + phi_f: f64, + available_signed_entity_types: BTreeSet, + signed_entity_types_config: HashMap< + SignedEntityTypeDiscriminants, + SignedEntityTypeConfiguration, + >, +) -> MithrilNetworkConfiguration { + MithrilNetworkConfiguration { + epoch: Epoch(epoch), + signer_registration_protocol_parameters: ProtocolParameters { k, m, phi_f }, + available_signed_entity_types, + signed_entity_types_config, + } +} diff --git a/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs b/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs new file mode 100644 index 00000000000..c37445218e8 --- /dev/null +++ b/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs @@ -0,0 +1,68 @@ +use crate::{interface::MithrilNetworkConfigurationProvider, model::MithrilNetworkConfiguration}; +use async_trait::async_trait; +use mithril_common::StdResult; + +/// A fake [MithrilNetworkConfigurationProvider] that return [MithrilNetworkConfiguration] +pub struct FakeMithrilNetworkConfigurationProvider { + configuration: MithrilNetworkConfiguration, +} + +impl FakeMithrilNetworkConfigurationProvider { + /// Create a new [FakeMithrilNetworkConfigurationProvider] + pub fn from_mithril_network_configuration(configuration: MithrilNetworkConfiguration) -> Self { + Self { configuration } + } +} + +#[cfg_attr(target_family = "wasm", async_trait(?Send))] +#[cfg_attr(not(target_family = "wasm"), async_trait)] +impl MithrilNetworkConfigurationProvider for FakeMithrilNetworkConfigurationProvider { + async fn get(&self) -> StdResult { + Ok(self.configuration.clone()) + } +} + +#[cfg(test)] +mod tests { + use std::collections::{BTreeSet, HashMap}; + + use mithril_common::{ + StdResult, + entities::{BlockNumber, CardanoTransactionsSigningConfig, SignedEntityTypeDiscriminants}, + }; + + use super::*; + use crate::{model::SignedEntityTypeConfiguration, test::double::fake_data}; + + #[tokio::test] + async fn fake_mithril_network_configuration_provider_returns_configuration() -> StdResult<()> { + let mut available_signed_entity_types = BTreeSet::new(); + available_signed_entity_types.insert(SignedEntityTypeDiscriminants::CardanoTransactions); + + let mut signed_entity_types_config = HashMap::new(); + signed_entity_types_config.insert( + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeConfiguration::CardanoTransactions(CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(13), + step: BlockNumber(26), + }), + ); + + let configuration = fake_data::mithril_network_configuration( + 42, + 1, + 2, + 0.123, + available_signed_entity_types, + signed_entity_types_config, + ); + let provider = FakeMithrilNetworkConfigurationProvider::from_mithril_network_configuration( + configuration.clone(), + ); + + let retrieved_configuration = provider.get().await?; + + assert_eq!(configuration, retrieved_configuration); + Ok(()) + } +} diff --git a/internal/mithril-protocol-config/src/test/double/mod.rs b/internal/mithril-protocol-config/src/test/double/mod.rs new file mode 100644 index 00000000000..677b623e64f --- /dev/null +++ b/internal/mithril-protocol-config/src/test/double/mod.rs @@ -0,0 +1,2 @@ +mod fake_data; +mod mithril_network_configuration_provider; diff --git a/internal/mithril-protocol-config/src/test/mod.rs b/internal/mithril-protocol-config/src/test/mod.rs new file mode 100644 index 00000000000..90278e1483d --- /dev/null +++ b/internal/mithril-protocol-config/src/test/mod.rs @@ -0,0 +1 @@ +pub mod double; From 1dc365b9aa198e3aaaf9cc9abb1fb06adcd790e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Tue, 30 Sep 2025 17:19:18 +0200 Subject: [PATCH 09/18] feature(protocol-config): make http implementation and struct public --- .../src/http_client/http_impl.rs | 8 ++------ .../mithril-protocol-config/src/http_client/mod.rs | 2 +- .../mithril_network_configuration_provider.rs | 13 +++++++++++++ .../mithril-protocol-config/src/test/double/mod.rs | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/internal/mithril-protocol-config/src/http_client/http_impl.rs b/internal/mithril-protocol-config/src/http_client/http_impl.rs index b4bc509e1b4..6f1b2adcb23 100644 --- a/internal/mithril-protocol-config/src/http_client/http_impl.rs +++ b/internal/mithril-protocol-config/src/http_client/http_impl.rs @@ -11,9 +11,8 @@ use async_trait::async_trait; use mithril_common::api_version::APIVersionProvider; use mithril_common::{StdResult, entities::SignedEntityTypeDiscriminants}; -struct HttpMithrilNetworkConfigurationProvider { +pub struct HttpMithrilNetworkConfigurationProvider { aggregator_client: AggregatorHTTPClient, - logger: slog::Logger, } impl HttpMithrilNetworkConfigurationProvider { @@ -31,10 +30,7 @@ impl HttpMithrilNetworkConfigurationProvider { logger.clone(), ); - Self { - aggregator_client, - logger, - } + Self { aggregator_client } } } diff --git a/internal/mithril-protocol-config/src/http_client/mod.rs b/internal/mithril-protocol-config/src/http_client/mod.rs index b57c837658c..40fd6cc26c7 100644 --- a/internal/mithril-protocol-config/src/http_client/mod.rs +++ b/internal/mithril-protocol-config/src/http_client/mod.rs @@ -1,2 +1,2 @@ mod aggregator_client; -mod http_impl; +pub mod http_impl; diff --git a/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs b/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs index c37445218e8..1455de9dd11 100644 --- a/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs +++ b/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs @@ -14,6 +14,19 @@ impl FakeMithrilNetworkConfigurationProvider { } } +impl Default for FakeMithrilNetworkConfigurationProvider { + fn default() -> Self { + Self { + configuration: MithrilNetworkConfiguration { + epoch: Default::default(), + signer_registration_protocol_parameters: Default::default(), + available_signed_entity_types: Default::default(), + signed_entity_types_config: Default::default(), + }, + } + } +} + #[cfg_attr(target_family = "wasm", async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait)] impl MithrilNetworkConfigurationProvider for FakeMithrilNetworkConfigurationProvider { diff --git a/internal/mithril-protocol-config/src/test/double/mod.rs b/internal/mithril-protocol-config/src/test/double/mod.rs index 677b623e64f..55f5fdfd79c 100644 --- a/internal/mithril-protocol-config/src/test/double/mod.rs +++ b/internal/mithril-protocol-config/src/test/double/mod.rs @@ -1,2 +1,2 @@ mod fake_data; -mod mithril_network_configuration_provider; +pub mod mithril_network_configuration_provider; From 42658eaa7bbe7171cc62b1e4463bb89ce917204a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Tue, 30 Sep 2025 17:20:13 +0200 Subject: [PATCH 10/18] feature(common): Derive Default for ProtocolParameters for tests --- mithril-common/src/entities/protocol_parameters.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mithril-common/src/entities/protocol_parameters.rs b/mithril-common/src/entities/protocol_parameters.rs index 346c51445f2..58177aa23d2 100644 --- a/mithril-common/src/entities/protocol_parameters.rs +++ b/mithril-common/src/entities/protocol_parameters.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; /// Protocol cryptographic parameters -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct ProtocolParameters { /// Quorum parameter pub k: u64, From a092441c73a0174baf962f3a56f84adc18ddbed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Tue, 30 Sep 2025 17:23:53 +0200 Subject: [PATCH 11/18] feature(signer): add dependency injection for MithrilNetworkConfigurationProvider --- Cargo.lock | 1 + mithril-signer/Cargo.toml | 1 + .../src/dependency_injection/builder.rs | 10 ++++++++++ .../src/dependency_injection/containers.rs | 5 +++++ mithril-signer/src/runtime/runner.rs | 15 +++++++++++++++ .../tests/test_extensions/state_machine_tester.rs | 5 +++++ 6 files changed, 37 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 54dd975294c..78397dab069 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4510,6 +4510,7 @@ dependencies = [ "mithril-era", "mithril-metric", "mithril-persistence", + "mithril-protocol-config", "mithril-signed-entity-lock", "mithril-signed-entity-preloader", "mithril-ticker", diff --git a/mithril-signer/Cargo.toml b/mithril-signer/Cargo.toml index 6beed3eb73c..2c062226b3e 100644 --- a/mithril-signer/Cargo.toml +++ b/mithril-signer/Cargo.toml @@ -32,6 +32,7 @@ mithril-doc = { path = "../internal/mithril-doc" } mithril-era = { path = "../internal/mithril-era" } mithril-metric = { path = "../internal/mithril-metric" } mithril-persistence = { path = "../internal/mithril-persistence" } +mithril-protocol-config = { path = "../internal/mithril-protocol-config" } mithril-signed-entity-lock = { path = "../internal/signed-entity/mithril-signed-entity-lock" } mithril-signed-entity-preloader = { path = "../internal/signed-entity/mithril-signed-entity-preloader" } mithril-ticker = { path = "../internal/mithril-ticker" } diff --git a/mithril-signer/src/dependency_injection/builder.rs b/mithril-signer/src/dependency_injection/builder.rs index 2a737d04244..de4ce819c57 100644 --- a/mithril-signer/src/dependency_injection/builder.rs +++ b/mithril-signer/src/dependency_injection/builder.rs @@ -41,6 +41,8 @@ use mithril_persistence::database::repository::CardanoTransactionRepository; use mithril_persistence::database::{ApplicationNodeType, SqlMigration}; use mithril_persistence::sqlite::{ConnectionBuilder, SqliteConnection, SqliteConnectionPool}; +use mithril_protocol_config::http_client::http_impl::HttpMithrilNetworkConfigurationProvider; + #[cfg(feature = "future_dmq")] use mithril_dmq::{DmqMessageBuilder, DmqPublisherClientPallas}; @@ -503,6 +505,13 @@ impl<'a> DependenciesBuilder<'a> { self.root_logger(), )); + let network_configuration_service = Arc::new(HttpMithrilNetworkConfigurationProvider::new( + self.config.aggregator_endpoint.clone(), + self.config.relay_endpoint.clone(), + api_version_provider.clone(), + self.root_logger(), + )); + let services = SignerDependencyContainer { ticker_service, certificate_handler: aggregator_client, @@ -522,6 +531,7 @@ impl<'a> DependenciesBuilder<'a> { epoch_service, certifier, kes_signer, + network_configuration_service, }; Ok(services) diff --git a/mithril-signer/src/dependency_injection/containers.rs b/mithril-signer/src/dependency_injection/containers.rs index 7f76d16d69b..ce74c456a38 100644 --- a/mithril-signer/src/dependency_injection/containers.rs +++ b/mithril-signer/src/dependency_injection/containers.rs @@ -9,6 +9,7 @@ use mithril_cardano_node_chain::chain_observer::ChainObserver; use mithril_cardano_node_internal_database::digesters::ImmutableDigester; use mithril_era::{EraChecker, EraReader}; use mithril_persistence::store::StakeStorer; +use mithril_protocol_config::interface::MithrilNetworkConfigurationProvider; use mithril_signed_entity_lock::SignedEntityTypeLock; use mithril_signed_entity_preloader::CardanoTransactionsPreloader; use mithril_ticker::TickerService; @@ -26,6 +27,7 @@ type DigesterService = Arc; type SingleSignerService = Arc; type TimePointProviderService = Arc; type ProtocolInitializerStoreService = Arc; +type MithrilNetworkConfigurationService = Arc; /// EpochServiceWrapper wraps a [EpochService] pub type EpochServiceWrapper = Arc>; @@ -85,4 +87,7 @@ pub struct SignerDependencyContainer { /// Kes signer service pub kes_signer: Option>, + + /// Mithril network configuration service + pub network_configuration_service: MithrilNetworkConfigurationService, } diff --git a/mithril-signer/src/runtime/runner.rs b/mithril-signer/src/runtime/runner.rs index d259f181757..ac664269ed7 100644 --- a/mithril-signer/src/runtime/runner.rs +++ b/mithril-signer/src/runtime/runner.rs @@ -10,6 +10,7 @@ use mithril_common::entities::{ Epoch, PartyId, ProtocolMessage, SignedEntityType, Signer, TimePoint, }; use mithril_common::logging::LoggerExtensions; +use mithril_protocol_config::model::MithrilNetworkConfiguration; use crate::Configuration; use crate::dependency_injection::SignerDependencyContainer; @@ -19,6 +20,9 @@ use crate::services::{EpochService, MithrilProtocolInitializerBuilder}; /// This trait is mainly intended for mocking. #[async_trait] pub trait Runner: Send + Sync { + ///Fetch the configuration parameters of a Mithril network + async fn get_mithril_network_configuration(&self) -> StdResult; + /// Fetch the current epoch settings if any. async fn get_epoch_settings(&self) -> StdResult>; @@ -102,6 +106,12 @@ impl SignerRunner { #[cfg_attr(test, mockall::automock)] #[async_trait] impl Runner for SignerRunner { + async fn get_mithril_network_configuration(&self) -> StdResult { + debug!(self.logger, ">> get_mithril_network_configuration"); + + self.services.network_configuration_service.get().await + } + async fn get_epoch_settings(&self) -> StdResult> { debug!(self.logger, ">> get_epoch_settings"); @@ -359,6 +369,7 @@ mod tests { }, }; use mithril_era::{EraChecker, EraReader, adapters::EraReaderBootstrapAdapter}; + use mithril_protocol_config::test::double::mithril_network_configuration_provider::FakeMithrilNetworkConfigurationProvider; use mithril_signed_entity_lock::SignedEntityTypeLock; use mithril_signed_entity_preloader::{ CardanoTransactionsPreloader, CardanoTransactionsPreloaderActivation, @@ -514,6 +525,9 @@ mod tests { )); let kes_signer = None; + let network_configuration_service = + Arc::new(FakeMithrilNetworkConfigurationProvider::default()); + SignerDependencyContainer { stake_store, certificate_handler: aggregator_client, @@ -533,6 +547,7 @@ mod tests { epoch_service, certifier, kes_signer, + network_configuration_service, } } diff --git a/mithril-signer/tests/test_extensions/state_machine_tester.rs b/mithril-signer/tests/test_extensions/state_machine_tester.rs index 43dd22662b4..b1e09e6cd91 100644 --- a/mithril-signer/tests/test_extensions/state_machine_tester.rs +++ b/mithril-signer/tests/test_extensions/state_machine_tester.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] use anyhow::anyhow; use mithril_metric::{MetricCollector, MetricsServiceExporter}; +use mithril_protocol_config::test::double::mithril_network_configuration_provider::FakeMithrilNetworkConfigurationProvider; use prometheus_parse::Value; use slog::Drain; use slog_scope::debug; @@ -294,6 +295,9 @@ impl StateMachineTester { config.operational_certificate_path.clone().unwrap(), )) as Arc); + let network_configuration_service = + Arc::new(FakeMithrilNetworkConfigurationProvider::default()); + let services = SignerDependencyContainer { certificate_handler: certificate_handler.clone(), ticker_service: ticker_service.clone(), @@ -313,6 +317,7 @@ impl StateMachineTester { epoch_service, certifier, kes_signer, + network_configuration_service, }; // set up stake distribution chain_observer.set_signers(signers_with_stake.to_owned()).await; From 837a88f3068c257920ae8dec4b2c530679bc5543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Wed, 1 Oct 2025 18:01:25 +0200 Subject: [PATCH 12/18] feature(protocol-config, signer): now retrieving epoch settings from MithrilNetworkConfiguration, adapt inform_epoch_settings signature --- Cargo.lock | 1 + internal/mithril-protocol-config/Cargo.toml | 1 + internal/mithril-protocol-config/src/model.rs | 72 +++++++ .../src/test/double/dummies.rs | 35 ++++ .../mithril_network_configuration_provider.rs | 123 ++++++------ .../src/test/double/mod.rs | 1 + mithril-signer/src/runtime/runner.rs | 96 +++++---- mithril-signer/src/runtime/state_machine.rs | 98 ++++++++-- .../src/services/aggregator_client.rs | 8 - mithril-signer/src/services/epoch_service.rs | 182 ++++++++++++------ .../test_extensions/certificate_handler.rs | 2 + .../test_extensions/state_machine_tester.rs | 30 ++- 12 files changed, 454 insertions(+), 195 deletions(-) create mode 100644 internal/mithril-protocol-config/src/test/double/dummies.rs diff --git a/Cargo.lock b/Cargo.lock index 78397dab069..f2b1b84ded8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4414,6 +4414,7 @@ dependencies = [ "http 1.3.1", "httpmock", "mithril-common", + "mithril-ticker", "mockall", "reqwest", "semver", diff --git a/internal/mithril-protocol-config/Cargo.toml b/internal/mithril-protocol-config/Cargo.toml index 953be99b68a..49af15d0bce 100644 --- a/internal/mithril-protocol-config/Cargo.toml +++ b/internal/mithril-protocol-config/Cargo.toml @@ -12,6 +12,7 @@ repository = { workspace = true } anyhow = { workspace = true } async-trait = { workspace = true } mithril-common = { path = "../../mithril-common" } +mithril-ticker = { path = "../mithril-ticker" } reqwest = { workspace = true, features = [ "default", "stream", diff --git a/internal/mithril-protocol-config/src/model.rs b/internal/mithril-protocol-config/src/model.rs index 86a5db6323d..93f0d6f87ef 100644 --- a/internal/mithril-protocol-config/src/model.rs +++ b/internal/mithril-protocol-config/src/model.rs @@ -26,3 +26,75 @@ pub struct MithrilNetworkConfiguration { pub signed_entity_types_config: HashMap, } + +impl MithrilNetworkConfiguration { + /// Get the Cardano Transactions signing configuration + pub fn get_cardano_transactions_signing_config( + &self, + ) -> Option { + match self + .signed_entity_types_config + .get(&SignedEntityTypeDiscriminants::CardanoTransactions) + { + Some(SignedEntityTypeConfiguration::CardanoTransactions(config)) => { + Some(config.clone()) + } + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use mithril_common::entities::{ + BlockNumber, CardanoTransactionsSigningConfig, SignedEntityTypeDiscriminants, + }; + + use crate::model::{MithrilNetworkConfiguration, SignedEntityTypeConfiguration}; + + fn default() -> MithrilNetworkConfiguration { + MithrilNetworkConfiguration { + epoch: Default::default(), + signer_registration_protocol_parameters: Default::default(), + available_signed_entity_types: Default::default(), + signed_entity_types_config: Default::default(), + } + } + + #[test] + fn test_get_cardano_transactions_signing_config_should_return_config_if_cardano_transactions_exist() + { + let config = MithrilNetworkConfiguration { + signed_entity_types_config: HashMap::from([( + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeConfiguration::CardanoTransactions( + CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(42), + step: BlockNumber(26), + }, + ), + )]), + ..default() + }; + + let result = config.get_cardano_transactions_signing_config(); + assert!(result.is_some()); + let unwrapped_result = result.unwrap(); + assert_eq!(unwrapped_result.security_parameter, BlockNumber(42)); + assert_eq!(unwrapped_result.step, BlockNumber(26)); + } + + #[test] + fn test_get_cardano_transactions_signing_config_should_return_none_if_there_is_no_cardano_transactions() + { + let config = MithrilNetworkConfiguration { + signed_entity_types_config: HashMap::new(), + ..default() + }; + + let result = config.get_cardano_transactions_signing_config(); + assert!(result.is_none()); + } +} diff --git a/internal/mithril-protocol-config/src/test/double/dummies.rs b/internal/mithril-protocol-config/src/test/double/dummies.rs new file mode 100644 index 00000000000..775b80d6691 --- /dev/null +++ b/internal/mithril-protocol-config/src/test/double/dummies.rs @@ -0,0 +1,35 @@ +use std::collections::{BTreeSet, HashMap}; + +use mithril_common::{ + entities::{CardanoTransactionsSigningConfig, SignedEntityTypeDiscriminants}, + test::double::{Dummy, fake_data}, +}; + +use crate::model::{MithrilNetworkConfiguration, SignedEntityTypeConfiguration}; + +impl Dummy for MithrilNetworkConfiguration { + /// Return a dummy [MithrilNetworkConfiguration] (test-only). + fn dummy() -> Self { + let beacon = fake_data::beacon(); + + let signer_registration_protocol_parameters = fake_data::protocol_parameters(); + + let mut available_signed_entity_types = BTreeSet::new(); + available_signed_entity_types.insert(SignedEntityTypeDiscriminants::CardanoTransactions); + + let mut signed_entity_types_config = HashMap::new(); + signed_entity_types_config.insert( + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeConfiguration::CardanoTransactions( + CardanoTransactionsSigningConfig::dummy(), + ), + ); + + Self { + epoch: beacon.epoch, + signer_registration_protocol_parameters, + available_signed_entity_types, + signed_entity_types_config, + } + } +} diff --git a/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs b/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs index 1455de9dd11..bf76943ae13 100644 --- a/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs +++ b/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs @@ -1,81 +1,82 @@ -use crate::{interface::MithrilNetworkConfigurationProvider, model::MithrilNetworkConfiguration}; +use std::{ + collections::{BTreeSet, HashMap}, + sync::Arc, +}; + +use tokio::sync::RwLock; + +use crate::{ + interface::MithrilNetworkConfigurationProvider, + model::{MithrilNetworkConfiguration, SignedEntityTypeConfiguration}, +}; +use anyhow::Error; use async_trait::async_trait; -use mithril_common::StdResult; +use mithril_common::{ + StdResult, + entities::{ProtocolParameters, SignedEntityTypeDiscriminants, TimePoint}, +}; +use mithril_ticker::{MithrilTickerService, TickerService}; /// A fake [MithrilNetworkConfigurationProvider] that return [MithrilNetworkConfiguration] pub struct FakeMithrilNetworkConfigurationProvider { - configuration: MithrilNetworkConfiguration, -} + pub signer_registration_protocol_parameters: ProtocolParameters, -impl FakeMithrilNetworkConfigurationProvider { - /// Create a new [FakeMithrilNetworkConfigurationProvider] - pub fn from_mithril_network_configuration(configuration: MithrilNetworkConfiguration) -> Self { - Self { configuration } - } + pub available_signed_entity_types: RwLock>, + + pub signed_entity_types_config: + HashMap, + + ticker_service: Arc, } -impl Default for FakeMithrilNetworkConfigurationProvider { - fn default() -> Self { +impl FakeMithrilNetworkConfigurationProvider { + pub fn new( + signer_registration_protocol_parameters: ProtocolParameters, + available_signed_entity_types: BTreeSet, + signed_entity_types_config: HashMap< + SignedEntityTypeDiscriminants, + SignedEntityTypeConfiguration, + >, + ticker_service: Arc, + ) -> Self { Self { - configuration: MithrilNetworkConfiguration { - epoch: Default::default(), - signer_registration_protocol_parameters: Default::default(), - available_signed_entity_types: Default::default(), - signed_entity_types_config: Default::default(), - }, + signer_registration_protocol_parameters, + available_signed_entity_types: RwLock::new(available_signed_entity_types), + signed_entity_types_config, + ticker_service, } } + + async fn get_time_point(&self) -> Result { + let time_point = self.ticker_service.get_current_time_point().await?; + + Ok(time_point) + } + + pub async fn change_allowed_discriminants( + &self, + discriminants: &BTreeSet, + ) { + let mut available_signed_entity_types = self.available_signed_entity_types.write().await; + *available_signed_entity_types = discriminants.clone(); + } } #[cfg_attr(target_family = "wasm", async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait)] impl MithrilNetworkConfigurationProvider for FakeMithrilNetworkConfigurationProvider { async fn get(&self) -> StdResult { - Ok(self.configuration.clone()) - } -} - -#[cfg(test)] -mod tests { - use std::collections::{BTreeSet, HashMap}; - - use mithril_common::{ - StdResult, - entities::{BlockNumber, CardanoTransactionsSigningConfig, SignedEntityTypeDiscriminants}, - }; - - use super::*; - use crate::{model::SignedEntityTypeConfiguration, test::double::fake_data}; - - #[tokio::test] - async fn fake_mithril_network_configuration_provider_returns_configuration() -> StdResult<()> { - let mut available_signed_entity_types = BTreeSet::new(); - available_signed_entity_types.insert(SignedEntityTypeDiscriminants::CardanoTransactions); - - let mut signed_entity_types_config = HashMap::new(); - signed_entity_types_config.insert( - SignedEntityTypeDiscriminants::CardanoTransactions, - SignedEntityTypeConfiguration::CardanoTransactions(CardanoTransactionsSigningConfig { - security_parameter: BlockNumber(13), - step: BlockNumber(26), - }), - ); - - let configuration = fake_data::mithril_network_configuration( - 42, - 1, - 2, - 0.123, - available_signed_entity_types, - signed_entity_types_config, - ); - let provider = FakeMithrilNetworkConfigurationProvider::from_mithril_network_configuration( - configuration.clone(), - ); + let time_point = self.get_time_point().await?; - let retrieved_configuration = provider.get().await?; + let available_signed_entity_types = self.available_signed_entity_types.read().await; - assert_eq!(configuration, retrieved_configuration); - Ok(()) + Ok(MithrilNetworkConfiguration { + epoch: time_point.epoch, + signer_registration_protocol_parameters: self + .signer_registration_protocol_parameters + .clone(), + available_signed_entity_types: available_signed_entity_types.clone(), + signed_entity_types_config: self.signed_entity_types_config.clone(), + }) } } diff --git a/internal/mithril-protocol-config/src/test/double/mod.rs b/internal/mithril-protocol-config/src/test/double/mod.rs index 55f5fdfd79c..bcc380e0458 100644 --- a/internal/mithril-protocol-config/src/test/double/mod.rs +++ b/internal/mithril-protocol-config/src/test/double/mod.rs @@ -1,2 +1,3 @@ +mod dummies; mod fake_data; pub mod mithril_network_configuration_provider; diff --git a/mithril-signer/src/runtime/runner.rs b/mithril-signer/src/runtime/runner.rs index ac664269ed7..718c6c27b8a 100644 --- a/mithril-signer/src/runtime/runner.rs +++ b/mithril-signer/src/runtime/runner.rs @@ -42,7 +42,12 @@ pub trait Runner: Send + Sync { async fn can_sign_current_epoch(&self) -> StdResult; /// Register epoch information - async fn inform_epoch_settings(&self, epoch_settings: SignerEpochSettings) -> StdResult<()>; + async fn inform_epoch_settings( + &self, + mithril_network_configuration: MithrilNetworkConfiguration, + current_signer: Vec, + next_signer: Vec, + ) -> StdResult<()>; /// Create the message to be signed with the single signature. async fn compute_message( @@ -250,25 +255,22 @@ impl Runner for SignerRunner { epoch_service.can_signer_sign_current_epoch(self.services.single_signer.get_party_id()) } - async fn inform_epoch_settings(&self, epoch_settings: SignerEpochSettings) -> StdResult<()> { + async fn inform_epoch_settings( + &self, + mithril_network_configuration: MithrilNetworkConfiguration, + current_signer: Vec, + next_signer: Vec, + ) -> StdResult<()> { debug!( self.logger, - ">> inform_epoch_settings(epoch:{})", epoch_settings.epoch + ">> inform_epoch_settings(epoch:{})", mithril_network_configuration.epoch ); - let aggregator_features = self - .services - .certificate_handler - .retrieve_aggregator_features() - .await?; self.services .epoch_service .write() .await - .inform_epoch_settings( - epoch_settings, - aggregator_features.capabilities.signed_entity_types, - ) + .inform_epoch_settings(mithril_network_configuration, current_signer, next_signer) .await } @@ -357,7 +359,6 @@ mod tests { api_version::APIVersionProvider, crypto_helper::{MKMap, MKMapNode, MKTreeNode, MKTreeStoreInMemory, MKTreeStorer}, entities::{BlockNumber, BlockRange, Epoch, SignedEntityTypeDiscriminants}, - messages::{AggregatorCapabilities, AggregatorFeaturesMessage}, signable_builder::{ BlockRangeRootRetriever, CardanoStakeDistributionSignableBuilder, CardanoTransactionsSignableBuilder, MithrilSignableBuilderService, @@ -525,8 +526,12 @@ mod tests { )); let kes_signer = None; - let network_configuration_service = - Arc::new(FakeMithrilNetworkConfigurationProvider::default()); + let network_configuration_service = Arc::new(FakeMithrilNetworkConfigurationProvider::new( + Default::default(), + Default::default(), + Default::default(), + ticker_service.clone(), + )); SignerDependencyContainer { stake_store, @@ -638,14 +643,20 @@ mod tests { .unwrap(); let runner = init_runner(Some(services), None).await; - // inform epoch settings - let epoch_settings = SignerEpochSettings { + + let mithril_network_configuration = MithrilNetworkConfiguration { epoch: current_epoch, - current_signers: fixture.signers(), - next_signers: fixture.signers(), - ..SignerEpochSettings::dummy().clone() + ..MithrilNetworkConfiguration::dummy() }; - runner.inform_epoch_settings(epoch_settings).await.unwrap(); + + runner + .inform_epoch_settings( + mithril_network_configuration, + fixture.signers(), + fixture.signers(), + ) + .await + .unwrap(); runner .register_signer_to_aggregator() @@ -697,26 +708,39 @@ mod tests { async fn test_inform_epoch_setting_pass_allowed_discriminant_to_epoch_service() { let mut services = init_services().await; let certificate_handler = Arc::new(DumbAggregatorClient::default()); - certificate_handler - .set_aggregator_features(AggregatorFeaturesMessage { - capabilities: AggregatorCapabilities { - signed_entity_types: BTreeSet::from([ - SignedEntityTypeDiscriminants::MithrilStakeDistribution, - SignedEntityTypeDiscriminants::CardanoTransactions, - ]), - ..AggregatorFeaturesMessage::dummy().capabilities - }, - ..AggregatorFeaturesMessage::dummy() - }) - .await; + // certificate_handler + // .set_aggregator_features(AggregatorFeaturesMessage { + // capabilities: AggregatorCapabilities { + // signed_entity_types: BTreeSet::from([ + // SignedEntityTypeDiscriminants::MithrilStakeDistribution, + // SignedEntityTypeDiscriminants::CardanoTransactions, + // ]), + // ..AggregatorFeaturesMessage::dummy().capabilities + // }, + // ..AggregatorFeaturesMessage::dummy() + // }) + // .await; services.certificate_handler = certificate_handler; let runner = init_runner(Some(services), None).await; - let epoch_settings = SignerEpochSettings { + let mithril_network_configuration = MithrilNetworkConfiguration { epoch: Epoch(1), - ..SignerEpochSettings::dummy() + available_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + ..MithrilNetworkConfiguration::dummy() }; - runner.inform_epoch_settings(epoch_settings).await.unwrap(); + + // Signers + let signers = fake_data::signers(5); + let current_signers = signers[1..3].to_vec(); + let next_signers = signers[2..5].to_vec(); + + runner + .inform_epoch_settings(mithril_network_configuration, current_signers, next_signers) + .await + .unwrap(); let epoch_service = runner.services.epoch_service.read().await; let recorded_allowed_discriminants = epoch_service.allowed_discriminants().unwrap(); diff --git a/mithril-signer/src/runtime/state_machine.rs b/mithril-signer/src/runtime/state_machine.rs index aea825ddcae..f1339eab710 100644 --- a/mithril-signer/src/runtime/state_machine.rs +++ b/mithril-signer/src/runtime/state_machine.rs @@ -1,20 +1,17 @@ use anyhow::Error; use chrono::Local; +use mithril_protocol_config::model::MithrilNetworkConfiguration; use slog::{Logger, debug, info}; use std::{fmt::Display, ops::Deref, sync::Arc, time::Duration}; use tokio::sync::Mutex; use mithril_common::{ crypto_helper::ProtocolInitializerError, - entities::{Epoch, TimePoint}, + entities::{Epoch, Signer, TimePoint}, logging::LoggerExtensions, }; -use crate::{ - MetricsService, - entities::{BeaconToSign, SignerEpochSettings}, - services::AggregatorClientError, -}; +use crate::{MetricsService, entities::BeaconToSign, services::AggregatorClientError}; use super::{Runner, RuntimeError}; @@ -167,7 +164,7 @@ impl StateMachine { "→ Epoch has changed, transiting to Unregistered" ); *state = self.transition_from_unregistered_to_unregistered(new_epoch).await?; - } else if let Some(epoch_settings) = self + } else if let Some(signer_settings) = self .runner .get_epoch_settings() .await @@ -176,19 +173,33 @@ impl StateMachine { nested_error: Some(e), })? { - info!(self.logger, "→ Epoch settings found"); - if epoch_settings.epoch >= *epoch { + info!(self.logger, "→ Epoch settings found"); //TODO Do we switch the logs and type from SignerEpochSettings to SignerSettings since now we only read current and next signer from it ? + let network_configuration = self + .runner + .get_mithril_network_configuration() + .await + .map_err(|e| RuntimeError::KeepState { + message: "could not retrieve mithril network configuration".to_string(), + nested_error: Some(e), + })?; + info!(self.logger, "→ Mithril network configuration found"); + + if network_configuration.epoch >= *epoch { info!(self.logger, "New Epoch found"); info!(self.logger, " ⋅ Transiting to Registered"); *state = self .transition_from_unregistered_to_one_of_registered_states( - epoch_settings, + network_configuration, + signer_settings.current_signers, + signer_settings.next_signers, ) .await?; } else { info!( - self.logger, " ⋅ Epoch settings found, but its epoch is behind the known epoch, waiting…"; - "epoch_settings" => ?epoch_settings, + self.logger, " ⋅ Signer settings and Network Configuration found, but its epoch is behind the known epoch, waiting…"; + "network_configuration" => ?network_configuration, + "current_singer" => ?signer_settings.current_signers, + "next_signer" => ?signer_settings.next_signers, "known_epoch" => ?epoch, ); } @@ -286,7 +297,9 @@ impl StateMachine { /// Launch the transition process from the `Unregistered` to `ReadyToSign` or `RegisteredNotAbleToSign` state. async fn transition_from_unregistered_to_one_of_registered_states( &self, - epoch_settings: SignerEpochSettings, + mithril_network_configuration: MithrilNetworkConfiguration, + current_signer: Vec, + next_signer: Vec, ) -> Result { self.metrics_service .get_signer_registration_total_since_startup_counter() @@ -302,7 +315,7 @@ impl StateMachine { })?; self.runner - .inform_epoch_settings(epoch_settings) + .inform_epoch_settings(mithril_network_configuration, current_signer, next_signer) .await .map_err(|e| RuntimeError::KeepState { message: format!( @@ -474,11 +487,13 @@ impl StateMachine { mod tests { use anyhow::anyhow; use chrono::DateTime; + use mithril_protocol_config::model::MithrilNetworkConfiguration; use mockall::predicate; use mithril_common::entities::{ChainPoint, Epoch, ProtocolMessage, SignedEntityType}; use mithril_common::test::double::{Dummy, fake_data}; + use crate::SignerEpochSettings; use crate::runtime::runner::MockSignerRunner; use crate::services::AggregatorClientError; use crate::test_tools::TestLogger; @@ -528,7 +543,7 @@ mod tests { async fn unregistered_epoch_settings_behind_known_epoch() { let mut runner = MockSignerRunner::new(); let epoch_settings = SignerEpochSettings { - epoch: Epoch(3), + epoch: Epoch(999), // epoch is no longer read from get_epoch_settings anymore but from mithril network configuration registration_protocol_parameters: fake_data::protocol_parameters(), current_signers: vec![], next_signers: vec![], @@ -539,6 +554,17 @@ mod tests { .expect_get_epoch_settings() .once() .returning(move || Ok(Some(epoch_settings.to_owned()))); + runner + .expect_get_mithril_network_configuration() + .once() + .returning(|| { + Ok(MithrilNetworkConfiguration { + epoch: Epoch(3), + signer_registration_protocol_parameters: fake_data::protocol_parameters(), + available_signed_entity_types: Default::default(), + signed_entity_types_config: Default::default(), + }) + }); runner.expect_get_current_time_point().once().returning(|| { Ok(TimePoint { epoch: Epoch(4), @@ -567,11 +593,21 @@ mod tests { .once() .returning(|| Ok(Some(SignerEpochSettings::dummy()))); + runner + .expect_get_mithril_network_configuration() + .once() + .returning(|| Ok(MithrilNetworkConfiguration::dummy())); + runner .expect_inform_epoch_settings() - .with(predicate::eq(SignerEpochSettings::dummy())) + .with( + //todo do we really want to specify a WITH ? + predicate::eq(MithrilNetworkConfiguration::dummy()), + predicate::eq(SignerEpochSettings::dummy().current_signers), + predicate::eq(SignerEpochSettings::dummy().next_signers), + ) .once() - .returning(|_| Ok(())); + .returning(|_, _, _| Ok(())); runner .expect_get_current_time_point() @@ -615,11 +651,21 @@ mod tests { .once() .returning(|| Ok(Some(SignerEpochSettings::dummy()))); + runner + .expect_get_mithril_network_configuration() + .once() + .returning(|| Ok(MithrilNetworkConfiguration::dummy())); + runner .expect_inform_epoch_settings() - .with(predicate::eq(SignerEpochSettings::dummy())) + .with( + //todo do we really want to specify a WITH ? + predicate::eq(MithrilNetworkConfiguration::dummy()), + predicate::eq(SignerEpochSettings::dummy().current_signers), + predicate::eq(SignerEpochSettings::dummy().next_signers), + ) .once() - .returning(|_| Ok(())); + .returning(|_, _, _| Ok(())); runner .expect_get_current_time_point() @@ -667,11 +713,21 @@ mod tests { .once() .returning(|| Ok(Some(SignerEpochSettings::dummy()))); + runner + .expect_get_mithril_network_configuration() + .once() + .returning(|| Ok(MithrilNetworkConfiguration::dummy())); + runner .expect_inform_epoch_settings() - .with(predicate::eq(SignerEpochSettings::dummy())) + .with( + //todo do we really want to specify a WITH ? + predicate::eq(MithrilNetworkConfiguration::dummy()), + predicate::eq(SignerEpochSettings::dummy().current_signers), + predicate::eq(SignerEpochSettings::dummy().next_signers), + ) .once() - .returning(|_| Ok(())); + .returning(|_, _, _| Ok(())); runner .expect_get_current_time_point() diff --git a/mithril-signer/src/services/aggregator_client.rs b/mithril-signer/src/services/aggregator_client.rs index 1ccd35c0c65..25e388a0208 100644 --- a/mithril-signer/src/services/aggregator_client.rs +++ b/mithril-signer/src/services/aggregator_client.rs @@ -399,14 +399,6 @@ pub(crate) mod dumb { pub async fn get_last_registered_signer(&self) -> Option { self.last_registered_signer.read().await.clone() } - - pub async fn set_aggregator_features( - &self, - aggregator_features: AggregatorFeaturesMessage, - ) { - let mut aggregator_features_writer = self.aggregator_features.write().await; - *aggregator_features_writer = aggregator_features; - } } impl Default for DumbAggregatorClient { diff --git a/mithril-signer/src/services/epoch_service.rs b/mithril-signer/src/services/epoch_service.rs index 4f13e45937f..79ca80dec1b 100644 --- a/mithril-signer/src/services/epoch_service.rs +++ b/mithril-signer/src/services/epoch_service.rs @@ -1,5 +1,6 @@ use anyhow::anyhow; use async_trait::async_trait; +use mithril_protocol_config::model::MithrilNetworkConfiguration; use slog::{Logger, debug, trace, warn}; use std::collections::BTreeSet; use std::sync::Arc; @@ -7,7 +8,6 @@ use thiserror::Error; use crate::RunnerError; use crate::dependency_injection::EpochServiceWrapper; -use crate::entities::SignerEpochSettings; use crate::services::SignedEntityConfigProvider; use crate::store::ProtocolInitializerStorer; use mithril_common::StdResult; @@ -36,8 +36,9 @@ pub trait EpochService: Sync + Send { /// internal state for the new epoch. async fn inform_epoch_settings( &mut self, - epoch_settings: SignerEpochSettings, - allowed_discriminants: BTreeSet, + mithril_network_configuration: MithrilNetworkConfiguration, + current_signers: Vec, + next_signers: Vec, ) -> StdResult<()>; /// Get the current epoch for which the data stored in this service are computed. @@ -168,25 +169,38 @@ impl MithrilEpochService { impl EpochService for MithrilEpochService { async fn inform_epoch_settings( &mut self, - epoch_settings: SignerEpochSettings, - allowed_discriminants: BTreeSet, + mithril_network_configuration: MithrilNetworkConfiguration, + current_signers: Vec, + next_signers: Vec, ) -> StdResult<()> { - debug!(self.logger, ">> inform_epoch_settings"; "epoch_settings" => ?epoch_settings); + debug!(self.logger, ">> inform_epoch_settings"; "mithril_network_configuration" => ?mithril_network_configuration); //TODO: what about current_signers and next_signers? + + let epoch = mithril_network_configuration.epoch; + + let registration_protocol_parameters = mithril_network_configuration + .signer_registration_protocol_parameters + .clone(); - let epoch = epoch_settings.epoch; let protocol_initializer = self .protocol_initializer_store .get_protocol_initializer(epoch.offset_to_signer_retrieval_epoch()?) .await?; + let allowed_discriminants = + mithril_network_configuration.available_signed_entity_types.clone(); + + let cardano_transactions_signing_config = mithril_network_configuration + .get_cardano_transactions_signing_config() + .clone(); + self.epoch_data = Some(EpochData { epoch, - registration_protocol_parameters: epoch_settings.registration_protocol_parameters, + registration_protocol_parameters, protocol_initializer, - current_signers: epoch_settings.current_signers, - next_signers: epoch_settings.next_signers, + current_signers, + next_signers, allowed_discriminants, - cardano_transactions_signing_config: epoch_settings.cardano_transactions_signing_config, + cardano_transactions_signing_config, }); Ok(()) @@ -363,8 +377,9 @@ pub(crate) mod mock_epoch_service { impl EpochService for EpochServiceImpl { async fn inform_epoch_settings( &mut self, - epoch_settings: SignerEpochSettings, - allowed_discriminants: BTreeSet, + mithril_network_configuration: MithrilNetworkConfiguration, + current_signers: Vec, + next_signers: Vec, ) -> StdResult<()>; fn epoch_of_current_data(&self) -> StdResult; @@ -405,6 +420,8 @@ pub(crate) mod mock_epoch_service { #[cfg(test)] mod tests { + use mithril_protocol_config::model::SignedEntityTypeConfiguration; + use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; @@ -416,7 +433,6 @@ mod tests { use crate::database::repository::{ProtocolInitializerRepository, StakePoolStore}; use crate::database::test_helper::main_db_connection; - use crate::entities::SignerEpochSettings; use crate::services::MithrilProtocolInitializerBuilder; use crate::test_tools::TestLogger; @@ -453,27 +469,40 @@ mod tests { { let fixtures = MithrilFixtureBuilder::default().with_signers(10).build(); let protocol_initializer = fixtures.signers_fixture()[0].protocol_initializer.to_owned(); - let epoch = Epoch(12); - let signers = fixtures.signers(); let connection = Arc::new(main_db_connection().unwrap()); let stake_store = Arc::new(StakePoolStore::new(connection.clone(), None)); let protocol_initializer_store = Arc::new(ProtocolInitializerRepository::new(connection, None)); - let epoch_settings = SignerEpochSettings { + // let epoch_settings = SignerEpochSettings { + // epoch, + // current_signers: signers[..5].to_vec(), + // ..SignerEpochSettings::dummy().clone() + // }; + + let epoch = Epoch(12); + let mithril_network_configuration = MithrilNetworkConfiguration { epoch, - current_signers: signers[..5].to_vec(), - ..SignerEpochSettings::dummy().clone() + available_signed_entity_types: BTreeSet::new(), + ..MithrilNetworkConfiguration::dummy().clone() }; + let signers = fixtures.signers(); + let current_signers = signers[..5].to_vec(); + let next_signers = signers[2..5].to_vec(); + let mut service = MithrilEpochService::new( stake_store, protocol_initializer_store, TestLogger::stdout(), ); service - .inform_epoch_settings(epoch_settings.clone(), BTreeSet::new()) + .inform_epoch_settings( + mithril_network_configuration.clone(), + current_signers.clone(), + next_signers.clone(), + ) .await .unwrap(); @@ -627,9 +656,7 @@ mod tests { #[tokio::test] async fn test_data_are_available_after_register_epoch_settings_call() { - let epoch = Epoch(12); // Signers and stake distribution - let signers = fake_data::signers(10); // Init stores let connection = Arc::new(main_db_connection().unwrap()); @@ -637,14 +664,19 @@ mod tests { let protocol_initializer_store = Arc::new(ProtocolInitializerRepository::new(connection, None)); - // Epoch settings - let epoch_settings = SignerEpochSettings { + let epoch = Epoch(12); + let mithril_network_configuration = MithrilNetworkConfiguration { epoch, - current_signers: signers[2..5].to_vec(), - next_signers: signers[3..7].to_vec(), - ..SignerEpochSettings::dummy().clone() + available_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoImmutableFilesFull, + ]), + ..MithrilNetworkConfiguration::dummy().clone() }; + let signers = fake_data::signers(10); + let current_signers = signers[2..5].to_vec(); + let next_signers = signers[3..7].to_vec(); + // Build service and register epoch settings let mut service = MithrilEpochService::new( stake_store, @@ -654,8 +686,9 @@ mod tests { service .inform_epoch_settings( - epoch_settings.clone(), - BTreeSet::from([SignedEntityTypeDiscriminants::CardanoImmutableFilesFull]), + mithril_network_configuration.clone(), + current_signers.clone(), + next_signers.clone(), ) .await .unwrap(); @@ -663,23 +696,23 @@ mod tests { // Check current_signers { let current_signers = service.current_signers().unwrap(); - let expected_current_signers = epoch_settings.current_signers.clone(); + let expected_current_signers = current_signers.clone(); assert_eq!(expected_current_signers, *current_signers); } // Check next_signers { let next_signers = service.next_signers().unwrap(); - let expected_next_signers = epoch_settings.next_signers.clone(); + let expected_next_signers = next_signers.clone(); assert_eq!(expected_next_signers, *next_signers); } // Check other data assert_eq!( - epoch_settings.epoch, + mithril_network_configuration.epoch, service.epoch_of_current_data().unwrap() ); assert_eq!( - epoch_settings.registration_protocol_parameters, + mithril_network_configuration.signer_registration_protocol_parameters, *service.registration_protocol_parameters().unwrap() ); assert!( @@ -695,7 +728,7 @@ mod tests { // Check cardano_transactions_signing_config assert_eq!( - epoch_settings.cardano_transactions_signing_config, + mithril_network_configuration.get_cardano_transactions_signing_config(), *service.cardano_transactions_signing_config().unwrap() ); } @@ -739,14 +772,16 @@ mod tests { let protocol_initializer_store = Arc::new(ProtocolInitializerRepository::new(connection, None)); - // Epoch settings - let epoch_settings = SignerEpochSettings { + //MithrilNetworkConfiguration + let mithril_network_configuration = MithrilNetworkConfiguration { epoch, - current_signers: signers[2..5].to_vec(), - next_signers: signers[3..7].to_vec(), - ..SignerEpochSettings::dummy().clone() + available_signed_entity_types: BTreeSet::new(), + ..MithrilNetworkConfiguration::dummy().clone() }; + let current_signers = signers[2..5].to_vec(); + let next_signers = signers[3..7].to_vec(); + // Build service and register epoch settings let mut service = MithrilEpochService::new( stake_store, @@ -754,16 +789,20 @@ mod tests { TestLogger::stdout(), ); service - .inform_epoch_settings(epoch_settings.clone(), BTreeSet::new()) + .inform_epoch_settings( + mithril_network_configuration, + current_signers.clone(), + next_signers.clone(), + ) .await .unwrap(); // Check current signers with stake { - let current_signers = service.current_signers_with_stake().await.unwrap(); + let actual_current_signers = service.current_signers_with_stake().await.unwrap(); - assert_eq!(epoch_settings.current_signers.len(), current_signers.len()); - for signer in current_signers { + assert_eq!(current_signers.len(), actual_current_signers.len()); + for signer in actual_current_signers { let expected_stake = stake_distribution.get(&signer.party_id).unwrap(); assert_eq!(expected_stake, &signer.stake); } @@ -771,10 +810,10 @@ mod tests { // Check next signers with stake { - let next_signers = service.next_signers_with_stake().await.unwrap(); + let actual_next_signers = service.next_signers_with_stake().await.unwrap(); - assert_eq!(epoch_settings.next_signers.len(), next_signers.len()); - for signer in next_signers { + assert_eq!(next_signers.len(), actual_next_signers.len()); + for signer in actual_next_signers { let expected_stake = next_stake_distribution.get(&signer.party_id).unwrap(); assert_eq!(expected_stake, &signer.stake); } @@ -802,12 +841,15 @@ mod tests { protocol_initializer_store, TestLogger::stdout(), ); - let epoch_settings = SignerEpochSettings { + + let mithril_network_configuration = MithrilNetworkConfiguration { epoch, - ..SignerEpochSettings::dummy().clone() + available_signed_entity_types: BTreeSet::new(), + ..MithrilNetworkConfiguration::dummy().clone() }; + service - .inform_epoch_settings(epoch_settings, BTreeSet::new()) + .inform_epoch_settings(mithril_network_configuration, Vec::new(), Vec::new()) .await .unwrap(); @@ -841,18 +883,22 @@ mod tests { } // Fail after `inform_epoch_settings` if `cardano_transactions_signing_config` is not set { - let allowed_discriminants = - BTreeSet::from([SignedEntityTypeDiscriminants::CardanoImmutableFilesFull]); + // Signers + let signers = fake_data::signers(5); + let current_signers = signers[1..3].to_vec(); + let next_signers = signers[2..5].to_vec(); epoch_service .write() .await .inform_epoch_settings( - SignerEpochSettings { - cardano_transactions_signing_config: None, - ..SignerEpochSettings::dummy() + MithrilNetworkConfiguration { + available_signed_entity_types: BTreeSet::new(), + signed_entity_types_config: HashMap::new(), + ..MithrilNetworkConfiguration::dummy() }, - allowed_discriminants.clone(), + current_signers, + next_signers, ) .await .unwrap(); @@ -866,17 +912,31 @@ mod tests { { let allowed_discriminants = BTreeSet::from([SignedEntityTypeDiscriminants::CardanoImmutableFilesFull]); + + let mut signed_entity_types_config = HashMap::new(); + signed_entity_types_config.insert( + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeConfiguration::CardanoTransactions( + CardanoTransactionsSigningConfig::dummy(), + ), + ); + + // Signers + let signers = fake_data::signers(5); + let current_signers = signers[1..3].to_vec(); + let next_signers = signers[2..5].to_vec(); + epoch_service .write() .await .inform_epoch_settings( - SignerEpochSettings { - cardano_transactions_signing_config: Some( - CardanoTransactionsSigningConfig::dummy(), - ), - ..SignerEpochSettings::dummy() + MithrilNetworkConfiguration { + available_signed_entity_types: allowed_discriminants.clone(), + signed_entity_types_config, + ..MithrilNetworkConfiguration::dummy() }, - allowed_discriminants.clone(), + current_signers, + next_signers, ) .await .unwrap(); diff --git a/mithril-signer/tests/test_extensions/certificate_handler.rs b/mithril-signer/tests/test_extensions/certificate_handler.rs index c24f1d4da54..9bdb4b85b84 100644 --- a/mithril-signer/tests/test_extensions/certificate_handler.rs +++ b/mithril-signer/tests/test_extensions/certificate_handler.rs @@ -231,6 +231,7 @@ mod tests { #[tokio::test] async fn retrieve_epoch_settings() { + //TODO split in two tests, to test only signers from retrieve_epoch_settings and CardanoTransactionsSigningConfig in a mithril_network_configuration_provider test ? let (chain_observer, fake_aggregator) = init().await; let fake_signers = fake_data::signers(3); let epoch = chain_observer.get_current_epoch().await.unwrap().unwrap(); @@ -298,6 +299,7 @@ mod tests { #[tokio::test] async fn retrieve_aggregator_features() { + //TODO aggregator features are now retrieved with mithril_network_configuration_provider, adapt or remove test and implementation ? let (_chain_observer, fake_aggregator) = init().await; { diff --git a/mithril-signer/tests/test_extensions/state_machine_tester.rs b/mithril-signer/tests/test_extensions/state_machine_tester.rs index b1e09e6cd91..c8de9253996 100644 --- a/mithril-signer/tests/test_extensions/state_machine_tester.rs +++ b/mithril-signer/tests/test_extensions/state_machine_tester.rs @@ -1,12 +1,15 @@ #![allow(dead_code)] use anyhow::anyhow; use mithril_metric::{MetricCollector, MetricsServiceExporter}; -use mithril_protocol_config::test::double::mithril_network_configuration_provider::FakeMithrilNetworkConfigurationProvider; +use mithril_protocol_config::{ + model::SignedEntityTypeConfiguration, + test::double::mithril_network_configuration_provider::FakeMithrilNetworkConfigurationProvider, +}; use prometheus_parse::Value; use slog::Drain; use slog_scope::debug; use std::{ - collections::{BTreeMap, BTreeSet}, + collections::{BTreeMap, BTreeSet, HashMap}, fmt::Debug, ops::RangeInclusive, path::Path, @@ -40,7 +43,7 @@ use mithril_common::{ MithrilSignableBuilderService, MithrilStakeDistributionSignableBuilder, SignableBuilderServiceDependencies, }, - test::double::Dummy, + test::double::{Dummy, fake_data}, }; use mithril_era::{EraChecker, EraMarker, EraReader, adapters::EraReaderDummyAdapter}; use mithril_persistence::{ @@ -90,6 +93,7 @@ pub struct StateMachineTester { immutable_observer: Arc, chain_observer: Arc, certificate_handler: Arc, + network_configuration_service: Arc, protocol_initializer_store: Arc, stake_store: Arc, era_checker: Arc, @@ -170,6 +174,18 @@ impl StateMachineTester { }, ticker_service.clone(), )); + let network_configuration_service = Arc::new(FakeMithrilNetworkConfigurationProvider::new( + fake_data::protocol_parameters(), + SignedEntityTypeDiscriminants::all(), + HashMap::from([( + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeConfiguration::CardanoTransactions( + cardano_transactions_signing_config.clone(), + ), + )]), + ticker_service.clone(), + )); + let digester = Arc::new(DumbImmutableDigester::default().with_digest("DIGEST")); let protocol_initializer_store = Arc::new(ProtocolInitializerRepository::new( sqlite_connection.clone(), @@ -295,9 +311,6 @@ impl StateMachineTester { config.operational_certificate_path.clone().unwrap(), )) as Arc); - let network_configuration_service = - Arc::new(FakeMithrilNetworkConfigurationProvider::default()); - let services = SignerDependencyContainer { certificate_handler: certificate_handler.clone(), ticker_service: ticker_service.clone(), @@ -317,7 +330,7 @@ impl StateMachineTester { epoch_service, certifier, kes_signer, - network_configuration_service, + network_configuration_service: network_configuration_service.clone(), }; // set up stake distribution chain_observer.set_signers(signers_with_stake.to_owned()).await; @@ -337,6 +350,7 @@ impl StateMachineTester { immutable_observer, chain_observer, certificate_handler, + network_configuration_service, protocol_initializer_store, stake_store, era_checker, @@ -462,7 +476,7 @@ impl StateMachineTester { &mut self, discriminants: &[SignedEntityTypeDiscriminants], ) -> &mut Self { - self.certificate_handler + self.network_configuration_service .change_allowed_discriminants(&BTreeSet::from_iter(discriminants.iter().cloned())) .await; self From 04469450eda42a550329ffaf4bd754c7d301c3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Fri, 3 Oct 2025 18:06:14 +0200 Subject: [PATCH 13/18] feature(protocol-config): improve tests of FakeMithrilNetworkConfigurationProvider --- Cargo.lock | 2 + internal/mithril-protocol-config/Cargo.toml | 2 + .../src/test/double/fake_data.rs | 24 ------ .../mithril_network_configuration_provider.rs | 86 +++++++++++++++++++ .../src/test/double/mod.rs | 1 - 5 files changed, 90 insertions(+), 25 deletions(-) delete mode 100644 internal/mithril-protocol-config/src/test/double/fake_data.rs diff --git a/Cargo.lock b/Cargo.lock index f2b1b84ded8..37547f55423 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4413,6 +4413,8 @@ dependencies = [ "async-trait", "http 1.3.1", "httpmock", + "mithril-cardano-node-chain", + "mithril-cardano-node-internal-database", "mithril-common", "mithril-ticker", "mockall", diff --git a/internal/mithril-protocol-config/Cargo.toml b/internal/mithril-protocol-config/Cargo.toml index 49af15d0bce..fdfc658731e 100644 --- a/internal/mithril-protocol-config/Cargo.toml +++ b/internal/mithril-protocol-config/Cargo.toml @@ -13,6 +13,8 @@ anyhow = { workspace = true } async-trait = { workspace = true } mithril-common = { path = "../../mithril-common" } mithril-ticker = { path = "../mithril-ticker" } +mithril-cardano-node-chain = { path = "../cardano-node/mithril-cardano-node-chain" } +mithril-cardano-node-internal-database = { path = "../cardano-node/mithril-cardano-node-internal-database" } reqwest = { workspace = true, features = [ "default", "stream", diff --git a/internal/mithril-protocol-config/src/test/double/fake_data.rs b/internal/mithril-protocol-config/src/test/double/fake_data.rs deleted file mode 100644 index 9b276bf761e..00000000000 --- a/internal/mithril-protocol-config/src/test/double/fake_data.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::collections::{BTreeSet, HashMap}; - -use mithril_common::entities::{Epoch, ProtocolParameters, SignedEntityTypeDiscriminants}; - -use crate::model::{MithrilNetworkConfiguration, SignedEntityTypeConfiguration}; - -pub fn mithril_network_configuration( - epoch: u64, - k: u64, - m: u64, - phi_f: f64, - available_signed_entity_types: BTreeSet, - signed_entity_types_config: HashMap< - SignedEntityTypeDiscriminants, - SignedEntityTypeConfiguration, - >, -) -> MithrilNetworkConfiguration { - MithrilNetworkConfiguration { - epoch: Epoch(epoch), - signer_registration_protocol_parameters: ProtocolParameters { k, m, phi_f }, - available_signed_entity_types, - signed_entity_types_config, - } -} diff --git a/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs b/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs index bf76943ae13..afa62c9ee02 100644 --- a/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs +++ b/internal/mithril-protocol-config/src/test/double/mithril_network_configuration_provider.rs @@ -80,3 +80,89 @@ impl MithrilNetworkConfigurationProvider for FakeMithrilNetworkConfigurationProv }) } } + +#[cfg(test)] +mod tests { + use std::{ + collections::{BTreeSet, HashMap}, + sync::Arc, + }; + + use mithril_common::{ + entities::{ + BlockNumber, CardanoTransactionsSigningConfig, ChainPoint, Epoch, ProtocolParameters, + SignedEntityTypeDiscriminants, TimePoint, + }, + test::double::Dummy, + }; + use mithril_ticker::MithrilTickerService; + + use crate::{ + interface::MithrilNetworkConfigurationProvider, model::SignedEntityTypeConfiguration, + test::double::mithril_network_configuration_provider::FakeMithrilNetworkConfigurationProvider, + }; + use mithril_cardano_node_chain::test::double::FakeChainObserver; + use mithril_cardano_node_internal_database::test::double::DumbImmutableFileObserver; + + async fn ticker_service() -> Arc { + let immutable_observer = Arc::new(DumbImmutableFileObserver::new()); + immutable_observer.shall_return(Some(1)).await; + let chain_observer = Arc::new(FakeChainObserver::new(Some(TimePoint { + epoch: Epoch(1), + immutable_file_number: 1, + chain_point: ChainPoint::dummy(), + }))); + + Arc::new(MithrilTickerService::new( + chain_observer.clone(), + immutable_observer.clone(), + )) + } + + #[tokio::test] + async fn test_get() { + let signer_registration_protocol_parameters = ProtocolParameters { + k: 2, + m: 3, + phi_f: 0.5, + }; + let available_signed_entity_types = BTreeSet::from([ + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoTransactions, + ]); + let signed_entity_types_config = HashMap::from([( + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeConfiguration::CardanoTransactions(CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(12), + step: BlockNumber(10), + }), + )]); + + let mithril_network_configuration_provider = FakeMithrilNetworkConfigurationProvider::new( + signer_registration_protocol_parameters.clone(), + available_signed_entity_types.clone(), + signed_entity_types_config.clone(), + ticker_service().await, + ); + + let actual_config = mithril_network_configuration_provider.get().await.unwrap(); + + assert_eq!(actual_config.epoch, Epoch(1)); + assert_eq!( + actual_config.signer_registration_protocol_parameters, + ProtocolParameters { + k: 2, + m: 3, + phi_f: 0.5 + } + ); + assert_eq!( + actual_config.available_signed_entity_types, + available_signed_entity_types + ); + assert_eq!( + actual_config.signed_entity_types_config, + signed_entity_types_config + ); + } +} diff --git a/internal/mithril-protocol-config/src/test/double/mod.rs b/internal/mithril-protocol-config/src/test/double/mod.rs index bcc380e0458..834deaf031b 100644 --- a/internal/mithril-protocol-config/src/test/double/mod.rs +++ b/internal/mithril-protocol-config/src/test/double/mod.rs @@ -1,3 +1,2 @@ mod dummies; -mod fake_data; pub mod mithril_network_configuration_provider; From 66e81d897299b90e9cb6845a931dd30ccadcc788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Fri, 3 Oct 2025 18:12:51 +0200 Subject: [PATCH 14/18] refactor(signer): refactor test, remove dead code --- mithril-signer/src/runtime/runner.rs | 15 ++------------- mithril-signer/src/services/epoch_service.rs | 6 ------ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/mithril-signer/src/runtime/runner.rs b/mithril-signer/src/runtime/runner.rs index 718c6c27b8a..b2b8b7c98b9 100644 --- a/mithril-signer/src/runtime/runner.rs +++ b/mithril-signer/src/runtime/runner.rs @@ -705,21 +705,10 @@ mod tests { } #[tokio::test] - async fn test_inform_epoch_setting_pass_allowed_discriminant_to_epoch_service() { + async fn test_inform_epoch_setting_pass_available_signed_entity_types_to_epoch_service() { let mut services = init_services().await; let certificate_handler = Arc::new(DumbAggregatorClient::default()); - // certificate_handler - // .set_aggregator_features(AggregatorFeaturesMessage { - // capabilities: AggregatorCapabilities { - // signed_entity_types: BTreeSet::from([ - // SignedEntityTypeDiscriminants::MithrilStakeDistribution, - // SignedEntityTypeDiscriminants::CardanoTransactions, - // ]), - // ..AggregatorFeaturesMessage::dummy().capabilities - // }, - // ..AggregatorFeaturesMessage::dummy() - // }) - // .await; + services.certificate_handler = certificate_handler; let runner = init_runner(Some(services), None).await; diff --git a/mithril-signer/src/services/epoch_service.rs b/mithril-signer/src/services/epoch_service.rs index 79ca80dec1b..95416ad3abf 100644 --- a/mithril-signer/src/services/epoch_service.rs +++ b/mithril-signer/src/services/epoch_service.rs @@ -475,12 +475,6 @@ mod tests { let protocol_initializer_store = Arc::new(ProtocolInitializerRepository::new(connection, None)); - // let epoch_settings = SignerEpochSettings { - // epoch, - // current_signers: signers[..5].to_vec(), - // ..SignerEpochSettings::dummy().clone() - // }; - let epoch = Epoch(12); let mithril_network_configuration = MithrilNetworkConfiguration { epoch, From f01d7df61dba03b5711b3a8465cf1b0816a2382f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Mon, 6 Oct 2025 14:58:30 +0200 Subject: [PATCH 15/18] refactor(signer): variable renaming, remove usage of predicate in tests --- mithril-signer/src/runtime/state_machine.rs | 31 ++++----------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/mithril-signer/src/runtime/state_machine.rs b/mithril-signer/src/runtime/state_machine.rs index f1339eab710..dc9b1f964ad 100644 --- a/mithril-signer/src/runtime/state_machine.rs +++ b/mithril-signer/src/runtime/state_machine.rs @@ -164,7 +164,7 @@ impl StateMachine { "→ Epoch has changed, transiting to Unregistered" ); *state = self.transition_from_unregistered_to_unregistered(new_epoch).await?; - } else if let Some(signer_settings) = self + } else if let Some(signer_registrations) = self .runner .get_epoch_settings() .await @@ -173,7 +173,7 @@ impl StateMachine { nested_error: Some(e), })? { - info!(self.logger, "→ Epoch settings found"); //TODO Do we switch the logs and type from SignerEpochSettings to SignerSettings since now we only read current and next signer from it ? + info!(self.logger, "→ Epoch Signer registrations found"); let network_configuration = self .runner .get_mithril_network_configuration() @@ -190,16 +190,16 @@ impl StateMachine { *state = self .transition_from_unregistered_to_one_of_registered_states( network_configuration, - signer_settings.current_signers, - signer_settings.next_signers, + signer_registrations.current_signers, + signer_registrations.next_signers, ) .await?; } else { info!( self.logger, " ⋅ Signer settings and Network Configuration found, but its epoch is behind the known epoch, waiting…"; "network_configuration" => ?network_configuration, - "current_singer" => ?signer_settings.current_signers, - "next_signer" => ?signer_settings.next_signers, + "current_singer" => ?signer_registrations.current_signers, + "next_signer" => ?signer_registrations.next_signers, "known_epoch" => ?epoch, ); } @@ -488,7 +488,6 @@ mod tests { use anyhow::anyhow; use chrono::DateTime; use mithril_protocol_config::model::MithrilNetworkConfiguration; - use mockall::predicate; use mithril_common::entities::{ChainPoint, Epoch, ProtocolMessage, SignedEntityType}; use mithril_common::test::double::{Dummy, fake_data}; @@ -600,12 +599,6 @@ mod tests { runner .expect_inform_epoch_settings() - .with( - //todo do we really want to specify a WITH ? - predicate::eq(MithrilNetworkConfiguration::dummy()), - predicate::eq(SignerEpochSettings::dummy().current_signers), - predicate::eq(SignerEpochSettings::dummy().next_signers), - ) .once() .returning(|_, _, _| Ok(())); @@ -658,12 +651,6 @@ mod tests { runner .expect_inform_epoch_settings() - .with( - //todo do we really want to specify a WITH ? - predicate::eq(MithrilNetworkConfiguration::dummy()), - predicate::eq(SignerEpochSettings::dummy().current_signers), - predicate::eq(SignerEpochSettings::dummy().next_signers), - ) .once() .returning(|_, _, _| Ok(())); @@ -720,12 +707,6 @@ mod tests { runner .expect_inform_epoch_settings() - .with( - //todo do we really want to specify a WITH ? - predicate::eq(MithrilNetworkConfiguration::dummy()), - predicate::eq(SignerEpochSettings::dummy().current_signers), - predicate::eq(SignerEpochSettings::dummy().next_signers), - ) .once() .returning(|_, _, _| Ok(())); From 82ab16597c2ae2b11fa7a1294ba8acffec8b533d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Mon, 6 Oct 2025 14:59:10 +0200 Subject: [PATCH 16/18] feature(signer): improve log of inform_epoch_settings with current and next signers --- mithril-signer/src/services/epoch_service.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mithril-signer/src/services/epoch_service.rs b/mithril-signer/src/services/epoch_service.rs index 95416ad3abf..c1fb77bcd35 100644 --- a/mithril-signer/src/services/epoch_service.rs +++ b/mithril-signer/src/services/epoch_service.rs @@ -173,7 +173,7 @@ impl EpochService for MithrilEpochService { current_signers: Vec, next_signers: Vec, ) -> StdResult<()> { - debug!(self.logger, ">> inform_epoch_settings"; "mithril_network_configuration" => ?mithril_network_configuration); //TODO: what about current_signers and next_signers? + debug!(self.logger, ">> inform_epoch_settings"; "mithril_network_configuration" => ?mithril_network_configuration, "current_signers" => ?current_signers, "next_signers" => ?next_signers); let epoch = mithril_network_configuration.epoch; From 8ea7d61dcd38a7eb5495c342a9bbc89786b61e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Mon, 6 Oct 2025 16:26:17 +0200 Subject: [PATCH 17/18] feature(signer): use Mithril Network configuration Provider instead of Aggregator client for the preloader checker --- .../src/dependency_injection/builder.rs | 24 ++-- .../cardano_transactions/preloader_checker.rs | 109 ++++++++++-------- 2 files changed, 72 insertions(+), 61 deletions(-) diff --git a/mithril-signer/src/dependency_injection/builder.rs b/mithril-signer/src/dependency_injection/builder.rs index de4ce819c57..32be1b7c7ed 100644 --- a/mithril-signer/src/dependency_injection/builder.rs +++ b/mithril-signer/src/dependency_injection/builder.rs @@ -41,7 +41,10 @@ use mithril_persistence::database::repository::CardanoTransactionRepository; use mithril_persistence::database::{ApplicationNodeType, SqlMigration}; use mithril_persistence::sqlite::{ConnectionBuilder, SqliteConnection, SqliteConnectionPool}; -use mithril_protocol_config::http_client::http_impl::HttpMithrilNetworkConfigurationProvider; +use mithril_protocol_config::{ + http_client::http_impl::HttpMithrilNetworkConfigurationProvider, + interface::MithrilNetworkConfigurationProvider, +}; #[cfg(feature = "future_dmq")] use mithril_dmq::{DmqMessageBuilder, DmqPublisherClientPallas}; @@ -375,8 +378,16 @@ impl<'a> DependenciesBuilder<'a> { self.root_logger(), )); let metrics_service = Arc::new(MetricsService::new(self.root_logger())?); - let preloader_activation = - CardanoTransactionsPreloaderActivationSigner::new(aggregator_client.clone()); + let network_configuration_service: Arc = + Arc::new(HttpMithrilNetworkConfigurationProvider::new( + self.config.aggregator_endpoint.clone(), + self.config.relay_endpoint.clone(), + api_version_provider.clone(), + self.root_logger(), + )); + let preloader_activation = CardanoTransactionsPreloaderActivationSigner::new( + network_configuration_service.clone(), + ); let cardano_transactions_preloader = Arc::new(CardanoTransactionsPreloader::new( signed_entity_type_lock.clone(), preloader_transactions_importer, @@ -505,13 +516,6 @@ impl<'a> DependenciesBuilder<'a> { self.root_logger(), )); - let network_configuration_service = Arc::new(HttpMithrilNetworkConfigurationProvider::new( - self.config.aggregator_endpoint.clone(), - self.config.relay_endpoint.clone(), - api_version_provider.clone(), - self.root_logger(), - )); - let services = SignerDependencyContainer { ticker_service, certificate_handler: aggregator_client, diff --git a/mithril-signer/src/services/cardano_transactions/preloader_checker.rs b/mithril-signer/src/services/cardano_transactions/preloader_checker.rs index 5343eb171ea..5f08bf7f9ec 100644 --- a/mithril-signer/src/services/cardano_transactions/preloader_checker.rs +++ b/mithril-signer/src/services/cardano_transactions/preloader_checker.rs @@ -4,32 +4,34 @@ use anyhow::Context; use async_trait::async_trait; use mithril_common::{StdResult, entities::SignedEntityTypeDiscriminants}; +use mithril_protocol_config::interface::MithrilNetworkConfigurationProvider; use mithril_signed_entity_preloader::CardanoTransactionsPreloaderChecker; - -use crate::services::AggregatorClient; - /// CardanoTransactionsPreloaderActivationSigner pub struct CardanoTransactionsPreloaderActivationSigner { - aggregator_client: Arc, + network_configuration_provider: Arc, } impl CardanoTransactionsPreloaderActivationSigner { /// Create a new instance of `CardanoTransactionsPreloaderActivationSigner` - pub fn new(aggregator_client: Arc) -> Self { - Self { aggregator_client } + pub fn new( + network_configuration_provider: Arc, + ) -> Self { + Self { + network_configuration_provider, + } } } #[async_trait] impl CardanoTransactionsPreloaderChecker for CardanoTransactionsPreloaderActivationSigner { async fn is_activated(&self) -> StdResult { - let message = self - .aggregator_client - .retrieve_aggregator_features() + let configuration = self + .network_configuration_provider + .get() .await - .with_context(|| "An error occurred while calling the Aggregator")?; + .context("An error occurred while retrieving Mithril network configuration")?; - let activated_signed_entity_types = message.capabilities.signed_entity_types; + let activated_signed_entity_types = configuration.available_signed_entity_types; Ok(activated_signed_entity_types .contains(&SignedEntityTypeDiscriminants::CardanoTransactions)) @@ -39,32 +41,38 @@ impl CardanoTransactionsPreloaderChecker for CardanoTransactionsPreloaderActivat #[cfg(test)] mod tests { use anyhow::anyhow; + use mithril_common::{entities::SignedEntityTypeDiscriminants, test::double::Dummy}; + use mithril_protocol_config::model::MithrilNetworkConfiguration; + use mockall::mock; use std::collections::BTreeSet; - use mithril_common::{ - entities::SignedEntityTypeDiscriminants, messages::AggregatorFeaturesMessage, - test::double::Dummy, - }; + use super::*; - use crate::services::{AggregatorClientError, MockAggregatorClient}; + mock! { + pub MithrilNetworkConfigurationProvider {} - use super::*; + #[async_trait] + impl MithrilNetworkConfigurationProvider for MithrilNetworkConfigurationProvider { + async fn get(&self) -> StdResult; + } + } #[tokio::test] async fn preloader_activation_state_activate_preloader_when_cardano_transactions_not_in_aggregator_capabilities() { - let mut aggregator_client = MockAggregatorClient::new(); - aggregator_client - .expect_retrieve_aggregator_features() - .times(1) - .returning(|| { - let mut message = AggregatorFeaturesMessage::dummy(); - message.capabilities.signed_entity_types = - BTreeSet::from([SignedEntityTypeDiscriminants::MithrilStakeDistribution]); - Ok(message) - }); - let preloader = - CardanoTransactionsPreloaderActivationSigner::new(Arc::new(aggregator_client)); + let mut network_configuration_provider = MockMithrilNetworkConfigurationProvider::new(); + network_configuration_provider.expect_get().times(1).returning(|| { + Ok(MithrilNetworkConfiguration { + available_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + ]), + ..Dummy::dummy() + }) + }); + + let preloader = CardanoTransactionsPreloaderActivationSigner::new(Arc::new( + network_configuration_provider, + )); let is_activated = preloader.is_activated().await.unwrap(); @@ -74,18 +82,19 @@ mod tests { #[tokio::test] async fn preloader_activation_state_activate_preloader_when_cardano_transactions_in_aggregator_capabilities() { - let mut aggregator_client = MockAggregatorClient::new(); - aggregator_client - .expect_retrieve_aggregator_features() - .times(1) - .returning(|| { - let mut message = AggregatorFeaturesMessage::dummy(); - message.capabilities.signed_entity_types = - BTreeSet::from([SignedEntityTypeDiscriminants::CardanoTransactions]); - Ok(message) - }); - let preloader = - CardanoTransactionsPreloaderActivationSigner::new(Arc::new(aggregator_client)); + let mut network_configuration_provider = MockMithrilNetworkConfigurationProvider::new(); + network_configuration_provider.expect_get().times(1).returning(|| { + Ok(MithrilNetworkConfiguration { + available_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + ..Dummy::dummy() + }) + }); + + let preloader = CardanoTransactionsPreloaderActivationSigner::new(Arc::new( + network_configuration_provider, + )); let is_activated = preloader.is_activated().await.unwrap(); @@ -94,17 +103,15 @@ mod tests { #[tokio::test] async fn preloader_activation_state_activate_preloader_when_aggregator_call_fails() { - let mut aggregator_client = MockAggregatorClient::new(); - aggregator_client - .expect_retrieve_aggregator_features() + let mut network_configuration_provider = MockMithrilNetworkConfigurationProvider::new(); + network_configuration_provider + .expect_get() .times(1) - .returning(|| { - Err(AggregatorClientError::RemoteServerTechnical(anyhow!( - "Aggregator call failed" - ))) - }); - let preloader = - CardanoTransactionsPreloaderActivationSigner::new(Arc::new(aggregator_client)); + .returning(|| Err(anyhow!("Aggregator call failure"))); + + let preloader = CardanoTransactionsPreloaderActivationSigner::new(Arc::new( + network_configuration_provider, + )); preloader .is_activated() From 6488a6680c9861d9120e1b8f6af33c1ce04c41cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Turmel?= Date: Mon, 6 Oct 2025 17:11:11 +0200 Subject: [PATCH 18/18] refactor(protocol-config): sort cargo.toml dependencies --- Cargo.toml | 4 ++-- internal/mithril-protocol-config/Cargo.toml | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c3f0cc44659..4f13ee52158 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,9 @@ members = [ "internal/mithril-doc", "internal/mithril-doc-derive", "internal/mithril-era", - "internal/mithril-metric", - "internal/mithril-protocol-config", + "internal/mithril-metric", "internal/mithril-persistence", + "internal/mithril-protocol-config", "internal/mithril-resource-pool", "internal/mithril-ticker", "internal/signed-entity/mithril-signed-entity-lock", diff --git a/internal/mithril-protocol-config/Cargo.toml b/internal/mithril-protocol-config/Cargo.toml index fdfc658731e..ba3a54d281f 100644 --- a/internal/mithril-protocol-config/Cargo.toml +++ b/internal/mithril-protocol-config/Cargo.toml @@ -11,10 +11,10 @@ repository = { workspace = true } [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } -mithril-common = { path = "../../mithril-common" } -mithril-ticker = { path = "../mithril-ticker" } mithril-cardano-node-chain = { path = "../cardano-node/mithril-cardano-node-chain" } mithril-cardano-node-internal-database = { path = "../cardano-node/mithril-cardano-node-internal-database" } +mithril-common = { path = "../../mithril-common" } +mithril-ticker = { path = "../mithril-ticker" } reqwest = { workspace = true, features = [ "default", "stream", @@ -33,11 +33,10 @@ slog = { workspace = true, features = [ thiserror = { workspace = true } tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] } - [dev-dependencies] -slog-async = { workspace = true } -slog-term = { workspace = true } #criterion = { version = "0.7.0", features = ["html_reports", "async_tokio"] } http = "1.3.1" httpmock = "0.7.0" -mockall = { workspace = true } \ No newline at end of file +mockall = { workspace = true } +slog-async = { workspace = true } +slog-term = { workspace = true }