mithril_aggregator/http_server/routes/
status.rs1use 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
16fn 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 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}