mithril_aggregator/services/signer_registration/
leader.rs

1use std::sync::Arc;
2
3use anyhow::{anyhow, Context};
4use async_trait::async_trait;
5use tokio::sync::RwLock;
6
7use mithril_common::{
8    entities::{Epoch, Signer, SignerWithStake, StakeDistribution},
9    StdResult,
10};
11
12use crate::{SignerRegistrationVerifier, VerificationKeyStorer};
13
14use super::{
15    SignerRecorder, SignerRegisterer, SignerRegistrationError, SignerRegistrationRound,
16    SignerRegistrationRoundOpener, SignerSynchronizer,
17};
18
19/// A [MithrilSignerRegistrationLeader] supports signer registrations in a leader aggregator
20pub struct MithrilSignerRegistrationLeader {
21    /// Current signer registration round
22    current_round: RwLock<Option<SignerRegistrationRound>>,
23
24    /// Verification key store
25    verification_key_store: Arc<dyn VerificationKeyStorer>,
26
27    /// Signer recorder
28    signer_recorder: Arc<dyn SignerRecorder>,
29
30    /// Signer registration verifier
31    signer_registration_verifier: Arc<dyn SignerRegistrationVerifier>,
32}
33
34impl MithrilSignerRegistrationLeader {
35    /// MithrilSignerRegistererLeader factory
36    pub fn new(
37        verification_key_store: Arc<dyn VerificationKeyStorer>,
38        signer_recorder: Arc<dyn SignerRecorder>,
39        signer_registration_verifier: Arc<dyn SignerRegistrationVerifier>,
40    ) -> Self {
41        Self {
42            current_round: RwLock::new(None),
43            verification_key_store,
44            signer_recorder,
45            signer_registration_verifier,
46        }
47    }
48
49    #[cfg(test)]
50    pub(crate) async fn get_current_round(&self) -> Option<SignerRegistrationRound> {
51        self.current_round.read().await.as_ref().cloned()
52    }
53}
54
55#[async_trait]
56impl SignerRegistrationRoundOpener for MithrilSignerRegistrationLeader {
57    async fn open_registration_round(
58        &self,
59        registration_epoch: Epoch,
60        stake_distribution: StakeDistribution,
61    ) -> StdResult<()> {
62        let mut current_round = self.current_round.write().await;
63        *current_round = Some(SignerRegistrationRound {
64            epoch: registration_epoch,
65            stake_distribution,
66        });
67
68        Ok(())
69    }
70
71    async fn close_registration_round(&self) -> StdResult<()> {
72        let mut current_round = self.current_round.write().await;
73        *current_round = None;
74
75        Ok(())
76    }
77}
78
79#[async_trait]
80impl SignerRegisterer for MithrilSignerRegistrationLeader {
81    async fn register_signer(
82        &self,
83        epoch: Epoch,
84        signer: &Signer,
85    ) -> Result<SignerWithStake, SignerRegistrationError> {
86        let registration_round = self.current_round.read().await;
87        let registration_round = registration_round
88            .as_ref()
89            .ok_or(SignerRegistrationError::RegistrationRoundNotYetOpened)?;
90        if registration_round.epoch != epoch {
91            return Err(SignerRegistrationError::RegistrationRoundUnexpectedEpoch {
92                current_round_epoch: registration_round.epoch,
93                received_epoch: epoch,
94            });
95        }
96
97        let signer_save = self
98            .signer_registration_verifier
99            .verify(signer, &registration_round.stake_distribution)
100            .await
101            .map_err(|e| SignerRegistrationError::FailedSignerRegistration(anyhow!(e)))?;
102
103        self.signer_recorder
104            .record_signer_registration(signer_save.party_id.clone())
105            .await
106            .map_err(|e| SignerRegistrationError::FailedSignerRecorder(e.to_string()))?;
107
108        match self
109            .verification_key_store
110            .save_verification_key(registration_round.epoch, signer_save.clone())
111            .await
112            .with_context(|| {
113                format!(
114                    "VerificationKeyStorer can not save verification keys for party_id: '{}' for epoch: '{}'",
115                    signer_save.party_id,
116                    registration_round.epoch
117                )
118            })
119            .map_err(|e| SignerRegistrationError::Store(anyhow!(e)))?
120        {
121            Some(_) => Err(SignerRegistrationError::ExistingSigner(Box::new(
122                signer_save,
123            ))),
124            None => Ok(signer_save),
125        }
126    }
127
128    async fn get_current_round(&self) -> Option<SignerRegistrationRound> {
129        self.current_round.read().await.as_ref().cloned()
130    }
131}
132
133#[async_trait]
134impl SignerSynchronizer for MithrilSignerRegistrationLeader {
135    async fn can_synchronize_signers(
136        &self,
137        _epoch: Epoch,
138    ) -> Result<bool, SignerRegistrationError> {
139        Ok(false)
140    }
141
142    async fn synchronize_all_signers(&self) -> Result<(), SignerRegistrationError> {
143        Err(SignerRegistrationError::SignerSynchronizationUnavailableOnLeaderAggregator)
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use std::{collections::HashMap, sync::Arc};
150
151    use mithril_common::{
152        entities::{Epoch, Signer, SignerWithStake},
153        test_utils::MithrilFixtureBuilder,
154    };
155
156    use crate::{
157        database::{repository::SignerRegistrationStore, test_helper::main_db_connection},
158        services::{MockSignerRecorder, MockSignerRegistrationVerifier, SignerSynchronizer},
159        MithrilSignerRegistrationLeader, SignerRegisterer, SignerRegistrationRoundOpener,
160        VerificationKeyStorer,
161    };
162
163    use test_utils::*;
164
165    use super::*;
166
167    mod test_utils {
168        use super::*;
169
170        /// MithrilSignerRegistrationLeaderBuilder is a test builder for [MithrilSignerRegistrationFollower]
171        pub struct MithrilSignerRegistrationLeaderBuilder {
172            signer_recorder: Arc<dyn SignerRecorder>,
173            signer_registration_verifier: Arc<dyn SignerRegistrationVerifier>,
174            verification_key_store: Arc<dyn VerificationKeyStorer>,
175        }
176
177        impl Default for MithrilSignerRegistrationLeaderBuilder {
178            fn default() -> Self {
179                Self {
180                    signer_recorder: Arc::new(MockSignerRecorder::new()),
181                    signer_registration_verifier: Arc::new(MockSignerRegistrationVerifier::new()),
182                    verification_key_store: Arc::new(SignerRegistrationStore::new(
183                        Arc::new(main_db_connection().unwrap()),
184                        None,
185                    )),
186                }
187            }
188        }
189
190        impl MithrilSignerRegistrationLeaderBuilder {
191            pub fn with_signer_recorder(self, signer_recorder: Arc<dyn SignerRecorder>) -> Self {
192                Self {
193                    signer_recorder,
194                    ..self
195                }
196            }
197
198            pub fn with_signer_registration_verifier(
199                self,
200                signer_registration_verifier: Arc<dyn SignerRegistrationVerifier>,
201            ) -> Self {
202                Self {
203                    signer_registration_verifier,
204                    ..self
205                }
206            }
207
208            pub fn build(self) -> MithrilSignerRegistrationLeader {
209                MithrilSignerRegistrationLeader {
210                    current_round: RwLock::new(None),
211                    verification_key_store: self.verification_key_store,
212                    signer_recorder: self.signer_recorder,
213                    signer_registration_verifier: self.signer_registration_verifier,
214                }
215            }
216        }
217    }
218
219    #[tokio::test]
220    async fn can_register_signer_if_registration_round_is_opened_with_operational_certificate() {
221        let registration_epoch = Epoch(1);
222        let fixture = MithrilFixtureBuilder::default().with_signers(5).build();
223        let signer_to_register: Signer = fixture.signers()[0].to_owned();
224        let stake_distribution = fixture.stake_distribution();
225        let signer_registration_leader = MithrilSignerRegistrationLeaderBuilder::default()
226            .with_signer_recorder({
227                let mut signer_recorder = MockSignerRecorder::new();
228                signer_recorder
229                    .expect_record_signer_registration()
230                    .returning(|_| Ok(()))
231                    .once();
232
233                Arc::new(signer_recorder)
234            })
235            .with_signer_registration_verifier({
236                let mut signer_registration_verifier = MockSignerRegistrationVerifier::new();
237                signer_registration_verifier
238                    .expect_verify()
239                    .returning(|signer, _| Ok(SignerWithStake::from_signer(signer.to_owned(), 123)))
240                    .once();
241
242                Arc::new(signer_registration_verifier)
243            })
244            .build();
245
246        signer_registration_leader
247            .open_registration_round(registration_epoch, stake_distribution)
248            .await
249            .expect("signer registration round opening should not fail");
250
251        signer_registration_leader
252            .register_signer(registration_epoch, &signer_to_register)
253            .await
254            .expect("signer registration should not fail");
255
256        let registered_signers = &signer_registration_leader
257            .verification_key_store
258            .get_verification_keys(registration_epoch)
259            .await
260            .expect("registered signers retrieval should not fail");
261
262        assert_eq!(
263            &Some(HashMap::from([(
264                signer_to_register.party_id.clone(),
265                signer_to_register
266            )])),
267            registered_signers
268        );
269    }
270
271    #[tokio::test]
272    async fn can_register_signer_if_registration_round_is_opened_without_operational_certificate() {
273        let registration_epoch = Epoch(1);
274        let fixture = MithrilFixtureBuilder::default()
275            .with_signers(5)
276            .disable_signers_certification()
277            .build();
278        let signer_to_register: Signer = fixture.signers()[0].to_owned();
279        let stake_distribution = fixture.stake_distribution();
280        let signer_registration_leader = MithrilSignerRegistrationLeaderBuilder::default()
281            .with_signer_recorder({
282                let mut signer_recorder = MockSignerRecorder::new();
283                signer_recorder
284                    .expect_record_signer_registration()
285                    .returning(|_| Ok(()))
286                    .once();
287
288                Arc::new(signer_recorder)
289            })
290            .with_signer_registration_verifier({
291                let mut signer_registration_verifier = MockSignerRegistrationVerifier::new();
292                signer_registration_verifier
293                    .expect_verify()
294                    .returning(|signer, _| Ok(SignerWithStake::from_signer(signer.to_owned(), 123)))
295                    .once();
296
297                Arc::new(signer_registration_verifier)
298            })
299            .build();
300
301        signer_registration_leader
302            .open_registration_round(registration_epoch, stake_distribution)
303            .await
304            .expect("signer registration round opening should not fail");
305
306        signer_registration_leader
307            .register_signer(registration_epoch, &signer_to_register)
308            .await
309            .expect("signer registration should not fail");
310
311        let registered_signers = &signer_registration_leader
312            .verification_key_store
313            .get_verification_keys(registration_epoch)
314            .await
315            .expect("registered signers retrieval should not fail");
316
317        assert_eq!(
318            &Some(HashMap::from([(
319                signer_to_register.party_id.clone(),
320                signer_to_register
321            )])),
322            registered_signers
323        );
324    }
325
326    #[tokio::test]
327    async fn cant_register_signer_if_registration_round_is_not_opened() {
328        let registration_epoch = Epoch(1);
329        let fixture = MithrilFixtureBuilder::default().with_signers(5).build();
330        let signer_to_register: Signer = fixture.signers()[0].to_owned();
331        let signer_registration_leader = MithrilSignerRegistrationLeaderBuilder::default().build();
332
333        signer_registration_leader
334            .register_signer(registration_epoch, &signer_to_register)
335            .await
336            .expect_err("signer registration should fail if no round opened");
337    }
338
339    #[tokio::test]
340    async fn synchronize_all_signers_always_fails() {
341        let signer_registration_leader = MithrilSignerRegistrationLeaderBuilder::default().build();
342
343        signer_registration_leader
344            .synchronize_all_signers()
345            .await
346            .expect_err("synchronize_signers");
347    }
348}