mithril_aggregator/database/record/
certificate.rs

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/// Certificate record is the representation of a stored certificate.
24#[derive(Debug, PartialEq, Clone)]
25pub struct CertificateRecord {
26    /// Certificate id.
27    pub certificate_id: String,
28
29    /// Parent Certificate id.
30    pub parent_certificate_id: Option<String>,
31
32    /// Message that is signed.
33    pub message: String,
34
35    /// Signature of the certificate.
36    /// Note: multi-signature if parent certificate id is set, genesis signature otherwise.
37    pub signature: HexEncodedKey,
38
39    /// Aggregate verification key
40    /// Note: used only if signature is a multi-signature
41    pub aggregate_verification_key: HexEncodedAggregateVerificationKey,
42
43    /// Epoch of creation of the certificate.
44    pub epoch: Epoch,
45
46    /// Cardano network of the certificate.
47    pub network: String,
48
49    /// Signed entity type of the message
50    pub signed_entity_type: SignedEntityType,
51
52    /// Protocol Version (semver)
53    pub protocol_version: ProtocolVersion,
54
55    /// Protocol parameters.
56    pub protocol_parameters: ProtocolParameters,
57
58    /// Structured message that is used to create the signed message
59    pub protocol_message: ProtocolMessage,
60
61    /// The list of the active signers with their stakes
62    pub signers: Vec<StakeDistributionParty>,
63
64    /// Date and time when the certificate was initiated
65    pub initiated_at: DateTime<Utc>,
66
67    /// Date and time when the certificate was sealed
68    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}