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 SignedEntityType::CardanoBlocksTransactions(_epoch, _block_number) => {
117 panic!("Cardano blocks transactions is not supported yet")
118 }
119 };
120
121 SignedEntityRecord {
122 signed_entity_id: id.clone(),
123 signed_entity_type,
124 certificate_id: format!("certificate-{id}"),
125 artifact,
126 created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
127 .unwrap()
128 .with_timezone(&Utc),
129 }
130 }
131
132 pub(crate) fn from_snapshot(
133 snapshot: Snapshot,
134 certificate_id: String,
135 created_at: DateTime<Utc>,
136 ) -> Self {
137 let entity = serde_json::to_string(&snapshot).unwrap();
138
139 SignedEntityRecord {
140 signed_entity_id: snapshot.digest,
141 signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(snapshot.beacon),
142 certificate_id,
143 artifact: entity,
144 created_at,
145 }
146 }
147
148 pub(crate) fn from_cardano_stake_distribution(
149 cardano_stake_distribution: CardanoStakeDistribution,
150 ) -> Self {
151 let entity = serde_json::to_string(&cardano_stake_distribution).unwrap();
152
153 SignedEntityRecord {
154 signed_entity_id: cardano_stake_distribution.hash.clone(),
155 signed_entity_type: SignedEntityType::CardanoStakeDistribution(
156 cardano_stake_distribution.epoch,
157 ),
158 certificate_id: format!("certificate-{}", cardano_stake_distribution.hash),
159 artifact: entity,
160 created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
161 .unwrap()
162 .with_timezone(&Utc),
163 }
164 }
165
166 pub(crate) fn fake_records(number_if_records: usize) -> Vec<SignedEntityRecord> {
167 use mithril_common::test::double::fake_data;
168
169 let snapshots = fake_data::snapshots(number_if_records as u64);
170 (0..number_if_records)
171 .map(|idx| {
172 let snapshot = snapshots.get(idx).unwrap().to_owned();
173 let entity = serde_json::to_string(&snapshot).unwrap();
174 SignedEntityRecord {
175 signed_entity_id: snapshot.digest,
176 signed_entity_type: SignedEntityType::CardanoImmutableFilesFull(
177 snapshot.beacon,
178 ),
179 certificate_id: format!("certificate-{idx}"),
180 artifact: entity,
181 created_at: DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
182 .unwrap()
183 .with_timezone(&Utc),
184 }
185 })
186 .collect()
187 }
188}
189
190impl From<SignedEntityRecord> for Snapshot {
191 fn from(other: SignedEntityRecord) -> Snapshot {
192 serde_json::from_str(&other.artifact).unwrap()
193 }
194}
195
196impl<T> TryFrom<SignedEntityRecord> for SignedEntity<T>
197where
198 for<'a> T: Artifact + Serialize + Deserialize<'a>,
199{
200 type Error = serde_json::error::Error;
201
202 fn try_from(other: SignedEntityRecord) -> Result<SignedEntity<T>, Self::Error> {
203 let signed_entity = SignedEntity {
204 signed_entity_id: other.signed_entity_id,
205 signed_entity_type: other.signed_entity_type,
206 created_at: other.created_at,
207 certificate_id: other.certificate_id,
208 artifact: serde_json::from_str::<T>(&other.artifact)?,
209 };
210
211 Ok(signed_entity)
212 }
213}
214
215impl TryFrom<SignedEntityRecord> for SnapshotMessage {
216 type Error = StdError;
217
218 fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
219 let artifact = serde_json::from_str::<Snapshot>(&value.artifact)?;
220 let snapshot_message = SnapshotMessage {
221 digest: artifact.digest,
222 network: artifact.network.clone(),
223 beacon: artifact.beacon,
224 certificate_hash: value.certificate_id,
225 size: artifact.size,
226 ancillary_size: artifact.ancillary_size,
227 created_at: value.created_at,
228 locations: artifact.locations,
229 ancillary_locations: artifact.ancillary_locations,
230 compression_algorithm: artifact.compression_algorithm,
231 cardano_node_version: artifact.cardano_node_version,
232 };
233
234 Ok(snapshot_message)
235 }
236}
237
238impl TryFrom<SignedEntityRecord> for CardanoDatabaseSnapshotMessage {
239 type Error = StdError;
240
241 fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
242 let artifact = serde_json::from_str::<CardanoDatabaseSnapshot>(&value.artifact)?;
243 let cardano_database_snapshot_message = CardanoDatabaseSnapshotMessage {
244 hash: artifact.hash,
245 merkle_root: artifact.merkle_root,
246 network: artifact.network.to_string(),
247 beacon: artifact.beacon,
248 certificate_hash: value.certificate_id,
249 total_db_size_uncompressed: artifact.total_db_size_uncompressed,
250 created_at: value.created_at,
251 digests: artifact.digests.into(),
252 immutables: artifact.immutables.into(),
253 ancillary: artifact.ancillary.into(),
254 cardano_node_version: artifact.cardano_node_version,
255 };
256
257 Ok(cardano_database_snapshot_message)
258 }
259}
260
261impl TryFrom<SignedEntityRecord> for CardanoDatabaseSnapshotListItemMessage {
262 type Error = StdError;
263
264 fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
265 let artifact = serde_json::from_str::<CardanoDatabaseSnapshot>(&value.artifact)?;
266 let cardano_database_snapshot_list_item_message = CardanoDatabaseSnapshotListItemMessage {
267 hash: artifact.hash,
268 merkle_root: artifact.merkle_root,
269 beacon: artifact.beacon,
270 certificate_hash: value.certificate_id,
271 total_db_size_uncompressed: artifact.total_db_size_uncompressed,
272 created_at: value.created_at,
273 cardano_node_version: artifact.cardano_node_version,
274 };
275
276 Ok(cardano_database_snapshot_list_item_message)
277 }
278}
279
280impl TryFrom<SignedEntityRecord> for MithrilStakeDistributionMessage {
281 type Error = StdError;
282
283 fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
284 #[derive(Deserialize)]
285 struct TmpMithrilStakeDistribution {
286 epoch: Epoch,
287 signers_with_stake: Vec<SignerWithStakeMessagePart>,
288 hash: String,
289 protocol_parameters: ProtocolParameters,
290 }
291 let artifact = serde_json::from_str::<TmpMithrilStakeDistribution>(&value.artifact)?;
292 let mithril_stake_distribution_message = MithrilStakeDistributionMessage {
293 epoch: artifact.epoch,
294 signers_with_stake: artifact.signers_with_stake,
295 hash: artifact.hash,
296 certificate_hash: value.certificate_id,
297 created_at: value.created_at,
298 protocol_parameters: artifact.protocol_parameters.into(),
299 };
300
301 Ok(mithril_stake_distribution_message)
302 }
303}
304
305impl TryFrom<SignedEntityRecord> for MithrilStakeDistributionListItemMessage {
306 type Error = StdError;
307
308 fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
309 #[derive(Deserialize)]
310 struct TmpMithrilStakeDistribution {
311 epoch: Epoch,
312 hash: String,
313 }
314 let artifact = serde_json::from_str::<TmpMithrilStakeDistribution>(&value.artifact)?;
315 let message = MithrilStakeDistributionListItemMessage {
316 epoch: artifact.epoch,
317 hash: artifact.hash,
318 certificate_hash: value.certificate_id,
319 created_at: value.created_at,
320 };
321
322 Ok(message)
323 }
324}
325
326impl TryFrom<SignedEntityRecord> for CardanoTransactionSnapshotMessage {
327 type Error = StdError;
328
329 fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
330 #[derive(Deserialize)]
331 struct TmpCardanoTransaction {
332 merkle_root: String,
333 block_number: BlockNumber,
334 hash: String,
335 }
336 let artifact = serde_json::from_str::<TmpCardanoTransaction>(&value.artifact)?;
337 let cardano_transaction_message = CardanoTransactionSnapshotMessage {
338 merkle_root: artifact.merkle_root,
339 epoch: value.signed_entity_type.get_epoch(),
340 block_number: artifact.block_number,
341 hash: artifact.hash,
342 certificate_hash: value.certificate_id,
343 created_at: value.created_at,
344 };
345
346 Ok(cardano_transaction_message)
347 }
348}
349
350impl TryFrom<SignedEntityRecord> for CardanoTransactionSnapshotListItemMessage {
351 type Error = StdError;
352
353 fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
354 #[derive(Deserialize)]
355 struct TmpCardanoTransaction {
356 merkle_root: String,
357 block_number: BlockNumber,
358 hash: String,
359 }
360 let artifact = serde_json::from_str::<TmpCardanoTransaction>(&value.artifact)?;
361 let message = CardanoTransactionSnapshotListItemMessage {
362 merkle_root: artifact.merkle_root,
363 epoch: value.signed_entity_type.get_epoch(),
364 block_number: artifact.block_number,
365 hash: artifact.hash,
366 certificate_hash: value.certificate_id,
367 created_at: value.created_at,
368 };
369
370 Ok(message)
371 }
372}
373
374impl TryFrom<SignedEntityRecord> for SnapshotListItemMessage {
375 type Error = StdError;
376
377 fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
378 let artifact = serde_json::from_str::<Snapshot>(&value.artifact)?;
379 let message = SnapshotListItemMessage {
380 digest: artifact.digest,
381 network: artifact.network.clone(),
382 beacon: artifact.beacon,
383 certificate_hash: value.certificate_id,
384 size: artifact.size,
385 ancillary_size: artifact.ancillary_size,
386 created_at: value.created_at,
387 locations: artifact.locations,
388 ancillary_locations: artifact.ancillary_locations,
389 compression_algorithm: artifact.compression_algorithm,
390 cardano_node_version: artifact.cardano_node_version,
391 };
392
393 Ok(message)
394 }
395}
396
397impl TryFrom<SignedEntityRecord> for CardanoStakeDistributionMessage {
398 type Error = StdError;
399
400 fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
401 #[derive(Deserialize)]
402 struct TmpCardanoStakeDistribution {
403 hash: String,
404 stake_distribution: StakeDistribution,
405 }
406 let artifact = serde_json::from_str::<TmpCardanoStakeDistribution>(&value.artifact)?;
407 let cardano_stake_distribution_message = CardanoStakeDistributionMessage {
408 epoch: value.signed_entity_type.get_epoch(),
411 stake_distribution: artifact.stake_distribution,
412 hash: artifact.hash,
413 certificate_hash: value.certificate_id,
414 created_at: value.created_at,
415 };
416
417 Ok(cardano_stake_distribution_message)
418 }
419}
420
421impl TryFrom<SignedEntityRecord> for CardanoStakeDistributionListItemMessage {
422 type Error = StdError;
423
424 fn try_from(value: SignedEntityRecord) -> Result<Self, Self::Error> {
425 #[derive(Deserialize)]
426 struct TmpCardanoStakeDistribution {
427 hash: String,
428 }
429 let artifact = serde_json::from_str::<TmpCardanoStakeDistribution>(&value.artifact)?;
430 let message = CardanoStakeDistributionListItemMessage {
431 epoch: value.signed_entity_type.get_epoch(),
434 hash: artifact.hash,
435 certificate_hash: value.certificate_id,
436 created_at: value.created_at,
437 };
438
439 Ok(message)
440 }
441}
442
443impl SqLiteEntity for SignedEntityRecord {
444 fn hydrate(row: sqlite::Row) -> Result<Self, HydrationError>
445 where
446 Self: Sized,
447 {
448 let signed_entity_id = row.read::<&str, _>(0).to_string();
449 let signed_entity_type_id_int = row.read::<i64, _>(1);
450 let certificate_id = row.read::<&str, _>(2).to_string();
451 let beacon_str = Hydrator::read_signed_entity_beacon_column(&row, 3);
452 let artifact_str = row.read::<&str, _>(4).to_string();
453 let created_at = row.read::<&str, _>(5);
454
455 let signed_entity_record = Self {
456 signed_entity_id,
457 signed_entity_type: Hydrator::hydrate_signed_entity_type(
458 signed_entity_type_id_int.try_into().map_err(|e| {
459 HydrationError::InvalidData(format!(
460 "Could not cast i64 ({signed_entity_type_id_int}) to u64. Error: '{e}'"
461 ))
462 })?,
463 &beacon_str,
464 )?,
465 certificate_id,
466 artifact: artifact_str,
467 created_at: DateTime::parse_from_rfc3339(created_at)
468 .map_err(|e| {
469 HydrationError::InvalidData(format!(
470 "Could not turn string '{created_at}' to rfc3339 Datetime. Error: {e}"
471 ))
472 })?
473 .with_timezone(&Utc),
474 };
475
476 Ok(signed_entity_record)
477 }
478
479 fn get_projection() -> Projection {
480 Projection::from(&[
481 (
482 "signed_entity_id",
483 "{:signed_entity:}.signed_entity_id",
484 "text",
485 ),
486 (
487 "signed_entity_type_id",
488 "{:signed_entity:}.signed_entity_type_id",
489 "integer",
490 ),
491 ("certificate_id", "{:signed_entity:}.certificate_id", "text"),
492 ("beacon", "{:signed_entity:}.beacon", "text"),
493 ("artifact", "{:signed_entity:}.artifact", "text"),
494 ("created_at", "{:signed_entity:}.created_at", "text"),
495 ])
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use mithril_common::test::double::fake_data;
502
503 use super::*;
504
505 #[test]
506 fn test_convert_signed_entity() {
507 let snapshot = fake_data::snapshot(1);
508 let snapshot_expected = snapshot.clone();
509
510 let signed_entity: SignedEntityRecord = SignedEntityRecord::from_snapshot(
511 snapshot,
512 "certificate-1".to_string(),
513 DateTime::parse_from_rfc3339("2023-01-19T13:43:05.618857482Z")
514 .unwrap()
515 .with_timezone(&Utc),
516 );
517 let snapshot: Snapshot = signed_entity.into();
518 assert_eq!(snapshot_expected, snapshot);
519 }
520}