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        CardanoTransactionsSigningConfig, ChainPoint, Epoch, ProtocolMessagePartKey,
157        SignedEntityTypeDiscriminants,
158    };
159    use mithril_common::test_utils::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                SignedEntityTypeDiscriminants::all(),
176            )),
177            ..SignerCertifierService::dumb_dependencies()
178        };
179
180        let beacon_to_sign = certifier_service
181            .get_beacon_to_sign(TimePoint::new(1, 14, ChainPoint::dummy()))
182            .await
183            .unwrap();
184        assert_eq!(beacon_to_sign, None);
185    }
186
187    #[tokio::test]
188    async fn only_one_unlocked_and_not_yet_signed_yield_a_beacon_to_sign() {
189        let locker = Arc::new(SignedEntityTypeLock::new());
190        for signed_entity_type in SignedEntityTypeDiscriminants::all().into_iter().skip(1) {
191            locker.lock(signed_entity_type).await;
192        }
193        let certifier_service = SignerCertifierService {
194            signed_entity_type_lock: locker.clone(),
195            signed_entity_config_provider: Arc::new(DumbSignedEntityConfigProvider::new(
196                CardanoTransactionsSigningConfig::dummy(),
197                SignedEntityTypeDiscriminants::all(),
198            )),
199            ..SignerCertifierService::dumb_dependencies()
200        };
201
202        let beacon_to_sign = certifier_service
203            .get_beacon_to_sign(TimePoint::new(3, 14, ChainPoint::dummy()))
204            .await
205            .unwrap();
206        let signed_discriminant: Option<SignedEntityTypeDiscriminants> =
207            beacon_to_sign.map(|b| b.signed_entity_type.into());
208
209        assert_eq!(
210            SignedEntityTypeDiscriminants::all().first().cloned(),
211            signed_discriminant
212        );
213    }
214
215    #[tokio::test]
216    async fn if_already_signed_a_beacon_is_not_returned_anymore() {
217        let signed_beacon_store = Arc::new(DumbSignedBeaconStore::default());
218        let certifier_service = SignerCertifierService {
219            signed_beacon_store: signed_beacon_store.clone(),
220            signed_entity_config_provider: Arc::new(DumbSignedEntityConfigProvider::new(
221                CardanoTransactionsSigningConfig::dummy(),
222                SignedEntityTypeDiscriminants::all(),
223            )),
224            ..SignerCertifierService::dumb_dependencies()
225        };
226
227        let time_point = TimePoint::new(1, 14, ChainPoint::dummy());
228        let first_beacon_to_sign = certifier_service
229            .get_beacon_to_sign(time_point.clone())
230            .await
231            .unwrap()
232            .unwrap();
233        signed_beacon_store
234            .mark_beacon_as_signed(&first_beacon_to_sign.clone())
235            .await
236            .unwrap();
237        let second_beacon_to_sign = certifier_service
238            .get_beacon_to_sign(time_point)
239            .await
240            .unwrap()
241            .unwrap();
242
243        assert_ne!(
244            first_beacon_to_sign.signed_entity_type,
245            second_beacon_to_sign.signed_entity_type
246        );
247    }
248
249    #[tokio::test]
250    async fn draining_out_all_beacons_to_sign_use_signed_entity_discriminant_order() {
251        let signed_beacon_store = Arc::new(DumbSignedBeaconStore::default());
252        let certifier_service = SignerCertifierService {
253            signed_beacon_store: signed_beacon_store.clone(),
254            signed_entity_config_provider: Arc::new(DumbSignedEntityConfigProvider::new(
255                CardanoTransactionsSigningConfig::dummy(),
256                SignedEntityTypeDiscriminants::all(),
257            )),
258            ..SignerCertifierService::dumb_dependencies()
259        };
260
261        let time_point = TimePoint::new(1, 14, ChainPoint::dummy());
262        let mut previous_beacon_to_sign = certifier_service
263            .get_beacon_to_sign(time_point.clone())
264            .await
265            .unwrap()
266            .expect("There should be a beacon to sign since nothing is locked or signed");
267
268        loop {
269            signed_beacon_store
270                .mark_beacon_as_signed(&previous_beacon_to_sign)
271                .await
272                .unwrap();
273            let next_beacon_to_sign = certifier_service
274                .get_beacon_to_sign(time_point.clone())
275                .await
276                .unwrap();
277
278            if let Some(beacon) = next_beacon_to_sign {
279                assert!(
280                    SignedEntityTypeDiscriminants::from(
281                        &previous_beacon_to_sign.signed_entity_type
282                    ) < SignedEntityTypeDiscriminants::from(&beacon.signed_entity_type),
283                    "Beacon should follow SignedEntityTypeDiscriminants order"
284                );
285
286                previous_beacon_to_sign = beacon;
287            } else {
288                break;
289            }
290        }
291    }
292
293    #[tokio::test]
294    async fn draining_out_all_beacons_to_sign_doesnt_repeat_value() {
295        let signed_beacon_store = Arc::new(DumbSignedBeaconStore::default());
296        let certifier_service = SignerCertifierService {
297            signed_beacon_store: signed_beacon_store.clone(),
298            signed_entity_config_provider: Arc::new(DumbSignedEntityConfigProvider::new(
299                CardanoTransactionsSigningConfig::dummy(),
300                SignedEntityTypeDiscriminants::all(),
301            )),
302            ..SignerCertifierService::dumb_dependencies()
303        };
304
305        let mut all_signed_beacons = vec![];
306        while let Some(beacon_to_sign) = certifier_service
307            .get_beacon_to_sign(TimePoint::new(1, 14, ChainPoint::dummy()))
308            .await
309            .unwrap()
310        {
311            signed_beacon_store
312                .mark_beacon_as_signed(&beacon_to_sign)
313                .await
314                .unwrap();
315            all_signed_beacons.push(beacon_to_sign);
316        }
317
318        let mut dedup_signed_beacons = all_signed_beacons.clone();
319        dedup_signed_beacons.dedup();
320        assert!(
321            !all_signed_beacons.is_empty(),
322            "There should be at least one beacon to sign"
323        );
324        assert_eq!(
325            all_signed_beacons, dedup_signed_beacons,
326            "Beacon should not repeat"
327        );
328    }
329
330    #[tokio::test]
331    async fn compute_publish_single_signature_success_if_a_signature_was_issued() {
332        let protocol_message = {
333            let mut message = ProtocolMessage::new();
334            message.set_message_part(ProtocolMessagePartKey::SnapshotDigest, "digest".to_string());
335            message
336        };
337        let beacon_to_sign = BeaconToSign::new(
338            Epoch(1),
339            SignedEntityType::MithrilStakeDistribution(Epoch(4)),
340            Utc::now(),
341        );
342
343        let signed_beacons_store = Arc::new(DumbSignedBeaconStore::default());
344        let certifier_service = SignerCertifierService {
345            single_signer: {
346                let mut single_signer = MockSingleSigner::new();
347                single_signer
348                    .expect_compute_single_signature()
349                    .with(eq(protocol_message.clone()))
350                    .return_once(|_| Ok(Some(fake_data::single_signature(vec![1, 5, 12]))));
351                Arc::new(single_signer)
352            },
353            signature_publisher: {
354                let mut signature_publisher = MockSignaturePublisher::new();
355                signature_publisher
356                    .expect_publish()
357                    .with(
358                        eq(beacon_to_sign.signed_entity_type.clone()),
359                        eq(fake_data::single_signature(vec![1, 5, 12])),
360                        eq(protocol_message.clone()),
361                    )
362                    .returning(|_, _, _| Ok(()));
363                Arc::new(signature_publisher)
364            },
365            signed_beacon_store: signed_beacons_store.clone(),
366            ..SignerCertifierService::dumb_dependencies()
367        };
368
369        certifier_service
370            .compute_publish_single_signature(&beacon_to_sign, &protocol_message)
371            .await
372            .expect("Single signature should be computed and published");
373
374        let signed_beacons = signed_beacons_store.signed_beacons().await;
375        assert_eq!(
376            vec![beacon_to_sign.signed_entity_type.clone()],
377            signed_beacons
378        );
379    }
380
381    #[tokio::test]
382    async fn compute_publish_single_signature_success_but_dont_publish_if_no_signature_were_issued()
383    {
384        let protocol_message = {
385            let mut message = ProtocolMessage::new();
386            message.set_message_part(ProtocolMessagePartKey::SnapshotDigest, "digest".to_string());
387            message
388        };
389        let beacon_to_sign = BeaconToSign::new(
390            Epoch(1),
391            SignedEntityType::MithrilStakeDistribution(Epoch(4)),
392            Utc::now(),
393        );
394
395        let signed_beacons_store = Arc::new(DumbSignedBeaconStore::default());
396        let certifier_service = SignerCertifierService {
397            single_signer: {
398                let mut single_signer = MockSingleSigner::new();
399                single_signer
400                    .expect_compute_single_signature()
401                    .with(eq(protocol_message.clone()))
402                    .return_once(|_| Ok(None));
403                Arc::new(single_signer)
404            },
405            signature_publisher: {
406                let mut signature_publisher = MockSignaturePublisher::new();
407                signature_publisher.expect_publish().never();
408                Arc::new(signature_publisher)
409            },
410            signed_beacon_store: signed_beacons_store.clone(),
411            ..SignerCertifierService::dumb_dependencies()
412        };
413
414        certifier_service
415            .compute_publish_single_signature(&beacon_to_sign, &protocol_message)
416            .await
417            .unwrap();
418
419        let signed_beacons = signed_beacons_store.signed_beacons().await;
420        assert_eq!(
421            vec![beacon_to_sign.signed_entity_type.clone()],
422            signed_beacons
423        );
424    }
425
426    #[tokio::test]
427    async fn beacon_isnt_mark_as_signed_if_computing_signature_fails() {
428        let beacon_to_sign = BeaconToSign::new(
429            Epoch(1),
430            SignedEntityType::MithrilStakeDistribution(Epoch(4)),
431            Utc::now(),
432        );
433
434        let signed_beacons_store = Arc::new(DumbSignedBeaconStore::default());
435        let certifier_service = SignerCertifierService {
436            single_signer: {
437                let mut single_signer = MockSingleSigner::new();
438                single_signer
439                    .expect_compute_single_signature()
440                    .return_once(|_| Err(anyhow::anyhow!("error")));
441                Arc::new(single_signer)
442            },
443            signed_beacon_store: signed_beacons_store.clone(),
444            ..SignerCertifierService::dumb_dependencies()
445        };
446
447        certifier_service
448            .compute_publish_single_signature(&beacon_to_sign, &ProtocolMessage::new())
449            .await
450            .unwrap_err();
451
452        let signed_beacons = signed_beacons_store.signed_beacons().await;
453        assert_eq!(Vec::<SignedEntityType>::new(), signed_beacons);
454    }
455
456    #[tokio::test]
457    async fn beacon_isnt_mark_as_signed_if_publishing_signature_fails() {
458        let beacon_to_sign = BeaconToSign::new(
459            Epoch(1),
460            SignedEntityType::MithrilStakeDistribution(Epoch(4)),
461            Utc::now(),
462        );
463
464        let signed_beacons_store = Arc::new(DumbSignedBeaconStore::default());
465        let certifier_service = SignerCertifierService {
466            single_signer: {
467                let mut single_signer = MockSingleSigner::new();
468                single_signer
469                    .expect_compute_single_signature()
470                    .return_once(|_| Ok(Some(fake_data::single_signature(vec![1, 5, 12]))));
471                Arc::new(single_signer)
472            },
473            signature_publisher: {
474                let mut signature_publisher = MockSignaturePublisher::new();
475                signature_publisher
476                    .expect_publish()
477                    .return_once(|_, _, _| Err(anyhow::anyhow!("error")));
478                Arc::new(signature_publisher)
479            },
480            signed_beacon_store: signed_beacons_store.clone(),
481            ..SignerCertifierService::dumb_dependencies()
482        };
483
484        certifier_service
485            .compute_publish_single_signature(&beacon_to_sign, &ProtocolMessage::new())
486            .await
487            .unwrap_err();
488
489        let signed_beacons = signed_beacons_store.signed_beacons().await;
490        assert_eq!(Vec::<SignedEntityType>::new(), signed_beacons);
491    }
492
493    pub mod tests_tooling {
494        use std::collections::BTreeSet;
495        use tokio::sync::RwLock;
496
497        use crate::test_tools::TestLogger;
498
499        use super::*;
500
501        impl SignerCertifierService {
502            pub(crate) fn dumb_dependencies() -> Self {
503                Self {
504                    signed_beacon_store: Arc::new(DumbSignedBeaconStore::default()),
505                    signed_entity_config_provider: Arc::new(DumbSignedEntityConfigProvider::new(
506                        CardanoTransactionsSigningConfig::dummy(),
507                        SignedEntityTypeDiscriminants::all(),
508                    )),
509                    signed_entity_type_lock: Arc::new(SignedEntityTypeLock::new()),
510                    single_signer: Arc::new(MockSingleSigner::new()),
511                    signature_publisher: Arc::new(MockSignaturePublisher::new()),
512                    logger: TestLogger::stdout(),
513                }
514            }
515        }
516
517        pub struct DumbSignedEntityConfigProvider {
518            config: SignedEntityConfig,
519        }
520
521        impl DumbSignedEntityConfigProvider {
522            pub fn new(
523                config: CardanoTransactionsSigningConfig,
524                allowed_discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
525            ) -> Self {
526                Self {
527                    config: SignedEntityConfig {
528                        cardano_transactions_signing_config: config,
529                        allowed_discriminants,
530                    },
531                }
532            }
533        }
534
535        #[async_trait]
536        impl SignedEntityConfigProvider for DumbSignedEntityConfigProvider {
537            async fn get(&self) -> StdResult<SignedEntityConfig> {
538                Ok(self.config.clone())
539            }
540        }
541
542        pub struct DumbSignedBeaconStore {
543            signed_beacons: RwLock<Vec<SignedEntityType>>,
544        }
545
546        impl DumbSignedBeaconStore {
547            pub fn new(signed_beacons: Vec<SignedEntityType>) -> Self {
548                Self {
549                    signed_beacons: RwLock::new(signed_beacons),
550                }
551            }
552
553            pub async fn signed_beacons(&self) -> Vec<SignedEntityType> {
554                self.signed_beacons.read().await.clone()
555            }
556        }
557
558        impl Default for DumbSignedBeaconStore {
559            fn default() -> Self {
560                Self::new(vec![])
561            }
562        }
563
564        #[async_trait]
565        impl SignedBeaconStore for DumbSignedBeaconStore {
566            async fn filter_out_already_signed_entities(
567                &self,
568                entities: Vec<SignedEntityType>,
569            ) -> StdResult<Vec<SignedEntityType>> {
570                let already_signed_entities = self.signed_beacons.read().await.clone();
571                Ok(entities
572                    .into_iter()
573                    .filter(|entity| !already_signed_entities.contains(entity))
574                    .collect())
575            }
576
577            async fn mark_beacon_as_signed(&self, beacon: &BeaconToSign) -> StdResult<()> {
578                let mut already_signed_entities = self.signed_beacons.write().await;
579                already_signed_entities.push(beacon.signed_entity_type.clone());
580                Ok(())
581            }
582        }
583    }
584}