mithril_signer/database/record/
signed_beacon_record.rs

1use chrono::{DateTime, Utc};
2use sqlite::Row;
3
4use mithril_common::entities::{Epoch, SignedEntityType};
5use mithril_persistence::database::Hydrator;
6use mithril_persistence::sqlite::{HydrationError, Projection, SqLiteEntity};
7
8use crate::entities::BeaconToSign;
9
10/// Database record of a beacon signed by the signer
11#[derive(Debug, Clone, PartialEq)]
12pub struct SignedBeaconRecord {
13    /// The epoch when the beacon was issued
14    pub epoch: Epoch,
15
16    /// The signed entity type to sign
17    pub signed_entity_type: SignedEntityType,
18
19    /// Datetime when the beacon was initiated
20    pub initiated_at: DateTime<Utc>,
21
22    /// Datetime when the beacon was signed
23    pub signed_at: DateTime<Utc>,
24}
25
26impl From<BeaconToSign> for SignedBeaconRecord {
27    fn from(beacon: BeaconToSign) -> Self {
28        Self {
29            epoch: beacon.epoch,
30            signed_entity_type: beacon.signed_entity_type,
31            initiated_at: beacon.initiated_at,
32            signed_at: Utc::now(),
33        }
34    }
35}
36
37#[cfg(test)]
38impl SignedBeaconRecord {
39    /// Create a fake `SignedBeaconRecord` for testing purposes
40    ///
41    /// The dates fields are set to constant values to make it easier to compare.
42    pub(crate) fn fake(epoch: Epoch, signed_entity_type: SignedEntityType) -> Self {
43        let initiated_at = DateTime::<Utc>::default();
44        Self {
45            epoch,
46            signed_entity_type,
47            initiated_at,
48            signed_at: initiated_at + chrono::TimeDelta::minutes(3),
49        }
50    }
51
52    pub(crate) fn fakes(records: &[(Epoch, Vec<SignedEntityType>)]) -> Vec<Self> {
53        records
54            .iter()
55            .flat_map(|(epoch, signed_entity_types)| {
56                signed_entity_types.iter().map(|se| Self::fake(*epoch, se.clone()))
57            })
58            .collect()
59    }
60}
61
62#[cfg(test)]
63impl PartialEq<BeaconToSign> for SignedBeaconRecord {
64    fn eq(&self, other: &BeaconToSign) -> bool {
65        self.epoch.eq(&other.epoch)
66            && self.signed_entity_type.eq(&other.signed_entity_type)
67            && self.initiated_at.eq(&other.initiated_at)
68    }
69}
70
71#[cfg(test)]
72impl PartialEq<SignedBeaconRecord> for BeaconToSign {
73    fn eq(&self, other: &SignedBeaconRecord) -> bool {
74        other.eq(self)
75    }
76}
77
78impl SqLiteEntity for SignedBeaconRecord {
79    fn hydrate(row: Row) -> Result<Self, HydrationError>
80    where
81        Self: Sized,
82    {
83        let epoch = row.read::<i64, _>(0);
84        let beacon_str = Hydrator::read_signed_entity_beacon_column(&row, 1);
85        let signed_entity_type_id = usize::try_from(row.read::<i64, _>(2)).map_err(|e| {
86            panic!(
87                "Integer field signed_beacon.signed_entity_type_id cannot be turned into usize: {e}"
88            )
89        })?;
90        let initiated_at = &row.read::<&str, _>(3);
91        let signed_at = &row.read::<&str, _>(4);
92
93        let signed_beacon = Self {
94            epoch: Epoch(epoch.try_into().map_err(|e| {
95                HydrationError::InvalidData(format!(
96                    "Could not cast i64 ({epoch}) to u64. Error: '{e}'"
97                ))
98            })?),
99            signed_entity_type: Hydrator::hydrate_signed_entity_type(signed_entity_type_id, &beacon_str)?,
100            initiated_at: DateTime::parse_from_rfc3339(initiated_at).map_err(|e| {
101                HydrationError::InvalidData(format!(
102                    "Could not turn signed_beacon.initiated_at field value '{initiated_at}' to rfc3339 Datetime. Error: {e}"
103                ))
104            })?.with_timezone(&Utc),
105            signed_at:DateTime::parse_from_rfc3339(signed_at).map_err(|e| {
106                HydrationError::InvalidData(format!(
107                    "Could not turn signed_beacon.initiated_at field value '{initiated_at}' to rfc3339 Datetime. Error: {e}"
108                ))
109            })?.with_timezone(&Utc),
110        };
111
112        Ok(signed_beacon)
113    }
114
115    fn get_projection() -> Projection {
116        Projection::from(&[
117            ("epoch", "{:signed_beacon:}.epoch", "int"),
118            ("beacon", "{:signed_beacon:}.beacon", "text"),
119            (
120                "signed_entity_type_id",
121                "{:signed_beacon:}.signed_entity_type_id",
122                "int",
123            ),
124            ("created_at", "{:signed_beacon:}.initiated_at", "text"),
125            ("expires_at", "{:signed_beacon:}.signed_at", "text"),
126        ])
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn eq_beacon_to_sign() {
136        let initiated_at = DateTime::<Utc>::default();
137        let beacon_to_sign = BeaconToSign {
138            epoch: Epoch(3),
139            signed_entity_type: SignedEntityType::MithrilStakeDistribution(Epoch(4)),
140            initiated_at,
141        };
142        let signed_beacon = SignedBeaconRecord {
143            epoch: Epoch(3),
144            signed_entity_type: SignedEntityType::MithrilStakeDistribution(Epoch(4)),
145            initiated_at,
146            signed_at: initiated_at + chrono::TimeDelta::minutes(2),
147        };
148
149        // Check `impl PartialEq<BeaconToSign> for SignedBeaconRecord`
150        assert_eq!(signed_beacon, beacon_to_sign);
151        assert_ne!(
152            signed_beacon,
153            BeaconToSign {
154                epoch: beacon_to_sign.epoch + 13,
155                ..beacon_to_sign.clone()
156            }
157        );
158
159        // Check `impl PartialEq<SignedBeaconRecord> for BeaconToSign`
160        assert_eq!(beacon_to_sign, signed_beacon);
161        assert_ne!(
162            beacon_to_sign,
163            SignedBeaconRecord {
164                epoch: signed_beacon.epoch + 11,
165                ..signed_beacon.clone()
166            }
167        );
168    }
169}