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#[derive(Debug, PartialEq, Clone)]
25pub struct SignedEntityRecord {
26 pub signed_entity_id: String,
28
29 pub signed_entity_type: SignedEntityType,
31
32 pub certificate_id: String,
34
35 pub artifact: String,
37
38 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 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 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}