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