mithril_common/entities/
cardano_database.rs

1use semver::Version;
2use serde::{Deserialize, Serialize};
3use sha2::{Digest, Sha256};
4
5use crate::entities::{CardanoDbBeacon, CompressionAlgorithm};
6
7use super::{CardanoNetwork, MultiFilesUri};
8
9/// Structure holding artifacts data needed to create a Cardano database snapshot.
10pub struct CardanoDatabaseSnapshotArtifactData {
11    /// Size of the uncompressed Cardano database files.
12    pub total_db_size_uncompressed: u64,
13
14    /// Locations of the Cardano database digests.
15    pub digests: DigestsLocations,
16
17    /// Locations of the Cardano database immutables.
18    pub immutables: ImmutablesLocations,
19
20    /// Locations of the Cardano database ancillary.
21    pub ancillary: AncillaryLocations,
22}
23
24/// Cardano database snapshot.
25#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
26pub struct CardanoDatabaseSnapshot {
27    /// Unique hash of the Cardano database snapshot.
28    pub hash: String,
29
30    /// Merkle root of the Cardano database snapshot.
31    pub merkle_root: String,
32
33    /// Cardano network.
34    pub network: CardanoNetwork,
35
36    /// Mithril beacon on the Cardano chain.
37    pub beacon: CardanoDbBeacon,
38
39    /// Size of the uncompressed Cardano database files.
40    pub total_db_size_uncompressed: u64,
41
42    /// Locations of the Cardano database digests.
43    pub digests: DigestsLocations,
44
45    /// Locations of the Cardano database immutables.
46    pub immutables: ImmutablesLocations,
47
48    /// Locations of the Cardano database ancillary.
49    pub ancillary: AncillaryLocations,
50
51    /// Version of the Cardano node used to create the snapshot.
52    pub cardano_node_version: String,
53}
54
55impl CardanoDatabaseSnapshot {
56    /// [CardanoDatabaseSnapshot] factory
57    pub fn new(
58        merkle_root: String,
59        network: CardanoNetwork,
60        beacon: CardanoDbBeacon,
61        content: CardanoDatabaseSnapshotArtifactData,
62        cardano_node_version: &Version,
63    ) -> Self {
64        let cardano_node_version = format!("{cardano_node_version}");
65        let mut cardano_database_snapshot = Self {
66            hash: "".to_string(),
67            merkle_root,
68            network,
69            beacon,
70            digests: content.digests,
71            immutables: content.immutables,
72            ancillary: content.ancillary,
73            total_db_size_uncompressed: content.total_db_size_uncompressed,
74            cardano_node_version,
75        };
76        cardano_database_snapshot.hash = cardano_database_snapshot.compute_hash();
77
78        cardano_database_snapshot
79    }
80
81    /// Cardano database snapshot hash computation
82    fn compute_hash(&self) -> String {
83        let mut hasher = Sha256::new();
84        hasher.update(self.beacon.epoch.to_be_bytes());
85        hasher.update(self.merkle_root.as_bytes());
86
87        hex::encode(hasher.finalize())
88    }
89}
90
91/// Locations of the immutable file digests.
92#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
93#[serde(rename_all = "snake_case", tag = "type")]
94pub enum DigestLocation {
95    /// Cloud storage location.
96    CloudStorage {
97        /// URI of the cloud storage location.
98        uri: String,
99
100        /// Compression algorithm of the Cardano database artifacts.
101        #[serde(skip_serializing_if = "Option::is_none")]
102        compression_algorithm: Option<CompressionAlgorithm>,
103    },
104    /// Aggregator digest route location.
105    Aggregator {
106        /// URI of the aggregator digests route location.
107        uri: String,
108    },
109    /// Catchall for unknown location variants.
110    #[serde(other)]
111    Unknown,
112}
113
114/// Locations of the immutable files.
115#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
116#[serde(rename_all = "snake_case", tag = "type")]
117pub enum ImmutablesLocation {
118    /// Cloud storage location.
119    CloudStorage {
120        /// URI of the cloud storage location.
121        uri: MultiFilesUri,
122
123        /// Compression algorithm of the Cardano database artifacts.
124        #[serde(skip_serializing_if = "Option::is_none")]
125        compression_algorithm: Option<CompressionAlgorithm>,
126    },
127    /// Catchall for unknown location variants.
128    #[serde(other)]
129    Unknown,
130}
131
132/// Locations of the ancillary files.
133#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
134#[serde(rename_all = "snake_case", tag = "type")]
135pub enum AncillaryLocation {
136    /// Cloud storage location.
137    CloudStorage {
138        /// URI of the cloud storage location.
139        uri: String,
140
141        /// Compression algorithm of the Cardano database artifacts.
142        #[serde(skip_serializing_if = "Option::is_none")]
143        compression_algorithm: Option<CompressionAlgorithm>,
144    },
145    /// Catchall for unknown location variants.
146    #[serde(other)]
147    Unknown,
148}
149
150/// Digests locations of the Cardano database related files.
151#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
152pub struct DigestsLocations {
153    /// Size of the uncompressed digests file.
154    pub size_uncompressed: u64,
155
156    /// Locations of the immutable files digests.
157    pub locations: Vec<DigestLocation>,
158}
159
160/// Immutables locations of the Cardano database related files.
161#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
162pub struct ImmutablesLocations {
163    /// Average size for one immutable file.
164    pub average_size_uncompressed: u64,
165
166    /// Locations of the immutable files.
167    pub locations: Vec<ImmutablesLocation>,
168}
169
170/// Ancillary locations of the Cardano database related files.
171#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
172pub struct AncillaryLocations {
173    /// Size of the uncompressed ancillary file.
174    pub size_uncompressed: u64,
175
176    /// Locations of the ancillary files.
177    pub locations: Vec<AncillaryLocation>,
178}
179
180#[cfg(test)]
181mod tests {
182    use crate::entities::TemplateUri;
183
184    use super::*;
185
186    fn dummy() -> CardanoDatabaseSnapshot {
187        CardanoDatabaseSnapshot::new(
188            "mk-root-1111111111".to_string(),
189            CardanoNetwork::DevNet(87),
190            CardanoDbBeacon::new(2222, 55555),
191            CardanoDatabaseSnapshotArtifactData {
192                total_db_size_uncompressed: 0,
193                digests: DigestsLocations {
194                    size_uncompressed: 0,
195                    locations: vec![],
196                },
197                immutables: ImmutablesLocations {
198                    average_size_uncompressed: 0,
199                    locations: vec![],
200                },
201                ancillary: AncillaryLocations {
202                    size_uncompressed: 0,
203                    locations: vec![],
204                },
205            },
206            &Version::new(1, 0, 0),
207        )
208    }
209
210    mod cardano_database_snapshot_compute_hash {
211        use super::*;
212
213        #[test]
214        fn test_cardano_database_snapshot_compute_hash() {
215            let cardano_database_snapshot = CardanoDatabaseSnapshot {
216                merkle_root: "mk-root-123".to_string(),
217                beacon: CardanoDbBeacon::new(123, 98),
218                ..dummy()
219            };
220
221            assert_eq!(
222                "b1cc5e0deaa7856e8e811e349d6e639fa667aa70288602955f438c5893ce29c8",
223                cardano_database_snapshot.compute_hash()
224            );
225        }
226
227        #[test]
228        fn compute_hash_returns_same_hash_with_same_cardano_database_snapshot() {
229            assert_eq!(
230                CardanoDatabaseSnapshot {
231                    merkle_root: "mk-root-123".to_string(),
232                    beacon: CardanoDbBeacon::new(123, 98),
233                    ..dummy()
234                }
235                .compute_hash(),
236                CardanoDatabaseSnapshot {
237                    merkle_root: "mk-root-123".to_string(),
238                    beacon: CardanoDbBeacon::new(123, 98),
239                    ..dummy()
240                }
241                .compute_hash()
242            );
243        }
244
245        #[test]
246        fn compute_hash_returns_different_hash_with_different_merkle_root() {
247            assert_ne!(
248                CardanoDatabaseSnapshot {
249                    merkle_root: "mk-root-123".to_string(),
250                    beacon: CardanoDbBeacon::new(123, 98),
251                    ..dummy()
252                }
253                .compute_hash(),
254                CardanoDatabaseSnapshot {
255                    merkle_root: "mk-root-456".to_string(),
256                    beacon: CardanoDbBeacon::new(123, 98),
257                    ..dummy()
258                }
259                .compute_hash()
260            );
261        }
262
263        #[test]
264        fn compute_hash_returns_different_hash_with_same_epoch_in_beacon() {
265            assert_eq!(
266                CardanoDatabaseSnapshot {
267                    merkle_root: "mk-root-123".to_string(),
268                    beacon: CardanoDbBeacon::new(123, 98),
269                    ..dummy()
270                }
271                .compute_hash(),
272                CardanoDatabaseSnapshot {
273                    merkle_root: "mk-root-123".to_string(),
274                    beacon: CardanoDbBeacon::new(123, 12),
275                    ..dummy()
276                }
277                .compute_hash()
278            );
279        }
280
281        #[test]
282        fn compute_hash_returns_different_hash_with_different_beacon() {
283            assert_ne!(
284                CardanoDatabaseSnapshot {
285                    merkle_root: "mk-root-123".to_string(),
286                    beacon: CardanoDbBeacon::new(123, 98),
287                    ..dummy()
288                }
289                .compute_hash(),
290                CardanoDatabaseSnapshot {
291                    merkle_root: "mk-root-123".to_string(),
292                    beacon: CardanoDbBeacon::new(456, 98),
293                    ..dummy()
294                }
295                .compute_hash()
296            );
297        }
298    }
299
300    #[test]
301    fn should_not_display_compression_algorithm_in_json_ancillary_location_when_none() {
302        let json = serde_json::json!(AncillaryLocation::CloudStorage {
303            uri: "https://example.com".to_string(),
304            compression_algorithm: None,
305        });
306        assert_eq!(
307            json.to_string(),
308            r#"{"type":"cloud_storage","uri":"https://example.com"}"#
309        );
310    }
311
312    #[test]
313    fn should_not_display_compression_algorithm_in_json_digests_location_when_none() {
314        let json = serde_json::json!(DigestLocation::CloudStorage {
315            uri: "https://example.com".to_string(),
316            compression_algorithm: None,
317        });
318        assert_eq!(
319            json.to_string(),
320            r#"{"type":"cloud_storage","uri":"https://example.com"}"#
321        );
322    }
323
324    #[test]
325    fn should_not_display_compression_algorithm_in_json_immutable_location_when_none() {
326        let json = serde_json::json!(ImmutablesLocation::CloudStorage {
327            uri: MultiFilesUri::Template(TemplateUri("https://example.com".to_string())),
328            compression_algorithm: None,
329        });
330        assert_eq!(
331            json.to_string(),
332            r#"{"type":"cloud_storage","uri":{"Template":"https://example.com"}}"#
333        );
334    }
335}