Skip to content

Commit b8e4ddc

Browse files
committed
Add MithrilFixture & its builder to common utilities
1 parent 07b5e8b commit b8e4ddc

File tree

4 files changed

+275
-0
lines changed

4 files changed

+275
-0
lines changed

mithril-common/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub mod entities;
2323
pub mod fake_data;
2424
pub mod sqlite;
2525
pub mod store;
26+
pub mod test_utils;
2627

2728
pub use beacon_provider::{BeaconProvider, BeaconProviderError, BeaconProviderImpl};
2829
pub use entities::{CardanoNetwork, MagicId};
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
use kes_summed_ed25519::{kes::Sum6Kes, traits::KesSk};
2+
use rand_chacha::ChaCha20Rng;
3+
use rand_core::{RngCore, SeedableRng};
4+
5+
use crate::{
6+
crypto_helper::{
7+
tests_setup, tests_setup::setup_temp_directory_for_signer, ColdKeyGenerator, OpCert,
8+
ProtocolStakeDistribution, SerDeShelleyFileFormat,
9+
},
10+
entities::{PartyId, ProtocolParameters},
11+
fake_data,
12+
test_utils::mithril_fixture::MithrilFixture,
13+
};
14+
15+
use super::mithril_fixture::SignerFixture;
16+
17+
/// A builder of mithril types.
18+
pub struct MithrilFixtureBuilder {
19+
protocol_parameters: ProtocolParameters,
20+
enable_signers_certification: bool,
21+
number_of_signers: usize,
22+
stake_distribution_generation_method: StakeDistributionGenerationMethod,
23+
}
24+
25+
impl Default for MithrilFixtureBuilder {
26+
fn default() -> Self {
27+
Self {
28+
protocol_parameters: fake_data::protocol_parameters(),
29+
enable_signers_certification: true,
30+
number_of_signers: 5,
31+
stake_distribution_generation_method:
32+
StakeDistributionGenerationMethod::RandomDistribution { seed: [0u8; 32] },
33+
}
34+
}
35+
}
36+
37+
/// Methods that can be used to generate the stake distribution.
38+
pub enum StakeDistributionGenerationMethod {
39+
/// Each party will have a random stake.
40+
RandomDistribution {
41+
/// The randomizer seed
42+
seed: [u8; 32],
43+
},
44+
}
45+
46+
impl MithrilFixtureBuilder {
47+
/// Set the protocol_parameters.
48+
pub fn with_protocol_parameters(mut self, protocol_parameters: ProtocolParameters) -> Self {
49+
self.protocol_parameters = protocol_parameters;
50+
self
51+
}
52+
53+
/// Set the number of signers that will be generated.
54+
pub fn with_signers(mut self, number_of_signers: usize) -> Self {
55+
self.number_of_signers = number_of_signers;
56+
self
57+
}
58+
59+
/// If set the generated signers won't be certified (meaning that they won't
60+
/// have a operational certificate).
61+
pub fn disable_signers_certification(mut self) -> Self {
62+
self.enable_signers_certification = false;
63+
self
64+
}
65+
66+
/// Set the generation method used to compute the stake distribution.
67+
pub fn with_stake_distribution(
68+
mut self,
69+
stake_distribution_generation_method: StakeDistributionGenerationMethod,
70+
) -> Self {
71+
self.stake_distribution_generation_method = stake_distribution_generation_method;
72+
self
73+
}
74+
75+
/// Transform the specified parameters to a [MithrilFixture].
76+
pub fn build(self) -> MithrilFixture {
77+
let protocol_stake_distribution = self.generate_stake_distribution();
78+
let signers = tests_setup::setup_signers_from_stake_distribution(
79+
&protocol_stake_distribution,
80+
&self.protocol_parameters.clone().into(),
81+
)
82+
.into_iter()
83+
.map(
84+
|(signer_with_stake, protocol_signer, protocol_initializer)| SignerFixture {
85+
signer_with_stake,
86+
protocol_signer,
87+
protocol_initializer,
88+
},
89+
)
90+
.collect();
91+
92+
MithrilFixture::new(
93+
self.protocol_parameters,
94+
signers,
95+
protocol_stake_distribution,
96+
)
97+
}
98+
99+
fn generate_stake_distribution(&self) -> ProtocolStakeDistribution {
100+
let mut kes_keys_seed = [0u8; 32];
101+
let signers_party_ids = (0..self.number_of_signers).into_iter().map(|party_index| {
102+
if self.enable_signers_certification {
103+
build_party_with_operational_certificate(party_index, &mut kes_keys_seed)
104+
} else {
105+
format!("{:<032}", party_index)
106+
}
107+
});
108+
109+
match self.stake_distribution_generation_method {
110+
StakeDistributionGenerationMethod::RandomDistribution { seed } => {
111+
let mut stake_rng = ChaCha20Rng::from_seed(seed);
112+
113+
signers_party_ids
114+
.map(|party_id| {
115+
let stake = 1 + stake_rng.next_u64() % 999;
116+
(party_id, stake)
117+
})
118+
.collect::<Vec<_>>()
119+
}
120+
}
121+
}
122+
}
123+
124+
fn build_party_with_operational_certificate(
125+
party_index: usize,
126+
kes_key_seed: &mut [u8],
127+
) -> PartyId {
128+
let keypair = ColdKeyGenerator::create_deterministic_keypair([party_index as u8; 32]);
129+
let (kes_secret_key, kes_verification_key) = Sum6Kes::keygen(kes_key_seed);
130+
let operational_certificate = OpCert::new(kes_verification_key, 0, 0, keypair);
131+
let party_id = operational_certificate
132+
.compute_protocol_party_id()
133+
.expect("compute protocol party id should not fail");
134+
let temp_dir = setup_temp_directory_for_signer(&party_id, true)
135+
.expect("setup temp directory should return a value");
136+
if !temp_dir.join("kes.sk").exists() {
137+
kes_secret_key
138+
.to_file(temp_dir.join("kes.sk"))
139+
.expect("KES secret key file export should not fail");
140+
}
141+
if !temp_dir.join("opcert.cert").exists() {
142+
operational_certificate
143+
.to_file(temp_dir.join("opcert.cert"))
144+
.expect("operational certificate file export should not fail");
145+
}
146+
party_id
147+
}
148+
149+
#[cfg(test)]
150+
mod tests {
151+
use super::*;
152+
use std::collections::BTreeSet;
153+
154+
#[test]
155+
fn with_protocol_params() {
156+
let protocol_parameters = ProtocolParameters::new(1, 10, 0.56);
157+
let result = MithrilFixtureBuilder::default()
158+
.with_protocol_parameters(protocol_parameters.clone())
159+
.build();
160+
161+
assert_eq!(protocol_parameters, result.protocol_parameters());
162+
}
163+
164+
#[test]
165+
fn with_signers() {
166+
let result = MithrilFixtureBuilder::default().with_signers(4).build();
167+
168+
assert_eq!(4, result.signers_with_stake().len());
169+
}
170+
171+
#[test]
172+
fn random_stake_distribution_generates_as_many_signers_as_parties() {
173+
let result = MithrilFixtureBuilder::default()
174+
.with_stake_distribution(StakeDistributionGenerationMethod::RandomDistribution {
175+
seed: [0u8; 32],
176+
})
177+
.with_signers(4)
178+
.build();
179+
180+
assert_eq!(4, result.stake_distribution().len());
181+
}
182+
183+
#[test]
184+
fn each_parties_generated_with_random_stake_distribution_have_different_stakes() {
185+
let result = MithrilFixtureBuilder::default()
186+
.with_stake_distribution(StakeDistributionGenerationMethod::RandomDistribution {
187+
seed: [0u8; 32],
188+
})
189+
.with_signers(5)
190+
.build();
191+
let stakes = result.stake_distribution();
192+
193+
// BtreeSet dedup values
194+
assert_eq!(stakes.len(), BTreeSet::from_iter(stakes.values()).len());
195+
}
196+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use crate::{
2+
crypto_helper::{ProtocolInitializer, ProtocolSigner, ProtocolStakeDistribution},
3+
entities::{ProtocolParameters, SignerWithStake, StakeDistribution},
4+
};
5+
use std::collections::HashMap;
6+
7+
/// A fixture of Mithril data types.
8+
#[derive(Debug, Clone)]
9+
pub struct MithrilFixture {
10+
protocol_parameters: ProtocolParameters,
11+
signers: Vec<SignerFixture>,
12+
stake_distribution: ProtocolStakeDistribution,
13+
}
14+
15+
/// A signer fixture, containing a [signer entity][SignerWithStake] with its
16+
/// corresponding protocol [signer][ProtocolSigner] and
17+
/// [initializer][ProtocolInitializer]
18+
#[derive(Debug, Clone)]
19+
pub struct SignerFixture {
20+
/// A [SignerWithStake].
21+
pub signer_with_stake: SignerWithStake,
22+
/// A [ProtocolSigner].
23+
pub protocol_signer: ProtocolSigner,
24+
/// A [ProtocolSigner].
25+
pub protocol_initializer: ProtocolInitializer,
26+
}
27+
28+
impl MithrilFixture {
29+
/// [MithrilFixture] factory.
30+
#[cfg(any(test, feature = "test_only"))]
31+
pub fn new(
32+
protocol_parameters: ProtocolParameters,
33+
signers: Vec<SignerFixture>,
34+
stake_distribution: ProtocolStakeDistribution,
35+
) -> Self {
36+
Self {
37+
protocol_parameters,
38+
signers,
39+
stake_distribution,
40+
}
41+
}
42+
43+
/// Get the fixture protocol parameters.
44+
pub fn protocol_parameters(&self) -> ProtocolParameters {
45+
self.protocol_parameters.clone()
46+
}
47+
48+
/// Get the fixture signers.
49+
pub fn signers_fixture(&self) -> Vec<SignerFixture> {
50+
self.signers.clone()
51+
}
52+
53+
/// Get the fixture signers with stake.
54+
pub fn signers_with_stake(&self) -> Vec<SignerWithStake> {
55+
self.signers
56+
.iter()
57+
.map(|s| &s.signer_with_stake)
58+
.cloned()
59+
.collect()
60+
}
61+
62+
/// Get the fixture stake distribution.
63+
pub fn stake_distribution(&self) -> StakeDistribution {
64+
HashMap::from_iter(self.stake_distribution.clone())
65+
}
66+
67+
/// Get the fixture protocol stake distribution.
68+
pub fn protocol_stake_distribution(&self) -> ProtocolStakeDistribution {
69+
self.stake_distribution.clone()
70+
}
71+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#[cfg(any(test, feature = "test_only"))]
2+
mod fixture_builder;
3+
mod mithril_fixture;
4+
5+
#[cfg(any(test, feature = "test_only"))]
6+
pub use fixture_builder::{MithrilFixtureBuilder, StakeDistributionGenerationMethod};
7+
pub use mithril_fixture::{MithrilFixture, SignerFixture};

0 commit comments

Comments
 (0)