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#[async_trait]
21pub trait Runner: Send + Sync {
22 async fn get_epoch_settings(&self) -> StdResult<Option<SignerEpochSettings>>;
24
25 async fn get_beacon_to_sign(&self, time_point: TimePoint) -> StdResult<Option<BeaconToSign>>;
27
28 async fn get_current_time_point(&self) -> StdResult<TimePoint>;
30
31 async fn register_signer_to_aggregator(&self) -> StdResult<()>;
33
34 async fn update_stake_distribution(&self, epoch: Epoch) -> StdResult<()>;
36
37 async fn can_sign_current_epoch(&self) -> StdResult<bool>;
39
40 async fn inform_epoch_settings(&self, epoch_settings: SignerEpochSettings) -> StdResult<()>;
42
43 async fn compute_message(
45 &self,
46 signed_entity_type: &SignedEntityType,
47 ) -> StdResult<ProtocolMessage>;
48
49 async fn compute_publish_single_signature(
51 &self,
52 beacon_to_sign: &BeaconToSign,
53 message: &ProtocolMessage,
54 ) -> StdResult<()>;
55
56 async fn update_era_checker(&self, epoch: Epoch) -> StdResult<()>;
58
59 async fn upkeep(&self, current_epoch: Epoch) -> StdResult<()>;
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Error)]
65pub enum RunnerError {
66 #[error("No value returned by the subsystem for `{0}`.")]
68 NoValueError(String),
69 #[error("No stake associated with myself.")]
71 NoStakeForSelf(),
72 #[error("No stake associated with this signer, party_id: {0}.")]
74 NoStakeForSigner(PartyId),
75 #[error("File parse failed: {0}.")]
77 FileParse(String),
78}
79
80pub struct SignerRunner {
82 config: Configuration,
83 services: SignerDependencyContainer,
84 logger: Logger,
85}
86
87impl SignerRunner {
88 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 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}