mithril_aggregator/database/record/
signed_entity.rs

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