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, anyhow};
7use async_trait::async_trait;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10
11use mithril_common::{
12    StdResult,
13    crypto_helper::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 = 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].protocol_initializer.clone();
145        let next_signers_with_stake = next_fixture.signers_with_stake();
146        let mut mock_container = MockDependencyInjector::new();
147        mock_container.mock_epoch_service =
148            MockEpochServiceImpl::new_with_config(|mock_epoch_service| {
149                mock_epoch_service
150                    .expect_epoch_of_current_data()
151                    .return_once(move || Ok(epoch))
152                    .once();
153                mock_epoch_service
154                    .expect_next_signers_with_stake()
155                    .return_once(move || Ok(next_signers_with_stake))
156                    .once();
157            });
158        mock_container
159            .mock_protocol_initializer_store
160            .expect_get_protocol_initializer()
161            .return_once(move |_| Ok(Some(protocol_initializer)))
162            .once();
163        let signable_seed_builder = mock_container.build_signable_builder_service();
164
165        let next_aggregate_verification_key = signable_seed_builder
166            .compute_next_aggregate_verification_key()
167            .await
168            .unwrap();
169
170        assert_eq!(
171            next_aggregate_verification_key,
172            next_fixture.compute_and_encode_avk()
173        );
174    }
175
176    #[tokio::test]
177    async fn test_compute_next_protocol_parameters_protocol_message_value() {
178        let epoch = Epoch(5);
179        let next_fixture = MithrilFixtureBuilder::default().with_signers(4).build();
180        let protocol_initializer = next_fixture.signers_fixture()[0].protocol_initializer.clone();
181        let protocol_parameters: ProtocolParameters =
182            protocol_initializer.get_protocol_parameters().into();
183        let expected_next_protocol_parameters = protocol_parameters.compute_hash();
184        let mut mock_container = MockDependencyInjector::new();
185        mock_container.mock_epoch_service =
186            MockEpochServiceImpl::new_with_config(|mock_epoch_service| {
187                mock_epoch_service
188                    .expect_epoch_of_current_data()
189                    .return_once(move || Ok(epoch))
190                    .once();
191            });
192        mock_container
193            .mock_protocol_initializer_store
194            .expect_get_protocol_initializer()
195            .return_once(move |_| Ok(Some(protocol_initializer)))
196            .once();
197        let signable_seed_builder = mock_container.build_signable_builder_service();
198
199        let next_protocol_parameters = signable_seed_builder
200            .compute_next_protocol_parameters()
201            .await
202            .unwrap();
203
204        assert_eq!(next_protocol_parameters, expected_next_protocol_parameters);
205    }
206
207    #[tokio::test]
208    async fn test_compute_current_epoch_protocol_message_value() {
209        let epoch = Epoch(5);
210        let expected_current_epoch = epoch.to_string();
211        let mut mock_container = MockDependencyInjector::new();
212        mock_container.mock_epoch_service =
213            MockEpochServiceImpl::new_with_config(|mock_epoch_service| {
214                mock_epoch_service
215                    .expect_epoch_of_current_data()
216                    .return_once(move || Ok(epoch))
217                    .once();
218            });
219        let signable_seed_builder = mock_container.build_signable_builder_service();
220
221        let current_epoch = signable_seed_builder.compute_current_epoch().await.unwrap();
222
223        assert_eq!(current_epoch, expected_current_epoch);
224    }
225}