mithril_signer/services/
certifier.rs

1use async_trait::async_trait;
2use chrono::Utc;
3use slog::{Logger, debug};
4use std::sync::Arc;
5
6use mithril_common::StdResult;
7use mithril_common::entities::{ProtocolMessage, SignedEntityConfig, SignedEntityType, TimePoint};
8use mithril_common::logging::LoggerExtensions;
9use mithril_signed_entity_lock::SignedEntityTypeLock;
10
11use crate::entities::BeaconToSign;
12use crate::services::{SignaturePublisher, SingleSigner};
13
14/// Certifier Service
15///
16/// This service is responsible for providing the beacons that need to be signed by the signer.
17#[cfg_attr(test, mockall::automock)]
18#[async_trait]
19pub trait CertifierService: Sync + Send {
20    /// Get the beacon to sign.
21    ///
22    /// If all available signed entity have already been signed, `None` is returned.
23    async fn get_beacon_to_sign(&self, time_point: TimePoint) -> StdResult<Option<BeaconToSign>>;
24
25    /// Compute and publish a single signature for a given protocol message.
26    async fn compute_publish_single_signature(
27        &self,
28        beacon_to_sign: &BeaconToSign,
29        protocol_message: &ProtocolMessage,
30    ) -> StdResult<()>;
31}
32
33/// Trait to provide the current signed entity configuration that can change over time.
34#[cfg_attr(test, mockall::automock)]
35#[async_trait]
36pub trait SignedEntityConfigProvider: Sync + Send {
37    /// Get the current signed entity configuration.
38    async fn get(&self) -> StdResult<SignedEntityConfig>;
39}
40
41/// Trait to store beacons that have been signed in order to avoid signing them twice.
42#[cfg_attr(test, mockall::automock)]
43#[async_trait]
44pub trait SignedBeaconStore: Sync + Send {
45    /// Filter out already signed entities from a list of signed entities.
46    async fn filter_out_already_signed_entities(
47        &self,
48        entities: Vec<SignedEntityType>,
49    ) -> StdResult<Vec<SignedEntityType>>;
50
51    /// Mark a beacon as signed.
52    async fn mark_beacon_as_signed(&self, entity: &BeaconToSign) -> StdResult<()>;
53}
54
55/// Implementation of the [Certifier Service][CertifierService] for the Mithril Signer.
56pub struct SignerCertifierService {
57    signed_beacon_store: Arc<dyn SignedBeaconStore>,
58    signed_entity_config_provider: Arc<dyn SignedEntityConfigProvider>,
59    signed_entity_type_lock: Arc<SignedEntityTypeLock>,
60    single_signer: Arc<dyn SingleSigner>,
61    signature_publisher: Arc<dyn SignaturePublisher>,
62    logger: Logger,
63}
64
65impl SignerCertifierService {
66    /// Create a new `SignerCertifierService` instance.
67    pub fn new(
68        signed_beacon_store: Arc<dyn SignedBeaconStore>,
69        signed_entity_config_provider: Arc<dyn SignedEntityConfigProvider>,
70        signed_entity_type_lock: Arc<SignedEntityTypeLock>,
71        single_signer: Arc<dyn SingleSigner>,
72        signature_publisher: Arc<dyn SignaturePublisher>,
73        logger: Logger,
74    ) -> Self {
75        Self {
76            signed_beacon_store,
77            signed_entity_config_provider,
78            signed_entity_type_lock,
79            single_signer,
80            signature_publisher,
81            logger: logger.new_with_component_name::<Self>(),
82        }
83    }
84
85    async fn list_available_signed_entity_types(
86        &self,
87        time_point: &TimePoint,
88    ) -> StdResult<Vec<SignedEntityType>> {
89        let signed_entity_types = self
90            .signed_entity_config_provider
91            .get()
92            .await?
93            .list_allowed_signed_entity_types(time_point)?;
94        let unlocked_signed_entities = self
95            .signed_entity_type_lock
96            .filter_unlocked_entries(signed_entity_types)
97            .await;
98        let not_already_signed_entities = self
99            .signed_beacon_store
100            .filter_out_already_signed_entities(unlocked_signed_entities)
101            .await?;
102
103        Ok(not_already_signed_entities)
104    }
105}
106
107#[async_trait]
108impl CertifierService for SignerCertifierService {
109    async fn get_beacon_to_sign(&self, time_point: TimePoint) -> StdResult<Option<BeaconToSign>> {
110        let available_signed_entity_types =
111            self.list_available_signed_entity_types(&time_point).await?;
112
113        if available_signed_entity_types.is_empty() {
114            Ok(None)
115        } else {
116            let signed_entity_type = available_signed_entity_types[0].clone();
117            let beacon_to_sign =
118                BeaconToSign::new(time_point.epoch, signed_entity_type, Utc::now());
119
120            Ok(Some(beacon_to_sign))
121        }
122    }
123
124    async fn compute_publish_single_signature(
125        &self,
126        beacon_to_sign: &BeaconToSign,
127        protocol_message: &ProtocolMessage,
128    ) -> StdResult<()> {
129        if let Some(single_signature) =
130            self.single_signer.compute_single_signature(protocol_message).await?
131        {
132            debug!(self.logger, " > There is a single signature to send");
133            self.signature_publisher
134                .publish(
135                    &beacon_to_sign.signed_entity_type,
136                    &single_signature,
137                    protocol_message,
138                )
139                .await?;
140        } else {
141            debug!(self.logger, " > NO single signature to send");
142        }
143
144        debug!(self.logger, " > Marking beacon as signed"; "beacon" => ?beacon_to_sign);
145        self.signed_beacon_store.mark_beacon_as_signed(beacon_to_sign).await?;
146
147        Ok(())
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use mockall::predicate::eq;
154
155    use mithril_common::entities::{
156        CardanoBlocksTransactionsSigningConfig, CardanoTransactionsSigningConfig, ChainPoint,
157        Epoch, ProtocolMessagePartKey, SignedEntityTypeDiscriminants,
158    };
159    use mithril_common::test::double::{Dummy, fake_data};
160
161    use crate::services::{MockSignaturePublisher, MockSingleSigner};
162
163    use super::{tests::tests_tooling::*, *};
164
165    #[tokio::test]
166    async fn no_beacon_can_be_signed_if_all_entities_are_locked() {
167        let locker = Arc::new(SignedEntityTypeLock::new());
168        for signed_entity_type in SignedEntityTypeDiscriminants::all() {
169            locker.lock(signed_entity_type).await;
170        }
171        let certifier_service = SignerCertifierService {
172            signed_entity_type_lock: locker.clone(),
173            signed_entity_config_provider: Arc::new(DumbSignedEntityConfigProvider::new(
174                CardanoTransactionsSigningConfig::dummy(),
175                CardanoBlocksTransactionsSigningConfig::dummy(),
176                SignedEntityTypeDiscriminants::all(),
177            )),
178            ..SignerCertifierService::dumb_dependencies()
179        };
180
181        let beacon_to_sign = certifier_service
182            .get_beacon_to_sign(TimePoint::new(1, 14, ChainPoint::dummy()))
183            .await
184            .unwrap();
185        assert_eq!(beacon_to_sign, None);
186    }
187
188    #[tokio::test]
189    async fn only_one_unlocked_and_not_yet_signed_yield_a_beacon_to_sign() {
190        let locker = Arc::new(SignedEntityTypeLock::new());
191        for signed_entity_type in SignedEntityTypeDiscriminants::all().into_iter().skip(1) {
192            locker.lock(signed_entity_type).await;
193        }
194        let certifier_service = SignerCertifierService {
195            signed_entity_type_lock: locker.clone(),
196            signed_entity_config_provider: Arc::new(DumbSignedEntityConfigProvider::new(
197                CardanoTransactionsSigningConfig::dummy(),
198                CardanoBlocksTransactionsSigningConfig::dummy(),
199                SignedEntityTypeDiscriminants::all(),
200            )),
201            ..SignerCertifierService::dumb_dependencies()
202        };
203
204        let beacon_to_sign = certifier_service
205            .get_beacon_to_sign(TimePoint::new(3, 14, ChainPoint::dummy()))
206            .await
207            .unwrap();
208        let signed_discriminant: Option<SignedEntityTypeDiscriminants> =
209            beacon_to_sign.map(|b| b.signed_entity_type.into());
210
211        assert_eq!(
212            SignedEntityTypeDiscriminants::all().first().cloned(),
213            signed_discriminant
214        );
215    }
216
217    #[tokio::test]
218    async fn if_already_signed_a_beacon_is_not_returned_anymore() {
219        let signed_beacon_store = Arc::new(DumbSignedBeaconStore::default());
220        let certifier_service = SignerCertifierService {
221            signed_beacon_store: signed_beacon_store.clone(),
222            signed_entity_config_provider: Arc::new(DumbSignedEntityConfigProvider::new(
223                CardanoTransactionsSigningConfig::dummy(),
224                CardanoBlocksTransactionsSigningConfig::dummy(),
225                SignedEntityTypeDiscriminants::all(),
226            )),
227            ..SignerCertifierService::dumb_dependencies()
228        };
229
230        let time_point = TimePoint::new(1, 14, ChainPoint::dummy());
231        let first_beacon_to_sign = certifier_service
232            .get_beacon_to_sign(time_point.clone())
233            .await
234            .unwrap()
235            .unwrap();
236        signed_beacon_store
237            .mark_beacon_as_signed(&first_beacon_to_sign.clone())
238            .await
239            .unwrap();
240        let second_beacon_to_sign = certifier_service
241            .get_beacon_to_sign(time_point)
242            .await
243            .unwrap()
244            .unwrap();
245
246        assert_ne!(
247            first_beacon_to_sign.signed_entity_type,
248            second_beacon_to_sign.signed_entity_type
249        );
250    }
251
252    #[tokio::test]
253    async fn draining_out_all_beacons_to_sign_use_signed_entity_discriminant_order() {
254        let signed_beacon_store = Arc::new(DumbSignedBeaconStore::default());
255        let certifier_service = SignerCertifierService {
256            signed_beacon_store: signed_beacon_store.clone(),
257            signed_entity_config_provider: Arc::new(DumbSignedEntityConfigProvider::new(
258                CardanoTransactionsSigningConfig::dummy(),
259                CardanoBlocksTransactionsSigningConfig::dummy(),
260                SignedEntityTypeDiscriminants::all(),
261            )),
262            ..SignerCertifierService::dumb_dependencies()
263        };
264
265        let time_point = TimePoint::new(1, 14, ChainPoint::dummy());
266        let mut previous_beacon_to_sign = certifier_service
267            .get_beacon_to_sign(time_point.clone())
268            .await
269            .unwrap()
270            .expect("There should be a beacon to sign since nothing is locked or signed");
271
272        loop {
273            signed_beacon_store
274                .mark_beacon_as_signed(&previous_beacon_to_sign)
275                .await
276                .unwrap();
277            let next_beacon_to_sign = certifier_service
278                .get_beacon_to_sign(time_point.clone())
279                .await
280                .unwrap();
281
282            if let Some(beacon) = next_beacon_to_sign {
283                assert!(
284                    SignedEntityTypeDiscriminants::from(
285                        &previous_beacon_to_sign.signed_entity_type
286                    ) < SignedEntityTypeDiscriminants::from(&beacon.signed_entity_type),
287                    "Beacon should follow SignedEntityTypeDiscriminants order"
288                );
289
290                previous_beacon_to_sign = beacon;
291            } else {
292                break;
293            }
294        }
295    }
296
297    #[tokio::test]
298    async fn draining_out_all_beacons_to_sign_doesnt_repeat_value() {
299        let signed_beacon_store = Arc::new(DumbSignedBeaconStore::default());
300        let certifier_service = SignerCertifierService {
301            signed_beacon_store: signed_beacon_store.clone(),
302            signed_entity_config_provider: Arc::new(DumbSignedEntityConfigProvider::new(
303                CardanoTransactionsSigningConfig::dummy(),
304                CardanoBlocksTransactionsSigningConfig::dummy(),
305                SignedEntityTypeDiscriminants::all(),
306            )),
307            ..SignerCertifierService::dumb_dependencies()
308        };
309
310        let mut all_signed_beacons = vec![];
311        while let Some(beacon_to_sign) = certifier_service
312            .get_beacon_to_sign(TimePoint::new(1, 14, ChainPoint::dummy()))
313            .await
314            .unwrap()
315        {
316            signed_beacon_store
317                .mark_beacon_as_signed(&beacon_to_sign)
318                .await
319                .unwrap();
320            all_signed_beacons.push(beacon_to_sign);
321        }
322
323        let mut dedup_signed_beacons = all_signed_beacons.clone();
324        dedup_signed_beacons.dedup();
325        assert!(
326            !all_signed_beacons.is_empty(),
327            "There should be at least one beacon to sign"
328        );
329        assert_eq!(
330            all_signed_beacons, dedup_signed_beacons,
331            "Beacon should not repeat"
332        );
333    }
334
335    #[tokio::test]
336    async fn compute_publish_single_signature_success_if_a_signature_was_issued() {
337        let protocol_message = {
338            let mut message = ProtocolMessage::new();
339            message.set_message_part(ProtocolMessagePartKey::SnapshotDigest, "digest".to_string());
340            message
341        };
342        let beacon_to_sign = BeaconToSign::new(
343            Epoch(1),
344            SignedEntityType::MithrilStakeDistribution(Epoch(4)),
345            Utc::now(),
346        );
347
348        let signed_beacons_store = Arc::new(DumbSignedBeaconStore::default());
349        let certifier_service = SignerCertifierService {
350            single_signer: {
351                let mut single_signer = MockSingleSigner::new();
352                single_signer
353                    .expect_compute_single_signature()
354                    .with(eq(protocol_message.clone()))
355                    .return_once(|_| Ok(Some(fake_data::single_signature(vec![1, 5, 12]))));
356                Arc::new(single_signer)
357            },
358            signature_publisher: {
359                let mut signature_publisher = MockSignaturePublisher::new();
360                signature_publisher
361                    .expect_publish()
362                    .with(
363                        eq(beacon_to_sign.signed_entity_type.clone()),
364                        eq(fake_data::single_signature(vec![1, 5, 12])),
365                        eq(protocol_message.clone()),
366                    )
367                    .returning(|_, _, _| Ok(()));
368                Arc::new(signature_publisher)
369            },
370            signed_beacon_store: signed_beacons_store.clone(),
371            ..SignerCertifierService::dumb_dependencies()
372        };
373
374        certifier_service
375            .compute_publish_single_signature(&beacon_to_sign, &protocol_message)
376            .await
377            .expect("Single signature should be computed and published");
378
379        let signed_beacons = signed_beacons_store.signed_beacons().await;
380        assert_eq!(
381            vec![beacon_to_sign.signed_entity_type.clone()],
382            signed_beacons
383        );
384    }
385
386    #[tokio::test]
387    async fn compute_publish_single_signature_success_but_dont_publish_if_no_signature_were_issued()
388    {
389        let protocol_message = {
390            let mut message = ProtocolMessage::new();
391            message.set_message_part(ProtocolMessagePartKey::SnapshotDigest, "digest".to_string());
392            message
393        };
394        let beacon_to_sign = BeaconToSign::new(
395            Epoch(1),
396            SignedEntityType::MithrilStakeDistribution(Epoch(4)),
397            Utc::now(),
398        );
399
400        let signed_beacons_store = Arc::new(DumbSignedBeaconStore::default());
401        let certifier_service = SignerCertifierService {
402            single_signer: {
403                let mut single_signer = MockSingleSigner::new();
404                single_signer
405                    .expect_compute_single_signature()
406                    .with(eq(protocol_message.clone()))
407                    .return_once(|_| Ok(None));
408                Arc::new(single_signer)
409            },
410            signature_publisher: {
411                let mut signature_publisher = MockSignaturePublisher::new();
412                signature_publisher.expect_publish().never();
413                Arc::new(signature_publisher)
414            },
415            signed_beacon_store: signed_beacons_store.clone(),
416            ..SignerCertifierService::dumb_dependencies()
417        };
418
419        certifier_service
420            .compute_publish_single_signature(&beacon_to_sign, &protocol_message)
421            .await
422            .unwrap();
423
424        let signed_beacons = signed_beacons_store.signed_beacons().await;
425        assert_eq!(
426            vec![beacon_to_sign.signed_entity_type.clone()],
427            signed_beacons
428        );
429    }
430
431    #[tokio::test]
432    async fn beacon_isnt_mark_as_signed_if_computing_signature_fails() {
433        let beacon_to_sign = BeaconToSign::new(
434            Epoch(1),
435            SignedEntityType::MithrilStakeDistribution(Epoch(4)),
436            Utc::now(),
437        );
438
439        let signed_beacons_store = Arc::new(DumbSignedBeaconStore::default());
440        let certifier_service = SignerCertifierService {
441            single_signer: {
442                let mut single_signer = MockSingleSigner::new();
443                single_signer
444                    .expect_compute_single_signature()
445                    .return_once(|_| Err(anyhow::anyhow!("error")));
446                Arc::new(single_signer)
447            },
448            signed_beacon_store: signed_beacons_store.clone(),
449            ..SignerCertifierService::dumb_dependencies()
450        };
451
452        certifier_service
453            .compute_publish_single_signature(&beacon_to_sign, &ProtocolMessage::new())
454            .await
455            .unwrap_err();
456
457        let signed_beacons = signed_beacons_store.signed_beacons().await;
458        assert_eq!(Vec::<SignedEntityType>::new(), signed_beacons);
459    }
460
461    #[tokio::test]
462    async fn beacon_isnt_mark_as_signed_if_publishing_signature_fails() {
463        let beacon_to_sign = BeaconToSign::new(
464            Epoch(1),
465            SignedEntityType::MithrilStakeDistribution(Epoch(4)),
466            Utc::now(),
467        );
468
469        let signed_beacons_store = Arc::new(DumbSignedBeaconStore::default());
470        let certifier_service = SignerCertifierService {
471            single_signer: {
472                let mut single_signer = MockSingleSigner::new();
473                single_signer
474                    .expect_compute_single_signature()
475                    .return_once(|_| Ok(Some(fake_data::single_signature(vec![1, 5, 12]))));
476                Arc::new(single_signer)
477            },
478            signature_publisher: {
479                let mut signature_publisher = MockSignaturePublisher::new();
480                signature_publisher
481                    .expect_publish()
482                    .return_once(|_, _, _| Err(anyhow::anyhow!("error")));
483                Arc::new(signature_publisher)
484            },
485            signed_beacon_store: signed_beacons_store.clone(),
486            ..SignerCertifierService::dumb_dependencies()
487        };
488
489        certifier_service
490            .compute_publish_single_signature(&beacon_to_sign, &ProtocolMessage::new())
491            .await
492            .unwrap_err();
493
494        let signed_beacons = signed_beacons_store.signed_beacons().await;
495        assert_eq!(Vec::<SignedEntityType>::new(), signed_beacons);
496    }
497
498    pub mod tests_tooling {
499        use mithril_common::entities::CardanoBlocksTransactionsSigningConfig;
500        use std::collections::BTreeSet;
501        use tokio::sync::RwLock;
502
503        use crate::test::TestLogger;
504
505        use super::*;
506
507        impl SignerCertifierService {
508            pub(crate) fn dumb_dependencies() -> Self {
509                Self {
510                    signed_beacon_store: Arc::new(DumbSignedBeaconStore::default()),
511                    signed_entity_config_provider: Arc::new(DumbSignedEntityConfigProvider::new(
512                        CardanoTransactionsSigningConfig::dummy(),
513                        CardanoBlocksTransactionsSigningConfig::dummy(),
514                        SignedEntityTypeDiscriminants::all(),
515                    )),
516                    signed_entity_type_lock: Arc::new(SignedEntityTypeLock::new()),
517                    single_signer: Arc::new(MockSingleSigner::new()),
518                    signature_publisher: Arc::new(MockSignaturePublisher::new()),
519                    logger: TestLogger::stdout(),
520                }
521            }
522        }
523
524        pub struct DumbSignedEntityConfigProvider {
525            config: SignedEntityConfig,
526        }
527
528        impl DumbSignedEntityConfigProvider {
529            pub fn new(
530                cardano_transactions_config: CardanoTransactionsSigningConfig,
531                cardano_blocks_transactions_config: CardanoBlocksTransactionsSigningConfig,
532                allowed_discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
533            ) -> Self {
534                Self {
535                    config: SignedEntityConfig {
536                        cardano_transactions_signing_config: Some(cardano_transactions_config),
537                        cardano_blocks_transactions_signing_config: Some(
538                            cardano_blocks_transactions_config,
539                        ),
540                        allowed_discriminants,
541                    },
542                }
543            }
544        }
545
546        #[async_trait]
547        impl SignedEntityConfigProvider for DumbSignedEntityConfigProvider {
548            async fn get(&self) -> StdResult<SignedEntityConfig> {
549                Ok(self.config.clone())
550            }
551        }
552
553        pub struct DumbSignedBeaconStore {
554            signed_beacons: RwLock<Vec<SignedEntityType>>,
555        }
556
557        impl DumbSignedBeaconStore {
558            pub fn new(signed_beacons: Vec<SignedEntityType>) -> Self {
559                Self {
560                    signed_beacons: RwLock::new(signed_beacons),
561                }
562            }
563
564            pub async fn signed_beacons(&self) -> Vec<SignedEntityType> {
565                self.signed_beacons.read().await.clone()
566            }
567        }
568
569        impl Default for DumbSignedBeaconStore {
570            fn default() -> Self {
571                Self::new(vec![])
572            }
573        }
574
575        #[async_trait]
576        impl SignedBeaconStore for DumbSignedBeaconStore {
577            async fn filter_out_already_signed_entities(
578                &self,
579                entities: Vec<SignedEntityType>,
580            ) -> StdResult<Vec<SignedEntityType>> {
581                let already_signed_entities = self.signed_beacons.read().await.clone();
582                Ok(entities
583                    .into_iter()
584                    .filter(|entity| !already_signed_entities.contains(entity))
585                    .collect())
586            }
587
588            async fn mark_beacon_as_signed(&self, beacon: &BeaconToSign) -> StdResult<()> {
589                let mut already_signed_entities = self.signed_beacons.write().await;
590                already_signed_entities.push(beacon.signed_entity_type.clone());
591                Ok(())
592            }
593        }
594    }
595}