1use std::collections::BTreeSet;
4use std::sync::Arc;
5
6use async_trait::async_trait;
7
8use mithril_common::{
9 StdResult,
10 entities::{Epoch, SignedEntityTypeDiscriminants},
11 messages::{
12 CardanoDatabaseDigestListItemMessage, CardanoDatabaseDigestListMessage,
13 CardanoDatabaseSnapshotListMessage, CardanoDatabaseSnapshotMessage,
14 CardanoStakeDistributionListMessage, CardanoStakeDistributionMessage,
15 CardanoTransactionSnapshotListMessage, CardanoTransactionSnapshotMessage,
16 CertificateListMessage, CertificateMessage, EpochSettingsMessage,
17 MithrilStakeDistributionListMessage, MithrilStakeDistributionMessage,
18 ProtocolConfigurationMessage, SignerMessagePart, SnapshotListMessage, SnapshotMessage,
19 },
20};
21
22use crate::{
23 EpochSettingsStorer, ImmutableFileDigestMapper,
24 database::repository::{CertificateRepository, SignedEntityStorer},
25 dependency_injection::EpochServiceWrapper,
26};
27
28#[cfg_attr(test, mockall::automock)]
30#[async_trait]
31pub trait MessageService: Sync + Send {
32 async fn get_epoch_settings_message(
34 &self,
35 allowed_discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
36 ) -> StdResult<EpochSettingsMessage>;
37
38 async fn get_protocol_configuration_message(
40 &self,
41 epoch: Epoch,
42 allowed_discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
43 ) -> StdResult<Option<ProtocolConfigurationMessage>>;
44
45 async fn get_certificate_message(
47 &self,
48 certificate_hash: &str,
49 ) -> StdResult<Option<CertificateMessage>>;
50
51 async fn get_latest_genesis_certificate_message(&self)
53 -> StdResult<Option<CertificateMessage>>;
54
55 async fn get_certificate_list_message(&self, limit: usize)
57 -> StdResult<CertificateListMessage>;
58
59 async fn get_snapshot_message(
61 &self,
62 signed_entity_id: &str,
63 ) -> StdResult<Option<SnapshotMessage>>;
64
65 async fn get_snapshot_list_message(&self, limit: usize) -> StdResult<SnapshotListMessage>;
68
69 async fn get_cardano_database_message(
71 &self,
72 signed_entity_id: &str,
73 ) -> StdResult<Option<CardanoDatabaseSnapshotMessage>>;
74
75 async fn get_cardano_database_list_message(
77 &self,
78 limit: usize,
79 ) -> StdResult<CardanoDatabaseSnapshotListMessage>;
80
81 async fn get_cardano_database_list_message_by_epoch(
83 &self,
84 limit: usize,
85 epoch: Epoch,
86 ) -> StdResult<CardanoDatabaseSnapshotListMessage>;
87
88 async fn get_cardano_database_digest_list_message(
90 &self,
91 ) -> StdResult<CardanoDatabaseDigestListMessage>;
92
93 async fn get_mithril_stake_distribution_message(
95 &self,
96 signed_entity_id: &str,
97 ) -> StdResult<Option<MithrilStakeDistributionMessage>>;
98
99 async fn get_mithril_stake_distribution_list_message(
101 &self,
102 limit: usize,
103 ) -> StdResult<MithrilStakeDistributionListMessage>;
104
105 async fn get_cardano_transaction_message(
107 &self,
108 signed_entity_id: &str,
109 ) -> StdResult<Option<CardanoTransactionSnapshotMessage>>;
110
111 async fn get_cardano_transaction_list_message(
113 &self,
114 limit: usize,
115 ) -> StdResult<CardanoTransactionSnapshotListMessage>;
116
117 async fn get_cardano_stake_distribution_message(
119 &self,
120 signed_entity_id: &str,
121 ) -> StdResult<Option<CardanoStakeDistributionMessage>>;
122
123 async fn get_cardano_stake_distribution_message_by_epoch(
125 &self,
126 epoch: Epoch,
127 ) -> StdResult<Option<CardanoStakeDistributionMessage>>;
128
129 async fn get_cardano_stake_distribution_list_message(
131 &self,
132 limit: usize,
133 ) -> StdResult<CardanoStakeDistributionListMessage>;
134}
135
136pub struct MithrilMessageService {
138 certificate_repository: Arc<CertificateRepository>,
139 signed_entity_storer: Arc<dyn SignedEntityStorer>,
140 epoch_settings_storer: Arc<dyn EpochSettingsStorer>,
141 immutable_file_digest_mapper: Arc<dyn ImmutableFileDigestMapper>,
142 epoch_service: EpochServiceWrapper,
143}
144
145impl MithrilMessageService {
146 pub fn new(
148 certificate_repository: Arc<CertificateRepository>,
149 signed_entity_storer: Arc<dyn SignedEntityStorer>,
150 epoch_settings_storer: Arc<dyn EpochSettingsStorer>,
151 immutable_file_digest_mapper: Arc<dyn ImmutableFileDigestMapper>,
152 epoch_service: EpochServiceWrapper,
153 ) -> Self {
154 Self {
155 certificate_repository,
156 signed_entity_storer,
157 epoch_settings_storer,
158 immutable_file_digest_mapper,
159 epoch_service,
160 }
161 }
162}
163
164#[async_trait]
165impl MessageService for MithrilMessageService {
166 async fn get_epoch_settings_message(
167 &self,
168 allowed_discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
169 ) -> StdResult<EpochSettingsMessage> {
170 let epoch_service = self.epoch_service.read().await;
171
172 let epoch = epoch_service.epoch_of_current_data()?;
173 let signer_registration_protocol_parameters =
174 epoch_service.signer_registration_protocol_parameters()?.clone();
175 let current_signers = epoch_service.current_signers()?;
176 let next_signers = epoch_service.next_signers()?;
177
178 let cardano_transactions_discriminant =
179 allowed_discriminants.get(&SignedEntityTypeDiscriminants::CardanoTransactions);
180
181 let signed_entity_config = cardano_transactions_discriminant
182 .map(|_| epoch_service.signed_entity_config())
183 .transpose()?
184 .cloned();
185
186 #[allow(deprecated)]
187 let epoch_settings_message = EpochSettingsMessage {
188 epoch,
189 signer_registration_protocol_parameters: Some(signer_registration_protocol_parameters),
190 current_signers: SignerMessagePart::from_signers(current_signers.to_vec()),
191 next_signers: SignerMessagePart::from_signers(next_signers.to_vec()),
192 cardano_transactions_signing_config: signed_entity_config
193 .and_then(|c| c.cardano_transactions_signing_config),
194 };
195
196 Ok(epoch_settings_message)
197 }
198
199 async fn get_protocol_configuration_message(
200 &self,
201 epoch: Epoch,
202 enabled_discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
203 ) -> StdResult<Option<ProtocolConfigurationMessage>> {
204 let epoch_settings = match self.epoch_settings_storer.get_epoch_settings(epoch).await? {
205 Some(settings) => settings,
206 None => return Ok(None),
207 };
208
209 let cardano_transactions_discriminant =
210 enabled_discriminants.get(&SignedEntityTypeDiscriminants::CardanoTransactions);
211
212 let cardano_transactions_signing_config = cardano_transactions_discriminant
213 .and(epoch_settings.cardano_transactions_signing_config);
214
215 let protocol_configuration_message = ProtocolConfigurationMessage {
216 protocol_parameters: epoch_settings.protocol_parameters,
217 cardano_transactions_signing_config,
218 available_signed_entity_types: enabled_discriminants,
219 };
220 Ok(Some(protocol_configuration_message))
221 }
222
223 async fn get_certificate_message(
224 &self,
225 certificate_hash: &str,
226 ) -> StdResult<Option<CertificateMessage>> {
227 self.certificate_repository.get_certificate(certificate_hash).await
228 }
229
230 async fn get_latest_genesis_certificate_message(
231 &self,
232 ) -> StdResult<Option<CertificateMessage>> {
233 self.certificate_repository.get_latest_genesis_certificate().await
234 }
235
236 async fn get_certificate_list_message(
237 &self,
238 limit: usize,
239 ) -> StdResult<CertificateListMessage> {
240 self.certificate_repository.get_latest_certificates(limit).await
241 }
242
243 async fn get_snapshot_message(
244 &self,
245 signed_entity_id: &str,
246 ) -> StdResult<Option<SnapshotMessage>> {
247 let signed_entity = self.signed_entity_storer.get_signed_entity(signed_entity_id).await?;
248
249 signed_entity.map(|s| s.try_into()).transpose()
250 }
251
252 async fn get_snapshot_list_message(&self, limit: usize) -> StdResult<SnapshotListMessage> {
253 let signed_entity_type_id = SignedEntityTypeDiscriminants::CardanoImmutableFilesFull;
254 let entities = self
255 .signed_entity_storer
256 .get_last_signed_entities_by_type(&signed_entity_type_id, limit)
257 .await?;
258
259 entities.into_iter().map(|i| i.try_into()).collect()
260 }
261
262 async fn get_cardano_database_message(
263 &self,
264 signed_entity_id: &str,
265 ) -> StdResult<Option<CardanoDatabaseSnapshotMessage>> {
266 let signed_entity = self.signed_entity_storer.get_signed_entity(signed_entity_id).await?;
267
268 signed_entity.map(|v| v.try_into()).transpose()
269 }
270
271 async fn get_cardano_database_list_message(
272 &self,
273 limit: usize,
274 ) -> StdResult<CardanoDatabaseSnapshotListMessage> {
275 let signed_entity_type_id = SignedEntityTypeDiscriminants::CardanoDatabase;
276 let entities = self
277 .signed_entity_storer
278 .get_last_signed_entities_by_type(&signed_entity_type_id, limit)
279 .await?;
280
281 entities.into_iter().map(|i| i.try_into()).collect()
282 }
283
284 async fn get_cardano_database_list_message_by_epoch(
285 &self,
286 limit: usize,
287 epoch: Epoch,
288 ) -> StdResult<CardanoDatabaseSnapshotListMessage> {
289 let signed_entity_type_id = SignedEntityTypeDiscriminants::CardanoDatabase;
290 let entities = self
291 .signed_entity_storer
292 .get_last_signed_entities_by_type_and_epoch(&signed_entity_type_id, epoch, limit)
293 .await?;
294
295 entities.into_iter().map(|i| i.try_into()).collect()
296 }
297
298 async fn get_cardano_database_digest_list_message(
299 &self,
300 ) -> StdResult<CardanoDatabaseDigestListMessage> {
301 Ok(self
302 .immutable_file_digest_mapper
303 .get_immutable_file_digest_map()
304 .await?
305 .into_iter()
306 .map(
307 |(immutable_file_name, digest)| CardanoDatabaseDigestListItemMessage {
308 immutable_file_name,
309 digest,
310 },
311 )
312 .collect::<Vec<_>>())
313 }
314
315 async fn get_mithril_stake_distribution_message(
316 &self,
317 signed_entity_id: &str,
318 ) -> StdResult<Option<MithrilStakeDistributionMessage>> {
319 let signed_entity = self.signed_entity_storer.get_signed_entity(signed_entity_id).await?;
320
321 signed_entity.map(|v| v.try_into()).transpose()
322 }
323
324 async fn get_mithril_stake_distribution_list_message(
325 &self,
326 limit: usize,
327 ) -> StdResult<MithrilStakeDistributionListMessage> {
328 let signed_entity_type_id = SignedEntityTypeDiscriminants::MithrilStakeDistribution;
329 let entities = self
330 .signed_entity_storer
331 .get_last_signed_entities_by_type(&signed_entity_type_id, limit)
332 .await?;
333
334 entities.into_iter().map(|i| i.try_into()).collect()
335 }
336
337 async fn get_cardano_transaction_message(
338 &self,
339 signed_entity_id: &str,
340 ) -> StdResult<Option<CardanoTransactionSnapshotMessage>> {
341 let signed_entity = self.signed_entity_storer.get_signed_entity(signed_entity_id).await?;
342
343 signed_entity.map(|v| v.try_into()).transpose()
344 }
345
346 async fn get_cardano_transaction_list_message(
347 &self,
348 limit: usize,
349 ) -> StdResult<CardanoTransactionSnapshotListMessage> {
350 let signed_entity_type_id = SignedEntityTypeDiscriminants::CardanoTransactions;
351 let entities = self
352 .signed_entity_storer
353 .get_last_signed_entities_by_type(&signed_entity_type_id, limit)
354 .await?;
355
356 entities.into_iter().map(|i| i.try_into()).collect()
357 }
358
359 async fn get_cardano_stake_distribution_message(
360 &self,
361 signed_entity_id: &str,
362 ) -> StdResult<Option<CardanoStakeDistributionMessage>> {
363 let signed_entity = self.signed_entity_storer.get_signed_entity(signed_entity_id).await?;
364
365 signed_entity.map(|v| v.try_into()).transpose()
366 }
367
368 async fn get_cardano_stake_distribution_message_by_epoch(
369 &self,
370 epoch: Epoch,
371 ) -> StdResult<Option<CardanoStakeDistributionMessage>> {
372 let signed_entity = self
373 .signed_entity_storer
374 .get_cardano_stake_distribution_signed_entity_by_epoch(epoch)
375 .await?;
376
377 signed_entity.map(|v| v.try_into()).transpose()
378 }
379
380 async fn get_cardano_stake_distribution_list_message(
381 &self,
382 limit: usize,
383 ) -> StdResult<CardanoStakeDistributionListMessage> {
384 let signed_entity_type_id = SignedEntityTypeDiscriminants::CardanoStakeDistribution;
385 let entities = self
386 .signed_entity_storer
387 .get_last_signed_entities_by_type(&signed_entity_type_id, limit)
388 .await?;
389
390 entities.into_iter().map(|i| i.try_into()).collect()
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use std::collections::BTreeMap;
397
398 use mithril_common::entities::{BlockNumber, CardanoDbBeacon, Certificate, SignedEntityType};
399 use mithril_common::test::double::{Dummy, fake_data};
400 use tokio::sync::RwLock;
401
402 use crate::database::record::SignedEntityRecord;
403 use crate::database::repository::{
404 EpochSettingsStore, ImmutableFileDigestRepository, SignedEntityStore,
405 };
406 use crate::database::test_helper::main_db_connection;
407 use crate::entities::AggregatorEpochSettings;
408 use crate::services::FakeEpochService;
409
410 use super::*;
411
412 struct MessageServiceBuilder {
413 certificates: Vec<Certificate>,
414 signed_entity_records: Vec<SignedEntityRecord>,
415 epoch_settings_map: BTreeMap<Epoch, AggregatorEpochSettings>,
416 immutable_file_digest_messages: Vec<CardanoDatabaseDigestListItemMessage>,
417 epoch_service: Option<FakeEpochService>,
418 }
419
420 impl MessageServiceBuilder {
421 fn new() -> Self {
422 Self {
423 certificates: Vec::new(),
424 signed_entity_records: Vec::new(),
425 epoch_settings_map: BTreeMap::new(),
426 immutable_file_digest_messages: Vec::new(),
427 epoch_service: None,
428 }
429 }
430
431 fn with_certificates(mut self, certificates: &[Certificate]) -> Self {
432 self.certificates.extend_from_slice(certificates);
433
434 self
435 }
436
437 fn with_signed_entity_records(
438 mut self,
439 signed_entity_record: &[SignedEntityRecord],
440 ) -> Self {
441 self.signed_entity_records.extend_from_slice(signed_entity_record);
442
443 self
444 }
445
446 fn with_epoch_settings(
447 mut self,
448 epoch_settings_map: BTreeMap<Epoch, AggregatorEpochSettings>,
449 ) -> Self {
450 self.epoch_settings_map = epoch_settings_map;
451
452 self
453 }
454
455 fn with_immutable_file_digest_messages(
456 mut self,
457 digests: &[CardanoDatabaseDigestListItemMessage],
458 ) -> Self {
459 self.immutable_file_digest_messages.extend_from_slice(digests);
460
461 self
462 }
463
464 fn with_epoch_service(mut self, epoch_service: FakeEpochService) -> Self {
465 self.epoch_service = Some(epoch_service);
466
467 self
468 }
469
470 async fn build(self) -> MithrilMessageService {
471 let connection = Arc::new(main_db_connection().unwrap());
472 let certificate_repository = CertificateRepository::new(connection.clone());
473 let signed_entity_store = SignedEntityStore::new(connection.clone());
474 let epoch_settings_store = EpochSettingsStore::new(connection.clone(), None);
475 let immutable_file_digest_mapper =
476 ImmutableFileDigestRepository::new(connection.clone());
477 let epoch_service = self.epoch_service.unwrap_or(FakeEpochService::without_data());
478
479 certificate_repository
480 .create_many_certificates(self.certificates)
481 .await
482 .unwrap();
483 for record in self.signed_entity_records {
484 signed_entity_store.store_signed_entity(&record).await.unwrap();
485 }
486
487 for (epoch, epoch_settings) in self.epoch_settings_map {
488 epoch_settings_store
489 .save_epoch_settings(epoch, epoch_settings)
490 .await
491 .unwrap();
492 }
493
494 for digest_message in self.immutable_file_digest_messages {
495 immutable_file_digest_mapper
496 .upsert_immutable_file_digest(
497 &digest_message.immutable_file_name,
498 &digest_message.digest,
499 )
500 .await
501 .unwrap();
502 }
503
504 MithrilMessageService::new(
505 Arc::new(certificate_repository),
506 Arc::new(signed_entity_store),
507 Arc::new(epoch_settings_store),
508 Arc::new(immutable_file_digest_mapper),
509 Arc::new(RwLock::new(epoch_service)),
510 )
511 }
512 }
513
514 #[allow(deprecated)]
515 mod epoch_settings {
516 use mithril_common::{
517 entities::{CardanoTransactionsSigningConfig, ProtocolParameters, SignedEntityConfig},
518 test::builder::MithrilFixtureBuilder,
519 };
520
521 use crate::{entities::AggregatorEpochSettings, services::FakeEpochServiceBuilder};
522
523 use super::*;
524
525 #[tokio::test]
526 async fn get_epoch_settings_message() {
527 let fixture = MithrilFixtureBuilder::default().with_signers(3).build();
528 let epoch_service = FakeEpochService::from_fixture(Epoch(4), &fixture);
529 let message_service = MessageServiceBuilder::new()
530 .with_epoch_service(epoch_service)
531 .build()
532 .await;
533
534 let message = message_service
535 .get_epoch_settings_message(SignedEntityTypeDiscriminants::all())
536 .await
537 .unwrap();
538
539 assert_eq!(message.epoch, Epoch(4));
540 assert_eq!(
541 message.signer_registration_protocol_parameters,
542 Some(ProtocolParameters::new(5, 100, 0.65))
543 );
544 assert_eq!(message.current_signers.len(), 3);
545 assert_eq!(message.next_signers.len(), 3);
546 assert_eq!(
547 message.cardano_transactions_signing_config,
548 Some(CardanoTransactionsSigningConfig {
549 security_parameter: BlockNumber(0),
550 step: BlockNumber(15)
551 })
552 );
553 }
554
555 #[tokio::test]
556 async fn get_epoch_settings_message_with_cardano_transactions_enabled() {
557 let fixture = MithrilFixtureBuilder::default().with_signers(3).build();
558 let epoch_service = FakeEpochService::from_fixture(Epoch(4), &fixture);
559 let message_service = MessageServiceBuilder::new()
560 .with_epoch_service(epoch_service)
561 .build()
562 .await;
563
564 let message = message_service
565 .get_epoch_settings_message(BTreeSet::from([
566 SignedEntityTypeDiscriminants::CardanoTransactions,
567 ]))
568 .await
569 .unwrap();
570
571 assert!(message.cardano_transactions_signing_config.is_some());
572 }
573
574 #[tokio::test]
575 async fn get_epoch_settings_message_with_cardano_transactions_not_enabled() {
576 let fixture = MithrilFixtureBuilder::default().with_signers(3).build();
577 let epoch_service = FakeEpochService::from_fixture(Epoch(4), &fixture);
578 let message_service = MessageServiceBuilder::new()
579 .with_epoch_service(epoch_service)
580 .build()
581 .await;
582
583 let message = message_service
584 .get_epoch_settings_message(BTreeSet::new())
585 .await
586 .unwrap();
587
588 assert_eq!(message.cardano_transactions_signing_config, None);
589 }
590
591 #[tokio::test]
592 async fn get_epoch_settings_message_retrieves_protocol_parameters_from_epoch_service() {
593 let current_epoch_settings = AggregatorEpochSettings {
594 protocol_parameters: ProtocolParameters::new(101, 10, 0.5),
595 ..AggregatorEpochSettings::dummy()
596 };
597 let next_epoch_settings = AggregatorEpochSettings {
598 protocol_parameters: ProtocolParameters::new(102, 20, 0.5),
599 ..AggregatorEpochSettings::dummy()
600 };
601 let signer_registration_epoch_settings = AggregatorEpochSettings {
602 protocol_parameters: ProtocolParameters::new(103, 30, 0.5),
603 ..AggregatorEpochSettings::dummy()
604 };
605 let epoch_service = FakeEpochServiceBuilder {
606 current_epoch_settings,
607 next_epoch_settings: next_epoch_settings.clone(),
608 signer_registration_epoch_settings: signer_registration_epoch_settings.clone(),
609 current_signers_with_stake: fake_data::signers_with_stakes(5),
610 next_signers_with_stake: fake_data::signers_with_stakes(3),
611 ..FakeEpochServiceBuilder::dummy(Epoch(1))
612 }
613 .build();
614 let message_service = MessageServiceBuilder::new()
615 .with_epoch_service(epoch_service)
616 .build()
617 .await;
618
619 let message = message_service
620 .get_epoch_settings_message(SignedEntityTypeDiscriminants::all())
621 .await
622 .unwrap();
623
624 assert_eq!(
625 message.signer_registration_protocol_parameters,
626 Some(signer_registration_epoch_settings.protocol_parameters)
627 );
628 }
629
630 #[tokio::test]
631 async fn get_epoch_settings_message_retrieves_signing_configuration_from_epoch_service() {
632 let expected_ctx_config = CardanoTransactionsSigningConfig {
633 security_parameter: BlockNumber(100),
634 step: BlockNumber(15),
635 };
636 let epoch_service = FakeEpochServiceBuilder {
637 signed_entity_config: SignedEntityConfig {
638 cardano_transactions_signing_config: Some(expected_ctx_config.clone()),
639 ..Dummy::dummy()
640 },
641 ..FakeEpochServiceBuilder::dummy(Epoch(1))
642 }
643 .build();
644 let message_service = MessageServiceBuilder::new()
645 .with_epoch_service(epoch_service)
646 .build()
647 .await;
648
649 let message = message_service
650 .get_epoch_settings_message(SignedEntityTypeDiscriminants::all())
651 .await
652 .unwrap();
653
654 assert_eq!(
655 message.cardano_transactions_signing_config,
656 Some(expected_ctx_config),
657 );
658 }
659 }
660
661 mod protocol_configuration {
662 use super::*;
663
664 use mithril_common::entities::{CardanoTransactionsSigningConfig, ProtocolParameters};
665
666 use crate::entities::AggregatorEpochSettings;
667
668 #[tokio::test]
669 async fn get_protocol_configuration_message() {
670 let epoch = Epoch(4);
671 let aggregator_epoch_settings = AggregatorEpochSettings {
672 protocol_parameters: ProtocolParameters::new(5, 100, 0.65),
673 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
674 security_parameter: BlockNumber(0),
675 step: BlockNumber(15),
676 }),
677 };
678 let message_service = MessageServiceBuilder::new()
679 .with_epoch_settings(BTreeMap::from([(epoch, aggregator_epoch_settings)]))
680 .build()
681 .await;
682
683 let message = message_service
684 .get_protocol_configuration_message(epoch, SignedEntityTypeDiscriminants::all())
685 .await
686 .unwrap()
687 .expect("Protocol configuration message should exist.");
688
689 assert_eq!(
690 message.protocol_parameters,
691 ProtocolParameters::new(5, 100, 0.65)
692 );
693 assert_eq!(
694 message.cardano_transactions_signing_config,
695 Some(CardanoTransactionsSigningConfig {
696 security_parameter: BlockNumber(0),
697 step: BlockNumber(15)
698 })
699 );
700 assert_eq!(
701 message.available_signed_entity_types,
702 SignedEntityTypeDiscriminants::all()
703 );
704 }
705
706 #[tokio::test]
707 async fn get_protocol_configuration_message_with_multiple_epochs_settings_stored() {
708 let message_service = MessageServiceBuilder::new()
709 .with_epoch_settings(BTreeMap::from([
710 (
711 Epoch(7),
712 AggregatorEpochSettings {
713 protocol_parameters: ProtocolParameters::new(1, 10, 0.11),
714 ..Dummy::dummy()
715 },
716 ),
717 (
718 Epoch(8),
719 AggregatorEpochSettings {
720 protocol_parameters: ProtocolParameters::new(2, 20, 0.22),
721 ..Dummy::dummy()
722 },
723 ),
724 (
725 Epoch(9),
726 AggregatorEpochSettings {
727 protocol_parameters: ProtocolParameters::new(3, 30, 0.33),
728 ..Dummy::dummy()
729 },
730 ),
731 ]))
732 .build()
733 .await;
734
735 let message = message_service
736 .get_protocol_configuration_message(Epoch(8), SignedEntityTypeDiscriminants::all())
737 .await
738 .unwrap()
739 .expect("Protocol configuration message should exist.");
740
741 assert_eq!(
742 message.protocol_parameters,
743 ProtocolParameters::new(2, 20, 0.22)
744 );
745 }
746
747 #[tokio::test]
748 async fn get_protocol_configuration_message_with_cardano_transactions_enabled() {
749 let epoch = Epoch(4);
750 let message_service = MessageServiceBuilder::new()
751 .with_epoch_settings(BTreeMap::from([(epoch, AggregatorEpochSettings::dummy())]))
752 .build()
753 .await;
754
755 let message = message_service
756 .get_protocol_configuration_message(
757 epoch,
758 BTreeSet::from([SignedEntityTypeDiscriminants::CardanoTransactions]),
759 )
760 .await
761 .unwrap()
762 .expect("Protocol configuration message should exist.");
763
764 assert!(message.cardano_transactions_signing_config.is_some());
765 }
766
767 #[tokio::test]
768 async fn get_protocol_configuration_message_without_cardano_transactions_does_not_return_signing_config()
769 {
770 let epoch = Epoch(4);
771 let message_service = MessageServiceBuilder::new()
772 .with_epoch_settings(BTreeMap::from([(epoch, AggregatorEpochSettings::dummy())]))
773 .build()
774 .await;
775
776 let message = message_service
777 .get_protocol_configuration_message(epoch, BTreeSet::new())
778 .await
779 .unwrap()
780 .expect("Protocol configuration message should exist.");
781
782 assert_eq!(message.cardano_transactions_signing_config, None);
783 }
784
785 #[tokio::test]
786 async fn get_protocol_configuration_message_return_none_if_epoch_not_found() {
787 let epoch_number = 7;
788 let epoch_without_correspondence = epoch_number + 42;
789 let message_service = MessageServiceBuilder::new()
790 .with_epoch_settings(BTreeMap::from([(
791 Epoch(epoch_number),
792 AggregatorEpochSettings::dummy(),
793 )]))
794 .build()
795 .await;
796
797 let message = message_service
798 .get_protocol_configuration_message(
799 Epoch(epoch_without_correspondence),
800 SignedEntityTypeDiscriminants::all(),
801 )
802 .await
803 .unwrap();
804
805 assert_eq!(message, None);
806 }
807 }
808
809 mod certificate {
810 use super::*;
811
812 #[tokio::test]
813 async fn get_no_certificate() {
814 let service = MessageServiceBuilder::new().build().await;
815
816 let certificate_hash = "whatever";
817 let certificate_message =
818 service.get_certificate_message(certificate_hash).await.unwrap();
819 assert!(certificate_message.is_none());
820 }
821
822 #[tokio::test]
823 async fn get_certificate() {
824 let genesis_certificate = fake_data::genesis_certificate("genesis_hash");
825 let service = MessageServiceBuilder::new()
826 .with_certificates(std::slice::from_ref(&genesis_certificate))
827 .build()
828 .await;
829
830 let certificate_message = service
831 .get_certificate_message(&genesis_certificate.hash)
832 .await
833 .unwrap()
834 .expect("There should be a certificate.");
835 assert_eq!(genesis_certificate.hash, certificate_message.hash);
836 }
837
838 #[tokio::test]
839 async fn get_no_latest_genesis_certificate() {
840 let service = MessageServiceBuilder::new().build().await;
841
842 let certificate_message =
843 service.get_latest_genesis_certificate_message().await.unwrap();
844 assert_eq!(None, certificate_message);
845 }
846
847 #[tokio::test]
848 async fn get_latest_genesis_certificate() {
849 let certificates = [
850 fake_data::genesis_certificate("certificate_1"),
851 fake_data::genesis_certificate("certificate_2"),
852 fake_data::certificate("certificate_3"),
853 ];
854 let last_genesis_hash = certificates[1].hash.clone();
855 let service = MessageServiceBuilder::new()
856 .with_certificates(&certificates)
857 .build()
858 .await;
859
860 let certificate_message = service
861 .get_latest_genesis_certificate_message()
862 .await
863 .unwrap()
864 .expect("There should be a genesis certificate.");
865 assert_eq!(last_genesis_hash, certificate_message.hash);
866 }
867
868 #[tokio::test]
869 async fn get_last_certificates() {
870 let certificates = [
871 fake_data::genesis_certificate("certificate_1"),
872 fake_data::genesis_certificate("certificate_2"),
873 ];
874 let last_certificate_hash = certificates[1].hash.clone();
875 let service = MessageServiceBuilder::new()
876 .with_certificates(&certificates)
877 .build()
878 .await;
879
880 let certificate_messages = service.get_certificate_list_message(5).await.unwrap();
881
882 assert_eq!(2, certificate_messages.len());
883 assert_eq!(last_certificate_hash, certificate_messages[0].hash);
884 }
885 }
886
887 mod snapshot {
888 use super::*;
889
890 #[tokio::test]
891 async fn get_snapshot_not_exist() {
892 let service = MessageServiceBuilder::new().build().await;
893 let snapshot = service.get_snapshot_message("whatever").await.unwrap();
894
895 assert!(snapshot.is_none());
896 }
897
898 #[tokio::test]
899 async fn get_snapshot() {
900 let record = SignedEntityRecord {
901 signed_entity_id: "signed_entity_id".to_string(),
902 signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(fake_data::beacon()),
903 certificate_id: "cert_id".to_string(),
904 artifact: serde_json::to_string(&fake_data::snapshot(1)).unwrap(),
905 created_at: Default::default(),
906 };
907 let message: SnapshotMessage = record.clone().try_into().unwrap();
908
909 let service = MessageServiceBuilder::new()
910 .with_signed_entity_records(std::slice::from_ref(&record))
911 .build()
912 .await;
913
914 let response = service
915 .get_snapshot_message(&record.signed_entity_id)
916 .await
917 .unwrap()
918 .expect("A SnapshotMessage was expected.");
919
920 assert_eq!(message, response);
921 }
922
923 #[tokio::test]
924 async fn get_snapshot_list_message() {
925 let records = vec![
926 SignedEntityRecord {
927 signed_entity_id: "signed_entity_id-1".to_string(),
928 signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(
929 fake_data::beacon(),
930 ),
931 certificate_id: "cert_id-1".to_string(),
932 artifact: serde_json::to_string(&fake_data::snapshot(1)).unwrap(),
933 created_at: Default::default(),
934 },
935 SignedEntityRecord {
936 signed_entity_id: "signed_entity_id-2".to_string(),
937 signed_entity_type: SignedEntityType::CardanoDatabase(fake_data::beacon()),
938 certificate_id: "cert_id-2".to_string(),
939 artifact: serde_json::to_string(&fake_data::cardano_database_snapshot(1))
940 .unwrap(),
941 created_at: Default::default(),
942 },
943 ];
944 let message: SnapshotListMessage = vec![records[0].clone().try_into().unwrap()];
945
946 let service = MessageServiceBuilder::new()
947 .with_signed_entity_records(&records)
948 .build()
949 .await;
950
951 let response = service.get_snapshot_list_message(0).await.unwrap();
952 assert!(response.is_empty());
953
954 let response = service.get_snapshot_list_message(3).await.unwrap();
955 assert_eq!(message, response);
956 }
957 }
958
959 mod cardano_database {
960 use super::*;
961
962 #[tokio::test]
963 async fn get_cardano_database_when_record_does_not_exist() {
964 let service = MessageServiceBuilder::new().build().await;
965 let snapshot = service.get_cardano_database_message("whatever").await.unwrap();
966
967 assert!(snapshot.is_none());
968 }
969
970 #[tokio::test]
971 async fn get_cardano_database() {
972 let record = SignedEntityRecord {
973 signed_entity_id: "signed_entity_id".to_string(),
974 signed_entity_type: SignedEntityType::CardanoDatabase(fake_data::beacon()),
975 certificate_id: "cert_id".to_string(),
976 artifact: serde_json::to_string(&fake_data::cardano_database_snapshot(1)).unwrap(),
977 created_at: Default::default(),
978 };
979 let message: CardanoDatabaseSnapshotMessage = record.clone().try_into().unwrap();
980
981 let service = MessageServiceBuilder::new()
982 .with_signed_entity_records(std::slice::from_ref(&record))
983 .build()
984 .await;
985
986 let response = service
987 .get_cardano_database_message(&record.signed_entity_id)
988 .await
989 .unwrap()
990 .expect("A CardanoDatabaseSnapshotMessage was expected.");
991
992 assert_eq!(message, response);
993 }
994
995 #[tokio::test]
996 async fn get_cardano_database_list_message() {
997 let records = vec![
998 SignedEntityRecord {
999 signed_entity_id: "signed_entity_id-1".to_string(),
1000 signed_entity_type: SignedEntityType::CardanoDatabase(fake_data::beacon()),
1001 certificate_id: "cert_id-1".to_string(),
1002 artifact: serde_json::to_string(&fake_data::cardano_database_snapshot(1))
1003 .unwrap(),
1004 created_at: Default::default(),
1005 },
1006 SignedEntityRecord {
1007 signed_entity_id: "signed_entity_id-2".to_string(),
1008 signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(
1009 fake_data::beacon(),
1010 ),
1011 certificate_id: "cert_id-2".to_string(),
1012 artifact: serde_json::to_string(&fake_data::snapshot(1)).unwrap(),
1013 created_at: Default::default(),
1014 },
1015 ];
1016 let message: CardanoDatabaseSnapshotListMessage =
1017 vec![records[0].clone().try_into().unwrap()];
1018
1019 let service = MessageServiceBuilder::new()
1020 .with_signed_entity_records(&records)
1021 .build()
1022 .await;
1023
1024 let response = service.get_cardano_database_list_message(0).await.unwrap();
1025 assert!(response.is_empty());
1026
1027 let response = service.get_cardano_database_list_message(3).await.unwrap();
1028 assert_eq!(message, response);
1029 }
1030
1031 #[tokio::test]
1032 async fn get_cardano_database_list_message_by_epoch() {
1033 let records = vec![
1034 SignedEntityRecord {
1036 signed_entity_id: "signed_entity_id-1".to_string(),
1037 signed_entity_type: SignedEntityType::CardanoDatabase(CardanoDbBeacon::new(
1038 3, 100,
1039 )),
1040 certificate_id: "cert_id-1".to_string(),
1041 artifact: serde_json::to_string(&fake_data::cardano_database_snapshot(100))
1042 .unwrap(),
1043 created_at: Default::default(),
1044 },
1045 SignedEntityRecord {
1047 signed_entity_id: "signed_entity_id-2".to_string(),
1048 signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(
1049 CardanoDbBeacon::new(3, 100),
1050 ),
1051 certificate_id: "cert_id-2".to_string(),
1052 artifact: serde_json::to_string(&fake_data::snapshot(1)).unwrap(),
1053 created_at: Default::default(),
1054 },
1055 SignedEntityRecord {
1057 signed_entity_id: "signed_entity_id-3".to_string(),
1058 signed_entity_type: SignedEntityType::CardanoDatabase(CardanoDbBeacon::new(
1059 3, 102,
1060 )),
1061 certificate_id: "cert_id-3".to_string(),
1062 artifact: serde_json::to_string(&fake_data::cardano_database_snapshot(102))
1063 .unwrap(),
1064 created_at: Default::default(),
1065 },
1066 SignedEntityRecord {
1068 signed_entity_id: "signed_entity_id-4".to_string(),
1069 signed_entity_type: SignedEntityType::CardanoDatabase(CardanoDbBeacon::new(
1070 4, 104,
1071 )),
1072 certificate_id: "cert_id-4".to_string(),
1073 artifact: serde_json::to_string(&fake_data::cardano_database_snapshot(104))
1074 .unwrap(),
1075 created_at: Default::default(),
1076 },
1077 ];
1078 let message: CardanoDatabaseSnapshotListMessage = vec![
1079 records[2].clone().try_into().unwrap(),
1080 records[0].clone().try_into().unwrap(),
1081 ];
1082
1083 let service = MessageServiceBuilder::new()
1084 .with_signed_entity_records(&records)
1085 .build()
1086 .await;
1087
1088 let response = service
1089 .get_cardano_database_list_message_by_epoch(0, Epoch(3))
1090 .await
1091 .unwrap();
1092 assert!(response.is_empty());
1093
1094 let response = service
1095 .get_cardano_database_list_message_by_epoch(3, Epoch(3))
1096 .await
1097 .unwrap();
1098 assert_eq!(message, response);
1099 }
1100
1101 #[tokio::test]
1102 async fn get_cardano_database_digest_list_message() {
1103 let messages: CardanoDatabaseDigestListMessage = vec![
1104 CardanoDatabaseDigestListItemMessage {
1105 immutable_file_name: "06685.chunk".to_string(),
1106 digest: "0af556ab2620dd9363bf76963a231abe8948a500ea6be31b131d87907ab09b1e"
1107 .to_string(),
1108 },
1109 CardanoDatabaseDigestListItemMessage {
1110 immutable_file_name: "06685.primary".to_string(),
1111 digest: "32dfd6b722d87f253e78eb8b478fb94f1e13463826e674d6ec7b6bf0892b2e39"
1112 .to_string(),
1113 },
1114 ];
1115
1116 let service = MessageServiceBuilder::new()
1117 .with_immutable_file_digest_messages(&messages)
1118 .build()
1119 .await;
1120
1121 let response = service.get_cardano_database_digest_list_message().await.unwrap();
1122
1123 assert_eq!(messages, response);
1124 }
1125 }
1126
1127 mod mithril_stake_distribution {
1128 use super::*;
1129
1130 #[tokio::test]
1131 async fn get_mithril_stake_distribution() {
1132 let record = SignedEntityRecord {
1133 signed_entity_id: "signed_entity_id".to_string(),
1134 signed_entity_type: SignedEntityType::MithrilStakeDistribution(Epoch(18)),
1135 certificate_id: "cert_id".to_string(),
1136 artifact: serde_json::to_string(&fake_data::mithril_stake_distribution(
1137 Epoch(1),
1138 vec![],
1139 ))
1140 .unwrap(),
1141 created_at: Default::default(),
1142 };
1143 let message: MithrilStakeDistributionMessage = record.clone().try_into().unwrap();
1144
1145 let service = MessageServiceBuilder::new()
1146 .with_signed_entity_records(std::slice::from_ref(&record))
1147 .build()
1148 .await;
1149
1150 let response = service
1151 .get_mithril_stake_distribution_message(&record.signed_entity_id)
1152 .await
1153 .unwrap()
1154 .expect("A MithrilStakeDistributionMessage was expected.");
1155
1156 assert_eq!(message, response);
1157 }
1158
1159 #[tokio::test]
1160 async fn get_mithril_stake_distribution_not_exist() {
1161 let service = MessageServiceBuilder::new().build().await;
1162
1163 let response = service
1164 .get_mithril_stake_distribution_message("whatever")
1165 .await
1166 .unwrap();
1167
1168 assert!(response.is_none());
1169 }
1170
1171 #[tokio::test]
1172 async fn get_mithril_stake_distribution_list_message() {
1173 let records = vec![
1174 SignedEntityRecord {
1175 signed_entity_id: "signed_entity_id-1".to_string(),
1176 signed_entity_type: SignedEntityType::MithrilStakeDistribution(Epoch(18)),
1177 certificate_id: "cert_id-1".to_string(),
1178 artifact: serde_json::to_string(&fake_data::mithril_stake_distribution(
1179 Epoch(1),
1180 vec![],
1181 ))
1182 .unwrap(),
1183 created_at: Default::default(),
1184 },
1185 SignedEntityRecord {
1186 signed_entity_id: "signed_entity_id-2".to_string(),
1187 signed_entity_type: SignedEntityType::CardanoDatabase(fake_data::beacon()),
1188 certificate_id: "cert_id-2".to_string(),
1189 artifact: serde_json::to_string(&fake_data::cardano_database_snapshot(1))
1190 .unwrap(),
1191 created_at: Default::default(),
1192 },
1193 ];
1194 let message: MithrilStakeDistributionListMessage =
1195 vec![records[0].clone().try_into().unwrap()];
1196
1197 let service = MessageServiceBuilder::new()
1198 .with_signed_entity_records(&records)
1199 .build()
1200 .await;
1201
1202 let response = service.get_mithril_stake_distribution_list_message(0).await.unwrap();
1203 assert!(response.is_empty());
1204
1205 let response = service.get_mithril_stake_distribution_list_message(3).await.unwrap();
1206 assert_eq!(message, response);
1207 }
1208 }
1209
1210 mod cardano_transaction {
1211 use super::*;
1212
1213 #[tokio::test]
1214 async fn get_cardano_transaction() {
1215 let record = SignedEntityRecord {
1216 signed_entity_id: "signed_entity_id".to_string(),
1217 signed_entity_type: SignedEntityType::CardanoTransactions(
1218 Epoch(18),
1219 BlockNumber(120),
1220 ),
1221 certificate_id: "cert_id".to_string(),
1222 artifact: serde_json::to_string(&fake_data::cardano_transactions_snapshot(
1223 BlockNumber(1),
1224 ))
1225 .unwrap(),
1226 created_at: Default::default(),
1227 };
1228 let message: CardanoTransactionSnapshotMessage = record.clone().try_into().unwrap();
1229
1230 let service = MessageServiceBuilder::new()
1231 .with_signed_entity_records(std::slice::from_ref(&record))
1232 .build()
1233 .await;
1234
1235 let response = service
1236 .get_cardano_transaction_message(&record.signed_entity_id)
1237 .await
1238 .unwrap()
1239 .expect("A CardanoTransactionMessage was expected.");
1240
1241 assert_eq!(message, response);
1242 }
1243
1244 #[tokio::test]
1245 async fn get_cardano_transaction_not_exist() {
1246 let service = MessageServiceBuilder::new().build().await;
1247
1248 let response = service.get_cardano_transaction_message("whatever").await.unwrap();
1249
1250 assert!(response.is_none());
1251 }
1252
1253 #[tokio::test]
1254 async fn get_cardano_transaction_list_message() {
1255 let records = vec![
1256 SignedEntityRecord {
1257 signed_entity_id: "signed_entity_id-1".to_string(),
1258 signed_entity_type: SignedEntityType::CardanoTransactions(
1259 Epoch(18),
1260 BlockNumber(120),
1261 ),
1262 certificate_id: "cert_id-1".to_string(),
1263 artifact: serde_json::to_string(&fake_data::cardano_transactions_snapshot(
1264 BlockNumber(1),
1265 ))
1266 .unwrap(),
1267 created_at: Default::default(),
1268 },
1269 SignedEntityRecord {
1270 signed_entity_id: "signed_entity_id-2".to_string(),
1271 signed_entity_type: SignedEntityType::CardanoDatabase(fake_data::beacon()),
1272 certificate_id: "cert_id-2".to_string(),
1273 artifact: serde_json::to_string(&fake_data::cardano_database_snapshot(1))
1274 .unwrap(),
1275 created_at: Default::default(),
1276 },
1277 ];
1278 let message: CardanoTransactionSnapshotListMessage =
1279 vec![records[0].clone().try_into().unwrap()];
1280
1281 let service = MessageServiceBuilder::new()
1282 .with_signed_entity_records(&records)
1283 .build()
1284 .await;
1285
1286 let response = service.get_cardano_transaction_list_message(0).await.unwrap();
1287 assert!(response.is_empty());
1288
1289 let response = service.get_cardano_transaction_list_message(3).await.unwrap();
1290 assert_eq!(message, response);
1291 }
1292 }
1293
1294 mod cardano_stake_distribution {
1295 use super::*;
1296
1297 #[tokio::test]
1298 async fn get_cardano_stake_distribution() {
1299 let record = SignedEntityRecord {
1300 signed_entity_id: "signed_entity_id".to_string(),
1301 signed_entity_type: SignedEntityType::CardanoStakeDistribution(Epoch(18)),
1302 certificate_id: "cert_id".to_string(),
1303 artifact: serde_json::to_string(&fake_data::cardano_stake_distribution(Epoch(1)))
1304 .unwrap(),
1305 created_at: Default::default(),
1306 };
1307 let message: CardanoStakeDistributionMessage = record.clone().try_into().unwrap();
1308
1309 let service = MessageServiceBuilder::new()
1310 .with_signed_entity_records(std::slice::from_ref(&record))
1311 .build()
1312 .await;
1313
1314 let response = service
1315 .get_cardano_stake_distribution_message(&record.signed_entity_id)
1316 .await
1317 .unwrap()
1318 .expect("A CardanoStakeDistributionMessage was expected.");
1319
1320 assert_eq!(message, response);
1321 }
1322
1323 #[tokio::test]
1324 async fn get_cardano_stake_distribution_not_exist() {
1325 let service = MessageServiceBuilder::new().build().await;
1326
1327 let response = service
1328 .get_cardano_stake_distribution_message("whatever")
1329 .await
1330 .unwrap();
1331
1332 assert!(response.is_none());
1333 }
1334
1335 #[tokio::test]
1336 async fn get_cardano_stake_distribution_by_epoch() {
1337 let record = SignedEntityRecord {
1338 signed_entity_id: "signed_entity_id".to_string(),
1339 signed_entity_type: SignedEntityType::CardanoStakeDistribution(Epoch(18)),
1340 certificate_id: "cert_id".to_string(),
1341 artifact: serde_json::to_string(&fake_data::cardano_stake_distribution(Epoch(1)))
1342 .unwrap(),
1343 created_at: Default::default(),
1344 };
1345 let message: CardanoStakeDistributionMessage = record.clone().try_into().unwrap();
1346
1347 let service = MessageServiceBuilder::new()
1348 .with_signed_entity_records(std::slice::from_ref(&record))
1349 .build()
1350 .await;
1351
1352 let response = service
1353 .get_cardano_stake_distribution_message_by_epoch(
1354 record.signed_entity_type.get_epoch(),
1355 )
1356 .await
1357 .unwrap()
1358 .expect("A CardanoStakeDistributionMessage was expected.");
1359
1360 assert_eq!(message, response);
1361 }
1362
1363 #[tokio::test]
1364 async fn get_cardano_stake_distribution_by_epoch_not_exist() {
1365 let service = MessageServiceBuilder::new().build().await;
1366
1367 let response = service
1368 .get_cardano_stake_distribution_message_by_epoch(Epoch(999))
1369 .await
1370 .unwrap();
1371
1372 assert!(response.is_none());
1373 }
1374
1375 #[tokio::test]
1376 async fn get_cardano_stake_distribution_list_message() {
1377 let records = vec![
1378 SignedEntityRecord {
1379 signed_entity_id: "signed_entity_id-1".to_string(),
1380 signed_entity_type: SignedEntityType::CardanoStakeDistribution(Epoch(18)),
1381 certificate_id: "cert_id-1".to_string(),
1382 artifact: serde_json::to_string(&fake_data::cardano_stake_distribution(Epoch(
1383 1,
1384 )))
1385 .unwrap(),
1386 created_at: Default::default(),
1387 },
1388 SignedEntityRecord {
1389 signed_entity_id: "signed_entity_id-2".to_string(),
1390 signed_entity_type: SignedEntityType::CardanoDatabase(fake_data::beacon()),
1391 certificate_id: "cert_id-2".to_string(),
1392 artifact: serde_json::to_string(&fake_data::cardano_database_snapshot(1))
1393 .unwrap(),
1394 created_at: Default::default(),
1395 },
1396 ];
1397 let message: CardanoStakeDistributionListMessage =
1398 vec![records[0].clone().try_into().unwrap()];
1399
1400 let service = MessageServiceBuilder::new()
1401 .with_signed_entity_records(&records)
1402 .build()
1403 .await;
1404
1405 let response = service.get_cardano_stake_distribution_list_message(0).await.unwrap();
1406 assert!(response.is_empty());
1407
1408 let response = service.get_cardano_stake_distribution_list_message(3).await.unwrap();
1409 assert_eq!(message, response);
1410 }
1411 }
1412}