Skip to main content

mithril_common/test/builder/
mithril_fixture.rs

1use rayon::prelude::*;
2use serde::{Deserialize, Serialize};
3use std::{
4    collections::HashMap,
5    path::{Path, PathBuf},
6    sync::Arc,
7};
8
9#[cfg(feature = "future_snark")]
10use crate::crypto_helper::{
11    ProtocolKey, ProtocolSignerVerificationKeyForSnark,
12    ProtocolSignerVerificationKeySignatureForSnark,
13};
14use crate::{
15    StdResult,
16    certificate_chain::CertificateGenesisProducer,
17    crypto_helper::{
18        ProtocolAggregateVerificationKey, ProtocolAggregateVerificationKeyForConcatenation,
19        ProtocolClosedKeyRegistration, ProtocolGenesisSigner, ProtocolInitializer, ProtocolOpCert,
20        ProtocolSigner, ProtocolSignerVerificationKeyForConcatenation,
21        ProtocolSignerVerificationKeySignatureForConcatenation, ProtocolStakeDistribution,
22    },
23    entities::{
24        Certificate, Epoch, HexEncodedAggregateVerificationKey, PartyId, ProtocolParameters,
25        Signer, SignerWithStake, SingleSignature, Stake, StakeDistribution, StakeDistributionParty,
26        SupportedEra,
27    },
28    protocol::{SignerBuilder, ToMessage},
29    test::crypto_helper::ProtocolInitializerTestExtension,
30};
31
32/// A fixture of Mithril data types.
33#[derive(Debug, Clone)]
34pub struct MithrilFixture {
35    protocol_parameters: ProtocolParameters,
36    signers: Vec<SignerFixture>,
37    stake_distribution: ProtocolStakeDistribution,
38}
39
40/// A signer fixture, containing a [signer entity][SignerWithStake] with its
41/// corresponding protocol [signer][ProtocolSigner] and
42/// [initializer][ProtocolInitializer]
43#[derive(Debug, Clone)]
44pub struct SignerFixture {
45    /// A [SignerWithStake].
46    pub signer_with_stake: SignerWithStake,
47    /// A [ProtocolSigner].
48    pub protocol_signer: ProtocolSigner,
49    /// A [ProtocolSigner].
50    pub protocol_initializer: ProtocolInitializer,
51    /// A [ProtocolClosedKeyRegistration].
52    pub protocol_closed_key_registration: ProtocolClosedKeyRegistration,
53    /// The path to this signer kes secret key file
54    pub kes_secret_key_path: Option<PathBuf>,
55    /// The path to this signer operational certificate file
56    pub operational_certificate_path: Option<PathBuf>,
57}
58
59impl SignerFixture {
60    /// Create a new SignerFixture with specific protocol parameters.
61    /// This is useful to simulate some adversarial behaviors.
62    pub fn try_new_with_protocol_parameters(
63        self,
64        protocol_parameters: ProtocolParameters,
65    ) -> StdResult<Self> {
66        let mut protocol_initializer = self.protocol_initializer.clone();
67        protocol_initializer.override_protocol_parameters(&protocol_parameters.into());
68        let protocol_signer =
69            protocol_initializer.new_signer(self.protocol_closed_key_registration.clone())?;
70        Ok(Self {
71            protocol_signer,
72            ..self
73        })
74    }
75}
76
77impl From<SignerFixture> for SignerWithStake {
78    fn from(fixture: SignerFixture) -> Self {
79        fixture.signer_with_stake
80    }
81}
82
83impl From<&SignerFixture> for SignerWithStake {
84    fn from(fixture: &SignerFixture) -> Self {
85        fixture.signer_with_stake.clone()
86    }
87}
88
89/// Represent the output of the `stake-snapshot` command of the cardano-cli
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct CardanoCliStakeDistribution {
92    #[serde(rename = "pools")]
93    pub signers: HashMap<String, CardanoCliSignerStake>,
94}
95
96/// Represent the stakes of a party in the output of the `stake-snapshot`
97/// command of the cardano-cli
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct CardanoCliSignerStake {
100    #[serde(rename = "stakeMark")]
101    actual_stake: Stake,
102    #[serde(rename = "stakeSet")]
103    previous_stake: Stake,
104    #[serde(rename = "stakeGo")]
105    penultimate_stake: Stake,
106}
107
108impl MithrilFixture {
109    /// [MithrilFixture] factory.
110    pub fn new(
111        protocol_parameters: ProtocolParameters,
112        signers: Vec<SignerFixture>,
113        stake_distribution: ProtocolStakeDistribution,
114    ) -> Self {
115        Self {
116            protocol_parameters,
117            signers,
118            stake_distribution,
119        }
120    }
121
122    /// Get the fixture protocol parameters.
123    pub fn protocol_parameters(&self) -> ProtocolParameters {
124        self.protocol_parameters.clone()
125    }
126
127    /// Get the fixture signers.
128    pub fn signers_fixture(&self) -> Vec<SignerFixture> {
129        self.signers.clone()
130    }
131
132    /// Get the fixture signers.
133    pub fn signers(&self) -> Vec<Signer> {
134        self.signers
135            .clone()
136            .into_iter()
137            .map(|s| s.signer_with_stake.into())
138            .collect()
139    }
140
141    /// Get the fixture signers with stake.
142    pub fn signers_with_stake(&self) -> Vec<SignerWithStake> {
143        self.signers.iter().map(|s| &s.signer_with_stake).cloned().collect()
144    }
145
146    /// Get certificate metadata signers
147    pub fn stake_distribution_parties(&self) -> Vec<StakeDistributionParty> {
148        self.signers
149            .iter()
150            .map(|s| StakeDistributionParty {
151                party_id: s.signer_with_stake.party_id.clone(),
152                stake: s.signer_with_stake.stake,
153            })
154            .collect()
155    }
156
157    /// Get the fixture stake distribution.
158    pub fn stake_distribution(&self) -> StakeDistribution {
159        StakeDistribution::from_iter(self.stake_distribution.clone())
160    }
161
162    /// Get the fixture protocol stake distribution.
163    pub fn protocol_stake_distribution(&self) -> ProtocolStakeDistribution {
164        self.stake_distribution.clone()
165    }
166
167    /// Get the stake distribution formated as a cardano-cli `stake-snapshot` output.
168    ///
169    /// Note: will fail if the signers certification was disabled
170    pub fn cardano_cli_stake_distribution(&self) -> CardanoCliStakeDistribution {
171        let signers = HashMap::from_iter(self.signers_fixture().into_iter().map(|signer| {
172            (
173                signer.compute_protocol_party_id_as_hash(),
174                CardanoCliSignerStake {
175                    actual_stake: signer.signer_with_stake.stake,
176                    previous_stake: signer.signer_with_stake.stake,
177                    penultimate_stake: signer.signer_with_stake.stake,
178                },
179            )
180        }));
181
182        CardanoCliStakeDistribution { signers }
183    }
184
185    /// Compute the Aggregate Verification Key for this fixture.
186    pub fn compute_aggregate_verification_key(&self) -> ProtocolAggregateVerificationKey {
187        SignerBuilder::new(&self.signers_with_stake(), &self.protocol_parameters)
188            .unwrap()
189            .compute_aggregate_verification_key()
190    }
191
192    /// Compute the Aggregate Verification Key for concatenation for this fixture.
193    pub fn compute_concatenation_aggregate_verification_key(
194        &self,
195    ) -> ProtocolAggregateVerificationKeyForConcatenation {
196        self.compute_aggregate_verification_key()
197            .to_concatenation_aggregate_verification_key()
198            .to_owned()
199            .into()
200    }
201
202    /// Compute the Aggregate Verification Key for concatenation for this fixture and returns it has a [HexEncodedAggregateVerificationKey].
203    pub fn compute_and_encode_concatenation_aggregate_verification_key(
204        &self,
205    ) -> HexEncodedAggregateVerificationKey {
206        let aggregate_verification_key = self.compute_concatenation_aggregate_verification_key();
207        aggregate_verification_key.to_json_hex().unwrap()
208    }
209
210    /// Compute the SNARK Aggregate Verification Key for this fixture, if available.
211    #[cfg(feature = "future_snark")]
212    pub fn compute_snark_aggregate_verification_key(
213        &self,
214    ) -> Option<crate::crypto_helper::ProtocolAggregateVerificationKeyForSnark> {
215        self.compute_aggregate_verification_key()
216            .to_snark_aggregate_verification_key()
217            .map(|key| ProtocolKey::new(key.to_owned()))
218    }
219
220    /// Compute the SNARK Aggregate Verification Key for this fixture and returns it as a hex-encoded string.
221    #[cfg(feature = "future_snark")]
222    pub fn compute_and_encode_snark_aggregate_verification_key(&self) -> Option<String> {
223        self.compute_snark_aggregate_verification_key()
224            .map(|avk| avk.to_bytes_hex().unwrap())
225    }
226
227    /// Create a genesis certificate using the fixture signers for the given beacon
228    pub fn create_genesis_certificate<T: Into<String>>(
229        &self,
230        network: T,
231        epoch: Epoch,
232    ) -> Certificate {
233        let genesis_avk = self.compute_aggregate_verification_key();
234        let genesis_signer = ProtocolGenesisSigner::create_deterministic_signer();
235        let genesis_producer = CertificateGenesisProducer::new(Some(Arc::new(genesis_signer)));
236        let mithril_era = SupportedEra::Pythagoras;
237        let genesis_protocol_message = genesis_producer
238            .create_genesis_protocol_message(
239                &self.protocol_parameters,
240                &genesis_avk,
241                &epoch,
242                mithril_era,
243            )
244            .unwrap();
245        let genesis_signature = genesis_producer
246            .sign_genesis_protocol_message(genesis_protocol_message)
247            .unwrap();
248
249        genesis_producer
250            .create_genesis_certificate(
251                self.protocol_parameters.clone(),
252                network,
253                epoch,
254                genesis_avk,
255                genesis_signature,
256                mithril_era,
257            )
258            .unwrap()
259    }
260
261    /// Make all underlying signers sign the given message, filter the resulting list to remove
262    /// the signers that did not sign because they loosed the lottery.
263    pub fn sign_all<T: ToMessage>(&self, message: &T) -> Vec<SingleSignature> {
264        self.signers.par_iter().filter_map(|s| s.sign(message)).collect()
265    }
266}
267
268impl From<MithrilFixture> for Vec<Signer> {
269    fn from(fixture: MithrilFixture) -> Self {
270        fixture.signers()
271    }
272}
273
274impl From<MithrilFixture> for Vec<SignerWithStake> {
275    fn from(fixture: MithrilFixture) -> Self {
276        fixture.signers_with_stake()
277    }
278}
279
280impl From<MithrilFixture> for Vec<SignerFixture> {
281    fn from(fixture: MithrilFixture) -> Self {
282        fixture.signers_fixture()
283    }
284}
285
286impl SignerFixture {
287    /// Sign the given protocol message.
288    pub fn sign<T: ToMessage>(&self, message: &T) -> Option<SingleSignature> {
289        let message = message.to_message();
290        self.protocol_signer.sign(message.as_bytes()).map(|signature| {
291            let won_indexes = signature.get_concatenation_signature_indices();
292
293            SingleSignature::new(
294                self.signer_with_stake.party_id.to_owned(),
295                signature.into(),
296                won_indexes,
297            )
298        })
299    }
300
301    /// Shortcut to get the party id from the inner signer with stake
302    pub fn party_id(&self) -> PartyId {
303        self.signer_with_stake.party_id.clone()
304    }
305
306    /// Get the operational certificate if any
307    pub fn operational_certificate(&self) -> Option<ProtocolOpCert> {
308        self.signer_with_stake.operational_certificate.clone()
309    }
310
311    /// Compute the party id hash
312    ///
313    /// Note: will fail if the signers certification was disabled
314    pub fn compute_protocol_party_id_as_hash(&self) -> String {
315        self.operational_certificate()
316            .unwrap()
317            .compute_protocol_party_id_as_hash()
318    }
319
320    /// Get the verification key for concatenation
321    pub fn verification_key_for_concatenation(
322        &self,
323    ) -> ProtocolSignerVerificationKeyForConcatenation {
324        self.signer_with_stake.verification_key_for_concatenation
325    }
326
327    /// Get the verification key signature for concatenation if any
328    pub fn verification_key_signature_for_concatenation(
329        &self,
330    ) -> Option<ProtocolSignerVerificationKeySignatureForConcatenation> {
331        self.signer_with_stake.verification_key_signature_for_concatenation
332    }
333
334    /// Get the verification key for snark if any
335    #[cfg(feature = "future_snark")]
336    pub fn verification_key_for_snark(&self) -> Option<ProtocolSignerVerificationKeyForSnark> {
337        self.signer_with_stake.verification_key_for_snark
338    }
339
340    /// Get the verification key signature for snark if any
341    #[cfg(feature = "future_snark")]
342    pub fn verification_key_signature_for_snark(
343        &self,
344    ) -> Option<ProtocolSignerVerificationKeySignatureForSnark> {
345        self.signer_with_stake.verification_key_signature_for_snark
346    }
347
348    /// Get the path to this signer kes secret key
349    pub fn kes_secret_key_path(&self) -> Option<&Path> {
350        self.kes_secret_key_path.as_deref()
351    }
352
353    /// Get the path to this signer operational certificate
354    pub fn operational_certificate_path(&self) -> Option<&Path> {
355        self.operational_certificate_path.as_deref()
356    }
357}