diff --git a/CHANGELOG.md b/CHANGELOG.md index cb55c3761c..c972ebde87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ As a minor extension, we have adopted a slightly different versioning convention - Implement a new `genesis generate-keypair` command in aggregator CLI to generate a new genesis keypair. +- Implement the `/status` route on the aggregator's REST API to provide information about its current status. + - Crates versions: | Crate | Version | diff --git a/Cargo.lock b/Cargo.lock index d145fb74df..ed2ad7e660 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3588,7 +3588,7 @@ dependencies = [ [[package]] name = "mithril-aggregator" -version = "0.5.109" +version = "0.5.110" dependencies = [ "anyhow", "async-trait", @@ -3745,7 +3745,7 @@ dependencies = [ [[package]] name = "mithril-common" -version = "0.4.85" +version = "0.4.86" dependencies = [ "anyhow", "async-trait", diff --git a/mithril-aggregator/Cargo.toml b/mithril-aggregator/Cargo.toml index c169e6dd95..18a714f517 100644 --- a/mithril-aggregator/Cargo.toml +++ b/mithril-aggregator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-aggregator" -version = "0.5.109" +version = "0.5.110" description = "A Mithril Aggregator server" authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-aggregator/src/dependency_injection/builder.rs b/mithril-aggregator/src/dependency_injection/builder.rs index 0783d0b4eb..e18c3359b1 100644 --- a/mithril-aggregator/src/dependency_injection/builder.rs +++ b/mithril-aggregator/src/dependency_injection/builder.rs @@ -1522,6 +1522,7 @@ impl DependenciesBuilder { .cardano_transactions_signing_config .clone(), snapshot_directory: self.configuration.snapshot_directory.clone(), + cardano_node_version: self.configuration.cardano_node_version.clone(), }, ); diff --git a/mithril-aggregator/src/http_server/routes/mod.rs b/mithril-aggregator/src/http_server/routes/mod.rs index 23fb5ae595..e40549f5a1 100644 --- a/mithril-aggregator/src/http_server/routes/mod.rs +++ b/mithril-aggregator/src/http_server/routes/mod.rs @@ -9,6 +9,7 @@ pub mod router; mod signatures_routes; mod signer_routes; mod statistics_routes; +mod status; /// Match the given result and do an early return with an internal server error (500) /// if it was an Error. Else return the unwrapped value. diff --git a/mithril-aggregator/src/http_server/routes/router.rs b/mithril-aggregator/src/http_server/routes/router.rs index 37b53ef768..a5530de722 100644 --- a/mithril-aggregator/src/http_server/routes/router.rs +++ b/mithril-aggregator/src/http_server/routes/router.rs @@ -1,6 +1,6 @@ use crate::http_server::routes::{ artifact_routes, certificate_routes, epoch_routes, http_server_child_logger, root_routes, - signatures_routes, signer_routes, statistics_routes, + signatures_routes, signer_routes, statistics_routes, status, }; use crate::http_server::SERVER_BASE_PATH; use crate::DependencyContainer; @@ -38,6 +38,7 @@ pub struct RouterConfig { pub cardano_transactions_prover_max_hashes_allowed_by_request: usize, pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig, pub snapshot_directory: PathBuf, + pub cardano_node_version: String, } #[cfg(test)] @@ -53,6 +54,7 @@ impl RouterConfig { cardano_transactions_prover_max_hashes_allowed_by_request: 1_000, cardano_transactions_signing_config: CardanoTransactionsSigningConfig::dummy(), snapshot_directory: PathBuf::from("/dummy/snapshot/directory"), + cardano_node_version: "1.2.3".to_string(), } } } @@ -110,6 +112,7 @@ pub fn routes( .or(epoch_routes::routes(&state)) .or(statistics_routes::routes(&state)) .or(root_routes::routes(&state)) + .or(status::routes(&state)) .with(cors), ) .recover(handle_custom) diff --git a/mithril-aggregator/src/http_server/routes/status.rs b/mithril-aggregator/src/http_server/routes/status.rs new file mode 100644 index 0000000000..bc9f8f72e3 --- /dev/null +++ b/mithril-aggregator/src/http_server/routes/status.rs @@ -0,0 +1,276 @@ +use warp::Filter; + +use mithril_common::{messages::AggregatorStatusMessage, StdResult}; + +use crate::{ + dependency_injection::EpochServiceWrapper, + http_server::routes::{middlewares, router::RouterState}, +}; + +pub fn routes( + router_state: &RouterState, +) -> impl Filter + Clone { + status(router_state) +} + +/// GET /status +fn status( + router_state: &RouterState, +) -> impl Filter + Clone { + warp::path!("status") + .and(warp::get()) + .and(middlewares::with_logger(router_state)) + .and(middlewares::with_epoch_service(router_state)) + .and(middlewares::extract_config(router_state, |config| { + config.cardano_node_version.clone() + })) + .and_then(handlers::status) +} + +async fn get_aggregator_status_message( + epoch_service: EpochServiceWrapper, + cardano_node_version: String, +) -> StdResult { + let epoch_service = epoch_service.read().await; + + let epoch = epoch_service.epoch_of_current_data()?; + let cardano_era = epoch_service.cardano_era()?; + let mithril_era = epoch_service.mithril_era()?; + let aggregator_node_version = env!("CARGO_PKG_VERSION").to_string(); + let protocol_parameters = epoch_service.current_protocol_parameters()?.clone(); + let next_protocol_parameters = epoch_service.next_protocol_parameters()?.clone(); + let total_signers = epoch_service.current_signers()?.len(); + let total_next_signers = epoch_service.next_signers()?.len(); + let total_stakes_signers = epoch_service.total_stakes_signers()?; + let total_next_stakes_signers = epoch_service.total_next_stakes_signers()?; + let total_cardano_spo = epoch_service.total_spo()?; + let total_cardano_stake = epoch_service.total_stake()?; + + let message = AggregatorStatusMessage { + epoch, + cardano_era, + mithril_era, + cardano_node_version, + aggregator_node_version, + protocol_parameters, + next_protocol_parameters, + total_signers, + total_next_signers, + total_stakes_signers, + total_next_stakes_signers, + total_cardano_spo, + total_cardano_stake, + }; + + Ok(message) +} + +mod handlers { + use std::convert::Infallible; + + use slog::{warn, Logger}; + use warp::http::StatusCode; + + use crate::{ + dependency_injection::EpochServiceWrapper, + http_server::routes::{reply, status::get_aggregator_status_message}, + }; + + /// Status + pub async fn status( + logger: Logger, + epoch_service: EpochServiceWrapper, + cardano_node_version: String, + ) -> Result { + let aggregator_status_message = + get_aggregator_status_message(epoch_service, cardano_node_version).await; + + match aggregator_status_message { + Ok(message) => Ok(reply::json(&message, StatusCode::OK)), + Err(err) => { + warn!(logger,"aggregator_status::error"; "error" => ?err); + Ok(reply::server_error(err)) + } + } + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use serde_json::Value::Null; + use tokio::sync::RwLock; + use warp::{ + http::{Method, StatusCode}, + test::request, + }; + + use mithril_common::{ + entities::{Epoch, ProtocolParameters, Stake}, + test_utils::{apispec::APISpec, fake_data, MithrilFixtureBuilder}, + }; + + use crate::{ + entities::AggregatorEpochSettings, + http_server::SERVER_BASE_PATH, + initialize_dependencies, + services::{FakeEpochService, FakeEpochServiceBuilder}, + }; + + 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(warp::path(SERVER_BASE_PATH)) + .and(routes(&state).with(cors)) + } + + #[tokio::test] + async fn status_route_ko_500() { + let dependency_manager = initialize_dependencies().await; + let method = Method::GET.as_str(); + let path = "/status"; + + let response = request() + .method(method) + .path(&format!("/{SERVER_BASE_PATH}{path}")) + .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new( + dependency_manager, + )))) + .await; + + APISpec::verify_conformity( + APISpec::get_all_spec_files(), + method, + path, + "application/json", + &Null, + &response, + &StatusCode::INTERNAL_SERVER_ERROR, + ) + .unwrap(); + } + + #[tokio::test] + async fn status_route_ok_200() { + let mut dependency_manager = initialize_dependencies().await; + let fixture = MithrilFixtureBuilder::default().build(); + let epoch_service = FakeEpochService::from_fixture(Epoch(5), &fixture); + dependency_manager.epoch_service = Arc::new(RwLock::new(epoch_service)); + + let method = Method::GET.as_str(); + let path = "/status"; + + let response = request() + .method(method) + .path(&format!("/{SERVER_BASE_PATH}{path}")) + .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new( + dependency_manager, + )))) + .await; + + APISpec::verify_conformity( + APISpec::get_all_spec_files(), + method, + path, + "application/json", + &Null, + &response, + &StatusCode::OK, + ) + .unwrap(); + } + + #[tokio::test] + async fn retrieves_correct_protocol_parameters_from_epoch_service() { + let current_epoch_settings = AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(101, 10, 0.5), + ..AggregatorEpochSettings::dummy() + }; + let next_epoch_settings = AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(102, 20, 0.5), + ..AggregatorEpochSettings::dummy() + }; + let signer_registration_epoch_settings = AggregatorEpochSettings { + protocol_parameters: ProtocolParameters::new(103, 30, 0.5), + ..AggregatorEpochSettings::dummy() + }; + + let epoch_service = FakeEpochServiceBuilder { + current_epoch_settings: current_epoch_settings.clone(), + next_epoch_settings: next_epoch_settings.clone(), + signer_registration_epoch_settings, + ..FakeEpochServiceBuilder::dummy(Epoch(3)) + } + .build(); + + let message = + get_aggregator_status_message(Arc::new(RwLock::new(epoch_service)), String::new()) + .await + .unwrap(); + + assert_eq!( + message.protocol_parameters, + current_epoch_settings.protocol_parameters + ); + + assert_eq!( + message.next_protocol_parameters, + next_epoch_settings.protocol_parameters + ); + } + + #[tokio::test] + async fn retrieves_correct_total_signers_from_epoch_service() { + let total_signers = 12; + let total_next_signers = 345; + let epoch_service = FakeEpochServiceBuilder { + current_signers_with_stake: fake_data::signers_with_stakes(total_signers), + next_signers_with_stake: fake_data::signers_with_stakes(total_next_signers), + ..FakeEpochServiceBuilder::dummy(Epoch(3)) + } + .build(); + let epoch_service = Arc::new(RwLock::new(epoch_service)); + + let message = get_aggregator_status_message(epoch_service.clone(), String::new()) + .await + .unwrap(); + + assert_eq!(message.total_signers, total_signers); + assert_eq!(message.total_next_signers, total_next_signers); + } + + #[tokio::test] + async fn retrieves_correct_total_stakes_from_epoch_service() { + let current_signers_with_stake = fake_data::signers_with_stakes(4); + let next_signers_with_stake = fake_data::signers_with_stakes(7); + let total_stakes_signers: Stake = current_signers_with_stake.iter().map(|s| s.stake).sum(); + let total_next_stakes_signers: Stake = + next_signers_with_stake.iter().map(|s| s.stake).sum(); + + assert_ne!(total_stakes_signers, total_next_stakes_signers); + + let epoch_service = FakeEpochServiceBuilder { + current_signers_with_stake, + next_signers_with_stake, + ..FakeEpochServiceBuilder::dummy(Epoch(3)) + } + .build(); + let epoch_service = Arc::new(RwLock::new(epoch_service)); + + let message = get_aggregator_status_message(epoch_service.clone(), String::new()) + .await + .unwrap(); + + assert_eq!(message.total_stakes_signers, total_stakes_signers); + assert_eq!(message.total_next_stakes_signers, total_next_stakes_signers); + } +} diff --git a/mithril-aggregator/src/services/epoch_service.rs b/mithril-aggregator/src/services/epoch_service.rs index bba144296c..f1d606ed7e 100644 --- a/mithril-aggregator/src/services/epoch_service.rs +++ b/mithril-aggregator/src/services/epoch_service.rs @@ -9,8 +9,8 @@ use thiserror::Error; use mithril_common::crypto_helper::ProtocolAggregateVerificationKey; use mithril_common::entities::{ - CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, SignedEntityConfig, - SignedEntityTypeDiscriminants, Signer, SignerWithStake, Stake, + CardanoEra, CardanoTransactionsSigningConfig, Epoch, ProtocolParameters, SignedEntityConfig, + SignedEntityTypeDiscriminants, Signer, SignerWithStake, Stake, TotalSPOs, }; use mithril_common::logging::LoggerExtensions; use mithril_common::protocol::{MultiSigner as ProtocolMultiSigner, SignerBuilder}; @@ -21,9 +21,6 @@ use crate::{ VerificationKeyStorer, }; -type TotalSPOs = u32; -type CardanoEra = String; - /// Errors dedicated to the CertifierService. #[derive(Debug, Error)] pub enum EpochServiceError { @@ -105,6 +102,12 @@ pub trait EpochService: Sync + Send { /// Get signers for the next epoch fn next_signers(&self) -> StdResult<&Vec>; + /// Get the total stakes of signers for the current epoch + fn total_stakes_signers(&self) -> StdResult; + + /// Get the total stakes of signers for the next epoch + fn total_next_stakes_signers(&self) -> StdResult; + /// Get the [protocol multi signer][ProtocolMultiSigner] for the current epoch fn protocol_multi_signer(&self) -> StdResult<&ProtocolMultiSigner>; @@ -132,6 +135,8 @@ struct EpochData { next_signers_with_stake: Vec, current_signers: Vec, next_signers: Vec, + total_stakes_signers: Stake, + total_next_stakes_signers: Stake, signed_entity_config: SignedEntityConfig, total_spo: TotalSPOs, total_stake: Stake, @@ -336,6 +341,8 @@ impl EpochService for MithrilEpochService { .await?; let current_signers = Signer::vec_from(current_signers_with_stake.clone()); let next_signers = Signer::vec_from(next_signers_with_stake.clone()); + let total_stakes_signers = current_signers_with_stake.iter().map(|s| s.stake).sum(); + let total_next_stakes_signers = next_signers_with_stake.iter().map(|s| s.stake).sum(); let signed_entity_config = SignedEntityConfig { allowed_discriminants: self.allowed_signed_entity_discriminants.clone(), @@ -358,6 +365,8 @@ impl EpochService for MithrilEpochService { next_signers_with_stake, current_signers, next_signers, + total_stakes_signers, + total_next_stakes_signers, signed_entity_config, total_spo, total_stake, @@ -481,6 +490,14 @@ impl EpochService for MithrilEpochService { Ok(&self.unwrap_data()?.next_signers) } + fn total_stakes_signers(&self) -> StdResult { + Ok(self.unwrap_data()?.total_stakes_signers) + } + + fn total_next_stakes_signers(&self) -> StdResult { + Ok(self.unwrap_data()?.total_next_stakes_signers) + } + fn protocol_multi_signer(&self) -> StdResult<&ProtocolMultiSigner> { Ok(&self.unwrap_computed_data()?.protocol_multi_signer) } @@ -550,6 +567,12 @@ impl FakeEpochServiceBuilder { pub fn build(self) -> FakeEpochService { let current_signers = Signer::vec_from(self.current_signers_with_stake.clone()); let next_signers = Signer::vec_from(self.next_signers_with_stake.clone()); + let total_stakes_signers = self + .current_signers_with_stake + .iter() + .map(|s| s.stake) + .sum(); + let total_next_stakes_signers = self.next_signers_with_stake.iter().map(|s| s.stake).sum(); let protocol_multi_signer = SignerBuilder::new( &self.current_signers_with_stake, @@ -578,6 +601,8 @@ impl FakeEpochServiceBuilder { next_signers_with_stake: self.next_signers_with_stake, current_signers, next_signers, + total_stakes_signers, + total_next_stakes_signers, signed_entity_config: self.signed_entity_config, total_spo: self.total_spo, total_stake: self.total_stake, @@ -763,6 +788,14 @@ impl EpochService for FakeEpochService { Ok(&self.unwrap_data()?.next_signers) } + fn total_stakes_signers(&self) -> StdResult { + Ok(self.unwrap_data()?.total_stakes_signers) + } + + fn total_next_stakes_signers(&self) -> StdResult { + Ok(self.unwrap_data()?.total_next_stakes_signers) + } + fn protocol_multi_signer(&self) -> StdResult<&ProtocolMultiSigner> { Ok(&self.unwrap_computed_data()?.protocol_multi_signer) } diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index cdc6f2184f..80d7ac8e2a 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-common" -version = "0.4.85" +version = "0.4.86" description = "Common types, interfaces, and utilities for Mithril nodes." authors = { workspace = true } edition = { workspace = true } diff --git a/mithril-common/src/entities/type_alias.rs b/mithril-common/src/entities/type_alias.rs index 9507deea26..cba93fe42d 100644 --- a/mithril-common/src/entities/type_alias.rs +++ b/mithril-common/src/entities/type_alias.rs @@ -56,3 +56,9 @@ pub type HexEncodedDigest = HexEncodedKey; /// Hex encoded Era Markers Secret Key pub type HexEncodedEraMarkersSecretKey = HexEncodedKey; + +/// Number of SPOs +pub type TotalSPOs = u32; + +/// Cardano Era +pub type CardanoEra = String; diff --git a/mithril-common/src/messages/aggregator_status.rs b/mithril-common/src/messages/aggregator_status.rs new file mode 100644 index 0000000000..3f8fe8c851 --- /dev/null +++ b/mithril-common/src/messages/aggregator_status.rs @@ -0,0 +1,109 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + entities::{CardanoEra, Epoch, ProtocolParameters, Stake, TotalSPOs}, + era::SupportedEra, +}; + +/// Message advertised by an aggregator to inform about its status +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct AggregatorStatusMessage { + /// Current epoch + pub epoch: Epoch, + + /// Current Cardano era + pub cardano_era: CardanoEra, + + /// Current Mithril era + pub mithril_era: SupportedEra, + + /// Cardano node version + pub cardano_node_version: String, + + /// Aggregator node version + pub aggregator_node_version: String, + + /// Current Protocol parameters + #[serde(rename = "protocol")] + pub protocol_parameters: ProtocolParameters, + + /// Next Protocol parameters + #[serde(rename = "next_protocol")] + pub next_protocol_parameters: ProtocolParameters, + + /// The number of signers for the current epoch + pub total_signers: usize, + + /// The number of signers that will be able to sign on the next epoch + pub total_next_signers: usize, + + /// The total stakes of the signers for the current epoch + pub total_stakes_signers: Stake, + + /// The total stakes of the signers that will be able to sign on the next epoch + pub total_next_stakes_signers: Stake, + + /// The number of Cardano SPOs + pub total_cardano_spo: TotalSPOs, + + /// The total stake in Cardano + pub total_cardano_stake: Stake, +} + +#[cfg(test)] +mod tests { + use super::*; + + const ACTUAL_JSON: &str = r#"{ + "epoch": 48, + "cardano_era": "conway", + "mithril_era": "pythagoras", + "cardano_node_version": "1.2.3", + "aggregator_node_version": "4.5.6", + "protocol": { "k": 5, "m": 100, "phi_f": 0.65 }, + "next_protocol": { "k": 50, "m": 1000, "phi_f": 0.65 }, + "total_signers": 1234, + "total_next_signers": 56789, + "total_stakes_signers": 123456789, + "total_next_stakes_signers": 987654321, + "total_cardano_spo": 7777, + "total_cardano_stake": 888888888 + }"#; + + fn golden_actual_message() -> AggregatorStatusMessage { + AggregatorStatusMessage { + epoch: Epoch(48), + cardano_era: "conway".to_string(), + mithril_era: SupportedEra::Pythagoras, + cardano_node_version: "1.2.3".to_string(), + aggregator_node_version: "4.5.6".to_string(), + protocol_parameters: ProtocolParameters { + k: 5, + m: 100, + phi_f: 0.65, + }, + next_protocol_parameters: ProtocolParameters { + k: 50, + m: 1000, + phi_f: 0.65, + }, + total_signers: 1234, + total_next_signers: 56789, + total_stakes_signers: 123456789, + total_next_stakes_signers: 987654321, + total_cardano_spo: 7777, + total_cardano_stake: 888888888, + } + } + + // Test the compatibility with current structure. + #[test] + fn test_actual_json_deserialized_into_actual_message() { + let json = ACTUAL_JSON; + let message: AggregatorStatusMessage = serde_json::from_str(json).expect( + "This JSON is expected to be successfully parsed into a AggregatorStatusMessage instance.", + ); + + assert_eq!(golden_actual_message(), message); + } +} diff --git a/mithril-common/src/messages/mod.rs b/mithril-common/src/messages/mod.rs index 7c0029f41a..286c61cfac 100644 --- a/mithril-common/src/messages/mod.rs +++ b/mithril-common/src/messages/mod.rs @@ -1,6 +1,7 @@ //! Messages module //! This module aims at providing shared structures for API communications. mod aggregator_features; +mod aggregator_status; mod cardano_stake_distribution; mod cardano_stake_distribution_list; mod cardano_transaction_snapshot; @@ -23,6 +24,7 @@ mod snapshot_list; pub use aggregator_features::{ AggregatorCapabilities, AggregatorFeaturesMessage, CardanoTransactionsProverCapabilities, }; +pub use aggregator_status::AggregatorStatusMessage; pub use cardano_stake_distribution::CardanoStakeDistributionMessage; pub use cardano_stake_distribution_list::{ CardanoStakeDistributionListItemMessage, CardanoStakeDistributionListMessage, diff --git a/openapi.yaml b/openapi.yaml index 57f6e63f12..3f244a6fbb 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.36 + version: 0.1.37 title: Mithril Aggregator Server description: | The REST API provided by a Mithril Aggregator Node in a Mithril network. @@ -48,6 +48,40 @@ paths: schema: $ref: "#/components/schemas/Error" + /status: + get: + summary: Get information about the aggregator status + description: | + Returns the aggregator status information: + * Current epoch + * Current Cardano era + * Current Mithril era + * Cardano node version + * Aggregator node version + * Protocol parameters for current epoch + * Protocol parameters for next epoch + * Total number of signers for current epoch + * Total number of signers for next epoch + * Total stakes of signers for current epoch + * Total stakes of signers for next epoch + * Total of Cardano SPOs + * Total stakes in Cardano + responses: + "200": + description: aggregator status found + content: + application/json: + schema: + $ref: "#/components/schemas/AggregatorStatusMessage" + "412": + description: API version mismatch + default: + description: root error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /epoch-settings: get: summary: Get current epoch settings @@ -626,6 +660,84 @@ paths: components: schemas: + AggregatorStatusMessage: + description: Represents the information related to the aggregator status + type: object + additionalProperties: false + required: + - epoch + - cardano_era + - mithril_era + - cardano_node_version + - aggregator_node_version + - protocol + - next_protocol + - total_signers + - total_next_signers + - total_stakes_signers + - total_next_stakes_signers + - total_cardano_spo + - total_cardano_stake + properties: + epoch: + $ref: "#/components/schemas/Epoch" + cardano_era: + description: Cardano era + type: string + mithril_era: + description: Mithril era + type: string + cardano_node_version: + description: Version of the Cardano node + type: string + aggregator_node_version: + description: Version of the Aggregator node + type: string + protocol: + $ref: "#/components/schemas/ProtocolParameters" + next_protocol: + $ref: "#/components/schemas/ProtocolParameters" + total_signers: + description: The number of signers for the current epoch + type: integer + format: int64 + total_next_signers: + description: The number of signers that will be able to sign on the next epoch + type: integer + format: int64 + total_stakes_signers: + description: The total stakes of signers for the current epoch + type: integer + format: int64 + total_next_stakes_signers: + description: The total stakes of signers for the next epoch + type: integer + format: int64 + total_cardano_spo: + description: The number of Cardano SPOs + type: integer + format: int64 + total_cardano_stake: + description: The total stakes in Cardano + type: integer + format: int64 + examples: + { + "epoch": 329, + "cardano_era": "Conway", + "mithril_era": "pythagoras", + "cardano_node_version": "1.2.3", + "aggregator_node_version": "4.5.6", + "protocol": { "k": 857, "m": 6172, "phi_f": 0.2 }, + "next_protocol": { "k": 2422, "m": 20973, "phi_f": 0.2 }, + "total_signers": 3, + "total_next_signers": 72, + "total_stakes_signers": 123456789, + "total_next_stakes_signers": 987654321, + "total_cardano_spo": 5738, + "total_cardano_stake": 999999999 + } + AggregatorFeaturesMessage: description: Represents general information about Aggregator public information and signing capabilities type: object