mithril_aggregator/http_server/routes/
status.rs

1use warp::Filter;
2
3use mithril_common::{messages::AggregatorStatusMessage, StdResult};
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,), Error = warp::Rejection> + Clone {
13    status(router_state)
14}
15
16/// GET /status
17fn status(
18    router_state: &RouterState,
19) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
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::{warn, Logger};
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 std::sync::Arc;
108
109    use serde_json::Value::Null;
110    use tokio::sync::RwLock;
111    use warp::{
112        http::{Method, StatusCode},
113        test::request,
114    };
115
116    use mithril_common::{
117        entities::{Epoch, ProtocolParameters, Stake},
118        test_utils::{apispec::APISpec, fake_data, MithrilFixtureBuilder},
119    };
120
121    use crate::{
122        entities::AggregatorEpochSettings,
123        initialize_dependencies,
124        services::{FakeEpochService, FakeEpochServiceBuilder},
125    };
126
127    use super::*;
128
129    fn setup_router(
130        state: RouterState,
131    ) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
132        let cors = warp::cors()
133            .allow_any_origin()
134            .allow_headers(vec!["content-type"])
135            .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS]);
136
137        warp::any().and(routes(&state).with(cors))
138    }
139
140    #[tokio::test]
141    async fn status_route_ko_500() {
142        let dependency_manager = initialize_dependencies!().await;
143        let method = Method::GET.as_str();
144        let path = "/status";
145
146        let response = request()
147            .method(method)
148            .path(path)
149            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
150                dependency_manager,
151            ))))
152            .await;
153
154        APISpec::verify_conformity(
155            APISpec::get_all_spec_files(),
156            method,
157            path,
158            "application/json",
159            &Null,
160            &response,
161            &StatusCode::INTERNAL_SERVER_ERROR,
162        )
163        .unwrap();
164    }
165
166    #[tokio::test]
167    async fn status_route_ok_200() {
168        let mut dependency_manager = initialize_dependencies!().await;
169        let fixture = MithrilFixtureBuilder::default().build();
170        let epoch_service = FakeEpochService::from_fixture(Epoch(5), &fixture);
171        dependency_manager.epoch_service = Arc::new(RwLock::new(epoch_service));
172
173        let method = Method::GET.as_str();
174        let path = "/status";
175
176        let response = request()
177            .method(method)
178            .path(path)
179            .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
180                dependency_manager,
181            ))))
182            .await;
183
184        APISpec::verify_conformity(
185            APISpec::get_all_spec_files(),
186            method,
187            path,
188            "application/json",
189            &Null,
190            &response,
191            &StatusCode::OK,
192        )
193        .unwrap();
194    }
195
196    #[tokio::test]
197    async fn retrieves_correct_protocol_parameters_from_epoch_service() {
198        let current_epoch_settings = AggregatorEpochSettings {
199            protocol_parameters: ProtocolParameters::new(101, 10, 0.5),
200            ..AggregatorEpochSettings::dummy()
201        };
202        let next_epoch_settings = AggregatorEpochSettings {
203            protocol_parameters: ProtocolParameters::new(102, 20, 0.5),
204            ..AggregatorEpochSettings::dummy()
205        };
206        let signer_registration_epoch_settings = AggregatorEpochSettings {
207            protocol_parameters: ProtocolParameters::new(103, 30, 0.5),
208            ..AggregatorEpochSettings::dummy()
209        };
210
211        let epoch_service = FakeEpochServiceBuilder {
212            current_epoch_settings: current_epoch_settings.clone(),
213            next_epoch_settings: next_epoch_settings.clone(),
214            signer_registration_epoch_settings,
215            ..FakeEpochServiceBuilder::dummy(Epoch(3))
216        }
217        .build();
218        let epoch_service = Arc::new(RwLock::new(epoch_service));
219
220        let message = get_aggregator_status_message(epoch_service, String::new(), String::new())
221            .await
222            .unwrap();
223
224        assert_eq!(
225            message.protocol_parameters,
226            current_epoch_settings.protocol_parameters
227        );
228
229        assert_eq!(
230            message.next_protocol_parameters,
231            next_epoch_settings.protocol_parameters
232        );
233    }
234
235    #[tokio::test]
236    async fn total_cardano_stakes_and_spo_are_0_if_none_in_epoch_service() {
237        let epoch_service = FakeEpochServiceBuilder {
238            total_spo: None,
239            total_stake: None,
240            ..FakeEpochServiceBuilder::dummy(Epoch(3))
241        }
242        .build();
243        let epoch_service = Arc::new(RwLock::new(epoch_service));
244
245        let message = get_aggregator_status_message(epoch_service, String::new(), String::new())
246            .await
247            .unwrap();
248
249        assert_eq!(message.total_cardano_spo, 0);
250        assert_eq!(message.total_cardano_stake, 0);
251    }
252
253    #[tokio::test]
254    async fn retrieves_correct_total_signers_from_epoch_service() {
255        let total_signers = 5;
256        let total_next_signers = 4;
257        let epoch_service = FakeEpochServiceBuilder {
258            current_signers_with_stake: fake_data::signers_with_stakes(total_signers),
259            next_signers_with_stake: fake_data::signers_with_stakes(total_next_signers),
260            ..FakeEpochServiceBuilder::dummy(Epoch(3))
261        }
262        .build();
263        let epoch_service = Arc::new(RwLock::new(epoch_service));
264
265        let message = get_aggregator_status_message(epoch_service, String::new(), String::new())
266            .await
267            .unwrap();
268
269        assert_eq!(message.total_signers, total_signers);
270        assert_eq!(message.total_next_signers, total_next_signers);
271    }
272
273    #[tokio::test]
274    async fn retrieves_correct_total_stakes_from_epoch_service() {
275        let current_signers_with_stake = fake_data::signers_with_stakes(4);
276        let next_signers_with_stake = fake_data::signers_with_stakes(7);
277        let total_stakes_signers: Stake = current_signers_with_stake.iter().map(|s| s.stake).sum();
278        let total_next_stakes_signers: Stake =
279            next_signers_with_stake.iter().map(|s| s.stake).sum();
280
281        assert_ne!(total_stakes_signers, total_next_stakes_signers);
282
283        let epoch_service = FakeEpochServiceBuilder {
284            current_signers_with_stake,
285            next_signers_with_stake,
286            ..FakeEpochServiceBuilder::dummy(Epoch(3))
287        }
288        .build();
289        let epoch_service = Arc::new(RwLock::new(epoch_service));
290
291        let message = get_aggregator_status_message(epoch_service, String::new(), String::new())
292            .await
293            .unwrap();
294
295        assert_eq!(message.total_stakes_signers, total_stakes_signers);
296        assert_eq!(message.total_next_stakes_signers, total_next_stakes_signers);
297    }
298
299    #[tokio::test]
300    async fn retrieves_node_version_and_network_from_parameters() {
301        let epoch_service = FakeEpochServiceBuilder::dummy(Epoch(3)).build();
302        let epoch_service = Arc::new(RwLock::new(epoch_service));
303
304        let message = get_aggregator_status_message(
305            epoch_service,
306            "1.0.4".to_string(),
307            "network".to_string(),
308        )
309        .await
310        .unwrap();
311
312        assert_eq!(message.cardano_node_version, "1.0.4");
313        assert_eq!(message.cardano_network, "network");
314    }
315}