mithril_aggregator/http_server/routes/
signer_routes.rs

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