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