1use slog::warn;
2use warp::Filter;
3
4use crate::dependency_injection::EpochServiceWrapper;
5use crate::http_server::routes::middlewares;
6use crate::http_server::routes::router::RouterState;
7
8const MITHRIL_SIGNER_VERSION_HEADER: &str = "signer-node-version";
9
10pub fn routes(
11 router_state: &RouterState,
12) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
13 register_signer(router_state)
14 .or(registered_signers(router_state))
15 .or(signers_tickers(router_state))
16}
17
18fn register_signer(
20 router_state: &RouterState,
21) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
22 warp::path!("register-signer")
23 .and(warp::post())
24 .and(middlewares::with_origin_tag(router_state))
25 .and(warp::header::optional::<String>(
26 MITHRIL_SIGNER_VERSION_HEADER,
27 ))
28 .and(warp::body::json())
29 .and(middlewares::with_logger(router_state))
30 .and(middlewares::with_signer_registerer(router_state))
31 .and(middlewares::with_event_transmitter(router_state))
32 .and(middlewares::with_epoch_service(router_state))
33 .and(middlewares::with_metrics_service(router_state))
34 .and_then(handlers::register_signer)
35}
36
37fn signers_tickers(
39 router_state: &RouterState,
40) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
41 warp::path!("signers" / "tickers")
42 .and(warp::get())
43 .and(middlewares::with_logger(router_state))
44 .and(middlewares::extract_config(router_state, |config| {
45 config.network.to_string()
46 }))
47 .and(middlewares::with_signer_getter(router_state))
48 .and_then(handlers::signers_tickers)
49}
50
51fn registered_signers(
53 router_state: &RouterState,
54) -> impl Filter<Extract = (impl warp::Reply + use<>,), Error = warp::Rejection> + Clone + use<> {
55 warp::path!("signers" / "registered" / String)
56 .and(warp::get())
57 .and(middlewares::with_logger(router_state))
58 .and(middlewares::with_epoch_service(router_state))
59 .and(middlewares::with_verification_key_store(router_state))
60 .and_then(handlers::registered_signers)
61}
62
63async fn fetch_epoch_header_value(
64 epoch_service: EpochServiceWrapper,
65 logger: &slog::Logger,
66) -> String {
67 match epoch_service.read().await.epoch_of_current_data() {
68 Ok(epoch) => format!("{epoch}"),
69 Err(e) => {
70 warn!(logger, "Could not fetch epoch header value from Epoch service"; "error" => ?e);
71 String::new()
72 }
73 }
74}
75
76mod handlers {
77 use mithril_common::messages::{RegisterSignerMessage, TryFromMessageAdapter};
78 use slog::{Logger, debug, warn};
79 use std::convert::Infallible;
80 use std::sync::Arc;
81 use warp::http::StatusCode;
82
83 use crate::{
84 FromRegisterSignerAdapter, MetricsService, SignerRegisterer, SignerRegistrationError,
85 VerificationKeyStorer,
86 database::repository::SignerGetter,
87 entities::{
88 SignerRegistrationsMessage, SignerTickerListItemMessage, SignersTickersMessage,
89 },
90 event_store::{EventMessage, TransmitterService},
91 http_server::{
92 parameters,
93 routes::{reply, signer_routes::fetch_epoch_header_value},
94 },
95 };
96
97 use super::*;
98
99 #[allow(clippy::too_many_arguments)]
101 pub async fn register_signer(
102 origin_tag: Option<String>,
103 signer_node_version: Option<String>,
104 register_signer_message: RegisterSignerMessage,
105 logger: Logger,
106 signer_registerer: Arc<dyn SignerRegisterer>,
107 event_transmitter: Arc<TransmitterService<EventMessage>>,
108 epoch_service: EpochServiceWrapper,
109 metrics_service: Arc<MetricsService>,
110 ) -> Result<impl warp::Reply, Infallible> {
111 debug!(logger, ">> register_signer"; "payload" => ?register_signer_message);
112
113 metrics_service
114 .get_signer_registration_total_received_since_startup()
115 .increment(&[origin_tag.as_deref().unwrap_or_default()]);
116
117 let registration_epoch = register_signer_message.epoch;
118
119 let signer = match FromRegisterSignerAdapter::try_adapt(register_signer_message) {
120 Ok(signer) => signer,
121 Err(err) => {
122 warn!(logger,"register_signer::payload decoding error"; "error" => ?err);
123 return Ok(reply::bad_request(
124 "Could not decode signer payload".to_string(),
125 err.to_string(),
126 ));
127 }
128 };
129
130 let epoch_str = fetch_epoch_header_value(epoch_service, &logger).await;
131
132 match signer_registerer.register_signer(registration_epoch, &signer).await {
133 Ok(signer_with_stake) => {
134 event_transmitter.send(EventMessage::signer_registration(
135 "HTTP::signer_register",
136 &signer_with_stake,
137 signer_node_version,
138 epoch_str.as_str(),
139 ));
140
141 Ok(reply::empty(StatusCode::CREATED))
142 }
143 Err(SignerRegistrationError::ExistingSigner(signer_with_stake)) => {
144 debug!(logger, "register_signer::already_registered");
145
146 event_transmitter.send(EventMessage::signer_registration(
147 "HTTP::signer_register",
148 &signer_with_stake,
149 signer_node_version,
150 epoch_str.as_str(),
151 ));
152
153 Ok(reply::empty(StatusCode::CREATED))
154 }
155 Err(SignerRegistrationError::FailedSignerRegistration(err)) => {
156 warn!(logger,"register_signer::failed_signer_registration"; "error" => ?err);
157 Ok(reply::bad_request(
158 "failed_signer_registration".to_string(),
159 err.to_string(),
160 ))
161 }
162 Err(SignerRegistrationError::RegistrationRoundNotYetOpened) => {
163 warn!(logger, "register_signer::registration_round_not_yed_opened");
164 Ok(reply::server_error(
165 SignerRegistrationError::RegistrationRoundNotYetOpened,
166 ))
167 }
168 Err(err) => {
169 warn!(logger,"register_signer::error"; "error" => ?err);
170 Ok(reply::server_error(err))
171 }
172 }
173 }
174
175 pub async fn registered_signers(
177 registered_at: String,
178 logger: Logger,
179 epoch_service: EpochServiceWrapper,
180 verification_key_store: Arc<dyn VerificationKeyStorer>,
181 ) -> Result<impl warp::Reply, Infallible> {
182 let expanded_epoch = match parameters::expand_epoch(®istered_at, epoch_service).await {
183 Ok(epoch) => epoch,
184 Err(err) => {
185 warn!(logger,"registered_signers::invalid_epoch"; "error" => ?err);
186 return Ok(reply::bad_request(
187 "invalid_epoch".to_string(),
188 err.to_string(),
189 ));
190 }
191 };
192
193 match verification_key_store
196 .get_signers(expanded_epoch.offset_to_recording_epoch())
197 .await
198 {
199 Ok(Some(signers)) => {
200 let message = SignerRegistrationsMessage::new(*expanded_epoch, signers);
201 Ok(reply::json(&message, StatusCode::OK))
202 }
203 Ok(None) => {
204 warn!(logger, "registered_signers::not_found");
205 Ok(reply::empty(StatusCode::NOT_FOUND))
206 }
207 Err(err) => {
208 warn!(logger,"registered_signers::error"; "error" => ?err);
209 Ok(reply::server_error(err))
210 }
211 }
212 }
213
214 pub async fn signers_tickers(
215 logger: Logger,
216 network: String,
217 signer_getter: Arc<dyn SignerGetter>,
218 ) -> Result<impl warp::Reply, Infallible> {
219 match signer_getter.get_all().await {
220 Ok(signers) => {
221 let signers: Vec<_> = signers
222 .into_iter()
223 .map(|s| SignerTickerListItemMessage {
224 party_id: s.signer_id,
225 pool_ticker: s.pool_ticker,
226 has_registered: s.last_registered_at.is_some(),
227 })
228 .collect();
229 Ok(reply::json(
230 &SignersTickersMessage { network, signers },
231 StatusCode::OK,
232 ))
233 }
234 Err(err) => {
235 warn!(logger,"registered_signers::error"; "error" => ?err);
236 Ok(reply::server_error(err))
237 }
238 }
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use anyhow::anyhow;
245 use mockall::predicate::eq;
246 use serde_json::Value::Null;
247 use std::sync::Arc;
248 use tokio::sync::RwLock;
249 use warp::{
250 http::{Method, StatusCode},
251 test::request,
252 };
253
254 use mithril_api_spec::APISpec;
255 use mithril_common::{
256 MITHRIL_ORIGIN_TAG_HEADER,
257 crypto_helper::ProtocolRegistrationError,
258 entities::Epoch,
259 messages::RegisterSignerMessage,
260 test::{
261 builder::MithrilFixtureBuilder,
262 double::{Dummy, fake_data},
263 },
264 };
265
266 use crate::{
267 SignerRegistrationError,
268 database::{record::SignerRecord, repository::MockSignerGetter},
269 http_server::routes::reply::MithrilStatusCode,
270 initialize_dependencies,
271 services::{FakeEpochService, MockSignerRegisterer},
272 store::MockVerificationKeyStorer,
273 test::TestLogger,
274 };
275
276 use super::*;
277
278 fn setup_router(
279 state: RouterState,
280 ) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
281 let cors = warp::cors()
282 .allow_any_origin()
283 .allow_headers(vec!["content-type"])
284 .allow_methods(vec![Method::GET, Method::POST, Method::OPTIONS]);
285
286 warp::any().and(routes(&state).with(cors))
287 }
288
289 #[tokio::test]
290 async fn test_register_signer_post_ok() {
291 let signer_with_stake = fake_data::signers_with_stakes(1).pop().unwrap();
292 let mut mock_signer_registerer = MockSignerRegisterer::new();
293 mock_signer_registerer
294 .expect_register_signer()
295 .return_once(|_, _| Ok(signer_with_stake));
296 let mut dependency_manager = initialize_dependencies!().await;
297 dependency_manager.signer_registerer = Arc::new(mock_signer_registerer);
298
299 let signer: RegisterSignerMessage = RegisterSignerMessage::dummy();
300
301 let method = Method::POST.as_str();
302 let path = "/register-signer";
303
304 let response = request()
305 .method(method)
306 .path(path)
307 .json(&signer)
308 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
309 dependency_manager,
310 ))))
311 .await;
312
313 APISpec::verify_conformity(
314 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
315 method,
316 path,
317 "application/json",
318 &signer,
319 &response,
320 &StatusCode::CREATED,
321 )
322 .unwrap();
323 }
324
325 #[tokio::test]
326 async fn test_register_signer_post_increments_signer_registration_total_received_since_startup_metric()
327 {
328 let method = Method::POST.as_str();
329 let path = "/register-signer";
330 let dependency_manager = Arc::new(initialize_dependencies!().await);
331 let initial_counter_value = dependency_manager
332 .metrics_service
333 .get_signer_registration_total_received_since_startup()
334 .get(&["TEST"]);
335
336 request()
337 .method(method)
338 .path(path)
339 .json(&RegisterSignerMessage::dummy())
340 .header(MITHRIL_ORIGIN_TAG_HEADER, "TEST")
341 .reply(&setup_router(RouterState::new_with_origin_tag_white_list(
342 dependency_manager.clone(),
343 &["TEST"],
344 )))
345 .await;
346
347 assert_eq!(
348 initial_counter_value + 1,
349 dependency_manager
350 .metrics_service
351 .get_signer_registration_total_received_since_startup()
352 .get(&["TEST"])
353 );
354 }
355
356 #[tokio::test]
357 async fn test_register_signer_post_ok_existing() {
358 let signer_with_stake = fake_data::signers_with_stakes(1).pop().unwrap();
359 let mut mock_signer_registerer = MockSignerRegisterer::new();
360 mock_signer_registerer.expect_register_signer().return_once(|_, _| {
361 Err(SignerRegistrationError::ExistingSigner(Box::new(
362 signer_with_stake,
363 )))
364 });
365 let mut dependency_manager = initialize_dependencies!().await;
366 dependency_manager.signer_registerer = Arc::new(mock_signer_registerer);
367
368 let signer: RegisterSignerMessage = RegisterSignerMessage::dummy();
369
370 let method = Method::POST.as_str();
371 let path = "/register-signer";
372
373 let response = request()
374 .method(method)
375 .path(path)
376 .json(&signer)
377 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
378 dependency_manager,
379 ))))
380 .await;
381
382 APISpec::verify_conformity(
383 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
384 method,
385 path,
386 "application/json",
387 &signer,
388 &response,
389 &StatusCode::CREATED,
390 )
391 .unwrap();
392 }
393
394 #[tokio::test]
395 async fn test_register_signer_post_ko_400() {
396 let mut mock_signer_registerer = MockSignerRegisterer::new();
397 mock_signer_registerer.expect_register_signer().return_once(|_, _| {
398 Err(SignerRegistrationError::FailedSignerRegistration(anyhow!(
399 ProtocolRegistrationError::OpCertInvalid
400 )))
401 });
402 let mut dependency_manager = initialize_dependencies!().await;
403 dependency_manager.signer_registerer = Arc::new(mock_signer_registerer);
404
405 let signer: RegisterSignerMessage = RegisterSignerMessage::dummy();
406
407 let method = Method::POST.as_str();
408 let path = "/register-signer";
409
410 let response = request()
411 .method(method)
412 .path(path)
413 .json(&signer)
414 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
415 dependency_manager,
416 ))))
417 .await;
418
419 APISpec::verify_conformity(
420 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
421 method,
422 path,
423 "application/json",
424 &signer,
425 &response,
426 &StatusCode::BAD_REQUEST,
427 )
428 .unwrap();
429 }
430
431 #[tokio::test]
432 async fn test_register_signer_post_ko_500() {
433 let mut mock_signer_registerer = MockSignerRegisterer::new();
434 mock_signer_registerer.expect_register_signer().return_once(|_, _| {
435 Err(SignerRegistrationError::FailedSignerRecorder(
436 "an error occurred".to_string(),
437 ))
438 });
439 let mut dependency_manager = initialize_dependencies!().await;
440 dependency_manager.signer_registerer = Arc::new(mock_signer_registerer);
441
442 let signer: RegisterSignerMessage = RegisterSignerMessage::dummy();
443 let method = Method::POST.as_str();
444 let path = "/register-signer";
445
446 let response = request()
447 .method(method)
448 .path(path)
449 .json(&signer)
450 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
451 dependency_manager,
452 ))))
453 .await;
454
455 APISpec::verify_conformity(
456 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
457 method,
458 path,
459 "application/json",
460 &signer,
461 &response,
462 &StatusCode::INTERNAL_SERVER_ERROR,
463 )
464 .unwrap();
465 }
466
467 #[tokio::test]
468 async fn test_register_signer_post_ko_550() {
469 let mut mock_signer_registerer = MockSignerRegisterer::new();
470 mock_signer_registerer
471 .expect_register_signer()
472 .return_once(|_, _| Err(SignerRegistrationError::RegistrationRoundNotYetOpened));
473 let mut dependency_manager = initialize_dependencies!().await;
474 dependency_manager.signer_registerer = Arc::new(mock_signer_registerer);
475
476 let signer: RegisterSignerMessage = RegisterSignerMessage::dummy();
477 let method = Method::POST.as_str();
478 let path = "/register-signer";
479
480 let response = request()
481 .method(method)
482 .path(path)
483 .json(&signer)
484 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
485 dependency_manager,
486 ))))
487 .await;
488
489 APISpec::verify_conformity(
490 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
491 method,
492 path,
493 "application/json",
494 &signer,
495 &response,
496 &MithrilStatusCode::registration_round_not_yet_opened(),
497 )
498 .unwrap();
499 }
500
501 #[tokio::test]
502 async fn test_registered_signers_get_offset_given_epoch_to_registration_epoch() {
503 let asked_epoch = Epoch(1);
504 let expected_retrieval_epoch = asked_epoch.offset_to_recording_epoch();
505 let mut mock_verification_key_store = MockVerificationKeyStorer::new();
506 mock_verification_key_store
507 .expect_get_signers()
508 .with(eq(expected_retrieval_epoch))
509 .return_once(|_| Ok(Some(fake_data::signers_with_stakes(3))))
510 .once();
511 let mut dependency_manager = initialize_dependencies!().await;
512 dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);
513
514 let method = Method::GET.as_str();
515 let base_path = "/signers/registered";
516
517 let response = request()
518 .method(method)
519 .path(&format!("{base_path}/{asked_epoch}"))
520 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
521 dependency_manager,
522 ))))
523 .await;
524
525 assert!(
526 response.status().is_success(),
527 "expected the response to succeed, was: {response:#?}"
528 );
529 }
530
531 #[tokio::test]
532 async fn test_registered_signers_get_ok() {
533 let mut mock_verification_key_store = MockVerificationKeyStorer::new();
534 mock_verification_key_store
535 .expect_get_signers()
536 .return_once(|_| Ok(Some(fake_data::signers_with_stakes(3))))
537 .once();
538 let mut dependency_manager = initialize_dependencies!().await;
539 dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);
540
541 let base_path = "/signers/registered";
542 let method = Method::GET.as_str();
543
544 let response = request()
545 .method(method)
546 .path(&format!("{base_path}/1"))
547 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
548 dependency_manager,
549 ))))
550 .await;
551
552 APISpec::verify_conformity(
553 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
554 method,
555 &format!("{base_path}/{{epoch}}"),
556 "application/json",
557 &Null,
558 &response,
559 &StatusCode::OK,
560 )
561 .unwrap();
562 }
563
564 #[tokio::test]
565 async fn test_registered_signers_returns_404_not_found_when_no_registration() {
566 let mut mock_verification_key_store = MockVerificationKeyStorer::new();
567 mock_verification_key_store
568 .expect_get_signers()
569 .return_once(|_| Ok(None))
570 .once();
571 let mut dependency_manager = initialize_dependencies!().await;
572 dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);
573
574 let method = Method::GET.as_str();
575 let base_path = "/signers/registered";
576
577 let response = request()
578 .method(method)
579 .path(&format!("{base_path}/3"))
580 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
581 dependency_manager,
582 ))))
583 .await;
584
585 APISpec::verify_conformity(
586 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
587 method,
588 &format!("{base_path}/{{epoch}}"),
589 "application/json",
590 &Null,
591 &response,
592 &StatusCode::NOT_FOUND,
593 )
594 .unwrap();
595 }
596
597 #[tokio::test]
598 async fn test_registered_signers_get_ko() {
599 let mut mock_verification_key_store = MockVerificationKeyStorer::new();
600 mock_verification_key_store
601 .expect_get_signers()
602 .return_once(|_| Err(anyhow!("invalid query")));
603 let mut dependency_manager = initialize_dependencies!().await;
604 dependency_manager.verification_key_store = Arc::new(mock_verification_key_store);
605
606 let method = Method::GET.as_str();
607 let base_path = "/signers/registered";
608
609 let response = request()
610 .method(method)
611 .path(&format!("{base_path}/1"))
612 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
613 dependency_manager,
614 ))))
615 .await;
616
617 APISpec::verify_conformity(
618 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
619 method,
620 &format!("{base_path}/{{epoch}}"),
621 "application/json",
622 &Null,
623 &response,
624 &StatusCode::INTERNAL_SERVER_ERROR,
625 )
626 .unwrap();
627 }
628
629 #[tokio::test]
630 async fn test_signers_tickers_get_ok() {
631 let mut mock_signer_getter = MockSignerGetter::new();
632 mock_signer_getter
633 .expect_get_all()
634 .return_once(|| {
635 Ok(vec![
636 SignerRecord {
637 signer_id: "pool_without_ticker".to_string(),
638 pool_ticker: None,
639 created_at: Default::default(),
640 updated_at: Default::default(),
641 last_registered_at: None,
642 },
643 SignerRecord {
644 signer_id: "pool_with_ticker".to_string(),
645 pool_ticker: Some("pool_ticker".to_string()),
646 created_at: Default::default(),
647 updated_at: Default::default(),
648 last_registered_at: None,
649 },
650 ])
651 })
652 .once();
653 let mut dependency_manager = initialize_dependencies!().await;
654 dependency_manager.signer_getter = Arc::new(mock_signer_getter);
655
656 let method = Method::GET.as_str();
657 let path = "/signers/tickers";
658
659 let response = request()
660 .method(method)
661 .path(path)
662 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
663 dependency_manager,
664 ))))
665 .await;
666
667 APISpec::verify_conformity(
668 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
669 method,
670 path,
671 "application/json",
672 &Null,
673 &response,
674 &StatusCode::OK,
675 )
676 .unwrap();
677 }
678
679 #[tokio::test]
680 async fn test_signers_tickers_get_ko() {
681 let mut mock_signer_getter = MockSignerGetter::new();
682 mock_signer_getter
683 .expect_get_all()
684 .return_once(|| Err(anyhow!("an error")))
685 .once();
686 let mut dependency_manager = initialize_dependencies!().await;
687 dependency_manager.signer_getter = Arc::new(mock_signer_getter);
688
689 let method = Method::GET.as_str();
690 let path = "/signers/tickers";
691
692 let response = request()
693 .method(method)
694 .path(path)
695 .reply(&setup_router(RouterState::new_with_dummy_config(Arc::new(
696 dependency_manager,
697 ))))
698 .await;
699
700 APISpec::verify_conformity(
701 APISpec::get_default_spec_file_from(crate::http_server::API_SPEC_LOCATION),
702 method,
703 path,
704 "application/json",
705 &Null,
706 &response,
707 &StatusCode::INTERNAL_SERVER_ERROR,
708 )
709 .unwrap();
710 }
711
712 #[tokio::test]
713 async fn test_fetch_epoch_header_value_when_epoch_service_return_epoch() {
714 let fixture = MithrilFixtureBuilder::default().build();
715 let epoch_service = Arc::new(RwLock::new(FakeEpochService::from_fixture(
716 Epoch(84),
717 &fixture,
718 )));
719
720 let epoch_str = fetch_epoch_header_value(epoch_service, &TestLogger::stdout()).await;
721
722 assert_eq!(epoch_str, "84".to_string());
723 }
724
725 #[tokio::test]
726 async fn test_fetch_epoch_header_value_when_epoch_service_error_return_empty_string() {
727 let epoch_service = Arc::new(RwLock::new(FakeEpochService::without_data()));
728
729 let epoch_str = fetch_epoch_header_value(epoch_service, &TestLogger::stdout()).await;
730
731 assert_eq!(epoch_str, "".to_string());
732 }
733}