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::{anyhow, Context};
7use async_trait::async_trait;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10
11use mithril_common::{
12    crypto_helper::ProtocolInitializer,
13    entities::{ProtocolMessagePartValue, ProtocolParameters, SignerWithStake},
14    protocol::SignerBuilder,
15    signable_builder::SignableSeedBuilder,
16    StdResult,
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 = signer_builder
51            .compute_aggregate_verification_key()
52            .to_json_hex()
53            .with_context(|| {
54                "SignerSignableSeedBuilder can not serialize aggregate verification key"
55            })?;
56
57        Ok(encoded_avk)
58    }
59}
60
61#[async_trait]
62impl SignableSeedBuilder for SignerSignableSeedBuilder {
63    async fn compute_next_aggregate_verification_key(&self) -> StdResult<ProtocolMessagePartValue> {
64        let epoch_service = self.epoch_service.read().await;
65        let epoch = (*epoch_service).epoch_of_current_data()?;
66        let next_signer_retrieval_epoch = epoch.offset_to_next_signer_retrieval_epoch();
67        let next_protocol_initializer = self
68            .protocol_initializer_store
69            .get_protocol_initializer(next_signer_retrieval_epoch)
70            .await?
71            .ok_or_else(|| {
72                anyhow!("can not get protocol_initializer at epoch {next_signer_retrieval_epoch}")
73            })?;
74        let next_signers_with_stake = epoch_service.next_signers_with_stake().await?;
75        let next_aggregate_verification_key =
76            self.compute_encode_avk(next_protocol_initializer, &next_signers_with_stake)?;
77
78        Ok(next_aggregate_verification_key)
79    }
80
81    async fn compute_next_protocol_parameters(&self) -> StdResult<ProtocolMessagePartValue> {
82        let epoch_service = self.epoch_service.read().await;
83        let epoch = (*epoch_service).epoch_of_current_data()?;
84        let next_signer_retrieval_epoch = epoch.offset_to_next_signer_retrieval_epoch();
85        let next_protocol_initializer = self
86            .protocol_initializer_store
87            .get_protocol_initializer(next_signer_retrieval_epoch)
88            .await?
89            .ok_or_else(|| {
90                anyhow!("can not get protocol_initializer at epoch {next_signer_retrieval_epoch}")
91            })?;
92        let next_protocol_parameters: ProtocolParameters =
93            next_protocol_initializer.get_protocol_parameters().into();
94
95        Ok(next_protocol_parameters.compute_hash())
96    }
97
98    async fn compute_current_epoch(&self) -> StdResult<ProtocolMessagePartValue> {
99        let epoch_service = self.epoch_service.read().await;
100        let current_epoch = epoch_service.epoch_of_current_data()?.to_string();
101
102        Ok(current_epoch)
103    }
104}
105
106#[cfg(test)]
107mod tests {
108
109    use mithril_common::{
110        entities::Epoch, entities::ProtocolParameters, test_utils::MithrilFixtureBuilder,
111    };
112
113    use crate::{
114        services::mock_epoch_service::MockEpochServiceImpl, store::MockProtocolInitializerStorer,
115    };
116
117    use super::*;
118
119    struct MockDependencyInjector {
120        mock_epoch_service: MockEpochServiceImpl,
121        mock_protocol_initializer_store: MockProtocolInitializerStorer,
122    }
123
124    impl MockDependencyInjector {
125        fn new() -> MockDependencyInjector {
126            MockDependencyInjector {
127                mock_epoch_service: MockEpochServiceImpl::new(),
128                mock_protocol_initializer_store: MockProtocolInitializerStorer::new(),
129            }
130        }
131
132        fn build_signable_builder_service(self) -> SignerSignableSeedBuilder {
133            SignerSignableSeedBuilder::new(
134                Arc::new(RwLock::new(self.mock_epoch_service)),
135                Arc::new(self.mock_protocol_initializer_store),
136            )
137        }
138    }
139
140    #[tokio::test]
141    async fn test_compute_next_aggregate_verification_key_protocol_message_value() {
142        let epoch = Epoch(5);
143        let next_fixture = MithrilFixtureBuilder::default().with_signers(4).build();
144        let protocol_initializer = next_fixture.signers_fixture()[0]
145            .protocol_initializer
146            .clone();
147        let next_signers_with_stake = next_fixture.signers_with_stake();
148        let mut mock_container = MockDependencyInjector::new();
149        mock_container.mock_epoch_service =
150            MockEpochServiceImpl::new_with_config(|mock_epoch_service| {
151                mock_epoch_service
152                    .expect_epoch_of_current_data()
153                    .return_once(move || Ok(epoch))
154                    .once();
155                mock_epoch_service
156                    .expect_next_signers_with_stake()
157                    .return_once(move || Ok(next_signers_with_stake))
158                    .once();
159            });
160        mock_container
161            .mock_protocol_initializer_store
162            .expect_get_protocol_initializer()
163            .return_once(move |_| Ok(Some(protocol_initializer)))
164            .once();
165        let signable_seed_builder = mock_container.build_signable_builder_service();
166
167        let next_aggregate_verification_key = signable_seed_builder
168            .compute_next_aggregate_verification_key()
169            .await
170            .unwrap();
171
172        assert_eq!(
173            next_aggregate_verification_key,
174            next_fixture.compute_and_encode_avk()
175        );
176    }
177
178    #[tokio::test]
179    async fn test_compute_next_protocol_parameters_protocol_message_value() {
180        let epoch = Epoch(5);
181        let next_fixture = MithrilFixtureBuilder::default().with_signers(4).build();
182        let protocol_initializer = next_fixture.signers_fixture()[0]
183            .protocol_initializer
184            .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}