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        };
117
118        SignedEntityRecord {
119            signed_entity_id: id.clone(),
120            signed_entity_type,
121            certificate_id: format!("certificate-{id}"),
122            artifact,
123            created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
124                .unwrap()
125                .with_timezone(&Utc),
126        }
127    }
128
129    pub(crate) fn from_snapshot(
130        snapshot: Snapshot,
131        certificate_id: String,
132        created_at: DateTime<Utc>,
133    ) -> Self {
134        let entity = serde_json::to_string(&snapshot).unwrap();
135
136        SignedEntityRecord {
137            signed_entity_id: snapshot.digest,
138            signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(snapshot.beacon),
139            certificate_id,
140            artifact: entity,
141            created_at,
142        }
143    }
144
145    pub(crate) fn from_cardano_stake_distribution(
146        cardano_stake_distribution: CardanoStakeDistribution,
147    ) -> Self {
148        let entity = serde_json::to_string(&cardano_stake_distribution).unwrap();
149
150        SignedEntityRecord {
151            signed_entity_id: cardano_stake_distribution.hash.clone(),
152            signed_entity_type: SignedEntityType::CardanoStakeDistribution(
153                cardano_stake_distribution.epoch,
154            ),
155            certificate_id: format!("certificate-{}", cardano_stake_distribution.hash),
156            artifact: entity,
157            created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
158                .unwrap()
159                .with_timezone(&Utc),
160        }
161    }
162
163    pub(crate) fn fake_records(number_if_records: usize) -> Vec<SignedEntityRecord> {
164        use mithril_common::test::double::fake_data;
165
166        let snapshots = fake_data::snapshots(number_if_records as u64);
167        (0..number_if_records)
168            .map(|idx| {
169                let snapshot = snapshots.get(idx).unwrap().to_owned();
170                let entity = serde_json::to_string(&snapshot).unwrap();
171                SignedEntityRecord {
172                    signed_entity_id: snapshot.digest,
173                    signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(
174                        snapshot.beacon,
175                    ),
176                    certificate_id: format!("certificate-{idx}"),
177                    artifact: entity,
178                    created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
179                        .unwrap()
180                        .with_timezone(&Utc),
181                }
182            })
183            .collect()
184    }
185}
186
187impl From<SignedEntityRecord> for Snapshot {
188    fn from(other: SignedEntityRecord) -> Snapshot {
189        serde_json::from_str(&other.artifact).unwrap()
190    }
191}
192
193impl<T> TryFrom<SignedEntityRecord> for SignedEntity<T>
194where
195    for<'a> T: Artifact + Serialize + Deserialize<'a>,
196{
197    type Error = serde_json::error::Error;
198
199    fn try_from(other: SignedEntityRecord) -> Result<SignedEntity<T>, Self::Error> {
200        let signed_entity = SignedEntity {
201            signed_entity_id: other.signed_entity_id,
202            signed_entity_type: other.signed_entity_type,
203            created_at: other.created_at,
204            certificate_id: other.certificate_id,
205            artifact: serde_json::from_str::<T>(&other.artifact)?,
206        };
207
208        Ok(signed_entity)
209    }
210}
211
212impl TryFrom<SignedEntityRecord> for SnapshotMessage {
213    type Error = StdError;
214
215    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
216        let artifact = serde_json::from_str::<Snapshot>(&value.artifact)?;
217        let snapshot_message = SnapshotMessage {
218            digest: artifact.digest,
219            network: artifact.network.clone(),
220            beacon: artifact.beacon,
221            certificate_hash: value.certificate_id,
222            size: artifact.size,
223            ancillary_size: artifact.ancillary_size,
224            created_at: value.created_at,
225            locations: artifact.locations,
226            ancillary_locations: artifact.ancillary_locations,
227            compression_algorithm: artifact.compression_algorithm,
228            cardano_node_version: artifact.cardano_node_version,
229        };
230
231        Ok(snapshot_message)
232    }
233}
234
235impl TryFrom<SignedEntityRecord> for CardanoDatabaseSnapshotMessage {
236    type Error = StdError;
237
238    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
239        let artifact = serde_json::from_str::<CardanoDatabaseSnapshot>(&value.artifact)?;
240        let cardano_database_snapshot_message = CardanoDatabaseSnapshotMessage {
241            hash: artifact.hash,
242            merkle_root: artifact.merkle_root,
243            network: artifact.network.to_string(),
244            beacon: artifact.beacon,
245            certificate_hash: value.certificate_id,
246            total_db_size_uncompressed: artifact.total_db_size_uncompressed,
247            created_at: value.created_at,
248            digests: artifact.digests.into(),
249            immutables: artifact.immutables.into(),
250            ancillary: artifact.ancillary.into(),
251            cardano_node_version: artifact.cardano_node_version,
252        };
253
254        Ok(cardano_database_snapshot_message)
255    }
256}
257
258impl TryFrom<SignedEntityRecord> for CardanoDatabaseSnapshotListItemMessage {
259    type Error = StdError;
260
261    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
262        let artifact = serde_json::from_str::<CardanoDatabaseSnapshot>(&value.artifact)?;
263        let cardano_database_snapshot_list_item_message = CardanoDatabaseSnapshotListItemMessage {
264            hash: artifact.hash,
265            merkle_root: artifact.merkle_root,
266            beacon: artifact.beacon,
267            certificate_hash: value.certificate_id,
268            total_db_size_uncompressed: artifact.total_db_size_uncompressed,
269            created_at: value.created_at,
270            cardano_node_version: artifact.cardano_node_version,
271        };
272
273        Ok(cardano_database_snapshot_list_item_message)
274    }
275}
276
277impl TryFrom<SignedEntityRecord> for MithrilStakeDistributionMessage {
278    type Error = StdError;
279
280    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
281        #[derive(Deserialize)]
282        struct TmpMithrilStakeDistribution {
283            epoch: Epoch,
284            signers_with_stake: Vec<SignerWithStakeMessagePart>,
285            hash: String,
286            protocol_parameters: ProtocolParameters,
287        }
288        let artifact = serde_json::from_str::<TmpMithrilStakeDistribution>(&value.artifact)?;
289        let mithril_stake_distribution_message = MithrilStakeDistributionMessage {
290            epoch: artifact.epoch,
291            signers_with_stake: artifact.signers_with_stake,
292            hash: artifact.hash,
293            certificate_hash: value.certificate_id,
294            created_at: value.created_at,
295            protocol_parameters: artifact.protocol_parameters.into(),
296        };
297
298        Ok(mithril_stake_distribution_message)
299    }
300}
301
302impl TryFrom<SignedEntityRecord> for MithrilStakeDistributionListItemMessage {
303    type Error = StdError;
304
305    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
306        #[derive(Deserialize)]
307        struct TmpMithrilStakeDistribution {
308            epoch: Epoch,
309            hash: String,
310        }
311        let artifact = serde_json::from_str::<TmpMithrilStakeDistribution>(&value.artifact)?;
312        let message = MithrilStakeDistributionListItemMessage {
313            epoch: artifact.epoch,
314            hash: artifact.hash,
315            certificate_hash: value.certificate_id,
316            created_at: value.created_at,
317        };
318
319        Ok(message)
320    }
321}
322
323impl TryFrom<SignedEntityRecord> for CardanoTransactionSnapshotMessage {
324    type Error = StdError;
325
326    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
327        #[derive(Deserialize)]
328        struct TmpCardanoTransaction {
329            merkle_root: String,
330            block_number: BlockNumber,
331            hash: String,
332        }
333        let artifact = serde_json::from_str::<TmpCardanoTransaction>(&value.artifact)?;
334        let cardano_transaction_message = CardanoTransactionSnapshotMessage {
335            merkle_root: artifact.merkle_root,
336            epoch: value.signed_entity_type.get_epoch(),
337            block_number: artifact.block_number,
338            hash: artifact.hash,
339            certificate_hash: value.certificate_id,
340            created_at: value.created_at,
341        };
342
343        Ok(cardano_transaction_message)
344    }
345}
346
347impl TryFrom<SignedEntityRecord> for CardanoTransactionSnapshotListItemMessage {
348    type Error = StdError;
349
350    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
351        #[derive(Deserialize)]
352        struct TmpCardanoTransaction {
353            merkle_root: String,
354            block_number: BlockNumber,
355            hash: String,
356        }
357        let artifact = serde_json::from_str::<TmpCardanoTransaction>(&value.artifact)?;
358        let message = CardanoTransactionSnapshotListItemMessage {
359            merkle_root: artifact.merkle_root,
360            epoch: value.signed_entity_type.get_epoch(),
361            block_number: artifact.block_number,
362            hash: artifact.hash,
363            certificate_hash: value.certificate_id,
364            created_at: value.created_at,
365        };
366
367        Ok(message)
368    }
369}
370
371impl TryFrom<SignedEntityRecord> for SnapshotListItemMessage {
372    type Error = StdError;
373
374    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
375        let artifact = serde_json::from_str::<Snapshot>(&value.artifact)?;
376        let message = SnapshotListItemMessage {
377            digest: artifact.digest,
378            network: artifact.network.clone(),
379            beacon: artifact.beacon,
380            certificate_hash: value.certificate_id,
381            size: artifact.size,
382            ancillary_size: artifact.ancillary_size,
383            created_at: value.created_at,
384            locations: artifact.locations,
385            ancillary_locations: artifact.ancillary_locations,
386            compression_algorithm: artifact.compression_algorithm,
387            cardano_node_version: artifact.cardano_node_version,
388        };
389
390        Ok(message)
391    }
392}
393
394impl TryFrom<SignedEntityRecord> for CardanoStakeDistributionMessage {
395    type Error = StdError;
396
397    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
398        #[derive(Deserialize)]
399        struct TmpCardanoStakeDistribution {
400            hash: String,
401            stake_distribution: StakeDistribution,
402        }
403        let artifact = serde_json::from_str::<TmpCardanoStakeDistribution>(&value.artifact)?;
404        let cardano_stake_distribution_message = CardanoStakeDistributionMessage {
405            // The epoch stored in the signed entity type beacon corresponds to epoch
406            // at the end of which the Cardano stake distribution is computed by the Cardano node.
407            epoch: value.signed_entity_type.get_epoch(),
408            stake_distribution: artifact.stake_distribution,
409            hash: artifact.hash,
410            certificate_hash: value.certificate_id,
411            created_at: value.created_at,
412        };
413
414        Ok(cardano_stake_distribution_message)
415    }
416}
417
418impl TryFrom<SignedEntityRecord> for CardanoStakeDistributionListItemMessage {
419    type Error = StdError;
420
421    fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
422        #[derive(Deserialize)]
423        struct TmpCardanoStakeDistribution {
424            hash: String,
425        }
426        let artifact = serde_json::from_str::<TmpCardanoStakeDistribution>(&value.artifact)?;
427        let message = CardanoStakeDistributionListItemMessage {
428            // The epoch stored in the signed entity type beacon corresponds to epoch
429            // at the end of which the Cardano stake distribution is computed by the Cardano node.
430            epoch: value.signed_entity_type.get_epoch(),
431            hash: artifact.hash,
432            certificate_hash: value.certificate_id,
433            created_at: value.created_at,
434        };
435
436        Ok(message)
437    }
438}
439
440impl SqLiteEntity for SignedEntityRecord {
441    fn hydrate(row: sqlite::Row) -> Result<Self, HydrationError>
442    where
443        Self: Sized,
444    {
445        let signed_entity_id = row.read::<&str, _>(0).to_string();
446        let signed_entity_type_id_int = row.read::<i64, _>(1);
447        let certificate_id = row.read::<&str, _>(2).to_string();
448        let beacon_str = Hydrator::read_signed_entity_beacon_column(&row, 3);
449        let artifact_str = row.read::<&str, _>(4).to_string();
450        let created_at = row.read::<&str, _>(5);
451
452        let signed_entity_record = Self {
453            signed_entity_id,
454            signed_entity_type: Hydrator::hydrate_signed_entity_type(
455                signed_entity_type_id_int.try_into().map_err(|e| {
456                    HydrationError::InvalidData(format!(
457                        "Could not cast i64 ({signed_entity_type_id_int}) to u64. Error: '{e}'"
458                    ))
459                })?,
460                &beacon_str,
461            )?,
462            certificate_id,
463            artifact: artifact_str,
464            created_at: DateTime::parse_from_rfc3339(created_at)
465                .map_err(|e| {
466                    HydrationError::InvalidData(format!(
467                        "Could not turn string '{created_at}' to rfc3339 Datetime. Error: {e}"
468                    ))
469                })?
470                .with_timezone(&Utc),
471        };
472
473        Ok(signed_entity_record)
474    }
475
476    fn get_projection() -> Projection {
477        Projection::from(&[
478            (
479                "signed_entity_id",
480                "{:signed_entity:}.signed_entity_id",
481                "text",
482            ),
483            (
484                "signed_entity_type_id",
485                "{:signed_entity:}.signed_entity_type_id",
486                "integer",
487            ),
488            ("certificate_id", "{:signed_entity:}.certificate_id", "text"),
489            ("beacon", "{:signed_entity:}.beacon", "text"),
490            ("artifact", "{:signed_entity:}.artifact", "text"),
491            ("created_at", "{:signed_entity:}.created_at", "text"),
492        ])
493    }
494}
495
496#[cfg(test)]
497mod tests {
498    use mithril_common::test::double::fake_data;
499
500    use super::*;
501
502    #[test]
503    fn test_convert_signed_entity() {
504        let snapshot = fake_data::snapshot(1);
505        let snapshot_expected = snapshot.clone();
506
507        let signed_entity: SignedEntityRecord = SignedEntityRecord::from_snapshot(
508            snapshot,
509            "certificate-1".to_string(),
510            DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
511                .unwrap()
512                .with_timezone(&Utc),
513        );
514        let snapshot: Snapshot = signed_entity.into();
515        assert_eq!(snapshot_expected, snapshot);
516    }
517}