1use chrono::{DateTime, Utc};
2
3use mithril_common::StdError;
4use mithril_common::entities::{
5 Certificate, CertificateMetadata, CertificateSignature, Epoch,
6 HexEncodedAggregateVerificationKey, HexEncodedKey, ProtocolMessage, ProtocolParameters,
7 ProtocolVersion, SignedEntityType, StakeDistributionParty,
8};
9use mithril_common::messages::{
10 CertificateListItemMessage, CertificateListItemMessageMetadata, CertificateMessage,
11 CertificateMetadataMessagePart,
12};
13#[cfg(test)]
14use mithril_common::{
15 entities::{CardanoDbBeacon, ImmutableFileNumber},
16 test::double::{fake_data, fake_keys},
17};
18use mithril_persistence::{
19 database::Hydrator,
20 sqlite::{HydrationError, Projection, SqLiteEntity},
21};
22
23#[derive(Debug, PartialEq, Clone)]
25pub struct CertificateRecord {
26 pub certificate_id: String,
28
29 pub parent_certificate_id: Option<String>,
31
32 pub message: String,
34
35 pub signature: HexEncodedKey,
38
39 pub aggregate_verification_key: HexEncodedAggregateVerificationKey,
42
43 pub epoch: Epoch,
45
46 pub network: String,
48
49 pub signed_entity_type: SignedEntityType,
51
52 pub protocol_version: ProtocolVersion,
54
55 pub protocol_parameters: ProtocolParameters,
57
58 pub protocol_message: ProtocolMessage,
60
61 pub signers: Vec<StakeDistributionParty>,
63
64 pub initiated_at: DateTime<Utc>,
66
67 pub sealed_at: DateTime<Utc>,
69}
70
71#[cfg(test)]
72impl CertificateRecord {
73 pub(crate) fn dummy_genesis(id: &str, epoch: Epoch) -> Self {
74 Self {
75 parent_certificate_id: None,
76 signature: fake_keys::genesis_signature()[0].to_owned(),
77 ..Self::dummy(id, "", epoch, SignedEntityType::genesis(epoch))
78 }
79 }
80
81 pub(crate) fn dummy_db_snapshot(
82 id: &str,
83 parent_id: &str,
84 epoch: Epoch,
85 immutable_file_number: ImmutableFileNumber,
86 ) -> Self {
87 Self::dummy(
88 id,
89 parent_id,
90 epoch,
91 SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(
92 *epoch,
93 immutable_file_number,
94 )),
95 )
96 }
97
98 pub(crate) fn dummy(
99 id: &str,
100 parent_id: &str,
101 epoch: Epoch,
102 signed_entity_type: SignedEntityType,
103 ) -> Self {
104 Self {
105 certificate_id: id.to_string(),
106 parent_certificate_id: Some(parent_id.to_string()),
107 message: "message".to_string(),
108 signature: fake_keys::multi_signature()[0].to_owned(),
109 aggregate_verification_key: fake_keys::aggregate_verification_key()[0].to_owned(),
110 epoch,
111 network: fake_data::network().to_string(),
112 signed_entity_type,
113 protocol_version: "protocol_version".to_string(),
114 protocol_parameters: ProtocolParameters {
115 k: 0,
116 m: 0,
117 phi_f: 0.0,
118 },
119 protocol_message: Default::default(),
120 signers: vec![],
121 initiated_at: DateTime::parse_from_rfc3339("2024-02-12T13:11:47Z")
122 .unwrap()
123 .with_timezone(&Utc),
124 sealed_at: DateTime::parse_from_rfc3339("2024-02-12T13:12:57Z")
125 .unwrap()
126 .with_timezone(&Utc),
127 }
128 }
129}
130
131impl TryFrom<Certificate> for CertificateRecord {
132 type Error = StdError;
133
134 fn try_from(other: Certificate) -> Result<Self, Self::Error> {
135 let signed_entity_type = other.signed_entity_type();
136 let (signature, parent_certificate_id) = match other.signature {
137 CertificateSignature::GenesisSignature(signature) => (signature.to_bytes_hex()?, None),
138 CertificateSignature::MultiSignature(_, signature) => {
139 (signature.to_json_hex()?, Some(other.previous_hash))
140 }
141 };
142
143 let certificate_record = CertificateRecord {
144 certificate_id: other.hash,
145 parent_certificate_id,
146 message: other.signed_message,
147 signature,
148 aggregate_verification_key: other.aggregate_verification_key.to_json_hex()?,
149 epoch: other.epoch,
150 network: other.metadata.network,
151 signed_entity_type,
152 protocol_version: other.metadata.protocol_version,
153 protocol_parameters: other.metadata.protocol_parameters,
154 protocol_message: other.protocol_message,
155 signers: other.metadata.signers,
156 initiated_at: other.metadata.initiated_at,
157 sealed_at: other.metadata.sealed_at,
158 };
159
160 Ok(certificate_record)
161 }
162}
163
164impl TryFrom<CertificateRecord> for Certificate {
165 type Error = StdError;
166
167 fn try_from(other: CertificateRecord) -> Result<Self, Self::Error> {
168 let certificate_metadata = CertificateMetadata::new(
169 other.network,
170 other.protocol_version,
171 other.protocol_parameters,
172 other.initiated_at,
173 other.sealed_at,
174 other.signers,
175 );
176 let (previous_hash, signature) = match other.parent_certificate_id {
177 None => (
178 String::new(),
179 CertificateSignature::GenesisSignature(other.signature.try_into()?),
180 ),
181 Some(parent_certificate_id) => (
182 parent_certificate_id,
183 CertificateSignature::MultiSignature(
184 other.signed_entity_type,
185 other.signature.try_into()?,
186 ),
187 ),
188 };
189
190 let certificate = Certificate {
191 hash: other.certificate_id,
192 previous_hash,
193 epoch: other.epoch,
194 metadata: certificate_metadata,
195 signed_message: other.protocol_message.compute_hash(),
196 protocol_message: other.protocol_message,
197 aggregate_verification_key: other.aggregate_verification_key.try_into()?,
198 signature,
199 };
200
201 Ok(certificate)
202 }
203}
204
205impl From<CertificateRecord> for CertificateMessage {
206 fn from(value: CertificateRecord) -> Self {
207 let metadata = CertificateMetadataMessagePart {
208 network: value.network,
209 protocol_version: value.protocol_version,
210 protocol_parameters: value.protocol_parameters,
211 initiated_at: value.initiated_at,
212 sealed_at: value.sealed_at,
213 signers: value.signers,
214 };
215 let (multi_signature, genesis_signature) = if value.parent_certificate_id.is_none() {
216 (String::new(), value.signature)
217 } else {
218 (value.signature, String::new())
219 };
220
221 CertificateMessage {
222 hash: value.certificate_id,
223 previous_hash: value.parent_certificate_id.unwrap_or_default(),
224 epoch: value.epoch,
225 signed_entity_type: value.signed_entity_type,
226 metadata,
227 protocol_message: value.protocol_message,
228 signed_message: value.message,
229 aggregate_verification_key: value.aggregate_verification_key,
230 multi_signature,
231 genesis_signature,
232 }
233 }
234}
235
236impl From<CertificateRecord> for CertificateListItemMessage {
237 fn from(value: CertificateRecord) -> Self {
238 let metadata = CertificateListItemMessageMetadata {
239 network: value.network,
240 protocol_version: value.protocol_version,
241 protocol_parameters: value.protocol_parameters,
242 initiated_at: value.initiated_at,
243 sealed_at: value.sealed_at,
244 total_signers: value.signers.len(),
245 };
246
247 CertificateListItemMessage {
248 hash: value.certificate_id,
249 previous_hash: value.parent_certificate_id.unwrap_or_default(),
250 epoch: value.epoch,
251 signed_entity_type: value.signed_entity_type,
252 metadata,
253 protocol_message: value.protocol_message,
254 signed_message: value.message,
255 aggregate_verification_key: value.aggregate_verification_key,
256 }
257 }
258}
259
260impl SqLiteEntity for CertificateRecord {
261 fn hydrate(row: sqlite::Row) -> Result<Self, HydrationError>
262 where
263 Self: Sized,
264 {
265 let certificate_id = row.read::<&str, _>(0).to_string();
266 let parent_certificate_id = row.read::<Option<&str>, _>(1).map(|s| s.to_owned());
267 let message = row.read::<&str, _>(2).to_string();
268 let signature = row.read::<&str, _>(3).to_string();
269 let aggregate_verification_key = row.read::<&str, _>(4).to_string();
270 let epoch_int = row.read::<i64, _>(5);
271 let network = row.read::<&str, _>(6).to_string();
272 let signed_entity_type_id = row.read::<i64, _>(7);
273 let signed_entity_beacon_string = Hydrator::read_signed_entity_beacon_column(&row, 8);
274 let protocol_version = row.read::<&str, _>(9).to_string();
275 let protocol_parameters_string = row.read::<&str, _>(10);
276 let protocol_message_string = row.read::<&str, _>(11);
277 let signers_string = row.read::<&str, _>(12);
278 let initiated_at = row.read::<&str, _>(13);
279 let sealed_at = row.read::<&str, _>(14);
280
281 let certificate_record = Self {
282 certificate_id,
283 parent_certificate_id,
284 message,
285 signature,
286 aggregate_verification_key,
287 epoch: Epoch(epoch_int.try_into().map_err(|e| {
288 HydrationError::InvalidData(format!(
289 "Could not cast i64 ({epoch_int}) to u64. Error: '{e}'"
290 ))
291 })?),
292 network,
293 signed_entity_type: Hydrator::hydrate_signed_entity_type(
294 signed_entity_type_id.try_into().map_err(|e| {
295 HydrationError::InvalidData(format!(
296 "Could not cast i64 ({signed_entity_type_id}) to u64. Error: '{e}'"
297 ))
298 })?,
299 &signed_entity_beacon_string,
300 )?,
301 protocol_version,
302 protocol_parameters: serde_json::from_str(protocol_parameters_string).map_err(
303 |e| {
304 HydrationError::InvalidData(format!(
305 "Could not turn string '{protocol_parameters_string}' to ProtocolParameters. Error: {e}"
306 ))
307 },
308 )?,
309 protocol_message: serde_json::from_str(protocol_message_string).map_err(
310 |e| {
311 HydrationError::InvalidData(format!(
312 "Could not turn string '{protocol_message_string}' to ProtocolMessage. Error: {e}"
313 ))
314 },
315 )?,
316 signers: serde_json::from_str(signers_string).map_err(
317 |e| {
318 HydrationError::InvalidData(format!(
319 "Could not turn string '{signers_string}' to Vec<StakeDistributionParty>. Error: {e}"
320 ))
321 },
322 )?,
323 initiated_at: DateTime::parse_from_rfc3339(initiated_at).map_err(
324 |e| {
325 HydrationError::InvalidData(format!(
326 "Could not turn string '{initiated_at}' to rfc3339 Datetime. Error: {e}"
327 ))
328 },
329 )?.with_timezone(&Utc),
330 sealed_at: DateTime::parse_from_rfc3339(sealed_at).map_err(
331 |e| {
332 HydrationError::InvalidData(format!(
333 "Could not turn string '{sealed_at}' to rfc3339 Datetime. Error: {e}"
334 ))
335 },
336 )?.with_timezone(&Utc),
337 };
338
339 Ok(certificate_record)
340 }
341
342 fn get_projection() -> Projection {
343 let mut projection = Projection::default();
344 projection.add_field("certificate_id", "{:certificate:}.certificate_id", "text");
345 projection.add_field(
346 "parent_certificate_id",
347 "{:certificate:}.parent_certificate_id",
348 "text",
349 );
350 projection.add_field("message", "{:certificate:}.message", "text");
351 projection.add_field("signature", "{:certificate:}.signature", "text");
352 projection.add_field(
353 "aggregate_verification_key",
354 "{:certificate:}.aggregate_verification_key",
355 "text",
356 );
357 projection.add_field("epoch", "{:certificate:}.epoch", "integer");
358 projection.add_field("network", "{:certificate:}.network", "text");
359 projection.add_field(
360 "signed_entity_type_id",
361 "{:certificate:}.signed_entity_type_id",
362 "integer",
363 );
364 projection.add_field(
365 "signed_entity_beacon",
366 "{:certificate:}.signed_entity_beacon",
367 "text",
368 );
369 projection.add_field(
370 "protocol_version",
371 "{:certificate:}.protocol_version",
372 "text",
373 );
374 projection.add_field(
375 "protocol_parameters",
376 "{:certificate:}.protocol_parameters",
377 "text",
378 );
379 projection.add_field(
380 "protocol_message",
381 "{:certificate:}.protocol_message",
382 "text",
383 );
384 projection.add_field("signers", "{:certificate:}.signers", "text");
385 projection.add_field("initiated_at", "{:certificate:}.initiated_at", "text");
386 projection.add_field("sealed_at", "{:certificate:}.sealed_at", "text");
387
388 projection
389 }
390}
391
392#[cfg(test)]
393mod tests {
394 use mithril_common::test::crypto_helper::setup_certificate_chain;
395
396 use super::*;
397
398 #[test]
399 fn test_convert_certificates() {
400 let certificates = setup_certificate_chain(20, 3);
401 let mut certificate_records: Vec<CertificateRecord> = Vec::new();
402 for certificate in certificates.certificates_chained.clone() {
403 certificate_records.push(certificate.try_into().unwrap());
404 }
405 let mut certificates_new: Vec<Certificate> = Vec::new();
406 for certificate_record in certificate_records {
407 certificates_new.push(certificate_record.try_into().unwrap());
408 }
409 assert_eq!(certificates.certificates_chained, certificates_new);
410 }
411
412 #[test]
413 fn converting_certificate_record_to_certificate_should_not_recompute_hash() {
414 let expected_hash = "my_hash";
415 let record = CertificateRecord::dummy_genesis(expected_hash, Epoch(1));
416 let certificate: Certificate = record.try_into().unwrap();
417
418 assert_eq!(expected_hash, &certificate.hash);
419 }
420}