mithril_signer/services/signable_builder/
signable_seed_builder.rs

1//! ## SignerSignableSeedBuilder
2//!
3//! This service is responsible for computing the seed protocol message
4//! that is used by the [SignableBuilder] to compute the final protocol message.
5//!
6use anyhow::Context;
7use async_trait::async_trait;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10
11use mithril_common::{
12    StdResult,
13    crypto_helper::{ProtocolAggregateVerificationKeyForConcatenation, ProtocolInitializer},
14    entities::{ProtocolMessagePartValue, ProtocolParameters, SignerWithStake},
15    protocol::SignerBuilder,
16    signable_builder::SignableSeedBuilder,
17};
18
19use crate::{services::EpochService, store::ProtocolInitializerStorer};
20
21/// SignableSeedBuilder signer implementation
22pub struct SignerSignableSeedBuilder {
23    epoch_service: Arc<RwLock<dyn EpochService>>,
24    protocol_initializer_store: Arc<dyn ProtocolInitializerStorer>,
25}
26
27impl SignerSignableSeedBuilder {
28    /// SignerSignableSeedBuilder factory
29    pub fn new(
30        epoch_service: Arc<RwLock<dyn EpochService>>,
31        protocol_initializer_store: Arc<dyn ProtocolInitializerStorer>,
32    ) -> Self {
33        Self {
34            epoch_service,
35            protocol_initializer_store,
36        }
37    }
38
39    fn compute_encode_avk(
40        &self,
41        protocol_initializer: ProtocolInitializer,
42        signers_with_stake: &[SignerWithStake],
43    ) -> StdResult<String> {
44        let signer_builder = SignerBuilder::new(
45            signers_with_stake,
46            &protocol_initializer.get_protocol_parameters().into(),
47        )
48        .with_context(|| "SignerSignableSeedBuilder can not compute aggregate verification key")?;
49
50        let encoded_avk: ProtocolAggregateVerificationKeyForConcatenation = signer_builder
51            .compute_aggregate_verification_key()
52            .to_concatenation_aggregate_verification_key()
53            .to_owned()
54            .into();
55        let encoded_avk = encoded_avk.to_json_hex().with_context(
56            || "SignerSignableSeedBuilder can not serialize aggregate verification key",
57        )?;
58
59        Ok(encoded_avk)
60    }
61}
62
63#[async_trait]
64impl SignableSeedBuilder for SignerSignableSeedBuilder {
65    async fn compute_next_aggregate_verification_key_for_concatenation(
66        &self,
67    ) -> StdResult<ProtocolMessagePartValue> {
68        let epoch_service = self.epoch_service.read().await;
69        let epoch = (*epoch_service).epoch_of_current_data()?;
70        let next_signer_retrieval_epoch = epoch.offset_to_next_signer_retrieval_epoch();
71        let next_protocol_initializer = self
72            .protocol_initializer_store
73            .get_protocol_initializer(next_signer_retrieval_epoch)
74            .await?
75            .with_context(|| {
76                format!("can not get protocol_initializer at epoch {next_signer_retrieval_epoch}")
77            })?;
78        let next_signers_with_stake = epoch_service.next_signers_with_stake().await?;
79        let next_aggregate_verification_key =
80            self.compute_encode_avk(next_protocol_initializer, &next_signers_with_stake)?;
81
82        Ok(next_aggregate_verification_key)
83    }
84
85    async fn compute_next_protocol_parameters(&self) -> StdResult<ProtocolMessagePartValue> {
86        let epoch_service = self.epoch_service.read().await;
87        let epoch = (*epoch_service).epoch_of_current_data()?;
88        let next_signer_retrieval_epoch = epoch.offset_to_next_signer_retrieval_epoch();
89        let next_protocol_initializer = self
90            .protocol_initializer_store
91            .get_protocol_initializer(next_signer_retrieval_epoch)
92            .await?
93            .with_context(|| {
94                format!("can not get protocol_initializer at epoch {next_signer_retrieval_epoch}")
95            })?;
96        let next_protocol_parameters: ProtocolParameters =
97            next_protocol_initializer.get_protocol_parameters().into();
98
99        Ok(next_protocol_parameters.compute_hash())
100    }
101
102    async fn compute_current_epoch(&self) -> StdResult<ProtocolMessagePartValue> {
103        let epoch_service = self.epoch_service.read().await;
104        let current_epoch = epoch_service.epoch_of_current_data()?.to_string();
105
106        Ok(current_epoch)
107    }
108}
109
110#[cfg(test)]
111mod tests {
112
113    use mithril_common::{
114        entities::Epoch, entities::ProtocolParameters, test::builder::MithrilFixtureBuilder,
115    };
116
117    use crate::{
118        services::mock_epoch_service::MockEpochServiceImpl, store::MockProtocolInitializerStorer,
119    };
120
121    use super::*;
122
123    struct MockDependencyInjector {
124        mock_epoch_service: MockEpochServiceImpl,
125        mock_protocol_initializer_store: MockProtocolInitializerStorer,
126    }
127
128    impl MockDependencyInjector {
129        fn new() -> MockDependencyInjector {
130            MockDependencyInjector {
131                mock_epoch_service: MockEpochServiceImpl::new(),
132                mock_protocol_initializer_store: MockProtocolInitializerStorer::new(),
133            }
134        }
135
136        fn build_signable_builder_service(self) -> SignerSignableSeedBuilder {
137            SignerSignableSeedBuilder::new(
138                Arc::new(RwLock::new(self.mock_epoch_service)),
139                Arc::new(self.mock_protocol_initializer_store),
140            )
141        }
142    }
143
144    #[tokio::test]
145    async fn test_compute_next_aggregate_verification_key_protocol_message_value() {
146        let epoch = Epoch(5);
147        let next_fixture = MithrilFixtureBuilder::default().with_signers(4).build();
148        let protocol_initializer = next_fixture.signers_fixture()[0].protocol_initializer.clone();
149        let next_signers_with_stake = next_fixture.signers_with_stake();
150        let mut mock_container = MockDependencyInjector::new();
151        mock_container.mock_epoch_service =
152            MockEpochServiceImpl::new_with_config(|mock_epoch_service| {
153                mock_epoch_service
154                    .expect_epoch_of_current_data()
155                    .return_once(move || Ok(epoch))
156                    .once();
157                mock_epoch_service
158                    .expect_next_signers_with_stake()
159                    .return_once(move || Ok(next_signers_with_stake))
160                    .once();
161            });
162        mock_container
163            .mock_protocol_initializer_store
164            .expect_get_protocol_initializer()
165            .return_once(move |_| Ok(Some(protocol_initializer)))
166            .once();
167        let signable_seed_builder = mock_container.build_signable_builder_service();
168
169        let next_aggregate_verification_key = signable_seed_builder
170            .compute_next_aggregate_verification_key_for_concatenation()
171            .await
172            .unwrap();
173
174        assert_eq!(
175            next_aggregate_verification_key,
176            next_fixture.compute_and_encode_concatenation_aggregate_verification_key()
177        );
178    }
179
180    #[tokio::test]
181    async fn test_compute_next_protocol_parameters_protocol_message_value() {
182        let epoch = Epoch(5);
183        let next_fixture = MithrilFixtureBuilder::default().with_signers(4).build();
184        let protocol_initializer = next_fixture.signers_fixture()[0].protocol_initializer.clone();
185        let protocol_parameters: ProtocolParameters =
186            protocol_initializer.get_protocol_parameters().into();
187        let expected_next_protocol_parameters = protocol_parameters.compute_hash();
188        let mut mock_container = MockDependencyInjector::new();
189        mock_container.mock_epoch_service =
190            MockEpochServiceImpl::new_with_config(|mock_epoch_service| {
191                mock_epoch_service
192                    .expect_epoch_of_current_data()
193                    .return_once(move || Ok(epoch))
194                    .once();
195            });
196        mock_container
197            .mock_protocol_initializer_store
198            .expect_get_protocol_initializer()
199            .return_once(move |_| Ok(Some(protocol_initializer)))
200            .once();
201        let signable_seed_builder = mock_container.build_signable_builder_service();
202
203        let next_protocol_parameters = signable_seed_builder
204            .compute_next_protocol_parameters()
205            .await
206            .unwrap();
207
208        assert_eq!(next_protocol_parameters, expected_next_protocol_parameters);
209    }
210
211    #[tokio::test]
212    async fn test_compute_current_epoch_protocol_message_value() {
213        let epoch = Epoch(5);
214        let expected_current_epoch = epoch.to_string();
215        let mut mock_container = MockDependencyInjector::new();
216        mock_container.mock_epoch_service =
217            MockEpochServiceImpl::new_with_config(|mock_epoch_service| {
218                mock_epoch_service
219                    .expect_epoch_of_current_data()
220                    .return_once(move || Ok(epoch))
221                    .once();
222            });
223        let signable_seed_builder = mock_container.build_signable_builder_service();
224
225        let current_epoch = signable_seed_builder.compute_current_epoch().await.unwrap();
226
227        assert_eq!(current_epoch, expected_current_epoch);
228    }
229}