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#[derive(Debug, PartialEq, Clone)]
24pub struct SignedEntityRecord {
25 pub signed_entity_id: String,
27
28 pub signed_entity_type: SignedEntityType,
30
31 pub certificate_id: String,
33
34 pub artifact: String,
36
37 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 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 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}