mithril_signer/runtime/
state_machine.rs

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