mithril_signer/runtime/
state_machine.rs

1use anyhow::Error;
2use chrono::Local;
3use slog::{debug, info, Logger};
4use std::{fmt::Display, ops::Deref, sync::Arc, time::Duration};
5use tokio::sync::Mutex;
6
7use mithril_common::{
8    crypto_helper::ProtocolInitializerError,
9    entities::{Epoch, TimePoint},
10    logging::LoggerExtensions,
11};
12
13use crate::{
14    entities::{BeaconToSign, SignerEpochSettings},
15    services::AggregatorClientError,
16    MetricsService,
17};
18
19use super::{Runner, RuntimeError};
20
21/// Different possible states of the state machine.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum SignerState {
24    /// Starting state
25    Init,
26    /// Hold the latest known epoch in order to help synchronisation
27    /// with the aggregator
28    Unregistered {
29        /// Current Epoch
30        epoch: Epoch,
31    },
32
33    /// `ReadyToSign` state. The signer is registered and ready to sign new messages.
34    ReadyToSign {
35        /// Epoch when signer transitioned to the state.
36        epoch: Epoch,
37    },
38
39    /// `RegisteredNotAbleToSign` state. The signer is registered but not able to sign for the duration of the epoch.
40    RegisteredNotAbleToSign {
41        /// Epoch when signer transitioned to the state.
42        epoch: Epoch,
43    },
44}
45
46impl SignerState {
47    /// Returns `true` if the state in `Init`
48    pub fn is_init(&self) -> bool {
49        matches!(*self, SignerState::Init)
50    }
51
52    /// Returns `true` if the state in `Unregistered`
53    pub fn is_unregistered(&self) -> bool {
54        matches!(*self, SignerState::Unregistered { .. })
55    }
56
57    /// Returns `true` if the state in `ReadyToSign`
58    pub fn is_ready_to_sign(&self) -> bool {
59        matches!(*self, SignerState::ReadyToSign { .. })
60    }
61
62    /// Returns `true` if the state in `RegisteredNotAbleToSign`
63    pub fn is_registered_not_able_to_sign(&self) -> bool {
64        matches!(*self, SignerState::RegisteredNotAbleToSign { .. })
65    }
66}
67
68impl Display for SignerState {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        match self {
71            Self::Init => write!(f, "Init"),
72            Self::Unregistered { epoch } => write!(f, "Unregistered - {epoch:?}"),
73            Self::RegisteredNotAbleToSign { epoch } => {
74                write!(f, "RegisteredNotAbleToSign - {epoch}")
75            }
76            Self::ReadyToSign { epoch } => {
77                write!(f, "ReadyToSign - {epoch}")
78            }
79        }
80    }
81}
82
83enum EpochStatus {
84    NewEpoch(Epoch),
85    Unchanged(TimePoint),
86}
87
88/// The state machine is responsible of the execution of the signer automate.
89pub struct StateMachine {
90    state: Mutex<SignerState>,
91    runner: Box<dyn Runner>,
92    interval: Duration,
93    metrics_service: Arc<MetricsService>,
94    logger: Logger,
95}
96
97impl StateMachine {
98    /// Create a new StateMachine instance.
99    pub fn new(
100        starting_state: SignerState,
101        runner: Box<dyn Runner>,
102        interval: Duration,
103        metrics_service: Arc<MetricsService>,
104        logger: Logger,
105    ) -> Self {
106        Self {
107            state: Mutex::new(starting_state),
108            runner,
109            interval,
110            metrics_service,
111            logger: logger.new_with_component_name::<Self>(),
112        }
113    }
114
115    /// Return the current state of the state machine.
116    pub async fn get_state(&self) -> SignerState {
117        self.state.lock().await.to_owned()
118    }
119
120    /// Launch the state machine until an error occurs or it is interrupted.
121    pub async fn run(&self) -> Result<(), RuntimeError> {
122        info!(self.logger, "Launching State Machine");
123        let mut interval = tokio::time::interval(self.interval);
124
125        loop {
126            interval.tick().await;
127            // Note: the "time" property in logs produced by our formatter (slog_bunyan) uses local
128            // time, so we must use it as well to avoid confusion.
129            let approximate_next_cycle_time = Local::now() + self.interval;
130
131            if let Err(e) = self.cycle().await {
132                e.write_to_log(&self.logger);
133                if e.is_critical() {
134                    return Err(e);
135                }
136            }
137
138            info!(
139                self.logger, "… Cycle finished";
140                "approximate_next_cycle_time" => %approximate_next_cycle_time.time().format("%H:%M:%S%.3f"),
141                "run_interval_in_ms" => self.interval.as_millis(),
142            );
143        }
144    }
145
146    /// Perform a cycle of the state machine.
147    pub async fn cycle(&self) -> Result<(), RuntimeError> {
148        let mut state = self.state.lock().await;
149        info!(
150            self.logger,
151            "================================================================================"
152        );
153        info!(self.logger, "New cycle: {}", *state);
154
155        self.metrics_service
156            .get_runtime_cycle_total_since_startup_counter()
157            .increment();
158
159        match state.deref() {
160            SignerState::Init => {
161                *state = self.transition_from_init_to_unregistered().await?;
162            }
163            SignerState::Unregistered { epoch } => {
164                if let EpochStatus::NewEpoch(new_epoch) = self.has_epoch_changed(*epoch).await? {
165                    info!(
166                        self.logger,
167                        "→ Epoch has changed, transiting to Unregistered"
168                    );
169                    *state = self
170                        .transition_from_unregistered_to_unregistered(new_epoch)
171                        .await?;
172                } else if let Some(epoch_settings) = self
173                    .runner
174                    .get_epoch_settings()
175                    .await
176                    .map_err(|e| RuntimeError::KeepState {
177                        message: format!("could not retrieve epoch settings at epoch {epoch:?}"),
178                        nested_error: Some(e),
179                    })?
180                {
181                    info!(self.logger, "→ Epoch settings found");
182                    if epoch_settings.epoch >= *epoch {
183                        info!(self.logger, "New Epoch found");
184                        info!(self.logger, " ⋅ Transiting to Registered");
185                        *state = self
186                            .transition_from_unregistered_to_one_of_registered_states(
187                                epoch_settings,
188                            )
189                            .await?;
190                    } else {
191                        info!(
192                            self.logger, " ⋅ Epoch settings found, but its epoch is behind the known epoch, waiting…";
193                            "epoch_settings" => ?epoch_settings,
194                            "known_epoch" => ?epoch,
195                        );
196                    }
197                } else {
198                    info!(self.logger, "→ No epoch settings found yet, waiting…");
199                }
200            }
201            SignerState::RegisteredNotAbleToSign { epoch } => {
202                if let EpochStatus::NewEpoch(new_epoch) = self.has_epoch_changed(*epoch).await? {
203                    info!(
204                        self.logger,
205                        " → New Epoch detected, transiting to Unregistered"
206                    );
207                    *state = self
208                        .transition_from_registered_not_able_to_sign_to_unregistered(new_epoch)
209                        .await?;
210                } else {
211                    info!(self.logger, " ⋅ Epoch has NOT changed, waiting…");
212                }
213            }
214
215            SignerState::ReadyToSign { epoch } => match self.has_epoch_changed(*epoch).await? {
216                EpochStatus::NewEpoch(new_epoch) => {
217                    info!(
218                        self.logger,
219                        "→ Epoch has changed, transiting to Unregistered"
220                    );
221                    *state = self
222                        .transition_from_ready_to_sign_to_unregistered(new_epoch)
223                        .await?;
224                }
225                EpochStatus::Unchanged(timepoint) => {
226                    let beacon_to_sign =
227                        self.runner
228                            .get_beacon_to_sign(timepoint)
229                            .await
230                            .map_err(|e| RuntimeError::KeepState {
231                                message: "could not fetch the beacon to sign".to_string(),
232                                nested_error: Some(e),
233                            })?;
234
235                    match beacon_to_sign {
236                        Some(beacon) => {
237                            info!(
238                                self.logger, "→ Epoch has NOT changed we can sign this beacon, transiting to ReadyToSign";
239                                "beacon_to_sign" => ?beacon,
240                            );
241                            *state = self
242                                .transition_from_ready_to_sign_to_ready_to_sign(*epoch, beacon)
243                                .await?;
244                        }
245                        None => {
246                            info!(self.logger, " ⋅ No beacon to sign, waiting…");
247                        }
248                    }
249                }
250            },
251        };
252
253        self.metrics_service
254            .get_runtime_cycle_success_since_startup_counter()
255            .increment();
256
257        Ok(())
258    }
259
260    /// Return the new epoch if the epoch is different than the given one, otherwise return the current time point.
261    async fn has_epoch_changed(&self, epoch: Epoch) -> Result<EpochStatus, RuntimeError> {
262        let current_time_point = self
263            .get_current_time_point("checking if epoch has changed")
264            .await?;
265
266        if current_time_point.epoch > epoch {
267            Ok(EpochStatus::NewEpoch(current_time_point.epoch))
268        } else {
269            Ok(EpochStatus::Unchanged(current_time_point))
270        }
271    }
272
273    async fn transition_from_unregistered_to_unregistered(
274        &self,
275        new_epoch: Epoch,
276    ) -> Result<SignerState, RuntimeError> {
277        self.update_era_checker(new_epoch, "unregistered → unregistered")
278            .await?;
279
280        Ok(SignerState::Unregistered { epoch: new_epoch })
281    }
282
283    async fn transition_from_init_to_unregistered(&self) -> Result<SignerState, RuntimeError> {
284        let current_epoch = self
285            .get_current_time_point("init → unregistered")
286            .await?
287            .epoch;
288        self.update_era_checker(current_epoch, "init → unregistered")
289            .await?;
290
291        Ok(SignerState::Unregistered {
292            epoch: current_epoch,
293        })
294    }
295
296    /// Launch the transition process from the `Unregistered` to `ReadyToSign` or `RegisteredNotAbleToSign` state.
297    async fn transition_from_unregistered_to_one_of_registered_states(
298        &self,
299        epoch_settings: SignerEpochSettings,
300    ) -> Result<SignerState, RuntimeError> {
301        self.metrics_service
302            .get_signer_registration_total_since_startup_counter()
303            .increment();
304
305        let time_point = self
306            .get_current_time_point("unregistered → registered")
307            .await?;
308        let epoch = time_point.epoch;
309        self.runner.update_stake_distribution(epoch)
310            .await
311            .map_err(|e| RuntimeError::KeepState {
312                message: format!("Could not update stake distribution in 'unregistered → registered' phase for epoch {:?}.", epoch),
313                nested_error: Some(e),
314            })?;
315
316        self.runner
317            .inform_epoch_settings(epoch_settings)
318            .await
319            .map_err(|e| RuntimeError::KeepState {
320                message: format!(
321                    "Could not register epoch information in 'unregistered → registered' phase for epoch {:?}.",
322                    epoch
323                ),
324                nested_error: Some(e),
325            })?;
326
327        fn handle_registration_result(
328            register_result: Result<(), Error>,
329            epoch: Epoch,
330        ) -> Result<Option<SignerState>, RuntimeError> {
331            if let Err(e) = register_result {
332                if let Some(AggregatorClientError::RegistrationRoundNotYetOpened(_)) =
333                    e.downcast_ref::<AggregatorClientError>()
334                {
335                    Ok(Some(SignerState::Unregistered { epoch }))
336                } else if e.downcast_ref::<ProtocolInitializerError>().is_some() {
337                    Err(RuntimeError::Critical { message: format!("Could not register to aggregator in 'unregistered → registered' phase for epoch {:?}.", epoch), nested_error: Some(e) })
338                } else {
339                    Err(RuntimeError::KeepState { message: format!("Could not register to aggregator in 'unregistered → registered' phase for epoch {:?}.", epoch), nested_error: Some(e) })
340                }
341            } else {
342                Ok(None)
343            }
344        }
345
346        let register_result = self.runner.register_signer_to_aggregator().await;
347        let next_state_found = handle_registration_result(register_result, epoch)?;
348
349        self.metrics_service
350            .get_signer_registration_success_since_startup_counter()
351            .increment();
352
353        if let Some(state) = next_state_found {
354            return Ok(state);
355        }
356
357        self.metrics_service
358            .get_signer_registration_success_last_epoch_gauge()
359            .record(epoch);
360
361        self.runner
362            .upkeep(epoch)
363            .await
364            .map_err(|e| RuntimeError::KeepState {
365                message: "Failed to upkeep signer in 'unregistered → registered' phase".to_string(),
366                nested_error: Some(e),
367            })?;
368
369        match self
370            .runner
371            .can_sign_current_epoch()
372            .await
373            .map_err(|e| RuntimeError::KeepState {
374                message: "Failed to check if signer can sign in the current epoch in 'unregistered → ?' phase".to_string(),
375                nested_error: Some(e),
376            })? {
377            true => Ok(SignerState::ReadyToSign { epoch }),
378            false => Ok(SignerState::RegisteredNotAbleToSign { epoch }),
379        }
380    }
381
382    async fn transition_from_registered_not_able_to_sign_to_unregistered(
383        &self,
384        epoch: Epoch,
385    ) -> Result<SignerState, RuntimeError> {
386        self.update_era_checker(epoch, "registered not able to sign → unregistered")
387            .await?;
388
389        Ok(SignerState::Unregistered { epoch })
390    }
391
392    async fn transition_from_ready_to_sign_to_unregistered(
393        &self,
394        epoch: Epoch,
395    ) -> Result<SignerState, RuntimeError> {
396        self.update_era_checker(epoch, "ready to sign → unregistered")
397            .await?;
398
399        Ok(SignerState::Unregistered { epoch })
400    }
401
402    /// Launch the transition process from the `ReadyToSign` to the `ReadyToSign` state.
403    async fn transition_from_ready_to_sign_to_ready_to_sign(
404        &self,
405        current_epoch: Epoch,
406        beacon_to_sign: BeaconToSign,
407    ) -> Result<SignerState, RuntimeError> {
408        let (retrieval_epoch, next_retrieval_epoch) = (
409            current_epoch.offset_to_signer_retrieval_epoch()?,
410            current_epoch.offset_to_next_signer_retrieval_epoch(),
411        );
412
413        debug!(
414            self.logger, ">> transition_from_ready_to_sign_to_ready_to_sign";
415            "current_epoch" => ?current_epoch,
416            "retrieval_epoch" => ?retrieval_epoch,
417            "next_retrieval_epoch" => ?next_retrieval_epoch,
418        );
419
420        self.metrics_service
421            .get_signature_registration_total_since_startup_counter()
422            .increment();
423
424        let message = self
425            .runner
426            .compute_message(&beacon_to_sign.signed_entity_type)
427            .await
428            .map_err(|e| RuntimeError::KeepState {
429                message: format!("Could not compute message during 'ready to sign → ready to sign' phase (current epoch {current_epoch:?})"),
430                nested_error: Some(e)
431            })?;
432
433        self.runner.compute_publish_single_signature(&beacon_to_sign, &message)
434            .await
435            .map_err(|e| RuntimeError::KeepState {
436                message: format!("Could not compute and publish single signature during 'ready to sign → ready to sign' phase (current epoch {current_epoch:?})"),
437                nested_error: Some(e)
438            })?;
439
440        self.metrics_service
441            .get_signature_registration_success_since_startup_counter()
442            .increment();
443        self.metrics_service
444            .get_signature_registration_success_last_epoch_gauge()
445            .record(current_epoch);
446
447        Ok(SignerState::ReadyToSign {
448            epoch: current_epoch,
449        })
450    }
451
452    async fn get_current_time_point(&self, context: &str) -> Result<TimePoint, RuntimeError> {
453        let current_time_point =
454            self.runner
455                .get_current_time_point()
456                .await
457                .map_err(|e| RuntimeError::KeepState {
458                    message: format!(
459                        "Could not retrieve current time point in context '{context}'."
460                    ),
461                    nested_error: Some(e),
462                })?;
463
464        Ok(current_time_point)
465    }
466
467    async fn update_era_checker(&self, epoch: Epoch, context: &str) -> Result<(), RuntimeError> {
468        self.runner
469            .update_era_checker(epoch)
470            .await
471            .map_err(|e| RuntimeError::Critical {
472                message: format!(
473                    "Could not update Era checker with context '{context}' for epoch {epoch:?}"
474                ),
475                nested_error: Some(e),
476            })
477    }
478}
479
480#[cfg(test)]
481mod tests {
482    use anyhow::anyhow;
483    use chrono::DateTime;
484    use mockall::predicate;
485
486    use mithril_common::entities::{ChainPoint, Epoch, ProtocolMessage, SignedEntityType};
487    use mithril_common::test_utils::fake_data;
488
489    use crate::runtime::runner::MockSignerRunner;
490    use crate::services::AggregatorClientError;
491    use crate::test_tools::TestLogger;
492
493    use super::*;
494
495    fn init_state_machine(init_state: SignerState, runner: MockSignerRunner) -> StateMachine {
496        let logger = TestLogger::stdout();
497        let metrics_service = Arc::new(MetricsService::new(logger.clone()).unwrap());
498        StateMachine {
499            state: init_state.into(),
500            runner: Box::new(runner),
501            interval: Duration::from_millis(100),
502            metrics_service,
503            logger,
504        }
505    }
506
507    #[tokio::test]
508    async fn unregistered_epoch_settings_not_found() {
509        let mut runner = MockSignerRunner::new();
510        runner
511            .expect_get_epoch_settings()
512            .once()
513            .returning(|| Ok(None));
514        runner
515            .expect_get_current_time_point()
516            .once()
517            .returning(|| Ok(TimePoint::dummy()));
518        let state_machine = init_state_machine(
519            SignerState::Unregistered {
520                epoch: TimePoint::dummy().epoch,
521            },
522            runner,
523        );
524        state_machine
525            .cycle()
526            .await
527            .expect("Cycling the state machine should not fail");
528
529        assert_eq!(
530            SignerState::Unregistered {
531                epoch: TimePoint::dummy().epoch
532            },
533            state_machine.get_state().await
534        );
535    }
536
537    #[tokio::test]
538    async fn unregistered_epoch_settings_behind_known_epoch() {
539        let mut runner = MockSignerRunner::new();
540        let epoch_settings = SignerEpochSettings {
541            epoch: Epoch(3),
542            registration_protocol_parameters: fake_data::protocol_parameters(),
543            current_signers: vec![],
544            next_signers: vec![],
545            cardano_transactions_signing_config: None,
546            next_cardano_transactions_signing_config: None,
547        };
548        let known_epoch = Epoch(4);
549        runner
550            .expect_get_epoch_settings()
551            .once()
552            .returning(move || Ok(Some(epoch_settings.to_owned())));
553        runner.expect_get_current_time_point().once().returning(|| {
554            Ok(TimePoint {
555                epoch: Epoch(4),
556                ..TimePoint::dummy()
557            })
558        });
559        let state_machine =
560            init_state_machine(SignerState::Unregistered { epoch: known_epoch }, runner);
561        state_machine
562            .cycle()
563            .await
564            .expect("Cycling the state machine should not fail");
565
566        assert_eq!(
567            SignerState::Unregistered { epoch: known_epoch },
568            state_machine.get_state().await
569        );
570    }
571
572    #[tokio::test]
573    async fn unregistered_to_registered_not_able_to_sign() {
574        let mut runner = MockSignerRunner::new();
575        runner.expect_upkeep().returning(|_| Ok(())).once();
576        runner
577            .expect_get_epoch_settings()
578            .once()
579            .returning(|| Ok(Some(SignerEpochSettings::dummy())));
580
581        runner
582            .expect_inform_epoch_settings()
583            .with(predicate::eq(SignerEpochSettings::dummy()))
584            .once()
585            .returning(|_| Ok(()));
586
587        runner
588            .expect_get_current_time_point()
589            .times(2)
590            .returning(|| Ok(TimePoint::dummy()));
591        runner
592            .expect_update_stake_distribution()
593            .once()
594            .returning(|_| Ok(()));
595        runner
596            .expect_register_signer_to_aggregator()
597            .once()
598            .returning(|| Ok(()));
599
600        runner
601            .expect_can_sign_current_epoch()
602            .once()
603            .returning(|| Ok(false));
604
605        let state_machine = init_state_machine(
606            SignerState::Unregistered {
607                epoch: TimePoint::dummy().epoch,
608            },
609            runner,
610        );
611
612        state_machine
613            .cycle()
614            .await
615            .expect("Cycling the state machine should not fail");
616
617        if let SignerState::RegisteredNotAbleToSign { epoch: _ } = state_machine.get_state().await {
618        } else {
619            panic!(
620                "state machine did not return a RegisteredNotAbleToSign state but {:?}",
621                state_machine.get_state().await
622            );
623        }
624    }
625
626    #[tokio::test]
627    async fn unregistered_to_ready_to_sign() {
628        let mut runner = MockSignerRunner::new();
629        runner.expect_upkeep().returning(|_| Ok(())).once();
630        runner
631            .expect_get_epoch_settings()
632            .once()
633            .returning(|| Ok(Some(SignerEpochSettings::dummy())));
634
635        runner
636            .expect_inform_epoch_settings()
637            .with(predicate::eq(SignerEpochSettings::dummy()))
638            .once()
639            .returning(|_| Ok(()));
640
641        runner
642            .expect_get_current_time_point()
643            .times(2)
644            .returning(|| Ok(TimePoint::dummy()));
645        runner
646            .expect_update_stake_distribution()
647            .once()
648            .returning(|_| Ok(()));
649        runner
650            .expect_register_signer_to_aggregator()
651            .once()
652            .returning(|| Ok(()));
653
654        runner
655            .expect_can_sign_current_epoch()
656            .once()
657            .returning(|| Ok(true));
658
659        let state_machine = init_state_machine(
660            SignerState::Unregistered {
661                epoch: TimePoint::dummy().epoch,
662            },
663            runner,
664        );
665
666        state_machine
667            .cycle()
668            .await
669            .expect("Cycling the state machine should not fail");
670
671        assert_eq!(
672            SignerState::ReadyToSign {
673                epoch: TimePoint::dummy().epoch,
674            },
675            state_machine.get_state().await
676        );
677
678        let metrics_service = state_machine.metrics_service;
679        let success_since_startup =
680            metrics_service.get_runtime_cycle_success_since_startup_counter();
681        assert_eq!(1, success_since_startup.get());
682    }
683
684    #[tokio::test]
685    async fn unregistered_to_ready_to_sign_counter() {
686        let mut runner = MockSignerRunner::new();
687
688        runner
689            .expect_get_epoch_settings()
690            .once()
691            .returning(|| Ok(Some(SignerEpochSettings::dummy())));
692
693        runner
694            .expect_inform_epoch_settings()
695            .with(predicate::eq(SignerEpochSettings::dummy()))
696            .once()
697            .returning(|_| Ok(()));
698
699        runner
700            .expect_get_current_time_point()
701            .times(2)
702            .returning(|| Ok(TimePoint::dummy()));
703        runner
704            .expect_update_stake_distribution()
705            .once()
706            .returning(|_| Ok(()));
707        runner
708            .expect_register_signer_to_aggregator()
709            .once()
710            .returning(|| {
711                Err(AggregatorClientError::RegistrationRoundNotYetOpened(
712                    anyhow!("Not yet opened"),
713                ))?
714            });
715
716        runner.expect_upkeep().never();
717        runner.expect_can_sign_current_epoch().never();
718
719        let state_machine = init_state_machine(
720            SignerState::Unregistered {
721                epoch: TimePoint::dummy().epoch,
722            },
723            runner,
724        );
725
726        state_machine
727            .cycle()
728            .await
729            .expect("Cycling the state machine should not fail");
730
731        assert_eq!(
732            SignerState::Unregistered {
733                epoch: TimePoint::dummy().epoch,
734            },
735            state_machine.get_state().await
736        );
737
738        let metrics_service = state_machine.metrics_service;
739        assert_eq!(
740            1,
741            metrics_service
742                .get_signer_registration_success_since_startup_counter()
743                .get()
744        );
745
746        assert_eq!(
747            0 as f64,
748            metrics_service
749                .get_signer_registration_success_last_epoch_gauge()
750                .get()
751        );
752
753        assert_eq!(
754            1,
755            metrics_service
756                .get_runtime_cycle_total_since_startup_counter()
757                .get()
758        );
759    }
760
761    #[tokio::test]
762    async fn registered_not_able_to_sign_to_unregistered() {
763        let mut runner = MockSignerRunner::new();
764        runner
765            .expect_get_current_time_point()
766            .once()
767            .returning(|| Ok(TimePoint::new(10, 100, ChainPoint::dummy())));
768        runner
769            .expect_update_era_checker()
770            .once()
771            .returning(|_e: Epoch| Ok(()));
772
773        let state_machine = init_state_machine(
774            SignerState::RegisteredNotAbleToSign { epoch: Epoch(0) },
775            runner,
776        );
777
778        state_machine
779            .cycle()
780            .await
781            .expect("Cycling the state machine should not fail");
782        assert_eq!(
783            SignerState::Unregistered { epoch: Epoch(10) },
784            state_machine.get_state().await
785        );
786    }
787
788    #[tokio::test]
789    async fn registered_not_able_to_sign_to_registered_not_able_to_sign() {
790        let mut runner = MockSignerRunner::new();
791        runner
792            .expect_get_current_time_point()
793            .once()
794            .returning(|| Ok(TimePoint::new(10, 100, ChainPoint::dummy())));
795
796        let state_machine = init_state_machine(
797            SignerState::RegisteredNotAbleToSign { epoch: Epoch(10) },
798            runner,
799        );
800
801        state_machine
802            .cycle()
803            .await
804            .expect("Cycling the state machine should not fail");
805        assert_eq!(
806            SignerState::RegisteredNotAbleToSign { epoch: Epoch(10) },
807            state_machine.get_state().await
808        );
809    }
810
811    #[tokio::test]
812    async fn ready_to_sign_to_unregistered() {
813        let mut runner = MockSignerRunner::new();
814        runner
815            .expect_get_current_time_point()
816            .once()
817            .returning(|| Ok(TimePoint::new(10, 100, ChainPoint::dummy())));
818        runner
819            .expect_update_era_checker()
820            .once()
821            .returning(|_e: Epoch| Ok(()));
822
823        let state_machine =
824            init_state_machine(SignerState::ReadyToSign { epoch: Epoch(0) }, runner);
825
826        state_machine
827            .cycle()
828            .await
829            .expect("Cycling the state machine should not fail");
830        assert_eq!(
831            SignerState::Unregistered { epoch: Epoch(10) },
832            state_machine.get_state().await
833        );
834    }
835
836    #[tokio::test]
837    async fn ready_to_sign_to_ready_to_sign_when_there_is_a_beacon_to_sign() {
838        let time_point = TimePoint::dummy();
839        let beacon_to_sign = BeaconToSign {
840            epoch: time_point.epoch,
841            signed_entity_type: SignedEntityType::MithrilStakeDistribution(time_point.epoch),
842            initiated_at: DateTime::default(),
843        };
844        let beacon_to_sign_clone = beacon_to_sign.clone();
845        let current_epoch = time_point.epoch;
846
847        let mut runner = MockSignerRunner::new();
848        runner
849            .expect_get_current_time_point()
850            .once()
851            .returning(move || Ok(time_point.to_owned()));
852        runner
853            .expect_get_beacon_to_sign()
854            .once()
855            .returning(move |_| Ok(Some(beacon_to_sign_clone.clone())));
856        runner
857            .expect_compute_message()
858            .once()
859            .returning(|_| Ok(ProtocolMessage::new()));
860        runner
861            .expect_compute_publish_single_signature()
862            .once()
863            .returning(|_, _| Ok(()));
864
865        let state_machine = init_state_machine(
866            SignerState::ReadyToSign {
867                epoch: current_epoch,
868            },
869            runner,
870        );
871        state_machine
872            .cycle()
873            .await
874            .expect("Cycling the state machine should not fail");
875
876        assert_eq!(
877            SignerState::ReadyToSign {
878                epoch: current_epoch
879            },
880            state_machine.get_state().await,
881            "state machine did not return a ReadyToSign state but {:?}",
882            state_machine.get_state().await
883        );
884    }
885
886    #[tokio::test]
887    async fn ready_to_sign_to_ready_to_sign_when_there_no_beacon_to_sign() {
888        let time_point = TimePoint::dummy();
889        let current_epoch = time_point.epoch;
890
891        let mut runner = MockSignerRunner::new();
892        runner
893            .expect_get_current_time_point()
894            .once()
895            .returning(move || Ok(time_point.to_owned()));
896        runner
897            .expect_get_beacon_to_sign()
898            .once()
899            .returning(move |_| Ok(None));
900
901        let state_machine = init_state_machine(
902            SignerState::ReadyToSign {
903                epoch: current_epoch,
904            },
905            runner,
906        );
907        state_machine
908            .cycle()
909            .await
910            .expect("Cycling the state machine should not fail");
911
912        assert_eq!(
913            SignerState::ReadyToSign {
914                epoch: current_epoch
915            },
916            state_machine.get_state().await,
917            "state machine did not return a ReadyToSign state but {:?}",
918            state_machine.get_state().await
919        );
920    }
921}