mithril_aggregator/http_server/routes/
status.rs

1use warp::Filter;
2
3use mithril_common::{StdResult, messages::AggregatorStatusMessage};
4
5use crate::{
6    dependency_injection::EpochServiceWrapper,
7    http_server::routes::{middlewares, router::RouterState},
8};
9
10pub fn routes(
11    router_state: &RouterState,
12) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
13    status(router_state)
14}
15
16/// GET /status
17fn status(
18    router_state: &RouterState,
19) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
20    warp::path!("status")
21        .and(warp::get())
22        .and(middlewares::with_logger(router_state))
23        .and(middlewares::with_epoch_service(router_state))
24        .and(middlewares::extract_config(router_state, |config| {
25            config.cardano_node_version.clone()
26        }))
27        .and(middlewares::extract_config(router_state, |config| {
28            config.network.to_string()
29        }))
30        .and_then(handlers::status)
31}
32
33async fn get_aggregator_status_message(
34    epoch_service: EpochServiceWrapper,
35    cardano_node_version: String,
36    cardano_network: String,
37) -> StdResult<AggregatorStatusMessage> {
38    let epoch_service = epoch_service.read().await;
39
40    let epoch = epoch_service.epoch_of_current_data()?;
41    let cardano_era = epoch_service.cardano_era()?;
42    let mithril_era = epoch_service.mithril_era()?;
43    let aggregator_node_version = env!("CARGO_PKG_VERSION").to_string();
44    let protocol_parameters = epoch_service.current_protocol_parameters()?.clone();
45    let next_protocol_parameters = epoch_service.next_protocol_parameters()?.clone();
46    let total_signers = epoch_service.current_signers()?.len();
47    let total_next_signers = epoch_service.next_signers()?.len();
48    let total_stakes_signers = epoch_service.total_stakes_signers()?;
49    let total_next_stakes_signers = epoch_service.total_next_stakes_signers()?;
50    let total_cardano_spo = epoch_service.total_spo()?.unwrap_or_default();
51    let total_cardano_stake = epoch_service.total_stake()?.unwrap_or_default();
52
53    let message = AggregatorStatusMessage {
54        epoch,
55        cardano_era,
56        cardano_network,
57        mithril_era,
58        cardano_node_version,
59        aggregator_node_version,
60        protocol_parameters,
61        next_protocol_parameters,
62        total_signers,
63        total_next_signers,
64        total_stakes_signers,
65        total_next_stakes_signers,
66        total_cardano_spo,
67        total_cardano_stake,
68    };
69
70    Ok(message)
71}
72
73mod handlers {
74    use std::convert::Infallible;
75
76    use slog::{Logger, warn};
77    use warp::http::StatusCode;
78
79    use crate::{
80        dependency_injection::EpochServiceWrapper,
81        http_server::routes::{reply, status::get_aggregator_status_message},
82    };
83
84    /// Status
85    pub async fn status(
86        logger: Logger,
87        epoch_service: EpochServiceWrapper,
88        cardano_node_version: String,
89        cardano_network: String,
90    ) -> Result<impl warp::Reply, Infallible> {
91        let aggregator_status_message =
92            get_aggregator_status_message(epoch_service, cardano_node_version, cardano_network)
93                .await;
94
95        match aggregator_status_message {
96            Ok(message) => Ok(reply::json(&message, StatusCode::OK)),
97            Err(err) => {
98                warn!(logger,"aggregator_status::error"; "error" => ?err);
99                Ok(reply::server_error(err))
100            }
101        }
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use serde_json::Value::Null;
108    use std::sync::Arc;
109    use tokio::sync::RwLock;
110    use warp::{
111        http::{Method, StatusCode},
112        test::request,
113    };
114
115    use mithril_api_spec::APISpec;
116    use mithril_common::{
117        entities::{Epoch, ProtocolParameters, Stake},
118        test::{
119            builder::MithrilFixtureBuilder,
120            double::{Dummy, fake_data},
121        },
122    };
123
124    use crate::{
125        entities::AggregatorEpochSettings,
126        initialize_dependencies,
127        services::{FakeEpochService, FakeEpochServiceBuilder},
128    };
129
130    use super::*;
131
132    fn setup_router(
133        state: RouterState,
134    ) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
135        let cors = warp::cors()
136            .allow_any_origin()
137            .allow_headers(vec!["content-type"])
138            .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS]);
139
140        warp::any().and(routes(&state).with(cors))
141    }
142
143    #[tokio::test]
144    async fn status_route_ko_500() {
145        let dependency_manager = initialize_dependencies!().await;
146        let method = Method::GET.as_str();
147        let path = "/status";
148
149        let response = request()
150            .method(method)
151            .path(path)
152            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
153                dependency_manager,
154            ))))
155            .await;
156
157        APISpec::verify_conformity(
158            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
159            method,
160            path,
161            "application/json",
162            &Null,
163            &response,
164            &StatusCode::INTERNAL_SERVER_ERROR,
165        )
166        .unwrap();
167    }
168
169    #[tokio::test]
170    async fn status_route_ok_200() {
171        let mut dependency_manager = initialize_dependencies!().await;
172        let fixture = MithrilFixtureBuilder::default().build();
173        let epoch_service = FakeEpochService::from_fixture(Epoch(5), &fixture);
174        dependency_manager.epoch_service = Arc::new(RwLock::new(epoch_service));
175
176        let method = Method::GET.as_str();
177        let path = "/status";
178
179        let response = request()
180            .method(method)
181            .path(path)
182            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
183                dependency_manager,
184            ))))
185            .await;
186
187        APISpec::verify_conformity(
188            APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
189            method,
190            path,
191            "application/json",
192            &Null,
193            &response,
194            &StatusCode::OK,
195        )
196        .unwrap();
197    }
198
199    #[tokio::test]
200    async fn retrieves_correct_protocol_parameters_from_epoch_service() {
201        let current_epoch_settings = AggregatorEpochSettings {
202            protocol_parameters: ProtocolParameters::new(101, 10, 0.5),
203            ..AggregatorEpochSettings::dummy()
204        };
205        let next_epoch_settings = AggregatorEpochSettings {
206            protocol_parameters: ProtocolParameters::new(102, 20, 0.5),
207            ..AggregatorEpochSettings::dummy()
208        };
209        let signer_registration_epoch_settings = AggregatorEpochSettings {
210            protocol_parameters: ProtocolParameters::new(103, 30, 0.5),
211            ..AggregatorEpochSettings::dummy()
212        };
213
214        let epoch_service = FakeEpochServiceBuilder {
215            current_epoch_settings: current_epoch_settings.clone(),
216            next_epoch_settings: next_epoch_settings.clone(),
217            signer_registration_epoch_settings,
218            ..FakeEpochServiceBuilder::dummy(Epoch(3))
219        }
220        .build();
221        let epoch_service = Arc::new(RwLock::new(epoch_service));
222
223        let message = get_aggregator_status_message(epoch_service, String::new(), String::new())
224            .await
225            .unwrap();
226
227        assert_eq!(
228            message.protocol_parameters,
229            current_epoch_settings.protocol_parameters
230        );
231
232        assert_eq!(
233            message.next_protocol_parameters,
234            next_epoch_settings.protocol_parameters
235        );
236    }
237
238    #[tokio::test]
239    async fn total_cardano_stakes_and_spo_are_0_if_none_in_epoch_service() {
240        let epoch_service = FakeEpochServiceBuilder {
241            total_spo: None,
242            total_stake: None,
243            ..FakeEpochServiceBuilder::dummy(Epoch(3))
244        }
245        .build();
246        let epoch_service = Arc::new(RwLock::new(epoch_service));
247
248        let message = get_aggregator_status_message(epoch_service, String::new(), String::new())
249            .await
250            .unwrap();
251
252        assert_eq!(message.total_cardano_spo, 0);
253        assert_eq!(message.total_cardano_stake, 0);
254    }
255
256    #[tokio::test]
257    async fn retrieves_correct_total_signers_from_epoch_service() {
258        let total_signers = 5;
259        let total_next_signers = 4;
260        let epoch_service = FakeEpochServiceBuilder {
261            current_signers_with_stake: fake_data::signers_with_stakes(total_signers),
262            next_signers_with_stake: fake_data::signers_with_stakes(total_next_signers),
263            ..FakeEpochServiceBuilder::dummy(Epoch(3))
264        }
265        .build();
266        let epoch_service = Arc::new(RwLock::new(epoch_service));
267
268        let message = get_aggregator_status_message(epoch_service, String::new(), String::new())
269            .await
270            .unwrap();
271
272        assert_eq!(message.total_signers, total_signers);
273        assert_eq!(message.total_next_signers, total_next_signers);
274    }
275
276    #[tokio::test]
277    async fn retrieves_correct_total_stakes_from_epoch_service() {
278        let current_signers_with_stake = fake_data::signers_with_stakes(4);
279        let next_signers_with_stake = fake_data::signers_with_stakes(7);
280        let total_stakes_signers: Stake = current_signers_with_stake.iter().map(|s| s.stake).sum();
281        let total_next_stakes_signers: Stake =
282            next_signers_with_stake.iter().map(|s| s.stake).sum();
283
284        assert_ne!(total_stakes_signers, total_next_stakes_signers);
285
286        let epoch_service = FakeEpochServiceBuilder {
287            current_signers_with_stake,
288            next_signers_with_stake,
289            ..FakeEpochServiceBuilder::dummy(Epoch(3))
290        }
291        .build();
292        let epoch_service = Arc::new(RwLock::new(epoch_service));
293
294        let message = get_aggregator_status_message(epoch_service, String::new(), String::new())
295            .await
296            .unwrap();
297
298        assert_eq!(message.total_stakes_signers, total_stakes_signers);
299        assert_eq!(message.total_next_stakes_signers, total_next_stakes_signers);
300    }
301
302    #[tokio::test]
303    async fn retrieves_node_version_and_network_from_parameters() {
304        let epoch_service = FakeEpochServiceBuilder::dummy(Epoch(3)).build();
305        let epoch_service = Arc::new(RwLock::new(epoch_service));
306
307        let message = get_aggregator_status_message(
308            epoch_service,
309            "1.0.4".to_string(),
310            "network".to_string(),
311        )
312        .await
313        .unwrap();
314
315        assert_eq!(message.cardano_node_version, "1.0.4");
316        assert_eq!(message.cardano_network, "network");
317    }
318}