mithril_aggregator/database/record/
signed_entity.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use mithril_common::StdError;
5use mithril_common::crypto_helper::ProtocolParameters;
6use mithril_common::entities::{
7    BlockNumber, CardanoDatabaseSnapshot, Epoch, SignedEntityType, Snapshot, StakeDistribution,
8};
9#[cfg(test)]
10use mithril_common::entities::{CardanoStakeDistribution, MithrilStakeDistribution};
11use mithril_common::messages::{
12    CardanoDatabaseSnapshotListItemMessage, CardanoDatabaseSnapshotMessage,
13    CardanoStakeDistributionListItemMessage, CardanoStakeDistributionMessage,
14    CardanoTransactionSnapshotListItemMessage, CardanoTransactionSnapshotMessage,
15    MithrilStakeDistributionListItemMessage, MithrilStakeDistributionMessage,
16    SignerWithStakeMessagePart, SnapshotListItemMessage, SnapshotMessage,
17};
18use mithril_common::signable_builder::{Artifact, SignedEntity};
19use mithril_persistence::database::Hydrator;
20use mithril_persistence::sqlite::{HydrationError, Projection, SqLiteEntity};
21
22/// SignedEntity record is the representation of a stored signed_entity.
23#[derive(Debug, PartialEq, Clone)]
24pub struct SignedEntityRecord {
25    /// Signed entity id.
26    pub signed_entity_id: String,
27
28    /// Signed entity type.
29    pub signed_entity_type: SignedEntityType,
30
31    /// Certificate id for this signed entity.
32    pub certificate_id: String,
33
34    /// Raw artifact (in JSON format).
35    pub artifact: String,
36
37    /// Date and time when the signed_entity was created
38    pub created_at: DateTime<Utc>,
39}
40
41#[cfg(test)]
42impl From<CardanoStakeDistribution> for SignedEntityRecord {
43    fn from(cardano_stake_distribution: CardanoStakeDistribution) -> Self {
44        SignedEntityRecord::from_cardano_stake_distribution(cardano_stake_distribution)
45    }
46}
47
48#[cfg(test)]
49impl From<MithrilStakeDistribution> for SignedEntityRecord {
50    fn from(mithril_stake_distribution: MithrilStakeDistribution) -> Self {
51        let entity = serde_json::to_string(&mithril_stake_distribution).unwrap();
52
53        SignedEntityRecord {
54            signed_entity_id: mithril_stake_distribution.hash.clone(),
55            signed_entity_type: SignedEntityType::MithrilStakeDistribution(
56                mithril_stake_distribution.epoch,
57            ),
58            certificate_id: format!("certificate-{}", mithril_stake_distribution.hash),
59            artifact: entity,
60            created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
61                .unwrap()
62                .with_timezone(&Utc),
63        }
64    }
65}
66
67#[cfg(test)]
68impl From<CardanoDatabaseSnapshot> for SignedEntityRecord {
69    fn from(value: CardanoDatabaseSnapshot) -> Self {
70        let entity = serde_json::to_string(&value).unwrap();
71
72        SignedEntityRecord {
73            signed_entity_id: value.hash.clone(),
74            signed_entity_type: SignedEntityType::CardanoDatabase(value.beacon),
75            certificate_id: format!("certificate-{}", value.hash),
76            artifact: entity,
77            created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
78                .unwrap()
79                .with_timezone(&Utc),
80        }
81    }
82}
83
84#[cfg(test)]
85impl SignedEntityRecord {
86    pub(crate) fn fake_with_signed_entity(signed_entity_type: SignedEntityType) -> Self {
87        use mithril_common::test::double::fake_data;
88        fn get_id_and_artifact(artifact: &(impl Artifact + serde::Serialize)) -> (String, String) {
89            (artifact.get_id(), serde_json::to_string(artifact).unwrap())
90        }
91
92        let (id, artifact) = match signed_entity_type.clone() {
93            SignedEntityType::MithrilStakeDistribution(epoch) => {
94                let artifact = fake_data::mithril_stake_distribution(epoch, vec![]);
95                get_id_and_artifact(&artifact)
96            }
97            SignedEntityType::CardanoStakeDistribution(epoch) => {
98                let artifact = fake_data::cardano_stake_distribution(epoch);
99                get_id_and_artifact(&artifact)
100            }
101            SignedEntityType::CardanoImmutableFilesFull(cardano_db_beacon) => {
102                let mut artifact = fake_data::snapshot(cardano_db_beacon.immutable_file_number);
103                artifact.beacon = cardano_db_beacon;
104                get_id_and_artifact(&artifact)
105            }
106            SignedEntityType::CardanoDatabase(cardano_db_beacon) => {
107                let mut artifact =
108                    fake_data::cardano_database_snapshot(cardano_db_beacon.immutable_file_number);
109                artifact.beacon = cardano_db_beacon;
110                get_id_and_artifact(&artifact)
111            }
112            SignedEntityType::CardanoTransactions(_epoch, block_number) => {
113                let artifact = fake_data::cardano_transactions_snapshot(block_number);
114                get_id_and_artifact(&artifact)
115            }
116            SignedEntityType::CardanoBlocksTransactions(_epoch, _block_number) => {
117                panic!("Cardano blocks transactions is not supported yet")
118            }
119        };
120
121        SignedEntityRecord {
122            signed_entity_id: id.clone(),
123            signed_entity_type,
124            certificate_id: format!("certificate-{id}"),
125            artifact,
126            created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
127                .unwrap()
128                .with_timezone(&Utc),
129        }
130    }
131
132    pub(crate) fn from_snapshot(
133        snapshot: Snapshot,
134        certificate_id: String,
135        created_at: DateTime<Utc>,
136    ) -> Self {
137        let entity = serde_json::to_string(&snapshot).unwrap();
138
139        SignedEntityRecord {
140            signed_entity_id: snapshot.digest,
141            signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(snapshot.beacon),
142            certificate_id,
143            artifact: entity,
144            created_at,
145        }
146    }
147
148    pub(crate) fn from_cardano_stake_distribution(
149        cardano_stake_distribution: CardanoStakeDistribution,
150    ) -> Self {
151        let entity = serde_json::to_string(&cardano_stake_distribution).unwrap();
152
153        SignedEntityRecord {
154            signed_entity_id: cardano_stake_distribution.hash.clone(),
155            signed_entity_type: SignedEntityType::CardanoStakeDistribution(
156                cardano_stake_distribution.epoch,
157            ),
158            certificate_id: format!("certificate-{}", cardano_stake_distribution.hash),
159            artifact: entity,
160            created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
161                .unwrap()
162                .with_timezone(&Utc),
163        }
164    }
165
166    pub(crate) fn fake_records(number_if_records: usize) -> Vec<SignedEntityRecord> {
167        use mithril_common::test::double::fake_data;
168
169        let snapshots = fake_data::snapshots(number_if_records as u64);
170        (0..number_if_records)
171            .map(|idx| {
172                let snapshot = snapshots.get(idx).unwrap().to_owned();
173                let entity = serde_json::to_string(&snapshot).unwrap();
174                SignedEntityRecord {
175                    signed_entity_id: snapshot.digest,
176                    signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(
177                        snapshot.beacon,
178                    ),
179                    certificate_id: format!("certificate-{idx}"),
180                    artifact: entity,
181                    created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
182                        .unwrap()
183                        .with_timezone(&Utc),
184                }
185            })
186            .collect()
187    }
188}
189
190impl From<SignedEntityRecord> for Snapshot {
191    fn from(other: SignedEntityRecord) -> Snapshot {
192        serde_json::from_str(&other.artifact).unwrap()
193    }
194}
195
196impl<T> TryFrom<SignedEntityRecord> for SignedEntity<T>
197where
198    for<'a> T: Artifact + Serialize + Deserialize<'a>,
199{
200    type Error = serde_json::error::Error;
201
202    fn try_from(other: SignedEntityRecord) -> Result<SignedEntity<T>, Self::Error> {
203        let signed_entity = SignedEntity {
204            signed_entity_id: other.signed_entity_id,
205            signed_entity_type: other.signed_entity_type,
206            created_at: other.created_at,
207            certificate_id: other.certificate_id,
208            artifact: serde_json::from_str::<T>(&other.artifact)?,
209        };
210
211        Ok(signed_entity)
212    }
213}
214
215impl TryFrom<SignedEntityRecord> for SnapshotMessage {
216    type Error = StdError;
217
218    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
219        let artifact = serde_json::from_str::<Snapshot>(&value.artifact)?;
220        let snapshot_message = SnapshotMessage {
221            digest: artifact.digest,
222            network: artifact.network.clone(),
223            beacon: artifact.beacon,
224            certificate_hash: value.certificate_id,
225            size: artifact.size,
226            ancillary_size: artifact.ancillary_size,
227            created_at: value.created_at,
228            locations: artifact.locations,
229            ancillary_locations: artifact.ancillary_locations,
230            compression_algorithm: artifact.compression_algorithm,
231            cardano_node_version: artifact.cardano_node_version,
232        };
233
234        Ok(snapshot_message)
235    }
236}
237
238impl TryFrom<SignedEntityRecord> for CardanoDatabaseSnapshotMessage {
239    type Error = StdError;
240
241    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
242        let artifact = serde_json::from_str::<CardanoDatabaseSnapshot>(&value.artifact)?;
243        let cardano_database_snapshot_message = CardanoDatabaseSnapshotMessage {
244            hash: artifact.hash,
245            merkle_root: artifact.merkle_root,
246            network: artifact.network.to_string(),
247            beacon: artifact.beacon,
248            certificate_hash: value.certificate_id,
249            total_db_size_uncompressed: artifact.total_db_size_uncompressed,
250            created_at: value.created_at,
251            digests: artifact.digests.into(),
252            immutables: artifact.immutables.into(),
253            ancillary: artifact.ancillary.into(),
254            cardano_node_version: artifact.cardano_node_version,
255        };
256
257        Ok(cardano_database_snapshot_message)
258    }
259}
260
261impl TryFrom<SignedEntityRecord> for CardanoDatabaseSnapshotListItemMessage {
262    type Error = StdError;
263
264    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
265        let artifact = serde_json::from_str::<CardanoDatabaseSnapshot>(&value.artifact)?;
266        let cardano_database_snapshot_list_item_message = CardanoDatabaseSnapshotListItemMessage {
267            hash: artifact.hash,
268            merkle_root: artifact.merkle_root,
269            beacon: artifact.beacon,
270            certificate_hash: value.certificate_id,
271            total_db_size_uncompressed: artifact.total_db_size_uncompressed,
272            created_at: value.created_at,
273            cardano_node_version: artifact.cardano_node_version,
274        };
275
276        Ok(cardano_database_snapshot_list_item_message)
277    }
278}
279
280impl TryFrom<SignedEntityRecord> for MithrilStakeDistributionMessage {
281    type Error = StdError;
282
283    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
284        #[derive(Deserialize)]
285        struct TmpMithrilStakeDistribution {
286            epoch: Epoch,
287            signers_with_stake: Vec<SignerWithStakeMessagePart>,
288            hash: String,
289            protocol_parameters: ProtocolParameters,
290        }
291        let artifact = serde_json::from_str::<TmpMithrilStakeDistribution>(&value.artifact)?;
292        let mithril_stake_distribution_message = MithrilStakeDistributionMessage {
293            epoch: artifact.epoch,
294            signers_with_stake: artifact.signers_with_stake,
295            hash: artifact.hash,
296            certificate_hash: value.certificate_id,
297            created_at: value.created_at,
298            protocol_parameters: artifact.protocol_parameters.into(),
299        };
300
301        Ok(mithril_stake_distribution_message)
302    }
303}
304
305impl TryFrom<SignedEntityRecord> for MithrilStakeDistributionListItemMessage {
306    type Error = StdError;
307
308    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
309        #[derive(Deserialize)]
310        struct TmpMithrilStakeDistribution {
311            epoch: Epoch,
312            hash: String,
313        }
314        let artifact = serde_json::from_str::<TmpMithrilStakeDistribution>(&value.artifact)?;
315        let message = MithrilStakeDistributionListItemMessage {
316            epoch: artifact.epoch,
317            hash: artifact.hash,
318            certificate_hash: value.certificate_id,
319            created_at: value.created_at,
320        };
321
322        Ok(message)
323    }
324}
325
326impl TryFrom<SignedEntityRecord> for CardanoTransactionSnapshotMessage {
327    type Error = StdError;
328
329    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
330        #[derive(Deserialize)]
331        struct TmpCardanoTransaction {
332            merkle_root: String,
333            block_number: BlockNumber,
334            hash: String,
335        }
336        let artifact = serde_json::from_str::<TmpCardanoTransaction>(&value.artifact)?;
337        let cardano_transaction_message = CardanoTransactionSnapshotMessage {
338            merkle_root: artifact.merkle_root,
339            epoch: value.signed_entity_type.get_epoch(),
340            block_number: artifact.block_number,
341            hash: artifact.hash,
342            certificate_hash: value.certificate_id,
343            created_at: value.created_at,
344        };
345
346        Ok(cardano_transaction_message)
347    }
348}
349
350impl TryFrom<SignedEntityRecord> for CardanoTransactionSnapshotListItemMessage {
351    type Error = StdError;
352
353    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
354        #[derive(Deserialize)]
355        struct TmpCardanoTransaction {
356            merkle_root: String,
357            block_number: BlockNumber,
358            hash: String,
359        }
360        let artifact = serde_json::from_str::<TmpCardanoTransaction>(&value.artifact)?;
361        let message = CardanoTransactionSnapshotListItemMessage {
362            merkle_root: artifact.merkle_root,
363            epoch: value.signed_entity_type.get_epoch(),
364            block_number: artifact.block_number,
365            hash: artifact.hash,
366            certificate_hash: value.certificate_id,
367            created_at: value.created_at,
368        };
369
370        Ok(message)
371    }
372}
373
374impl TryFrom<SignedEntityRecord> for SnapshotListItemMessage {
375    type Error = StdError;
376
377    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
378        let artifact = serde_json::from_str::<Snapshot>(&value.artifact)?;
379        let message = SnapshotListItemMessage {
380            digest: artifact.digest,
381            network: artifact.network.clone(),
382            beacon: artifact.beacon,
383            certificate_hash: value.certificate_id,
384            size: artifact.size,
385            ancillary_size: artifact.ancillary_size,
386            created_at: value.created_at,
387            locations: artifact.locations,
388            ancillary_locations: artifact.ancillary_locations,
389            compression_algorithm: artifact.compression_algorithm,
390            cardano_node_version: artifact.cardano_node_version,
391        };
392
393        Ok(message)
394    }
395}
396
397impl TryFrom<SignedEntityRecord> for CardanoStakeDistributionMessage {
398    type Error = StdError;
399
400    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
401        #[derive(Deserialize)]
402        struct TmpCardanoStakeDistribution {
403            hash: String,
404            stake_distribution: StakeDistribution,
405        }
406        let artifact = serde_json::from_str::<TmpCardanoStakeDistribution>(&value.artifact)?;
407        let cardano_stake_distribution_message = CardanoStakeDistributionMessage {
408            // The epoch stored in the signed entity type beacon corresponds to epoch
409            // at the end of which the Cardano stake distribution is computed by the Cardano node.
410            epoch: value.signed_entity_type.get_epoch(),
411            stake_distribution: artifact.stake_distribution,
412            hash: artifact.hash,
413            certificate_hash: value.certificate_id,
414            created_at: value.created_at,
415        };
416
417        Ok(cardano_stake_distribution_message)
418    }
419}
420
421impl TryFrom<SignedEntityRecord> for CardanoStakeDistributionListItemMessage {
422    type Error = StdError;
423
424    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
425        #[derive(Deserialize)]
426        struct TmpCardanoStakeDistribution {
427            hash: String,
428        }
429        let artifact = serde_json::from_str::<TmpCardanoStakeDistribution>(&value.artifact)?;
430        let message = CardanoStakeDistributionListItemMessage {
431            // The epoch stored in the signed entity type beacon corresponds to epoch
432            // at the end of which the Cardano stake distribution is computed by the Cardano node.
433            epoch: value.signed_entity_type.get_epoch(),
434            hash: artifact.hash,
435            certificate_hash: value.certificate_id,
436            created_at: value.created_at,
437        };
438
439        Ok(message)
440    }
441}
442
443impl SqLiteEntity for SignedEntityRecord {
444    fn hydrate(row: sqlite::Row) -> Result<Self, HydrationError>
445    where
446        Self: Sized,
447    {
448        let signed_entity_id = row.read::<&str, _>(0).to_string();
449        let signed_entity_type_id_int = row.read::<i64, _>(1);
450        let certificate_id = row.read::<&str, _>(2).to_string();
451        let beacon_str = Hydrator::read_signed_entity_beacon_column(&row, 3);
452        let artifact_str = row.read::<&str, _>(4).to_string();
453        let created_at = row.read::<&str, _>(5);
454
455        let signed_entity_record = Self {
456            signed_entity_id,
457            signed_entity_type: Hydrator::hydrate_signed_entity_type(
458                signed_entity_type_id_int.try_into().map_err(|e| {
459                    HydrationError::InvalidData(format!(
460                        "Could not cast i64 ({signed_entity_type_id_int}) to u64. Error: '{e}'"
461                    ))
462                })?,
463                &beacon_str,
464            )?,
465            certificate_id,
466            artifact: artifact_str,
467            created_at: DateTime::parse_from_rfc3339(created_at)
468                .map_err(|e| {
469                    HydrationError::InvalidData(format!(
470                        "Could not turn string '{created_at}' to rfc3339 Datetime. Error: {e}"
471                    ))
472                })?
473                .with_timezone(&Utc),
474        };
475
476        Ok(signed_entity_record)
477    }
478
479    fn get_projection() -> Projection {
480        Projection::from(&[
481            (
482                "signed_entity_id",
483                "{:signed_entity:}.signed_entity_id",
484                "text",
485            ),
486            (
487                "signed_entity_type_id",
488                "{:signed_entity:}.signed_entity_type_id",
489                "integer",
490            ),
491            ("certificate_id", "{:signed_entity:}.certificate_id", "text"),
492            ("beacon", "{:signed_entity:}.beacon", "text"),
493            ("artifact", "{:signed_entity:}.artifact", "text"),
494            ("created_at", "{:signed_entity:}.created_at", "text"),
495        ])
496    }
497}
498
499#[cfg(test)]
500mod tests {
501    use mithril_common::test::double::fake_data;
502
503    use super::*;
504
505    #[test]
506    fn test_convert_signed_entity() {
507        let snapshot = fake_data::snapshot(1);
508        let snapshot_expected = snapshot.clone();
509
510        let signed_entity: SignedEntityRecord = SignedEntityRecord::from_snapshot(
511            snapshot,
512            "certificate-1".to_string(),
513            DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
514                .unwrap()
515                .with_timezone(&Utc),
516        );
517        let snapshot: Snapshot = signed_entity.into();
518        assert_eq!(snapshot_expected, snapshot);
519    }
520}