mithril_signer/runtime/
runner.rs

1use anyhow::Context;
2use async_trait::async_trait;
3use slog::{Logger, debug, warn};
4use thiserror::Error;
5use tokio::sync::RwLockReadGuard;
6
7use mithril_common::StdResult;
8use mithril_common::crypto_helper::{KesPeriod, OpCert, ProtocolOpCert, SerDeShelleyFileFormat};
9use mithril_common::entities::{
10    Epoch, PartyId, ProtocolMessage, SignedEntityType, Signer, TimePoint,
11};
12use mithril_common::logging::LoggerExtensions;
13
14use crate::Configuration;
15use crate::dependency_injection::SignerDependencyContainer;
16use crate::entities::{BeaconToSign, SignerEpochSettings};
17use crate::services::{EpochService, MithrilProtocolInitializerBuilder};
18
19/// This trait is mainly intended for mocking.
20#[async_trait]
21pub trait Runner: Send + Sync {
22    /// Fetch the current epoch settings if any.
23    async fn get_epoch_settings(&self) -> StdResult<Option<SignerEpochSettings>>;
24
25    /// Fetch the beacon to sign if any.
26    async fn get_beacon_to_sign(&self, time_point: TimePoint) -> StdResult<Option<BeaconToSign>>;
27
28    /// Fetch the current time point from the Cardano node.
29    async fn get_current_time_point(&self) -> StdResult<TimePoint>;
30
31    /// Register the signer verification key to the aggregator.
32    async fn register_signer_to_aggregator(&self) -> StdResult<()>;
33
34    /// Read the stake distribution and store it.
35    async fn update_stake_distribution(&self, epoch: Epoch) -> StdResult<()>;
36
37    /// Check if the signer can sign the current epoch.
38    async fn can_sign_current_epoch(&self) -> StdResult<bool>;
39
40    /// Register epoch information
41    async fn inform_epoch_settings(&self, epoch_settings: SignerEpochSettings) -> StdResult<()>;
42
43    /// Create the message to be signed with the single signature.
44    async fn compute_message(
45        &self,
46        signed_entity_type: &SignedEntityType,
47    ) -> StdResult<ProtocolMessage>;
48
49    /// Create the single signature.
50    async fn compute_publish_single_signature(
51        &self,
52        beacon_to_sign: &BeaconToSign,
53        message: &ProtocolMessage,
54    ) -> StdResult<()>;
55
56    /// Read the current era and update the EraChecker.
57    async fn update_era_checker(&self, epoch: Epoch) -> StdResult<()>;
58
59    /// Perform the upkeep tasks.
60    async fn upkeep(&self, current_epoch: Epoch) -> StdResult<()>;
61}
62
63/// This type represents the errors thrown from the Runner.
64#[derive(Debug, Clone, PartialEq, Eq, Error)]
65pub enum RunnerError {
66    /// Value was expected from a subsystem but None was returned.
67    #[error("No value returned by the subsystem for `{0}`.")]
68    NoValueError(String),
69    /// Could not associate my node with a stake.
70    #[error("No stake associated with myself.")]
71    NoStakeForSelf(),
72    /// Could not find the stake for one of the signers.
73    #[error("No stake associated with this signer, party_id: {0}.")]
74    NoStakeForSigner(PartyId),
75    /// Parse file error
76    #[error("File parse failed: {0}.")]
77    FileParse(String),
78}
79
80/// Controller methods for the Signer's state machine.
81pub struct SignerRunner {
82    config: Configuration,
83    services: SignerDependencyContainer,
84    logger: Logger,
85}
86
87impl SignerRunner {
88    /// Create a new Runner instance.
89    pub fn new(config: Configuration, services: SignerDependencyContainer, logger: Logger) -> Self {
90        Self {
91            services,
92            config,
93            logger: logger.new_with_component_name::<Self>(),
94        }
95    }
96
97    async fn epoch_service_read(&self) -> RwLockReadGuard<'_, dyn EpochService> {
98        self.services.epoch_service.read().await
99    }
100}
101
102#[cfg_attr(test, mockall::automock)]
103#[async_trait]
104impl Runner for SignerRunner {
105    async fn get_epoch_settings(&self) -> StdResult<Option<SignerEpochSettings>> {
106        debug!(self.logger, ">> get_epoch_settings");
107
108        self.services
109            .certificate_handler
110            .retrieve_epoch_settings()
111            .await
112            .map_err(|e| e.into())
113    }
114
115    async fn get_beacon_to_sign(&self, time_point: TimePoint) -> StdResult<Option<BeaconToSign>> {
116        debug!(
117            self.logger,
118            ">> get_beacon_to_sign(time_point: {time_point})"
119        );
120
121        self.services.certifier.get_beacon_to_sign(time_point).await
122    }
123
124    async fn get_current_time_point(&self) -> StdResult<TimePoint> {
125        debug!(self.logger, ">> get_current_time_point");
126
127        self.services
128            .ticker_service
129            .get_current_time_point()
130            .await
131            .with_context(|| "Runner can not get current time point")
132    }
133
134    async fn register_signer_to_aggregator(&self) -> StdResult<()> {
135        debug!(self.logger, ">> register_signer_to_aggregator");
136
137        let (epoch, protocol_parameters) = {
138            let epoch_service = self.services.epoch_service.read().await;
139            let epoch = epoch_service.epoch_of_current_data()?;
140            let protocol_parameters = epoch_service.registration_protocol_parameters()?;
141
142            (epoch, protocol_parameters.clone())
143        };
144
145        let epoch_offset_to_recording_epoch = epoch.offset_to_recording_epoch();
146        let stake_distribution = self
147            .services
148            .stake_store
149            .get_stakes(epoch_offset_to_recording_epoch)
150            .await?
151            .ok_or_else(|| {
152                RunnerError::NoValueError(format!(
153                    "stakes at epoch {epoch_offset_to_recording_epoch}"
154                ))
155            })?;
156        let stake = stake_distribution
157            .get(&self.services.single_signer.get_party_id())
158            .ok_or_else(RunnerError::NoStakeForSelf)?;
159        let (operational_certificate, protocol_operational_certificate) = match &self
160            .config
161            .operational_certificate_path
162        {
163            Some(operational_certificate_path) => {
164                let opcert: OpCert = OpCert::from_file(operational_certificate_path)
165                    .map_err(|_| RunnerError::FileParse("operational_certificate_path".to_string()))
166                    .with_context(
167                        || "register_signer_to_aggregator can not decode OpCert from file",
168                    )?;
169                (Some(opcert.clone()), Some(ProtocolOpCert::new(opcert)))
170            }
171            _ => (None, None),
172        };
173
174        let kes_period = match operational_certificate {
175            Some(operational_certificate) => Some(
176                self.services
177                    .chain_observer
178                    .get_current_kes_period(&operational_certificate)
179                    .await?
180                    .unwrap_or_default()
181                    - operational_certificate.start_kes_period as KesPeriod,
182            ),
183            None => None,
184        };
185        let protocol_initializer = MithrilProtocolInitializerBuilder::build(
186            stake,
187            &protocol_parameters,
188            self.services.kes_signer.clone(),
189            kes_period,
190        )?;
191        let signer = Signer::new(
192            self.services.single_signer.get_party_id(),
193            protocol_initializer.verification_key().into(),
194            protocol_initializer.verification_key_signature(),
195            protocol_operational_certificate,
196            kes_period,
197        );
198        self.services
199            .certificate_handler
200            .register_signer(epoch_offset_to_recording_epoch, &signer)
201            .await?;
202        self.services
203            .protocol_initializer_store
204            .save_protocol_initializer(epoch_offset_to_recording_epoch, protocol_initializer)
205            .await?;
206
207        Ok(())
208    }
209
210    async fn update_stake_distribution(&self, epoch: Epoch) -> StdResult<()> {
211        debug!(self.logger, ">> update_stake_distribution(epoch: {epoch})");
212
213        let exists_stake_distribution = !self
214            .services
215            .stake_store
216            .get_stakes(epoch.offset_to_recording_epoch())
217            .await?
218            .unwrap_or_default()
219            .is_empty();
220        if exists_stake_distribution {
221            return Ok(());
222        }
223
224        let stake_distribution = self
225            .services
226            .chain_observer
227            .get_current_stake_distribution()
228            .await?
229            .ok_or_else(|| RunnerError::NoValueError("current_stake_distribution".to_string()))?;
230        self.services
231            .stake_store
232            .save_stakes(epoch.offset_to_recording_epoch(), stake_distribution)
233            .await?;
234
235        Ok(())
236    }
237
238    async fn can_sign_current_epoch(&self) -> StdResult<bool> {
239        let epoch_service = self.epoch_service_read().await;
240        epoch_service.can_signer_sign_current_epoch(self.services.single_signer.get_party_id())
241    }
242
243    async fn inform_epoch_settings(&self, epoch_settings: SignerEpochSettings) -> StdResult<()> {
244        debug!(
245            self.logger,
246            ">> inform_epoch_settings(epoch:{})", epoch_settings.epoch
247        );
248        let aggregator_features = self
249            .services
250            .certificate_handler
251            .retrieve_aggregator_features()
252            .await?;
253
254        self.services
255            .epoch_service
256            .write()
257            .await
258            .inform_epoch_settings(
259                epoch_settings,
260                aggregator_features.capabilities.signed_entity_types,
261            )
262            .await
263    }
264
265    async fn compute_message(
266        &self,
267        signed_entity_type: &SignedEntityType,
268    ) -> StdResult<ProtocolMessage> {
269        debug!(self.logger, ">> compute_message({signed_entity_type:?})");
270
271        let protocol_message = self
272            .services
273            .signable_builder_service
274            .compute_protocol_message(signed_entity_type.to_owned())
275            .await
276            .with_context(|| format!("Runner can not compute protocol message for signed entity type: '{signed_entity_type}'"))?;
277
278        Ok(protocol_message)
279    }
280
281    async fn compute_publish_single_signature(
282        &self,
283        beacon_to_sign: &BeaconToSign,
284        message: &ProtocolMessage,
285    ) -> StdResult<()> {
286        debug!(self.logger, ">> compute_publish_single_signature"; "beacon_to_sign" => ?beacon_to_sign);
287        self.services
288            .certifier
289            .compute_publish_single_signature(beacon_to_sign, message)
290            .await
291    }
292
293    async fn update_era_checker(&self, epoch: Epoch) -> StdResult<()> {
294        debug!(self.logger, ">> update_era_checker(epoch:{epoch})");
295
296        let era_token = self
297            .services
298            .era_reader
299            .read_era_epoch_token(epoch)
300            .await
301            .map_err(Box::new)?;
302        let current_era = era_token.get_current_supported_era()?;
303        self.services
304            .era_checker
305            .change_era(current_era, era_token.get_current_epoch());
306        debug!(
307            self.logger,
308            "Current Era is {} (Epoch {}).",
309            current_era,
310            era_token.get_current_epoch()
311        );
312
313        if era_token.get_next_supported_era().is_err() {
314            let era_name = &era_token.get_next_era_marker().unwrap().name;
315            warn!(
316                self.logger,
317                "Upcoming Era '{era_name}' is not supported by this version of the software. Please update!"
318            );
319        }
320
321        Ok(())
322    }
323
324    async fn upkeep(&self, current_epoch: Epoch) -> StdResult<()> {
325        debug!(self.logger, ">> upkeep(current_epoch:{current_epoch})");
326        self.services.upkeep_service.run(current_epoch).await?;
327        Ok(())
328    }
329}
330
331#[cfg(test)]
332mod tests {
333    use mockall::mock;
334    use mockall::predicate::eq;
335    use std::collections::BTreeSet;
336    use std::{path::Path, sync::Arc};
337    use tokio::sync::RwLock;
338
339    use mithril_cardano_node_chain::test::double::{DumbBlockScanner, FakeChainObserver};
340    use mithril_cardano_node_internal_database::{
341        signable_builder::{
342            CardanoDatabaseSignableBuilder, CardanoImmutableFilesFullSignableBuilder,
343        },
344        test::double::{DumbImmutableDigester, DumbImmutableFileObserver},
345    };
346    use mithril_common::{
347        api_version::APIVersionProvider,
348        crypto_helper::{MKMap, MKMapNode, MKTreeNode, MKTreeStoreInMemory, MKTreeStorer},
349        entities::{BlockNumber, BlockRange, Epoch, SignedEntityTypeDiscriminants},
350        messages::{AggregatorCapabilities, AggregatorFeaturesMessage},
351        signable_builder::{
352            BlockRangeRootRetriever, CardanoStakeDistributionSignableBuilder,
353            CardanoTransactionsSignableBuilder, MithrilSignableBuilderService,
354            MithrilStakeDistributionSignableBuilder, SignableBuilderServiceDependencies,
355        },
356        test::{
357            builder::MithrilFixtureBuilder,
358            double::{Dummy, fake_data},
359        },
360    };
361    use mithril_era::{EraChecker, EraReader, adapters::EraReaderBootstrapAdapter};
362    use mithril_signed_entity_lock::SignedEntityTypeLock;
363    use mithril_signed_entity_preloader::{
364        CardanoTransactionsPreloader, CardanoTransactionsPreloaderActivation,
365    };
366    use mithril_ticker::{MithrilTickerService, TickerService};
367
368    use crate::database::repository::{
369        ProtocolInitializerRepository, SignedBeaconRepository, StakePoolStore,
370    };
371    use crate::database::test_helper::main_db_connection;
372    use crate::metrics::MetricsService;
373    use crate::services::{
374        CardanoTransactionsImporter, DumbAggregatorClient, MithrilEpochService,
375        MithrilSingleSigner, MockTransactionStore, MockUpkeepService, SignerCertifierService,
376        SignerSignableSeedBuilder, SignerSignedEntityConfigProvider,
377    };
378    use crate::test_tools::TestLogger;
379
380    use super::*;
381
382    const DIGESTER_RESULT: &str = "a digest";
383
384    mock! {
385        pub FakeTimePointProvider { }
386
387        #[async_trait]
388        impl TickerService for FakeTimePointProvider {
389            async fn get_current_time_point(&self) -> StdResult<TimePoint>;
390        }
391    }
392
393    mock! {
394        pub BlockRangeRootRetrieverImpl<S: MKTreeStorer> { }
395
396        #[async_trait]
397        impl<S: MKTreeStorer> BlockRangeRootRetriever<S> for BlockRangeRootRetrieverImpl<S> {
398            async fn retrieve_block_range_roots<'a>(
399                &'a self,
400                up_to_beacon: BlockNumber,
401            ) -> StdResult<Box<dyn Iterator<Item = (BlockRange, MKTreeNode)> + 'a>>;
402
403            async fn compute_merkle_map_from_block_range_roots(
404                &self,
405                up_to_beacon: BlockNumber,
406            ) -> StdResult<MKMap<BlockRange, MKMapNode<BlockRange,S>, S>>;
407        }
408    }
409
410    async fn init_services() -> SignerDependencyContainer {
411        let logger = TestLogger::stdout();
412        let sqlite_connection = Arc::new(main_db_connection().unwrap());
413        let stake_distribution_signers = fake_data::signers_with_stakes(2);
414        let party_id = stake_distribution_signers[1].party_id.clone();
415        let fake_observer = FakeChainObserver::default();
416        fake_observer.set_signers(stake_distribution_signers).await;
417        let chain_observer = Arc::new(fake_observer);
418        let ticker_service = Arc::new(MithrilTickerService::new(
419            chain_observer.clone(),
420            Arc::new(DumbImmutableFileObserver::default()),
421        ));
422        let era_reader = Arc::new(EraReader::new(Arc::new(EraReaderBootstrapAdapter)));
423        let era_epoch_token = era_reader
424            .read_era_epoch_token(ticker_service.get_current_epoch().await.unwrap())
425            .await
426            .unwrap();
427        let era_checker = Arc::new(EraChecker::new(
428            era_epoch_token.get_current_supported_era().unwrap(),
429            era_epoch_token.get_current_epoch(),
430        ));
431
432        let api_version_provider = Arc::new(APIVersionProvider::new(era_checker.clone()));
433        let digester = Arc::new(DumbImmutableDigester::default().with_digest(DIGESTER_RESULT));
434        let cardano_immutable_signable_builder =
435            Arc::new(CardanoImmutableFilesFullSignableBuilder::new(
436                digester.clone(),
437                Path::new(""),
438                logger.clone(),
439            ));
440        let mithril_stake_distribution_signable_builder =
441            Arc::new(MithrilStakeDistributionSignableBuilder::default());
442        let transaction_parser = Arc::new(DumbBlockScanner::new());
443        let transaction_store = Arc::new(MockTransactionStore::new());
444        let transactions_importer = Arc::new(CardanoTransactionsImporter::new(
445            transaction_parser.clone(),
446            transaction_store.clone(),
447            logger.clone(),
448        ));
449        let block_range_root_retriever =
450            Arc::new(MockBlockRangeRootRetrieverImpl::<MKTreeStoreInMemory>::new());
451        let cardano_transactions_builder = Arc::new(CardanoTransactionsSignableBuilder::new(
452            transactions_importer.clone(),
453            block_range_root_retriever,
454        ));
455        let stake_store = Arc::new(StakePoolStore::new(sqlite_connection.clone(), None));
456        let cardano_stake_distribution_builder = Arc::new(
457            CardanoStakeDistributionSignableBuilder::new(stake_store.clone()),
458        );
459        let cardano_database_signable_builder = Arc::new(CardanoDatabaseSignableBuilder::new(
460            digester.clone(),
461            Path::new(""),
462            logger.clone(),
463        ));
464        let protocol_initializer_store = Arc::new(ProtocolInitializerRepository::new(
465            sqlite_connection.clone(),
466            None,
467        ));
468        let epoch_service = Arc::new(RwLock::new(MithrilEpochService::new(
469            stake_store.clone(),
470            protocol_initializer_store.clone(),
471            logger.clone(),
472        )));
473        let single_signer = Arc::new(MithrilSingleSigner::new(
474            party_id,
475            epoch_service.clone(),
476            logger.clone(),
477        ));
478        let signable_seed_builder_service = Arc::new(SignerSignableSeedBuilder::new(
479            epoch_service.clone(),
480            protocol_initializer_store.clone(),
481        ));
482        let signable_builders_dependencies = SignableBuilderServiceDependencies::new(
483            mithril_stake_distribution_signable_builder,
484            cardano_immutable_signable_builder,
485            cardano_transactions_builder,
486            cardano_stake_distribution_builder,
487            cardano_database_signable_builder,
488        );
489        let signable_builder_service = Arc::new(MithrilSignableBuilderService::new(
490            signable_seed_builder_service,
491            signable_builders_dependencies,
492            logger.clone(),
493        ));
494        let metrics_service = Arc::new(MetricsService::new(logger.clone()).unwrap());
495        let signed_entity_type_lock = Arc::new(SignedEntityTypeLock::default());
496        let security_parameter = BlockNumber(0);
497        let cardano_transactions_preloader = Arc::new(CardanoTransactionsPreloader::new(
498            signed_entity_type_lock.clone(),
499            transactions_importer.clone(),
500            security_parameter,
501            chain_observer.clone(),
502            logger.clone(),
503            Arc::new(CardanoTransactionsPreloaderActivation::new(true)),
504        ));
505        let upkeep_service = Arc::new(MockUpkeepService::new());
506        let aggregator_client = Arc::new(DumbAggregatorClient::default());
507        let certifier = Arc::new(SignerCertifierService::new(
508            Arc::new(SignedBeaconRepository::new(sqlite_connection.clone(), None)),
509            Arc::new(SignerSignedEntityConfigProvider::new(epoch_service.clone())),
510            signed_entity_type_lock.clone(),
511            single_signer.clone(),
512            aggregator_client.clone(),
513            logger.clone(),
514        ));
515        let kes_signer = None;
516
517        SignerDependencyContainer {
518            stake_store,
519            certificate_handler: aggregator_client,
520            chain_observer,
521            digester,
522            single_signer,
523            ticker_service,
524            protocol_initializer_store,
525            era_checker,
526            era_reader,
527            api_version_provider,
528            signable_builder_service,
529            metrics_service,
530            signed_entity_type_lock,
531            cardano_transactions_preloader,
532            upkeep_service,
533            epoch_service,
534            certifier,
535            kes_signer,
536        }
537    }
538
539    async fn init_runner(
540        maybe_services: Option<SignerDependencyContainer>,
541        maybe_config: Option<Configuration>,
542    ) -> SignerRunner {
543        SignerRunner::new(
544            maybe_config.unwrap_or(Configuration::new_sample("1")),
545            maybe_services.unwrap_or(init_services().await),
546            TestLogger::stdout(),
547        )
548    }
549
550    #[tokio::test]
551    async fn test_get_current_time_point() {
552        let mut services = init_services().await;
553        let expected = TimePoint::dummy();
554        let mut ticker_service = MockFakeTimePointProvider::new();
555        ticker_service
556            .expect_get_current_time_point()
557            .once()
558            .returning(move || Ok(TimePoint::dummy()));
559        services.ticker_service = Arc::new(ticker_service);
560        let runner = init_runner(Some(services), None).await;
561
562        assert_eq!(
563            expected,
564            runner
565                .get_current_time_point()
566                .await
567                .expect("Get current time point should not fail.")
568        );
569    }
570
571    #[tokio::test]
572    async fn test_update_stake_distribution() {
573        let services = init_services().await;
574        let stake_store = services.stake_store.clone();
575        let current_epoch = services
576            .chain_observer
577            .get_current_epoch()
578            .await
579            .expect("chain observer should not fail")
580            .expect("the observer should return an epoch");
581        let runner = init_runner(Some(services), None).await;
582        assert!(
583            stake_store
584                .get_stakes(current_epoch)
585                .await
586                .expect("getting stakes from store should not fail")
587                .is_none()
588        );
589
590        runner
591            .update_stake_distribution(current_epoch)
592            .await
593            .expect("update_stake_distribution should not fail.");
594
595        let stake_distribution = stake_store
596            .get_stakes(current_epoch.offset_to_recording_epoch())
597            .await
598            .expect("getting stakes from store should not fail")
599            .expect("there should be stakes for this epoch");
600
601        assert_eq!(2, stake_distribution.len());
602    }
603
604    #[tokio::test]
605    async fn test_register_signer_to_aggregator() {
606        let mut services = init_services().await;
607        let fixture = MithrilFixtureBuilder::default().with_signers(5).build();
608        let certificate_handler = Arc::new(DumbAggregatorClient::default());
609        services.certificate_handler = certificate_handler.clone();
610        let protocol_initializer_store = services.protocol_initializer_store.clone();
611        let current_epoch = services.ticker_service.get_current_epoch().await.unwrap();
612
613        let stakes = services
614            .chain_observer
615            .get_current_stake_distribution()
616            .await
617            .unwrap()
618            .unwrap();
619        services
620            .stake_store
621            .save_stakes(current_epoch.offset_to_recording_epoch(), stakes)
622            .await
623            .unwrap();
624
625        let runner = init_runner(Some(services), None).await;
626        // inform epoch settings
627        let epoch_settings = SignerEpochSettings {
628            epoch: current_epoch,
629            current_signers: fixture.signers(),
630            next_signers: fixture.signers(),
631            ..SignerEpochSettings::dummy().clone()
632        };
633        runner.inform_epoch_settings(epoch_settings).await.unwrap();
634
635        runner
636            .register_signer_to_aggregator()
637            .await
638            .expect("registering a signer to the aggregator should not fail");
639
640        assert!(certificate_handler.get_last_registered_signer().await.is_some());
641        let maybe_protocol_initializer = protocol_initializer_store
642            .get_protocol_initializer(current_epoch.offset_to_recording_epoch())
643            .await
644            .expect("get_protocol_initializer should not fail");
645        assert!(
646            maybe_protocol_initializer.is_some(),
647            "A protocol initializer should have been registered at the 'Recording' epoch"
648        );
649    }
650
651    #[tokio::test]
652    async fn test_update_era_checker() {
653        let services = init_services().await;
654        let ticker_service = services.ticker_service.clone();
655        let era_checker = services.era_checker.clone();
656        let mut time_point = ticker_service.get_current_time_point().await.unwrap();
657
658        assert_eq!(time_point.epoch, era_checker.current_epoch());
659        let runner = init_runner(Some(services), None).await;
660        time_point.epoch += 1;
661        runner.update_era_checker(time_point.epoch).await.unwrap();
662
663        assert_eq!(time_point.epoch, era_checker.current_epoch());
664    }
665
666    #[tokio::test]
667    async fn test_upkeep() {
668        let mut services = init_services().await;
669        let mut upkeep_service_mock = MockUpkeepService::new();
670        upkeep_service_mock
671            .expect_run()
672            .with(eq(Epoch(17)))
673            .returning(|_| Ok(()))
674            .once();
675        services.upkeep_service = Arc::new(upkeep_service_mock);
676
677        let runner = init_runner(Some(services), None).await;
678        runner.upkeep(Epoch(17)).await.expect("upkeep should not fail");
679    }
680
681    #[tokio::test]
682    async fn test_inform_epoch_setting_pass_allowed_discriminant_to_epoch_service() {
683        let mut services = init_services().await;
684        let certificate_handler = Arc::new(DumbAggregatorClient::default());
685        certificate_handler
686            .set_aggregator_features(AggregatorFeaturesMessage {
687                capabilities: AggregatorCapabilities {
688                    signed_entity_types: BTreeSet::from([
689                        SignedEntityTypeDiscriminants::MithrilStakeDistribution,
690                        SignedEntityTypeDiscriminants::CardanoTransactions,
691                    ]),
692                    ..AggregatorFeaturesMessage::dummy().capabilities
693                },
694                ..AggregatorFeaturesMessage::dummy()
695            })
696            .await;
697        services.certificate_handler = certificate_handler;
698        let runner = init_runner(Some(services), None).await;
699
700        let epoch_settings = SignerEpochSettings {
701            epoch: Epoch(1),
702            ..SignerEpochSettings::dummy()
703        };
704        runner.inform_epoch_settings(epoch_settings).await.unwrap();
705
706        let epoch_service = runner.services.epoch_service.read().await;
707        let recorded_allowed_discriminants = epoch_service.allowed_discriminants().unwrap();
708
709        assert_eq!(
710            &BTreeSet::from([
711                SignedEntityTypeDiscriminants::MithrilStakeDistribution,
712                SignedEntityTypeDiscriminants::CardanoTransactions,
713            ]),
714            recorded_allowed_discriminants
715        );
716    }
717}