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_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}