diff --git a/CHANGELOG.md b/CHANGELOG.md index dab4ec33ce6..8e3d0d8e231 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 As a minor extension, we have adopted a slightly different versioning convention for the Mithril distributions () +## Mithril Distribution [XXXX] - UNRELEASED + +- **UNSTABLE**: + - Added the `/protocol-configuration/{epoch}` route to fetch aggregator configuration for a given epoch, `{epoch}` must be a number. + + - Enhanced `MithrilNetworkConfigurationProvider` to return configuration with a window of three epoch. + - Adapt Signer to read configurations from HttpMithrilNetworkConfigurationProvider + +- Crates versions: + +| Crate | Version | +| ----- | ------- | +| N/A | `-` | + ## Mithril Distribution [2543] - UNRELEASED - Client library, CLI and WASM: diff --git a/Cargo.lock b/Cargo.lock index 8d3952070eb..941f376f243 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3594,7 +3594,7 @@ dependencies = [ [[package]] name = "mithril-aggregator" -version = "0.7.90" +version = "0.7.91" dependencies = [ "anyhow", "async-trait", @@ -3867,7 +3867,7 @@ dependencies = [ [[package]] name = "mithril-common" -version = "0.6.25" +version = "0.6.26" dependencies = [ "anyhow", "async-trait", @@ -4023,31 +4023,23 @@ dependencies = [ [[package]] name = "mithril-protocol-config" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "async-trait", "http", "httpmock", - "mithril-cardano-node-chain", - "mithril-cardano-node-internal-database", "mithril-common", - "mithril-ticker", "mockall", - "reqwest", - "semver", - "serde", - "serde_json", "slog", "slog-async", "slog-term", - "thiserror 2.0.17", "tokio", ] [[package]] name = "mithril-relay" -version = "0.1.53" +version = "0.1.54" dependencies = [ "anyhow", "bincode", @@ -4109,7 +4101,7 @@ dependencies = [ [[package]] name = "mithril-signer" -version = "0.2.273" +version = "0.2.274" dependencies = [ "anyhow", "async-trait", diff --git a/internal/mithril-protocol-config/Cargo.toml b/internal/mithril-protocol-config/Cargo.toml index b9bc8b67d67..58cfb2536de 100644 --- a/internal/mithril-protocol-config/Cargo.toml +++ b/internal/mithril-protocol-config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-protocol-config" -version = "0.1.0" +version = "0.1.1" description = "Configuraton parameters for Mithril network" authors = { workspace = true } edition = { workspace = true } @@ -12,21 +12,13 @@ include = ["**/*.rs", "Cargo.toml", "README.md", ".gitignore"] [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } -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 } -semver = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -slog = { workspace = true } -thiserror = { workspace = true } tokio = { workspace = true } [dev-dependencies] http = "1.3.1" httpmock = "0.8.1" mockall = { workspace = true } +slog = { workspace = true } slog-async = { workspace = true } slog-term = { workspace = true } diff --git a/internal/mithril-protocol-config/src/aggregator_client.rs b/internal/mithril-protocol-config/src/aggregator_client.rs deleted file mode 100644 index 2e7761b1bf1..00000000000 --- a/internal/mithril-protocol-config/src/aggregator_client.rs +++ /dev/null @@ -1,759 +0,0 @@ -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::{AggregatorFeaturesMessage, EpochSettingsMessage}, -}; - -/// HTTP request timeout duration in milliseconds -pub const HTTP_REQUEST_TIMEOUT_DURATION: u64 = 30000; - -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), -} - -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; - 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>; - - /// Retrieves aggregator features message from the aggregator - async fn retrieve_aggregator_features( - &self, - ) -> Result; -} - -/// 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))), - } - } - - 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; - - match response { - Ok(response) => match response.status() { - StatusCode::OK => { - self.warn_if_api_version_mismatch(&response); - - Ok(response - .json::() - .await - .map_err(|e| AggregatorClientError::JsonParseFailed(anyhow!(e)))?) - } - _ => Err(AggregatorClientError::from_response(response).await), - }, - Err(err) => Err(AggregatorClientError::RemoteServerUnreachable(anyhow!(err))), - } - } -} - -#[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::test_tools::TestLogger; - - 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)); - - 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, - ); - }; - } - - mod epoch_settings { - use super::*; - - #[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:?}" - ); - } - } - - 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 message = client.retrieve_aggregator_features().await.unwrap(); - - 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] - 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_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_client/http_impl.rs b/internal/mithril-protocol-config/src/http_client/http_impl.rs index b748aff0c35..bb00af77752 100644 --- a/internal/mithril-protocol-config/src/http_client/http_impl.rs +++ b/internal/mithril-protocol-config/src/http_client/http_impl.rs @@ -1,68 +1,168 @@ //! HTTP implementation of MithrilNetworkConfigurationProvider. -use anyhow::anyhow; +use anyhow::{Context, anyhow}; use async_trait::async_trait; -use std::{sync::Arc, time::Duration}; +use std::sync::Arc; use mithril_common::StdResult; -use mithril_common::api_version::APIVersionProvider; +use mithril_common::entities::Epoch; +use mithril_common::messages::ProtocolConfigurationMessage; -use crate::{ - aggregator_client::{AggregatorClient, AggregatorHTTPClient, HTTP_REQUEST_TIMEOUT_DURATION}, - interface::MithrilNetworkConfigurationProvider, - model::{MithrilNetworkConfiguration, SignedEntityTypeConfiguration}, -}; +use crate::interface::MithrilNetworkConfigurationProvider; +use crate::model::{MithrilNetworkConfiguration, MithrilNetworkConfigurationForEpoch}; + +/// Trait to retrieve protocol configuration +#[cfg_attr(test, mockall::automock)] +#[async_trait] +pub trait ProtocolConfigurationRetrieverFromAggregator: Sync + Send { + /// Retrieves protocol configuration for a given epoch from the aggregator + async fn retrieve_protocol_configuration( + &self, + epoch: Epoch, + ) -> StdResult; +} /// Structure implementing MithrilNetworkConfigurationProvider using HTTP. pub struct HttpMithrilNetworkConfigurationProvider { - aggregator_client: AggregatorHTTPClient, + protocol_configuration_retriever: Arc, } impl HttpMithrilNetworkConfigurationProvider { /// HttpMithrilNetworkConfigurationProvider factory pub fn new( - aggregator_endpoint: String, - relay_endpoint: Option, - api_version_provider: Arc, - logger: slog::Logger, + protocol_configuration_retriever: Arc, ) -> 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 } + Self { + protocol_configuration_retriever, + } } } #[async_trait] impl MithrilNetworkConfigurationProvider for HttpMithrilNetworkConfigurationProvider { - async fn get_network_configuration(&self) -> StdResult { - let Some(epoch_settings) = self.aggregator_client.retrieve_epoch_settings().await? else { - return Err(anyhow!("Failed to retrieve epoch settings")); - }; + async fn get_network_configuration( + &self, + epoch: Epoch, + ) -> StdResult { + let aggregation_epoch = + epoch.offset_to_signer_retrieval_epoch().with_context(|| { + format!("MithrilNetworkConfigurationProvider could not compute aggregation epoch from epoch: {epoch}") + })?; + let next_aggregation_epoch = epoch.offset_to_next_signer_retrieval_epoch(); + let registration_epoch = epoch.offset_to_next_signer_retrieval_epoch().next(); - let aggregator_features = self.aggregator_client.retrieve_aggregator_features().await?; - let available_signed_entity_types = aggregator_features.capabilities.signed_entity_types; + let configuration_for_aggregation: MithrilNetworkConfigurationForEpoch = self + .protocol_configuration_retriever + .retrieve_protocol_configuration(aggregation_epoch) + .await? + .into(); - let cardano_transactions = - epoch_settings.cardano_transactions_signing_config.ok_or_else(|| { - anyhow!("Cardano transactions signing config is missing in epoch settings") - })?; + let configuration_for_next_aggregation = self + .protocol_configuration_retriever + .retrieve_protocol_configuration(next_aggregation_epoch) + .await? + .into(); - let signed_entity_types_config = SignedEntityTypeConfiguration { - cardano_transactions: Some(cardano_transactions), - }; + let configuration_for_registration = self + .protocol_configuration_retriever + .retrieve_protocol_configuration(registration_epoch) + .await? + .into(); + + configuration_for_aggregation.signed_entity_types_config.cardano_transactions.clone() + .ok_or_else(|| { + anyhow!(format!("Cardano transactions signing config is missing in aggregation configuration for epoch {epoch}")) + })?; Ok(MithrilNetworkConfiguration { - epoch: epoch_settings.epoch, - signer_registration_protocol_parameters: epoch_settings - .signer_registration_protocol_parameters, - available_signed_entity_types, - signed_entity_types_config, + epoch, + configuration_for_aggregation, + configuration_for_next_aggregation, + configuration_for_registration, }) } } + +#[cfg(test)] +mod tests { + use mockall::predicate::eq; + use std::sync::Arc; + + use mithril_common::{ + entities::{Epoch, ProtocolParameters}, + messages::ProtocolConfigurationMessage, + test::double::Dummy, + }; + + use crate::{ + http_client::http_impl::{ + HttpMithrilNetworkConfigurationProvider, + MockProtocolConfigurationRetrieverFromAggregator, + }, + interface::MithrilNetworkConfigurationProvider, + }; + + #[tokio::test] + async fn test_get_network_configuration_retrieve_configurations_for_aggregation_next_aggregation_and_registration() + { + let mut protocol_configuration_retriever = + MockProtocolConfigurationRetrieverFromAggregator::new(); + + protocol_configuration_retriever + .expect_retrieve_protocol_configuration() + .once() + .with(eq(Epoch(41))) + .returning(|_| { + Ok(ProtocolConfigurationMessage { + protocol_parameters: ProtocolParameters::new(1000, 100, 0.1), + ..Dummy::dummy() + }) + }); + + protocol_configuration_retriever + .expect_retrieve_protocol_configuration() + .once() + .with(eq(Epoch(42))) + .returning(|_| { + Ok(ProtocolConfigurationMessage { + protocol_parameters: ProtocolParameters::new(2000, 200, 0.2), + ..Dummy::dummy() + }) + }); + + protocol_configuration_retriever + .expect_retrieve_protocol_configuration() + .once() + .with(eq(Epoch(43))) + .returning(|_| { + Ok(ProtocolConfigurationMessage { + protocol_parameters: ProtocolParameters::new(3000, 300, 0.3), + ..Dummy::dummy() + }) + }); + + let mithril_configuration_provider = HttpMithrilNetworkConfigurationProvider::new( + Arc::new(protocol_configuration_retriever), + ); + + let configuration = mithril_configuration_provider + .get_network_configuration(Epoch(42)) + .await + .expect("should have configuration"); + + assert_eq!( + configuration.configuration_for_aggregation.protocol_parameters, + ProtocolParameters::new(1000, 100, 0.1) + ); + + assert_eq!( + configuration.configuration_for_next_aggregation.protocol_parameters, + ProtocolParameters::new(2000, 200, 0.2) + ); + + assert_eq!( + configuration.configuration_for_registration.protocol_parameters, + ProtocolParameters::new(3000, 300, 0.3) + ); + } +} diff --git a/internal/mithril-protocol-config/src/interface.rs b/internal/mithril-protocol-config/src/interface.rs index bb0418a2d18..099a0450eca 100644 --- a/internal/mithril-protocol-config/src/interface.rs +++ b/internal/mithril-protocol-config/src/interface.rs @@ -1,13 +1,16 @@ //! Interface definition for Mithril Protocol Configuration provider. use async_trait::async_trait; -use mithril_common::StdResult; +use mithril_common::{StdResult, entities::Epoch}; use crate::model::MithrilNetworkConfiguration; -/// A provider for the Mithril network configuration of the current epoch. +/// A provider for the Mithril network configuration of the a given epoch. #[async_trait] pub trait MithrilNetworkConfigurationProvider: Sync + Send { - /// Get the Mithril network configuration for the current epoch. - async fn get_network_configuration(&self) -> StdResult; + /// Get the Mithril network configuration for a given epoch. + async fn get_network_configuration( + &self, + epoch: Epoch, + ) -> StdResult; } diff --git a/internal/mithril-protocol-config/src/lib.rs b/internal/mithril-protocol-config/src/lib.rs index 3d7430e2ae0..a9f42fb2097 100644 --- a/internal/mithril-protocol-config/src/lib.rs +++ b/internal/mithril-protocol-config/src/lib.rs @@ -1,7 +1,6 @@ #![warn(missing_docs)] //! This crate provides mechanisms to read and check the configuration parameters of a Mithril network. -mod aggregator_client; /// HTTP client Implementation for interacting with Mithril Network. pub mod http_client { pub mod http_impl; diff --git a/internal/mithril-protocol-config/src/model.rs b/internal/mithril-protocol-config/src/model.rs index a0c2baea472..0fee7862feb 100644 --- a/internal/mithril-protocol-config/src/model.rs +++ b/internal/mithril-protocol-config/src/model.rs @@ -2,8 +2,11 @@ use std::collections::BTreeSet; -use mithril_common::entities::{ - CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, SignedEntityTypeDiscriminants, +use mithril_common::{ + entities::{ + CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, SignedEntityTypeDiscriminants, + }, + messages::ProtocolConfigurationMessage, }; #[derive(PartialEq, Clone, Debug)] @@ -20,12 +23,39 @@ pub struct MithrilNetworkConfiguration { /// Epoch pub epoch: Epoch, + /// Configuration for aggregation + pub configuration_for_aggregation: MithrilNetworkConfigurationForEpoch, + + /// Configuration for next aggregation + pub configuration_for_next_aggregation: MithrilNetworkConfigurationForEpoch, + + /// Configuration for registration + pub configuration_for_registration: MithrilNetworkConfigurationForEpoch, +} + +//A epoch configuration +#[derive(PartialEq, Clone, Debug)] + +/// A network configuration available for an epoch +pub struct MithrilNetworkConfigurationForEpoch { /// Cryptographic protocol parameters (`k`, `m` and `phi_f`) - pub signer_registration_protocol_parameters: ProtocolParameters, + pub protocol_parameters: ProtocolParameters, /// List of available types of certifications - pub available_signed_entity_types: BTreeSet, + pub enabled_signed_entity_types: BTreeSet, /// Custom configurations for signed entity types pub signed_entity_types_config: SignedEntityTypeConfiguration, } + +impl From for MithrilNetworkConfigurationForEpoch { + fn from(message: ProtocolConfigurationMessage) -> Self { + MithrilNetworkConfigurationForEpoch { + protocol_parameters: message.protocol_parameters, + enabled_signed_entity_types: message.available_signed_entity_types, + signed_entity_types_config: SignedEntityTypeConfiguration { + cardano_transactions: message.cardano_transactions_signing_config, + }, + } + } +} diff --git a/internal/mithril-protocol-config/src/test/double/configuration_provider.rs b/internal/mithril-protocol-config/src/test/double/configuration_provider.rs index 166b72f4557..35a23a26042 100644 --- a/internal/mithril-protocol-config/src/test/double/configuration_provider.rs +++ b/internal/mithril-protocol-config/src/test/double/configuration_provider.rs @@ -1,154 +1,157 @@ //! provides test doubles for MithrilNetworkConfigurationProvider -use std::{collections::BTreeSet, sync::Arc}; use tokio::sync::RwLock; use crate::{ interface::MithrilNetworkConfigurationProvider, - model::{MithrilNetworkConfiguration, SignedEntityTypeConfiguration}, + model::{MithrilNetworkConfiguration, MithrilNetworkConfigurationForEpoch}, }; use async_trait::async_trait; -use mithril_common::{ - StdResult, - entities::{ProtocolParameters, SignedEntityTypeDiscriminants}, -}; -use mithril_ticker::TickerService; +use mithril_common::{StdResult, entities::Epoch}; /// A fake [MithrilNetworkConfigurationProvider] that return [MithrilNetworkConfiguration] pub struct FakeMithrilNetworkConfigurationProvider { - /// The protocol parameters for the signer registration - pub signer_registration_protocol_parameters: ProtocolParameters, - - /// The available signed entity types - pub available_signed_entity_types: RwLock>, + /// Configuration for aggregation + pub configuration_for_aggregation: RwLock, - /// The configuration for each signed entity type - pub signed_entity_types_config: SignedEntityTypeConfiguration, + /// Configuration for next aggregation + pub configuration_for_next_aggregation: RwLock, - ticker_service: Arc, + /// Configuration for registration + pub configuration_for_registration: RwLock, } impl FakeMithrilNetworkConfigurationProvider { /// FakeMithrilNetworkConfigurationProvider factory pub fn new( - signer_registration_protocol_parameters: ProtocolParameters, - available_signed_entity_types: BTreeSet, - signed_entity_types_config: SignedEntityTypeConfiguration, - ticker_service: Arc, + configuration_for_aggregation: MithrilNetworkConfigurationForEpoch, + configuration_for_next_aggregation: MithrilNetworkConfigurationForEpoch, + configuration_for_registration: MithrilNetworkConfigurationForEpoch, ) -> Self { Self { - signer_registration_protocol_parameters, - available_signed_entity_types: RwLock::new(available_signed_entity_types), - signed_entity_types_config, - ticker_service, + configuration_for_aggregation: RwLock::new(configuration_for_aggregation), + configuration_for_next_aggregation: RwLock::new(configuration_for_next_aggregation), + configuration_for_registration: RwLock::new(configuration_for_registration), } } - /// Change the allowed signed entity discriminants (signed entity types) returned by the provider - pub async fn change_allowed_discriminants( + ///Change the configuration of the aggregation + pub async fn change_aggregation_configuration( &self, - discriminants: &BTreeSet, + conf: MithrilNetworkConfigurationForEpoch, ) { - let mut available_signed_entity_types = self.available_signed_entity_types.write().await; - *available_signed_entity_types = discriminants.clone(); + let mut configuration_for_aggregation = self.configuration_for_aggregation.write().await; + *configuration_for_aggregation = conf; } } #[cfg_attr(target_family = "wasm", async_trait(?Send))] #[cfg_attr(not(target_family = "wasm"), async_trait)] impl MithrilNetworkConfigurationProvider for FakeMithrilNetworkConfigurationProvider { - async fn get_network_configuration(&self) -> StdResult { - let time_point = self.ticker_service.get_current_time_point().await?; - let available_signed_entity_types = self.available_signed_entity_types.read().await; + async fn get_network_configuration( + &self, + epoch: Epoch, + ) -> StdResult { + let configuration_for_aggregation = self.configuration_for_aggregation.read().await.clone(); + + let configuration_for_next_aggregation = + self.configuration_for_next_aggregation.read().await.clone(); + + let configuration_for_registration = + self.configuration_for_registration.read().await.clone(); 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(), + epoch, + configuration_for_aggregation, + configuration_for_next_aggregation, + configuration_for_registration, }) } } #[cfg(test)] mod tests { - use std::{collections::BTreeSet, sync::Arc}; - - use mithril_common::{ - entities::{ - BlockNumber, CardanoTransactionsSigningConfig, ChainPoint, Epoch, ProtocolParameters, - SignedEntityTypeDiscriminants, TimePoint, - }, - test::double::Dummy, + use mithril_common::entities::{ + BlockNumber, CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, + SignedEntityTypeDiscriminants, }; - use mithril_ticker::MithrilTickerService; use crate::{ - interface::MithrilNetworkConfigurationProvider, model::SignedEntityTypeConfiguration, + interface::MithrilNetworkConfigurationProvider, + model::{MithrilNetworkConfigurationForEpoch, SignedEntityTypeConfiguration}, test::double::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, + async fn test_get_network_configuration() { + let configuration_for_aggregation = MithrilNetworkConfigurationForEpoch { + protocol_parameters: ProtocolParameters { + k: 1, + m: 11, + phi_f: 0.1, + }, + enabled_signed_entity_types: SignedEntityTypeDiscriminants::all(), + signed_entity_types_config: SignedEntityTypeConfiguration { + cardano_transactions: Some(CardanoTransactionsSigningConfig { + step: BlockNumber(10), + security_parameter: BlockNumber(100), + }), + }, + }; + + let configuration_for_next_aggregation = MithrilNetworkConfigurationForEpoch { + protocol_parameters: ProtocolParameters { + k: 2, + m: 22, + phi_f: 0.2, + }, + enabled_signed_entity_types: SignedEntityTypeDiscriminants::all(), + signed_entity_types_config: SignedEntityTypeConfiguration { + cardano_transactions: Some(CardanoTransactionsSigningConfig { + step: BlockNumber(20), + security_parameter: BlockNumber(200), + }), + }, }; - let available_signed_entity_types = BTreeSet::from([ - SignedEntityTypeDiscriminants::MithrilStakeDistribution, - SignedEntityTypeDiscriminants::CardanoTransactions, - ]); - let signed_entity_types_config = SignedEntityTypeConfiguration { - cardano_transactions: Some(CardanoTransactionsSigningConfig { - security_parameter: BlockNumber(12), - step: BlockNumber(10), - }), + + let configuration_for_registration = MithrilNetworkConfigurationForEpoch { + protocol_parameters: ProtocolParameters { + k: 3, + m: 33, + phi_f: 0.3, + }, + enabled_signed_entity_types: SignedEntityTypeDiscriminants::all(), + signed_entity_types_config: SignedEntityTypeConfiguration { + cardano_transactions: Some(CardanoTransactionsSigningConfig { + step: BlockNumber(30), + security_parameter: BlockNumber(300), + }), + }, }; 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, + configuration_for_aggregation.clone(), + configuration_for_next_aggregation.clone(), + configuration_for_registration.clone(), ); let actual_config = mithril_network_configuration_provider - .get_network_configuration() + .get_network_configuration(Epoch(1)) .await .unwrap(); assert_eq!(actual_config.epoch, Epoch(1)); assert_eq!( - actual_config.signer_registration_protocol_parameters, - signer_registration_protocol_parameters + actual_config.configuration_for_aggregation, + configuration_for_aggregation ); assert_eq!( - actual_config.available_signed_entity_types, - available_signed_entity_types + actual_config.configuration_for_next_aggregation, + configuration_for_next_aggregation ); assert_eq!( - actual_config.signed_entity_types_config, - signed_entity_types_config + actual_config.configuration_for_registration, + configuration_for_registration ); } } diff --git a/internal/mithril-protocol-config/src/test/double/dummies.rs b/internal/mithril-protocol-config/src/test/double/dummies.rs index b05770639e7..fcbc06c814b 100644 --- a/internal/mithril-protocol-config/src/test/double/dummies.rs +++ b/internal/mithril-protocol-config/src/test/double/dummies.rs @@ -5,24 +5,35 @@ use mithril_common::{ test::double::{Dummy, fake_data}, }; -use crate::model::{MithrilNetworkConfiguration, SignedEntityTypeConfiguration}; +use crate::model::{ + MithrilNetworkConfiguration, MithrilNetworkConfigurationForEpoch, 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 available_signed_entity_types = - BTreeSet::from([SignedEntityTypeDiscriminants::CardanoTransactions]); - let signed_entity_types_config = SignedEntityTypeConfiguration { - cardano_transactions: Some(CardanoTransactionsSigningConfig::dummy()), - }; Self { epoch: beacon.epoch, - signer_registration_protocol_parameters, - available_signed_entity_types, - signed_entity_types_config, + configuration_for_aggregation: MithrilNetworkConfigurationForEpoch::dummy(), + configuration_for_next_aggregation: MithrilNetworkConfigurationForEpoch::dummy(), + configuration_for_registration: MithrilNetworkConfigurationForEpoch::dummy(), + } + } +} + +impl Dummy for MithrilNetworkConfigurationForEpoch { + /// Return a dummy for [EpochConfiguration] (test-only). + fn dummy() -> Self { + Self { + protocol_parameters: fake_data::protocol_parameters(), + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + signed_entity_types_config: SignedEntityTypeConfiguration { + cardano_transactions: Some(CardanoTransactionsSigningConfig::dummy()), + }, } } } diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml index f7d222b6e88..5e6b4084385 100644 --- a/mithril-aggregator/Cargo.toml +++ b/mithril-aggregator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-aggregator" -version = "0.7.90" +version = "0.7.91" description = "A Mithril Aggregator server" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-aggregator/src/dependency_injection/builder/enablers/misc.rs b/mithril-aggregator/src/dependency_injection/builder/enablers/misc.rs index a30783e8754..6e8e0dc2780 100644 --- a/mithril-aggregator/src/dependency_injection/builder/enablers/misc.rs +++ b/mithril-aggregator/src/dependency_injection/builder/enablers/misc.rs @@ -41,11 +41,13 @@ impl DependenciesBuilder { self.get_sqlite_connection().await?, )); let signed_entity_storer = self.get_signed_entity_storer().await?; + let epoch_settings_storer = self.get_epoch_settings_store().await?; let immutable_file_digest_mapper = self.get_immutable_file_digest_mapper().await?; let epoch_service = self.get_epoch_service().await?; let service = MithrilMessageService::new( certificate_repository, signed_entity_storer, + epoch_settings_storer, immutable_file_digest_mapper, epoch_service, ); diff --git a/mithril-aggregator/src/http_server/routes/mod.rs b/mithril-aggregator/src/http_server/routes/mod.rs index e40549f5a17..83421031c9b 100644 --- a/mithril-aggregator/src/http_server/routes/mod.rs +++ b/mithril-aggregator/src/http_server/routes/mod.rs @@ -3,6 +3,7 @@ mod certificate_routes; mod epoch_routes; mod middlewares; mod proof_routes; +mod protocol_configuration_routes; pub(crate) mod reply; mod root_routes; pub mod router; diff --git a/mithril-aggregator/src/http_server/routes/protocol_configuration_routes.rs b/mithril-aggregator/src/http_server/routes/protocol_configuration_routes.rs new file mode 100644 index 00000000000..a16b42716ed --- /dev/null +++ b/mithril-aggregator/src/http_server/routes/protocol_configuration_routes.rs @@ -0,0 +1,186 @@ +use warp::Filter; + +use crate::http_server::routes::middlewares; +use crate::http_server::routes::router::RouterState; + +pub fn routes( + router_state: &RouterState, +) -> impl Filter,), Error = warp::Rejection> + Clone + use<> { + protocol_configuration(router_state) +} + +/// GET /protocol-configuration +fn protocol_configuration( + router_state: &RouterState, +) -> impl Filter,), Error = warp::Rejection> + Clone + use<> { + warp::path!("protocol-configuration" / u64) + .and(warp::get()) + .and(middlewares::with_logger(router_state)) + .and(middlewares::with_http_message_service(router_state)) + .and(middlewares::extract_config(router_state, |config| { + config.allowed_discriminants.clone() + })) + .and_then(handlers::protocol_configuration) +} + +mod handlers { + use slog::{Logger, warn}; + use std::{collections::BTreeSet, convert::Infallible, sync::Arc}; + use warp::http::StatusCode; + + use mithril_common::entities::{Epoch, SignedEntityTypeDiscriminants}; + + use crate::{http_server::routes::reply, services::MessageService}; + + /// Protocol Configuration + pub async fn protocol_configuration( + epoch: u64, + logger: Logger, + http_message_service: Arc, + allowed_discriminants: BTreeSet, + ) -> Result { + let epoch = Epoch(epoch); + + let protocol_configuration_message = http_message_service + .get_protocol_configuration_message(epoch, allowed_discriminants) + .await; + + match protocol_configuration_message { + Ok(Some(message)) => Ok(reply::json(&message, warp::http::StatusCode::OK)), + Ok(None) => { + warn!(logger, "protocol_configuration::not_found"); + Ok(reply::empty(StatusCode::NOT_FOUND)) + } + Err(err) => { + slog::warn!(logger, "protocol_configuration::error"; "error" => ?err); + Ok(reply::server_error(err)) + } + } + } +} + +#[cfg(test)] +mod tests { + use anyhow::anyhow; + use serde_json::Value::Null; + use std::sync::Arc; + use warp::{ + http::{Method, StatusCode}, + test::request, + }; + + use mithril_api_spec::APISpec; + use mithril_common::messages::ProtocolConfigurationMessage; + use mithril_common::test::double::Dummy; + + use crate::{initialize_dependencies, services::MockMessageService}; + + use super::*; + + fn setup_router( + state: RouterState, + ) -> impl Filter + Clone { + let cors = warp::cors() + .allow_any_origin() + .allow_headers(vec!["content-type"]) + .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS]); + + warp::any().and(routes(&state).with(cors)) + } + + #[tokio::test] + async fn test_protocol_configuration_get_ok() { + let method = Method::GET.as_str(); + let base_path = "/protocol-configuration"; + let mut dependency_manager = initialize_dependencies!().await; + let mut mock_http_message_service = MockMessageService::new(); + mock_http_message_service + .expect_get_protocol_configuration_message() + .return_once(|_, _| Ok(Some(ProtocolConfigurationMessage::dummy()))) + .once(); + dependency_manager.message_service = Arc::new(mock_http_message_service); + + let response = request() + .method(method) + .path(&format!("{base_path}/42")) + .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new( + dependency_manager, + )))) + .await; + + APISpec::verify_conformity( + APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION), + method, + &format!("{base_path}/{{epoch}}"), + "application/json", + &Null, + &response, + &StatusCode::OK, + ) + .unwrap(); + } + + #[tokio::test] + async fn test_protocol_configuration_return_404_when_no_configuration_found() { + let method = Method::GET.as_str(); + let base_path = "/protocol-configuration"; + let mut dependency_manager = initialize_dependencies!().await; + let mut mock_http_message_service = MockMessageService::new(); + mock_http_message_service + .expect_get_protocol_configuration_message() + .return_once(|_, _| Ok(None)) + .once(); + dependency_manager.message_service = Arc::new(mock_http_message_service); + + let response = request() + .method(method) + .path(&format!("{base_path}/42")) + .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new( + dependency_manager, + )))) + .await; + + APISpec::verify_conformity( + APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION), + method, + &format!("{base_path}/{{epoch}}"), + "application/json", + &Null, + &response, + &StatusCode::NOT_FOUND, + ) + .unwrap(); + } + + #[tokio::test] + async fn test_protocol_configuration_get_ko_500() { + let method = Method::GET.as_str(); + let base_path = "/protocol-configuration"; + let mut dependency_manager = initialize_dependencies!().await; + let mut mock_http_message_service = MockMessageService::new(); + mock_http_message_service + .expect_get_protocol_configuration_message() + .return_once(|_, _| Err(anyhow!("an error"))) + .once(); + dependency_manager.message_service = Arc::new(mock_http_message_service); + + let response = request() + .method(method) + .path(&format!("{base_path}/42")) + .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new( + dependency_manager, + )))) + .await; + + APISpec::verify_conformity( + APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION), + method, + &format!("{base_path}/{{epoch}}"), + "application/json", + &Null, + &response, + &StatusCode::INTERNAL_SERVER_ERROR, + ) + .unwrap(); + } +} diff --git a/mithril-aggregator/src/http_server/routes/root_routes.rs b/mithril-aggregator/src/http_server/routes/root_routes.rs index 5cfc6e280d8..4ef21e45a17 100644 --- a/mithril-aggregator/src/http_server/routes/root_routes.rs +++ b/mithril-aggregator/src/http_server/routes/root_routes.rs @@ -125,7 +125,10 @@ mod tests { SignedEntityTypeDiscriminants::CardanoStakeDistribution, SignedEntityTypeDiscriminants::CardanoImmutableFilesFull, SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, ]), + cardano_transactions_prover_max_hashes_allowed_by_request: 500, ..RouterConfig::dummy() }; let dependency_manager = initialize_dependencies!().await; @@ -161,9 +164,13 @@ mod tests { SignedEntityTypeDiscriminants::CardanoStakeDistribution, SignedEntityTypeDiscriminants::CardanoImmutableFilesFull, SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoTransactions, + SignedEntityTypeDiscriminants::CardanoDatabase, ]), aggregate_signature_type: AggregateSignatureType::Concatenation, - cardano_transactions_prover: None, + cardano_transactions_prover: Some(CardanoTransactionsProverCapabilities { + max_hashes_allowed_by_request: 500 + }), }, } ); diff --git a/mithril-aggregator/src/http_server/routes/router.rs b/mithril-aggregator/src/http_server/routes/router.rs index 8dae6707ec4..8abdb09bd32 100644 --- a/mithril-aggregator/src/http_server/routes/router.rs +++ b/mithril-aggregator/src/http_server/routes/router.rs @@ -1,8 +1,8 @@ use crate::ServeCommandDependenciesContainer; use crate::http_server::SERVER_BASE_PATH; use crate::http_server::routes::{ - artifact_routes, certificate_routes, epoch_routes, root_routes, signatures_routes, - signer_routes, statistics_routes, status, + artifact_routes, certificate_routes, epoch_routes, protocol_configuration_routes, root_routes, + signatures_routes, signer_routes, statistics_routes, status, }; use crate::tools::url_sanitizer::SanitizedUrlWithTrailingSlash; @@ -140,6 +140,7 @@ pub fn routes( .or(signer_routes::routes(&state)) .or(signatures_routes::routes(&state)) .or(epoch_routes::routes(&state)) + .or(protocol_configuration_routes::routes(&state)) .or(statistics_routes::routes(&state)) .or(root_routes::routes(&state)) .or(status::routes(&state)), diff --git a/mithril-aggregator/src/services/message.rs b/mithril-aggregator/src/services/message.rs index c7e0f665dcb..02b5a0c0cb6 100644 --- a/mithril-aggregator/src/services/message.rs +++ b/mithril-aggregator/src/services/message.rs @@ -14,13 +14,13 @@ use mithril_common::{ CardanoStakeDistributionListMessage, CardanoStakeDistributionMessage, CardanoTransactionSnapshotListMessage, CardanoTransactionSnapshotMessage, CertificateListMessage, CertificateMessage, EpochSettingsMessage, - MithrilStakeDistributionListMessage, MithrilStakeDistributionMessage, SignerMessagePart, - SnapshotListMessage, SnapshotMessage, + MithrilStakeDistributionListMessage, MithrilStakeDistributionMessage, + ProtocolConfigurationMessage, SignerMessagePart, SnapshotListMessage, SnapshotMessage, }, }; use crate::{ - ImmutableFileDigestMapper, + EpochSettingsStorer, ImmutableFileDigestMapper, database::repository::{CertificateRepository, SignedEntityStorer}, dependency_injection::EpochServiceWrapper, }; @@ -35,6 +35,13 @@ pub trait MessageService: Sync + Send { allowed_discriminants: BTreeSet, ) -> StdResult; + /// Return the protocol configuration message for the given epoch if it exists. + async fn get_protocol_configuration_message( + &self, + epoch: Epoch, + allowed_discriminants: BTreeSet, + ) -> StdResult>; + /// Return the message representation of a certificate if it exists. async fn get_certificate_message( &self, @@ -130,6 +137,7 @@ pub trait MessageService: Sync + Send { pub struct MithrilMessageService { certificate_repository: Arc, signed_entity_storer: Arc, + epoch_settings_storer: Arc, immutable_file_digest_mapper: Arc, epoch_service: EpochServiceWrapper, } @@ -139,12 +147,14 @@ impl MithrilMessageService { pub fn new( certificate_repository: Arc, signed_entity_storer: Arc, + epoch_settings_storer: Arc, immutable_file_digest_mapper: Arc, epoch_service: EpochServiceWrapper, ) -> Self { Self { certificate_repository, signed_entity_storer, + epoch_settings_storer, immutable_file_digest_mapper, epoch_service, } @@ -184,6 +194,30 @@ impl MessageService for MithrilMessageService { Ok(epoch_settings_message) } + async fn get_protocol_configuration_message( + &self, + epoch: Epoch, + enabled_discriminants: BTreeSet, + ) -> StdResult> { + let epoch_settings = match self.epoch_settings_storer.get_epoch_settings(epoch).await? { + Some(settings) => settings, + None => return Ok(None), + }; + + let cardano_transactions_discriminant = + enabled_discriminants.get(&SignedEntityTypeDiscriminants::CardanoTransactions); + + let cardano_transactions_signing_config = cardano_transactions_discriminant + .map(|_| epoch_settings.cardano_transactions_signing_config); + + let protocol_configuration_message = ProtocolConfigurationMessage { + protocol_parameters: epoch_settings.protocol_parameters, + cardano_transactions_signing_config, + available_signed_entity_types: enabled_discriminants, + }; + Ok(Some(protocol_configuration_message)) + } + async fn get_certificate_message( &self, certificate_hash: &str, @@ -357,13 +391,18 @@ impl MessageService for MithrilMessageService { #[cfg(test)] mod tests { + use std::collections::BTreeMap; + use mithril_common::entities::{BlockNumber, CardanoDbBeacon, Certificate, SignedEntityType}; use mithril_common::test::double::{Dummy, fake_data}; use tokio::sync::RwLock; use crate::database::record::SignedEntityRecord; - use crate::database::repository::{ImmutableFileDigestRepository, SignedEntityStore}; + use crate::database::repository::{ + EpochSettingsStore, ImmutableFileDigestRepository, SignedEntityStore, + }; use crate::database::test_helper::main_db_connection; + use crate::entities::AggregatorEpochSettings; use crate::services::FakeEpochService; use super::*; @@ -371,6 +410,7 @@ mod tests { struct MessageServiceBuilder { certificates: Vec, signed_entity_records: Vec, + epoch_settings_map: BTreeMap, immutable_file_digest_messages: Vec, epoch_service: Option, } @@ -380,6 +420,7 @@ mod tests { Self { certificates: Vec::new(), signed_entity_records: Vec::new(), + epoch_settings_map: BTreeMap::new(), immutable_file_digest_messages: Vec::new(), epoch_service: None, } @@ -400,6 +441,15 @@ mod tests { self } + fn with_epoch_settings( + mut self, + epoch_settings_map: BTreeMap, + ) -> Self { + self.epoch_settings_map = epoch_settings_map; + + self + } + fn with_immutable_file_digest_messages( mut self, digests: &[CardanoDatabaseDigestListItemMessage], @@ -419,6 +469,7 @@ mod tests { let connection = Arc::new(main_db_connection().unwrap()); let certificate_repository = CertificateRepository::new(connection.clone()); let signed_entity_store = SignedEntityStore::new(connection.clone()); + let epoch_settings_store = EpochSettingsStore::new(connection.clone(), None); let immutable_file_digest_mapper = ImmutableFileDigestRepository::new(connection.clone()); let epoch_service = self.epoch_service.unwrap_or(FakeEpochService::without_data()); @@ -431,6 +482,13 @@ mod tests { signed_entity_store.store_signed_entity(&record).await.unwrap(); } + for (epoch, epoch_settings) in self.epoch_settings_map { + epoch_settings_store + .save_epoch_settings(epoch, epoch_settings) + .await + .unwrap(); + } + for digest_message in self.immutable_file_digest_messages { immutable_file_digest_mapper .upsert_immutable_file_digest( @@ -444,6 +502,7 @@ mod tests { MithrilMessageService::new( Arc::new(certificate_repository), Arc::new(signed_entity_store), + Arc::new(epoch_settings_store), Arc::new(immutable_file_digest_mapper), Arc::new(RwLock::new(epoch_service)), ) @@ -607,6 +666,154 @@ mod tests { } } + mod protocol_configuration { + use super::*; + + use mithril_common::entities::{CardanoTransactionsSigningConfig, ProtocolParameters}; + + use crate::entities::AggregatorEpochSettings; + + #[tokio::test] + async fn get_protocol_configuration_message() { + let epoch = Epoch(4); + let aggregator_epoch_settings = AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(5, 100, 0.65), + cardano_transactions_signing_config: CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(0), + step: BlockNumber(15), + }, + }; + let message_service = MessageServiceBuilder::new() + .with_epoch_settings(BTreeMap::from([(epoch, aggregator_epoch_settings)])) + .build() + .await; + + let message = message_service + .get_protocol_configuration_message(epoch, SignedEntityTypeDiscriminants::all()) + .await + .unwrap() + .expect("Protocol configuration message should exist."); + + assert_eq!( + message.protocol_parameters, + ProtocolParameters::new(5, 100, 0.65) + ); + assert_eq!( + message.cardano_transactions_signing_config, + Some(CardanoTransactionsSigningConfig { + security_parameter: BlockNumber(0), + step: BlockNumber(15) + }) + ); + assert_eq!( + message.available_signed_entity_types, + SignedEntityTypeDiscriminants::all() + ); + } + + #[tokio::test] + async fn get_protocol_configuration_message_with_multiple_epochs_settings_stored() { + let message_service = MessageServiceBuilder::new() + .with_epoch_settings(BTreeMap::from([ + ( + Epoch(7), + AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(1, 10, 0.11), + ..Dummy::dummy() + }, + ), + ( + Epoch(8), + AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(2, 20, 0.22), + ..Dummy::dummy() + }, + ), + ( + Epoch(9), + AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(3, 30, 0.33), + ..Dummy::dummy() + }, + ), + ])) + .build() + .await; + + let message = message_service + .get_protocol_configuration_message(Epoch(8), SignedEntityTypeDiscriminants::all()) + .await + .unwrap() + .expect("Protocol configuration message should exist."); + + assert_eq!( + message.protocol_parameters, + ProtocolParameters::new(2, 20, 0.22) + ); + } + + #[tokio::test] + async fn get_protocol_configuration_message_with_cardano_transactions_enabled() { + let epoch = Epoch(4); + let message_service = MessageServiceBuilder::new() + .with_epoch_settings(BTreeMap::from([(epoch, AggregatorEpochSettings::dummy())])) + .build() + .await; + + let message = message_service + .get_protocol_configuration_message( + epoch, + BTreeSet::from([SignedEntityTypeDiscriminants::CardanoTransactions]), + ) + .await + .unwrap() + .expect("Protocol configuration message should exist."); + + assert!(message.cardano_transactions_signing_config.is_some()); + } + + #[tokio::test] + async fn get_protocol_configuration_message_without_cardano_transactions_does_not_return_signing_config() + { + let epoch = Epoch(4); + let message_service = MessageServiceBuilder::new() + .with_epoch_settings(BTreeMap::from([(epoch, AggregatorEpochSettings::dummy())])) + .build() + .await; + + let message = message_service + .get_protocol_configuration_message(epoch, BTreeSet::new()) + .await + .unwrap() + .expect("Protocol configuration message should exist."); + + assert_eq!(message.cardano_transactions_signing_config, None); + } + + #[tokio::test] + async fn get_protocol_configuration_message_return_none_if_epoch_not_found() { + let epoch_number = 7; + let epoch_without_correspondence = epoch_number + 42; + let message_service = MessageServiceBuilder::new() + .with_epoch_settings(BTreeMap::from([( + Epoch(epoch_number), + AggregatorEpochSettings::dummy(), + )])) + .build() + .await; + + let message = message_service + .get_protocol_configuration_message( + Epoch(epoch_without_correspondence), + SignedEntityTypeDiscriminants::all(), + ) + .await + .unwrap(); + + assert_eq!(message, None); + } + } + mod certificate { use super::*; diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index 9944e13d93b..4fca5a39faa 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-common" -version = "0.6.25" +version = "0.6.26" description = "Common types, interfaces, and utilities for Mithril nodes." authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-common/src/messages/mod.rs b/mithril-common/src/messages/mod.rs index d25b57103ab..6fd0089e1ca 100644 --- a/mithril-common/src/messages/mod.rs +++ b/mithril-common/src/messages/mod.rs @@ -18,6 +18,7 @@ mod interface; mod message_parts; mod mithril_stake_distribution; mod mithril_stake_distribution_list; +mod protocol_configuration; mod register_signature; mod register_signer; mod snapshot; @@ -61,6 +62,7 @@ pub use mithril_stake_distribution::MithrilStakeDistributionMessage; pub use mithril_stake_distribution_list::{ MithrilStakeDistributionListItemMessage, MithrilStakeDistributionListMessage, }; +pub use protocol_configuration::ProtocolConfigurationMessage; pub use register_signature::{RegisterSignatureMessageDmq, RegisterSignatureMessageHttp}; pub use register_signer::RegisterSignerMessage; pub use snapshot::SnapshotMessage; diff --git a/mithril-common/src/messages/protocol_configuration.rs b/mithril-common/src/messages/protocol_configuration.rs new file mode 100644 index 00000000000..49a94ebd200 --- /dev/null +++ b/mithril-common/src/messages/protocol_configuration.rs @@ -0,0 +1,21 @@ +use std::collections::BTreeSet; + +use serde::{Deserialize, Serialize}; + +use crate::entities::{ + CardanoTransactionsSigningConfig, ProtocolParameters, SignedEntityTypeDiscriminants, +}; + +/// ProtocolConfiguration represents the protocol configuration of an epoch +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ProtocolConfigurationMessage { + /// Protocol parameters + pub protocol_parameters: ProtocolParameters, + + /// Cardano transactions signing configuration + #[serde(skip_serializing_if = "Option::is_none")] + pub cardano_transactions_signing_config: Option, + + /// Aggregator enabled signed entity types + pub available_signed_entity_types: BTreeSet, +} diff --git a/mithril-common/src/test/double/dummies.rs b/mithril-common/src/test/double/dummies.rs index 07559150530..93878fedc73 100644 --- a/mithril-common/src/test/double/dummies.rs +++ b/mithril-common/src/test/double/dummies.rs @@ -420,6 +420,21 @@ mod messages { } } + impl Dummy for ProtocolConfigurationMessage { + /// Return a dummy [ProtocolConfigurationMessage] (test-only). + fn dummy() -> Self { + Self { + protocol_parameters: ProtocolParameters { + k: 5, + m: 100, + phi_f: 0.65, + }, + cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig::dummy()), + available_signed_entity_types: SignedEntityTypeDiscriminants::all(), + } + } + } + impl Dummy for MithrilStakeDistributionMessage { /// Return a dummy [MithrilStakeDistributionMessage] (test-only). fn dummy() -> Self { diff --git a/mithril-relay/Cargo.toml b/mithril-relay/Cargo.toml index 3e7c68a30e2..236c21448de 100644 --- a/mithril-relay/Cargo.toml +++ b/mithril-relay/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-relay" -version = "0.1.53" +version = "0.1.54" description = "A Mithril relay" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-relay/src/relay/signer.rs b/mithril-relay/src/relay/signer.rs index 65e7c6b8bdc..f94225107eb 100644 --- a/mithril-relay/src/relay/signer.rs +++ b/mithril-relay/src/relay/signer.rs @@ -238,7 +238,14 @@ impl SignerRelay { .and(middlewares::with_aggregator_endpoint( configuration.aggregator_endpoint.to_string(), )) - .and_then(handlers::epoch_settings_handler)), + .and_then(handlers::epoch_settings_handler)) + .or(warp::path!("protocol-configuration" / u64) + .and(warp::get()) + .and(middlewares::with_logger(&server_logger)) + .and(middlewares::with_aggregator_endpoint( + configuration.aggregator_endpoint.to_string(), + )) + .and_then(handlers::protocol_configuration_handler)), ([0, 0, 0, 0], *configuration.server_port).into(), ) } @@ -494,6 +501,21 @@ mod handlers { reply_response(logger, response).await } + pub async fn protocol_configuration_handler( + epoch: u64, + logger: Logger, + aggregator_endpoint: String, + ) -> Result { + debug!(logger, "Serve HTTP route /protocol-configuration/{epoch}"); + let response = reqwest::Client::new() + .get(format!( + "{aggregator_endpoint}/protocol-configuration/{epoch}" + )) + .send() + .await; + reply_response(logger, response).await + } + pub async fn reply_response( logger: Logger, response: Result, @@ -587,6 +609,22 @@ mod tests { mock.assert(); } + #[tokio::test] + async fn epoch_protocol_configuration_handler() { + let test_logger = TestLogger::stdout(); + let server = MockServer::start(); + let mock = server.mock(|when, then| { + when.method(GET).path("/protocol-configuration/42"); + then.status(201).body("ok"); + }); + + handlers::protocol_configuration_handler(42, test_logger, server.url("")) + .await + .unwrap(); + + mock.assert(); + } + #[tokio::test] async fn register_signer_handler_with_passthrough() { let test_logger = TestLogger::stdout(); diff --git a/mithril-signer/Cargo.toml b/mithril-signer/Cargo.toml index 8066cb0831e..ab9b9b99167 100644 --- a/mithril-signer/Cargo.toml +++ b/mithril-signer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-signer" -version = "0.2.273" +version = "0.2.274" description = "A Mithril Signer" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-signer/src/dependency_injection/builder.rs b/mithril-signer/src/dependency_injection/builder.rs index 15e3154c663..d5d37687819 100644 --- a/mithril-signer/src/dependency_injection/builder.rs +++ b/mithril-signer/src/dependency_injection/builder.rs @@ -376,13 +376,11 @@ impl<'a> DependenciesBuilder<'a> { )); let metrics_service = Arc::new(MetricsService::new(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(), + aggregator_client.clone(), )); let preloader_activation = CardanoTransactionsPreloaderActivationSigner::new( network_configuration_service.clone(), + ticker_service.clone(), ); let cardano_transactions_preloader = Arc::new(CardanoTransactionsPreloader::new( signed_entity_type_lock.clone(), diff --git a/mithril-signer/src/entities/signer_epoch_settings.rs b/mithril-signer/src/entities/signer_epoch_settings.rs index a2fbacf407e..a193a8282b6 100644 --- a/mithril-signer/src/entities/signer_epoch_settings.rs +++ b/mithril-signer/src/entities/signer_epoch_settings.rs @@ -1,6 +1,4 @@ -use mithril_common::entities::{ - CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, Signer, -}; +use mithril_common::entities::{Epoch, Signer}; /// SignerEpochSettings represents the settings of an epoch #[derive(Clone, Debug, PartialEq)] @@ -8,17 +6,11 @@ pub struct SignerEpochSettings { /// Current Epoch pub epoch: Epoch, - /// Registration protocol parameters - pub registration_protocol_parameters: ProtocolParameters, - /// Current Signers pub current_signers: Vec, /// Signers that will be able to sign on the next epoch pub next_signers: Vec, - - /// Cardano transactions signing configuration for the current epoch - pub cardano_transactions_signing_config: Option, } #[cfg(test)] @@ -30,24 +22,16 @@ impl mithril_common::test::double::Dummy for SignerEpochSettings { // Beacon let beacon = fake_data::beacon(); - // Registration protocol parameters - let registration_protocol_parameters = fake_data::protocol_parameters(); - // Signers let signers = fake_data::signers(5); let current_signers = signers[1..3].to_vec(); let next_signers = signers[2..5].to_vec(); - // Cardano transactions signing configuration - let cardano_transactions_signing_config = Some(CardanoTransactionsSigningConfig::dummy()); - // Signer Epoch settings SignerEpochSettings { epoch: beacon.epoch, - registration_protocol_parameters, current_signers, next_signers, - cardano_transactions_signing_config, } } } diff --git a/mithril-signer/src/message_adapters/from_epoch_settings.rs b/mithril-signer/src/message_adapters/from_epoch_settings.rs index a057a559125..9e3b0b3567d 100644 --- a/mithril-signer/src/message_adapters/from_epoch_settings.rs +++ b/mithril-signer/src/message_adapters/from_epoch_settings.rs @@ -14,12 +14,10 @@ impl TryFromMessageAdapter for FromEp fn try_adapt(message: EpochSettingsMessage) -> StdResult { let epoch_settings = SignerEpochSettings { epoch: message.epoch, - registration_protocol_parameters: message.signer_registration_protocol_parameters, current_signers: SignerMessagePart::try_into_signers(message.current_signers) .with_context(|| "'FromMessageAdapter' can not convert the current signers")?, next_signers: SignerMessagePart::try_into_signers(message.next_signers) .with_context(|| "'FromMessageAdapter' can not convert the next signers")?, - cardano_transactions_signing_config: message.cardano_transactions_signing_config, }; Ok(epoch_settings) } diff --git a/mithril-signer/src/runtime/runner.rs b/mithril-signer/src/runtime/runner.rs index e558d26a87e..16789529ef5 100644 --- a/mithril-signer/src/runtime/runner.rs +++ b/mithril-signer/src/runtime/runner.rs @@ -21,7 +21,10 @@ use crate::services::{EpochService, MithrilProtocolInitializerBuilder}; #[async_trait] pub trait Runner: Send + Sync { /// Fetch the configuration parameters of the Mithril network - async fn get_mithril_network_configuration(&self) -> StdResult; + async fn get_mithril_network_configuration( + &self, + epoch: Epoch, + ) -> StdResult; /// Fetch the current epoch settings if any. async fn get_signer_registrations_from_aggregator( @@ -114,12 +117,15 @@ impl SignerRunner { #[cfg_attr(test, mockall::automock)] #[async_trait] impl Runner for SignerRunner { - async fn get_mithril_network_configuration(&self) -> StdResult { + async fn get_mithril_network_configuration( + &self, + epoch: Epoch, + ) -> StdResult { debug!(self.logger, ">> get_mithril_network_configuration"); self.services .network_configuration_service - .get_network_configuration() + .get_network_configuration(epoch) .await } @@ -366,8 +372,7 @@ impl Runner for SignerRunner { #[cfg(test)] mod tests { - use mithril_common::entities::{CardanoTransactionsSigningConfig, ProtocolParameters}; - use mithril_protocol_config::model::SignedEntityTypeConfiguration; + use mithril_protocol_config::model::MithrilNetworkConfigurationForEpoch; use mockall::mock; use mockall::predicate::eq; use std::collections::BTreeSet; @@ -552,13 +557,14 @@ mod tests { )); let kes_signer = None; + let configuration_for_aggregation = MithrilNetworkConfigurationForEpoch::dummy(); + let configuration_for_next_aggregation = MithrilNetworkConfigurationForEpoch::dummy(); + let configuration_for_registration = MithrilNetworkConfigurationForEpoch::dummy(); + let network_configuration_service = Arc::new(FakeMithrilNetworkConfigurationProvider::new( - ProtocolParameters::new(1000, 100, 0.1234), - SignedEntityTypeDiscriminants::all(), - SignedEntityTypeConfiguration { - cardano_transactions: Some(CardanoTransactionsSigningConfig::dummy()), - }, - ticker_service.clone(), + configuration_for_aggregation, + configuration_for_next_aggregation, + configuration_for_registration, )); SignerDependencyContainer { @@ -770,11 +776,14 @@ mod tests { let mithril_network_configuration = MithrilNetworkConfiguration { epoch, - available_signed_entity_types: BTreeSet::from([ - SignedEntityTypeDiscriminants::MithrilStakeDistribution, - SignedEntityTypeDiscriminants::CardanoTransactions, - ]), - ..MithrilNetworkConfiguration::dummy() + configuration_for_aggregation: MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::MithrilStakeDistribution, + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + ..Dummy::dummy() + }, + ..Dummy::dummy() }; runner diff --git a/mithril-signer/src/runtime/state_machine.rs b/mithril-signer/src/runtime/state_machine.rs index 1cace528224..2fbf27bb943 100644 --- a/mithril-signer/src/runtime/state_machine.rs +++ b/mithril-signer/src/runtime/state_machine.rs @@ -175,14 +175,15 @@ impl StateMachine { })? { info!(self.logger, "→ Epoch Signer registrations found"); - 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), - })?; + let network_configuration: MithrilNetworkConfiguration = + self.runner.get_mithril_network_configuration(*epoch).await.map_err( + |e| RuntimeError::KeepState { + message: format!( + "could not retrieve Mithril network configuration for epoch {epoch:?}" + ), + nested_error: Some(e), + }, + )?; info!(self.logger, "→ Mithril network configuration found"); if signer_registrations.epoch >= *epoch { @@ -491,12 +492,8 @@ mod tests { use anyhow::anyhow; use chrono::DateTime; - use mithril_common::entities::{ - CardanoTransactionsSigningConfig, ChainPoint, Epoch, ProtocolMessage, SignedEntityType, - SignedEntityTypeDiscriminants, - }; - use mithril_common::test::double::{Dummy, fake_data}; - use mithril_protocol_config::model::SignedEntityTypeConfiguration; + use mithril_common::entities::{ChainPoint, Epoch, ProtocolMessage, SignedEntityType}; + use mithril_common::test::double::Dummy; use crate::SignerEpochSettings; use crate::runtime::runner::MockSignerRunner; @@ -552,10 +549,8 @@ mod tests { let mut runner = MockSignerRunner::new(); let epoch_settings = SignerEpochSettings { epoch: Epoch(3), - registration_protocol_parameters: fake_data::protocol_parameters(), current_signers: vec![], next_signers: vec![], - cardano_transactions_signing_config: None, }; let known_epoch = Epoch(4); runner @@ -565,14 +560,12 @@ mod tests { runner .expect_get_mithril_network_configuration() .once() - .returning(|| { + .returning(|_| { Ok(MithrilNetworkConfiguration { epoch: Epoch(999), - signer_registration_protocol_parameters: fake_data::protocol_parameters(), - available_signed_entity_types: SignedEntityTypeDiscriminants::all(), - signed_entity_types_config: SignedEntityTypeConfiguration { - cardano_transactions: Some(CardanoTransactionsSigningConfig::dummy()), - }, + configuration_for_aggregation: Dummy::dummy(), + configuration_for_next_aggregation: Dummy::dummy(), + configuration_for_registration: Dummy::dummy(), }) }); runner.expect_get_current_time_point().once().returning(|| { @@ -606,7 +599,7 @@ mod tests { runner .expect_get_mithril_network_configuration() .once() - .returning(|| Ok(MithrilNetworkConfiguration::dummy())); + .returning(|_| Ok(MithrilNetworkConfiguration::dummy())); runner .expect_inform_epoch_settings() @@ -658,7 +651,7 @@ mod tests { runner .expect_get_mithril_network_configuration() .once() - .returning(|| Ok(MithrilNetworkConfiguration::dummy())); + .returning(|_| Ok(MithrilNetworkConfiguration::dummy())); runner .expect_inform_epoch_settings() @@ -714,7 +707,7 @@ mod tests { runner .expect_get_mithril_network_configuration() .once() - .returning(|| Ok(MithrilNetworkConfiguration::dummy())); + .returning(|_| Ok(MithrilNetworkConfiguration::dummy())); runner .expect_inform_epoch_settings() diff --git a/mithril-signer/src/services/aggregator_client.rs b/mithril-signer/src/services/aggregator_client.rs index 99aa4fd6c88..e5601b57d3d 100644 --- a/mithril-signer/src/services/aggregator_client.rs +++ b/mithril-signer/src/services/aggregator_client.rs @@ -1,5 +1,8 @@ use anyhow::anyhow; use async_trait::async_trait; +use mithril_common::StdResult; +use mithril_common::messages::ProtocolConfigurationMessage; +use mithril_protocol_config::http_client::http_impl::ProtocolConfigurationRetrieverFromAggregator; use reqwest::header::{self, HeaderValue}; use reqwest::{self, Client, Proxy, RequestBuilder, Response, StatusCode}; use semver::Version; @@ -378,6 +381,40 @@ impl AggregatorClient for AggregatorHTTPClient { } } +#[async_trait] +impl ProtocolConfigurationRetrieverFromAggregator for AggregatorHTTPClient { + async fn retrieve_protocol_configuration( + &self, + epoch: Epoch, + ) -> StdResult { + debug!(self.logger, "Retrieve protocol configuration"); + let url = format!( + "{}/protocol-configuration/{}", + self.aggregator_endpoint, epoch + ); + 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(message), + Err(err) => { + Err(AggregatorClientError::JsonParseFailed(anyhow!(err)).into()) + } + } + } + _ => Err(AggregatorClientError::from_response(response).await.into()), + }, + Err(err) => Err(AggregatorClientError::RemoteServerUnreachable(anyhow!(err)).into()), + } + } +} + #[cfg(test)] pub(crate) mod dumb { use mithril_common::test::double::Dummy; @@ -644,6 +681,39 @@ mod tests { ); } + mod protocol_configuration { + + use super::*; + + #[tokio::test] + async fn test_ok_200() { + let (server, client) = setup_server_and_client(); + let message_expected = ProtocolConfigurationMessage::dummy(); + let _server_mock = server.mock(|when, then| { + when.path("/protocol-configuration/42"); + then.status(200).body(json!(message_expected).to_string()); + }); + + let message = client.retrieve_protocol_configuration(Epoch(42)).await.unwrap(); + + assert_eq!(message_expected, message); + } + + #[tokio::test] + async fn test_ko_500() { + let (server, client) = setup_server_and_client(); + let _server_mock = server.mock(|when, then| { + when.path("/protocol-configuration/42"); + then.status(500).body("an error occurred"); + }); + + client + .retrieve_protocol_configuration(Epoch(42)) + .await + .expect_err("should throw a error"); + } + } + #[tokio::test] async fn test_register_signer_ok_201() { let epoch = Epoch(1); diff --git a/mithril-signer/src/services/cardano_transactions/preloader_checker.rs b/mithril-signer/src/services/cardano_transactions/preloader_checker.rs index 0008fc282b6..ad7c01738d1 100644 --- a/mithril-signer/src/services/cardano_transactions/preloader_checker.rs +++ b/mithril-signer/src/services/cardano_transactions/preloader_checker.rs @@ -6,18 +6,22 @@ use async_trait::async_trait; use mithril_common::{StdResult, entities::SignedEntityTypeDiscriminants}; use mithril_protocol_config::interface::MithrilNetworkConfigurationProvider; use mithril_signed_entity_preloader::CardanoTransactionsPreloaderChecker; +use mithril_ticker::TickerService; /// CardanoTransactionsPreloaderActivationSigner pub struct CardanoTransactionsPreloaderActivationSigner { network_configuration_provider: Arc, + ticker_service: Arc, } impl CardanoTransactionsPreloaderActivationSigner { /// Create a new instance of `CardanoTransactionsPreloaderActivationSigner` pub fn new( network_configuration_provider: Arc, + ticker_service: Arc, ) -> Self { Self { network_configuration_provider, + ticker_service, } } } @@ -25,24 +29,40 @@ impl CardanoTransactionsPreloaderActivationSigner { #[async_trait] impl CardanoTransactionsPreloaderChecker for CardanoTransactionsPreloaderActivationSigner { async fn is_activated(&self) -> StdResult { + let epoch = self.ticker_service.get_current_epoch().await?; + let configuration = self .network_configuration_provider - .get_network_configuration() + .get_network_configuration(epoch) .await - .context("An error occurred while retrieving Mithril network configuration")?; + .context(format!( + "An error occurred while retrieving Mithril network configuration for epoch {epoch}" + ))?; + + let activated_signed_entity_types = configuration + .configuration_for_aggregation + .enabled_signed_entity_types; - let activated_signed_entity_types = configuration.available_signed_entity_types; + let next_activated_signed_entity_types = configuration + .configuration_for_next_aggregation + .enabled_signed_entity_types; Ok(activated_signed_entity_types - .contains(&SignedEntityTypeDiscriminants::CardanoTransactions)) + .union(&next_activated_signed_entity_types) + .any(|s| s == &SignedEntityTypeDiscriminants::CardanoTransactions)) } } #[cfg(test)] mod tests { use anyhow::anyhow; - use mithril_common::{entities::SignedEntityTypeDiscriminants, test::double::Dummy}; - use mithril_protocol_config::model::MithrilNetworkConfiguration; + use mithril_common::{ + entities::{Epoch, SignedEntityTypeDiscriminants, TimePoint}, + test::double::Dummy, + }; + use mithril_protocol_config::model::{ + MithrilNetworkConfiguration, MithrilNetworkConfigurationForEpoch, + }; use mockall::mock; use std::collections::BTreeSet; @@ -53,29 +73,49 @@ mod tests { #[async_trait] impl MithrilNetworkConfigurationProvider for MithrilNetworkConfigurationProvider { - async fn get_network_configuration(&self) -> StdResult; + async fn get_network_configuration(&self, epoch: Epoch) -> StdResult; + } + } + mock! { + pub TickerService {} + + #[async_trait] + impl TickerService for TickerService { + async fn get_current_time_point(&self) -> StdResult; + async fn get_current_epoch(&self) -> StdResult; } } #[tokio::test] - async fn preloader_activation_state_activate_preloader_when_cardano_transactions_not_in_aggregator_capabilities() + async fn preloader_activation_is_not_activated_when_cardano_transactions_not_in_current_or_next_configuration_for_aggregation() { let mut network_configuration_provider = MockMithrilNetworkConfigurationProvider::new(); network_configuration_provider .expect_get_network_configuration() .times(1) - .returning(|| { + .returning(|_| { Ok(MithrilNetworkConfiguration { - available_signed_entity_types: BTreeSet::from([ - SignedEntityTypeDiscriminants::MithrilStakeDistribution, - ]), + configuration_for_aggregation: MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: BTreeSet::from([]), + ..Dummy::dummy() + }, + configuration_for_next_aggregation: MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: BTreeSet::from([]), + ..Dummy::dummy() + }, ..Dummy::dummy() }) }); + let mut ticker_service = MockTickerService::new(); + ticker_service + .expect_get_current_epoch() + .times(1) + .returning(|| Ok(Epoch(1))); - let preloader = CardanoTransactionsPreloaderActivationSigner::new(Arc::new( - network_configuration_provider, - )); + let preloader = CardanoTransactionsPreloaderActivationSigner::new( + Arc::new(network_configuration_provider), + Arc::new(ticker_service), + ); let is_activated = preloader.is_activated().await.unwrap(); @@ -83,24 +123,77 @@ mod tests { } #[tokio::test] - async fn preloader_activation_state_activate_preloader_when_cardano_transactions_in_aggregator_capabilities() + async fn preloader_activation_is_activated_when_cardano_transactions_is_in_configuration_for_aggregation() { let mut network_configuration_provider = MockMithrilNetworkConfigurationProvider::new(); network_configuration_provider .expect_get_network_configuration() .times(1) - .returning(|| { + .returning(|_| { Ok(MithrilNetworkConfiguration { - available_signed_entity_types: BTreeSet::from([ - SignedEntityTypeDiscriminants::CardanoTransactions, - ]), + configuration_for_aggregation: MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + ..Dummy::dummy() + }, + configuration_for_next_aggregation: MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: BTreeSet::from([]), + ..Dummy::dummy() + }, ..Dummy::dummy() }) }); - let preloader = CardanoTransactionsPreloaderActivationSigner::new(Arc::new( - network_configuration_provider, - )); + let mut ticker_service = MockTickerService::new(); + ticker_service + .expect_get_current_epoch() + .times(1) + .returning(|| Ok(Epoch(1))); + + let preloader = CardanoTransactionsPreloaderActivationSigner::new( + Arc::new(network_configuration_provider), + Arc::new(ticker_service), + ); + + let is_activated = preloader.is_activated().await.unwrap(); + + assert!(is_activated); + } + + #[tokio::test] + async fn preloader_activation_is_activated_when_cardano_transactions_is_in_configuration_for_next_aggregation() + { + let mut network_configuration_provider = MockMithrilNetworkConfigurationProvider::new(); + network_configuration_provider + .expect_get_network_configuration() + .times(1) + .returning(|_| { + Ok(MithrilNetworkConfiguration { + configuration_for_aggregation: MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: BTreeSet::from([]), + ..Dummy::dummy() + }, + configuration_for_next_aggregation: MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoTransactions, + ]), + ..Dummy::dummy() + }, + ..Dummy::dummy() + }) + }); + + let mut ticker_service = MockTickerService::new(); + ticker_service + .expect_get_current_epoch() + .times(1) + .returning(|| Ok(Epoch(1))); + + let preloader = CardanoTransactionsPreloaderActivationSigner::new( + Arc::new(network_configuration_provider), + Arc::new(ticker_service), + ); let is_activated = preloader.is_activated().await.unwrap(); @@ -113,15 +206,43 @@ mod tests { network_configuration_provider .expect_get_network_configuration() .times(1) - .returning(|| Err(anyhow!("Aggregator call failure"))); + .returning(|_| Err(anyhow!("Aggregator call failure"))); - let preloader = CardanoTransactionsPreloaderActivationSigner::new(Arc::new( - network_configuration_provider, - )); + let mut ticker_service = MockTickerService::new(); + ticker_service + .expect_get_current_epoch() + .times(1) + .returning(|| Ok(Epoch(1))); + + let preloader = CardanoTransactionsPreloaderActivationSigner::new( + Arc::new(network_configuration_provider), + Arc::new(ticker_service), + ); preloader .is_activated() .await .expect_err("Should fail due to aggregator call failure"); } + + #[tokio::test] + async fn preloader_activation_state_activate_preloader_when_ticker_service_call_fails() { + let network_configuration_provider = MockMithrilNetworkConfigurationProvider::new(); + + let mut ticker_service = MockTickerService::new(); + ticker_service + .expect_get_current_epoch() + .times(1) + .returning(|| Err(anyhow!("Ticker service call failure"))); + + let preloader = CardanoTransactionsPreloaderActivationSigner::new( + Arc::new(network_configuration_provider), + Arc::new(ticker_service), + ); + + preloader + .is_activated() + .await + .expect_err("Should fail due to ticker service call failure"); + } } diff --git a/mithril-signer/src/services/epoch_service.rs b/mithril-signer/src/services/epoch_service.rs index cdfe885a1c3..7a0bcc602ae 100644 --- a/mithril-signer/src/services/epoch_service.rs +++ b/mithril-signer/src/services/epoch_service.rs @@ -178,7 +178,8 @@ impl EpochService for MithrilEpochService { debug!(self.logger, ">> inform_epoch_settings"; "aggregator_signer_registration_epoch" => ?aggregator_signer_registration_epoch, "mithril_network_configuration" => ?mithril_network_configuration, "current_signers" => ?current_signers, "next_signers" => ?next_signers); let registration_protocol_parameters = mithril_network_configuration - .signer_registration_protocol_parameters + .configuration_for_registration + .protocol_parameters .clone(); let protocol_initializer = self @@ -188,10 +189,13 @@ impl EpochService for MithrilEpochService { ) .await?; - let allowed_discriminants = - mithril_network_configuration.available_signed_entity_types.clone(); + let allowed_discriminants = mithril_network_configuration + .configuration_for_aggregation + .enabled_signed_entity_types + .clone(); let cardano_transactions_signing_config = mithril_network_configuration + .configuration_for_aggregation .signed_entity_types_config .cardano_transactions .clone(); @@ -433,7 +437,9 @@ mod tests { double::{Dummy, fake_data}, }; - use mithril_protocol_config::model::SignedEntityTypeConfiguration; + use mithril_protocol_config::model::{ + MithrilNetworkConfigurationForEpoch, SignedEntityTypeConfiguration, + }; use crate::database::repository::{ProtocolInitializerRepository, StakePoolStore}; use crate::database::test_helper::main_db_connection; @@ -482,8 +488,11 @@ mod tests { let epoch = Epoch(12); let mithril_network_configuration = MithrilNetworkConfiguration { epoch, - available_signed_entity_types: BTreeSet::new(), - ..MithrilNetworkConfiguration::dummy().clone() + configuration_for_aggregation: MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: BTreeSet::new(), + ..Dummy::dummy() + }, + ..Dummy::dummy() }; let signers = fixtures.signers(); @@ -666,10 +675,13 @@ mod tests { let epoch = Epoch(12); let mithril_network_configuration = MithrilNetworkConfiguration { epoch, - available_signed_entity_types: BTreeSet::from([ - SignedEntityTypeDiscriminants::CardanoImmutableFilesFull, - ]), - ..MithrilNetworkConfiguration::dummy().clone() + configuration_for_aggregation: MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: BTreeSet::from([ + SignedEntityTypeDiscriminants::CardanoImmutableFilesFull, + ]), + ..Dummy::dummy() + }, + ..Dummy::dummy() }; let signers = fake_data::signers(10); @@ -712,7 +724,9 @@ mod tests { service.epoch_of_current_data().unwrap() ); assert_eq!( - mithril_network_configuration.signer_registration_protocol_parameters, + mithril_network_configuration + .configuration_for_registration + .protocol_parameters, *service.registration_protocol_parameters().unwrap() ); assert!( @@ -729,6 +743,7 @@ mod tests { // Check cardano_transactions_signing_config assert_eq!( mithril_network_configuration + .configuration_for_aggregation .signed_entity_types_config .cardano_transactions, *service.cardano_transactions_signing_config().unwrap() @@ -777,8 +792,11 @@ mod tests { // MithrilNetworkConfiguration let mithril_network_configuration = MithrilNetworkConfiguration { epoch, - available_signed_entity_types: BTreeSet::new(), - ..MithrilNetworkConfiguration::dummy().clone() + configuration_for_aggregation: MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: BTreeSet::new(), + ..Dummy::dummy() + }, + ..Dummy::dummy() }; let current_signers = signers[2..5].to_vec(); @@ -847,8 +865,11 @@ mod tests { let mithril_network_configuration = MithrilNetworkConfiguration { epoch, - available_signed_entity_types: BTreeSet::new(), - ..MithrilNetworkConfiguration::dummy().clone() + configuration_for_aggregation: MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: BTreeSet::new(), + ..Dummy::dummy() + }, + ..Dummy::dummy() }; service @@ -890,17 +911,24 @@ mod tests { let current_signers = signers[1..3].to_vec(); let next_signers = signers[2..5].to_vec(); + let configuration_for_aggregation = MithrilNetworkConfigurationForEpoch { + signed_entity_types_config: SignedEntityTypeConfiguration { + cardano_transactions: None, + }, + enabled_signed_entity_types: BTreeSet::new(), + ..Dummy::dummy() + }; + let configuration_for_next_aggregation = MithrilNetworkConfigurationForEpoch::dummy(); + epoch_service .write() .await .inform_epoch_settings( fake_data::beacon().epoch, MithrilNetworkConfiguration { - available_signed_entity_types: BTreeSet::new(), - signed_entity_types_config: SignedEntityTypeConfiguration { - cardano_transactions: None, - }, - ..MithrilNetworkConfiguration::dummy() + configuration_for_aggregation, + configuration_for_next_aggregation, + ..Dummy::dummy() }, current_signers, next_signers, @@ -918,8 +946,9 @@ mod tests { let allowed_discriminants = BTreeSet::from([SignedEntityTypeDiscriminants::CardanoImmutableFilesFull]); - let signed_entity_types_config = SignedEntityTypeConfiguration { - cardano_transactions: Some(CardanoTransactionsSigningConfig::dummy()), + let configuration_for_aggregation = MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: allowed_discriminants.clone(), + ..Dummy::dummy() }; let signers = fake_data::signers(5); @@ -932,9 +961,8 @@ mod tests { .inform_epoch_settings( fake_data::beacon().epoch, MithrilNetworkConfiguration { - available_signed_entity_types: allowed_discriminants.clone(), - signed_entity_types_config, - ..MithrilNetworkConfiguration::dummy() + configuration_for_aggregation, + ..Dummy::dummy() }, current_signers, next_signers, diff --git a/mithril-signer/tests/test_extensions/certificate_handler.rs b/mithril-signer/tests/test_extensions/certificate_handler.rs index c24f1d4da54..c693a89e2bc 100644 --- a/mithril-signer/tests/test_extensions/certificate_handler.rs +++ b/mithril-signer/tests/test_extensions/certificate_handler.rs @@ -10,7 +10,7 @@ use mithril_common::{ SignedEntityType, SignedEntityTypeDiscriminants, Signer, SingleSignature, TimePoint, }, messages::AggregatorFeaturesMessage, - test::double::{Dummy, fake_data}, + test::double::Dummy, }; use mithril_ticker::{MithrilTickerService, TickerService}; @@ -57,15 +57,6 @@ impl FakeAggregator { signed_entity_config.allowed_discriminants = discriminants.clone(); } - pub async fn change_transaction_signing_config( - &self, - transaction_signing_config: &CardanoTransactionsSigningConfig, - ) { - let mut signed_entity_config = self.signed_entity_config.write().await; - signed_entity_config.cardano_transactions_signing_config = - transaction_signing_config.clone(); - } - async fn get_time_point(&self) -> Result { let time_point = self .ticker_service @@ -109,7 +100,6 @@ impl AggregatorClient for FakeAggregator { Ok(None) } else { let store = self.registered_signers.read().await; - let signed_entity_config = self.signed_entity_config.read().await; let time_point = self.get_time_point().await?; let current_signers = self.get_current_signers(&store).await?; let next_signers = self.get_next_signers(&store).await?; @@ -118,10 +108,6 @@ impl AggregatorClient for FakeAggregator { epoch: time_point.epoch, current_signers, next_signers, - registration_protocol_parameters: fake_data::protocol_parameters(), - cardano_transactions_signing_config: Some( - signed_entity_config.cardano_transactions_signing_config.clone(), - ), })) } } @@ -168,7 +154,7 @@ mod tests { use mithril_cardano_node_chain::chain_observer::ChainObserver; use mithril_cardano_node_chain::test::double::FakeChainObserver; use mithril_cardano_node_internal_database::test::double::DumbImmutableFileObserver; - use mithril_common::entities::{BlockNumber, ChainPoint}; + use mithril_common::entities::ChainPoint; use mithril_common::test::double::fake_data; use super::*; @@ -276,24 +262,6 @@ mod tests { assert_eq!(2, epoch_settings.current_signers.len()); assert_eq!(1, epoch_settings.next_signers.len()); - - let new_transaction_signing_config = CardanoTransactionsSigningConfig { - security_parameter: BlockNumber(70), - step: BlockNumber(20), - }; - fake_aggregator - .change_transaction_signing_config(&new_transaction_signing_config) - .await; - - let epoch_settings = fake_aggregator - .retrieve_epoch_settings() - .await - .expect("we should have a result, None found!") - .expect("we should have an EpochSettings, None found!"); - assert_eq!( - &Some(new_transaction_signing_config), - &epoch_settings.cardano_transactions_signing_config, - ); } #[tokio::test] diff --git a/mithril-signer/tests/test_extensions/state_machine_tester.rs b/mithril-signer/tests/test_extensions/state_machine_tester.rs index df73a361dc9..1435ab6aba3 100644 --- a/mithril-signer/tests/test_extensions/state_machine_tester.rs +++ b/mithril-signer/tests/test_extensions/state_machine_tester.rs @@ -2,7 +2,7 @@ use anyhow::anyhow; use mithril_metric::{MetricCollector, MetricsServiceExporter}; use mithril_protocol_config::{ - model::SignedEntityTypeConfiguration, + model::{MithrilNetworkConfigurationForEpoch, SignedEntityTypeConfiguration}, test::double::configuration_provider::FakeMithrilNetworkConfigurationProvider, }; use prometheus_parse::Value; @@ -174,13 +174,26 @@ impl StateMachineTester { }, ticker_service.clone(), )); - let network_configuration_service = Arc::new(FakeMithrilNetworkConfigurationProvider::new( - fake_data::protocol_parameters(), - SignedEntityTypeDiscriminants::all(), - SignedEntityTypeConfiguration { + + let configuration_for_aggregation = MithrilNetworkConfigurationForEpoch { + signed_entity_types_config: SignedEntityTypeConfiguration { cardano_transactions: Some(cardano_transactions_signing_config.clone()), }, - ticker_service.clone(), + enabled_signed_entity_types: SignedEntityTypeDiscriminants::all(), + ..Dummy::dummy() + }; + + let configuration_for_next_aggregation = MithrilNetworkConfigurationForEpoch::dummy(); + + let configuration_for_registration = MithrilNetworkConfigurationForEpoch { + protocol_parameters: fake_data::protocol_parameters(), + ..Dummy::dummy() + }; + + let network_configuration_service = Arc::new(FakeMithrilNetworkConfigurationProvider::new( + configuration_for_aggregation, + configuration_for_next_aggregation, + configuration_for_registration, )); let digester = Arc::new(DumbImmutableDigester::default().with_digest("DIGEST")); @@ -468,13 +481,22 @@ impl StateMachineTester { self } - /// change the signed entities allowed by the aggregator (returned by its '/' endpoint) + /// change the signed entities allowed by the aggregator pub async fn aggregator_allow_signed_entities( &mut self, discriminants: &[SignedEntityTypeDiscriminants], ) -> &mut Self { + let config = MithrilNetworkConfigurationForEpoch { + enabled_signed_entity_types: BTreeSet::from_iter(discriminants.iter().cloned()), + ..self + .network_configuration_service + .configuration_for_aggregation + .read() + .await + .clone() + }; self.network_configuration_service - .change_allowed_discriminants(&BTreeSet::from_iter(discriminants.iter().cloned())) + .change_aggregation_configuration(config) .await; self } diff --git a/openapi.yaml b/openapi.yaml index 23139255045..07e5fe2c3fc 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -4,7 +4,7 @@ info: # `mithril-common/src/lib.rs` file. If you plan to update it # here to reflect changes in the API, please also update the constant in the # Rust file. - version: 0.1.54 + version: 0.1.55 title: Mithril Aggregator Server description: | The REST API provided by a Mithril Aggregator Node in a Mithril network. @@ -83,12 +83,10 @@ paths: summary: Get current epoch settings description: | Returns the information related to the current epoch: - * protocol parameters for current epoch * protocol parameters for next epoch (to setup cryptography, allowing signers to register) * signers for current epoch * signers for next epoch * cardano transactions signing configuration for current epoch - * cardano transactions signing configuration for next epoch responses: "200": description: epoch settings found @@ -103,6 +101,41 @@ paths: schema: $ref: "#/components/schemas/Error" + /protocol-configuration/{epoch}: + get: + summary: Get protocol configuration for a specific epoch + description: | + Returns the information related to the protocol configuration at a given epoch: + * epoch + * protocol parameters + * cardano transactions signing configuration + * enabled signed entity types + parameters: + - name: epoch + in: path + description: Epoch of the protocol configuration to list + required: true + schema: + type: integer + format: int64 + examples: + - 419 + responses: + "200": + description: protocol configuration found + content: + application/json: + schema: + $ref: "#/components/schemas/ProtocolConfigurationMessage" + "404": + description: protocol configuration not found + default: + description: protocol configuration error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /certificates: get: summary: Get most recent certificates @@ -886,13 +919,8 @@ components: type: array minItems: 1 items: - description: Signed entity types that can be signed - type: string - enum: - - MithrilStakeDistribution - - CardanoStakeDistribution - - CardanoImmutableFilesFull - - CardanoTransactions + $ref: "#/components/schemas/SignedEntityTypes" + aggregate_signature_type: description: Aggregate signature type used by the aggregator to create certificates type: string @@ -949,6 +977,16 @@ components: - "latest" - "latest-5" + SignedEntityTypes: + description: Signed entity types that can be signed + type: string + enum: + - MithrilStakeDistribution + - CardanoStakeDistribution + - CardanoImmutableFilesFull + - CardanoTransactions + - CardanoDatabase + EpochSettingsMessage: description: Epoch settings type: object @@ -1015,6 +1053,38 @@ components: { "security_parameter": 100, "step": 10 } } + ProtocolConfigurationMessage: + description: Protocol configuration + type: object + additionalProperties: false + required: + - protocol_parameters + - cardano_transactions_signing_config + - available_signed_entity_types + properties: + protocol_parameters: + $ref: "#/components/schemas/ProtocolParameters" + cardano_transactions_signing_config: + $ref: "#/components/schemas/CardanoTransactionsSigningConfig" + available_signed_entity_types: + description: Available signed entity types that are signed by the aggregator + type: array + minItems: 1 + items: + $ref: "#/components/schemas/SignedEntityTypes" + examples: + - { + "protocol_parameters": { "k": 9, "m": 77, "phi_f": 0.5 }, + "cardano_transactions_signing_config": + { "security_parameter": 100, "step": 10 }, + "available_signed_entity_types": + [ + "MithrilStakeDistribution", + "CardanoImmutableFilesFull", + "CardanoTransactions" + ] + } + ProtocolParameters: description: Protocol cryptographic parameters type: object