mithril_aggregator/database/record/
certificate.rs

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