mithril_common/entities/
certificate_metadata.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sha2::{Digest, Sha256};
4
5use crate::entities::{ProtocolParameters, ProtocolVersion, SignerWithStake, StakeDistribution};
6
7use super::{PartyId, Stake};
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub struct StakeDistributionParty {
12 pub party_id: PartyId,
14
15 pub stake: Stake,
17}
18
19impl From<SignerWithStake> for StakeDistributionParty {
20 fn from(value: SignerWithStake) -> Self {
21 Self {
22 party_id: value.party_id,
23 stake: value.stake,
24 }
25 }
26}
27
28impl StakeDistributionParty {
29 pub fn compute_hash(&self) -> String {
31 let mut hasher = Sha256::new();
32 hasher.update(self.party_id.as_bytes());
33 hasher.update(self.stake.to_be_bytes());
34
35 hex::encode(hasher.finalize())
36 }
37
38 pub fn from_signers(signers: Vec<SignerWithStake>) -> Vec<Self> {
40 signers.into_iter().map(|s| s.into()).collect()
41 }
42}
43
44#[derive(Clone, Debug, PartialEq)]
46pub struct CertificateMetadata {
47 pub network: String,
49
50 pub protocol_version: ProtocolVersion,
54
55 pub protocol_parameters: ProtocolParameters,
58
59 pub initiated_at: DateTime<Utc>,
63
64 pub sealed_at: DateTime<Utc>,
68
69 pub signers: Vec<StakeDistributionParty>,
72}
73
74impl CertificateMetadata {
75 pub fn new<T: Into<String>, U: Into<ProtocolVersion>>(
77 network: T,
78 protocol_version: U,
79 protocol_parameters: ProtocolParameters,
80 initiated_at: DateTime<Utc>,
81 sealed_at: DateTime<Utc>,
82 signers: Vec<StakeDistributionParty>,
83 ) -> CertificateMetadata {
84 CertificateMetadata {
85 network: network.into(),
86 protocol_version: protocol_version.into(),
87 protocol_parameters,
88 initiated_at,
89 sealed_at,
90 signers,
91 }
92 }
93
94 pub fn get_stake_distribution(&self) -> StakeDistribution {
96 self.signers
97 .clone()
98 .iter()
99 .map(|s| (s.party_id.clone(), s.stake))
100 .collect()
101 }
102
103 pub fn compute_hash(&self) -> String {
105 let mut hasher = Sha256::new();
106 hasher.update(self.network.as_bytes());
107 hasher.update(self.protocol_version.as_bytes());
108 hasher.update(self.protocol_parameters.compute_hash().as_bytes());
109 hasher.update(
110 self.initiated_at
111 .timestamp_nanos_opt()
112 .unwrap_or_default()
113 .to_be_bytes(),
114 );
115 hasher.update(
116 self.sealed_at
117 .timestamp_nanos_opt()
118 .unwrap_or_default()
119 .to_be_bytes(),
120 );
121
122 for party in &self.signers {
123 hasher.update(party.compute_hash().as_bytes());
124 }
125
126 hex::encode(hasher.finalize())
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use chrono::{Duration, TimeZone, Timelike};
133
134 use super::*;
135
136 fn get_parties() -> Vec<StakeDistributionParty> {
137 vec![
138 StakeDistributionParty {
139 party_id: "1".to_string(),
140 stake: 10,
141 },
142 StakeDistributionParty {
143 party_id: "2".to_string(),
144 stake: 20,
145 },
146 ]
147 }
148
149 #[test]
150 fn test_certificate_metadata_compute_hash() {
151 let hash_expected = "f16631f048b33746aa0141cf607ee53ddb76308725e6912530cc41cc54834206";
152
153 let initiated_at = Utc
154 .with_ymd_and_hms(2024, 2, 12, 13, 11, 47)
155 .unwrap()
156 .with_nanosecond(123043)
157 .unwrap();
158 let sealed_at = initiated_at + Duration::try_seconds(100).unwrap();
159 let metadata = CertificateMetadata::new(
160 "devnet",
161 "0.1.0",
162 ProtocolParameters::new(1000, 100, 0.123),
163 initiated_at,
164 sealed_at,
165 get_parties(),
166 );
167
168 assert_eq!(hash_expected, metadata.compute_hash());
169
170 assert_ne!(
171 hash_expected,
172 CertificateMetadata {
173 network: "modified".into(),
174 ..metadata.clone()
175 }
176 .compute_hash(),
177 );
178
179 assert_ne!(
180 hash_expected,
181 CertificateMetadata {
182 protocol_version: "0.1.0-modified".to_string(),
183 ..metadata.clone()
184 }
185 .compute_hash(),
186 );
187
188 assert_ne!(
189 hash_expected,
190 CertificateMetadata {
191 protocol_parameters: ProtocolParameters::new(2000, 100, 0.123),
192 ..metadata.clone()
193 }
194 .compute_hash(),
195 );
196
197 assert_ne!(
198 hash_expected,
199 CertificateMetadata {
200 initiated_at: metadata.initiated_at - Duration::try_seconds(78).unwrap(),
201 ..metadata.clone()
202 }
203 .compute_hash()
204 );
205
206 let mut signers_with_different_party_id = get_parties();
207 signers_with_different_party_id[0].party_id = "1-modified".to_string();
208
209 assert_ne!(
210 hash_expected,
211 CertificateMetadata {
212 sealed_at: metadata.sealed_at - Duration::try_seconds(78).unwrap(),
213 ..metadata.clone()
214 }
215 .compute_hash(),
216 );
217
218 let mut signers = get_parties();
219 signers.truncate(1);
220
221 assert_ne!(
222 hash_expected,
223 CertificateMetadata {
224 signers,
225 ..metadata.clone()
226 }
227 .compute_hash(),
228 );
229 }
230}