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#[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 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 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 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}