1use chrono::{DateTime, Utc};
2
3use mithril_common::entities::{
4 Certificate, CertificateMetadata, CertificateSignature, Epoch,
5 HexEncodedAggregateVerificationKey, HexEncodedKey, ProtocolMessage, ProtocolParameters,
6 ProtocolVersion, SignedEntityType, StakeDistributionParty,
7};
8use mithril_common::messages::{
9 CertificateListItemMessage, CertificateListItemMessageMetadata, CertificateMessage,
10 CertificateMetadataMessagePart,
11};
12#[cfg(test)]
13use mithril_common::{
14 entities::{CardanoDbBeacon, ImmutableFileNumber},
15 test_utils::{fake_data, fake_keys},
16};
17use mithril_persistence::{
18 database::Hydrator,
19 sqlite::{HydrationError, Projection, SqLiteEntity},
20};
21
22#[derive(Debug, PartialEq, Clone)]
24pub struct CertificateRecord {
25 pub certificate_id: String,
27
28 pub parent_certificate_id: Option<String>,
30
31 pub message: String,
33
34 pub signature: HexEncodedKey,
37
38 pub aggregate_verification_key: HexEncodedAggregateVerificationKey,
41
42 pub epoch: Epoch,
44
45 pub network: String,
47
48 pub signed_entity_type: SignedEntityType,
50
51 pub protocol_version: ProtocolVersion,
53
54 pub protocol_parameters: ProtocolParameters,
56
57 pub protocol_message: ProtocolMessage,
59
60 pub signers: Vec<StakeDistributionParty>,
62
63 pub initiated_at: DateTime<Utc>,
65
66 pub sealed_at: DateTime<Utc>,
68}
69
70#[cfg(test)]
71impl CertificateRecord {
72 pub(crate) fn dummy_genesis(id: &str, epoch: Epoch) -> Self {
73 Self {
74 parent_certificate_id: None,
75 signature: fake_keys::genesis_signature()[0].to_owned(),
76 ..Self::dummy(id, "", epoch, SignedEntityType::genesis(epoch))
77 }
78 }
79
80 pub(crate) fn dummy_db_snapshot(
81 id: &str,
82 parent_id: &str,
83 epoch: Epoch,
84 immutable_file_number: ImmutableFileNumber,
85 ) -> Self {
86 Self::dummy(
87 id,
88 parent_id,
89 epoch,
90 SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(
91 *epoch,
92 immutable_file_number,
93 )),
94 )
95 }
96
97 pub(crate) fn dummy(
98 id: &str,
99 parent_id: &str,
100 epoch: Epoch,
101 signed_entity_type: SignedEntityType,
102 ) -> Self {
103 Self {
104 certificate_id: id.to_string(),
105 parent_certificate_id: Some(parent_id.to_string()),
106 message: "message".to_string(),
107 signature: fake_keys::multi_signature()[0].to_owned(),
108 aggregate_verification_key: fake_keys::aggregate_verification_key()[0].to_owned(),
109 epoch,
110 network: fake_data::network().to_string(),
111 signed_entity_type,
112 protocol_version: "protocol_version".to_string(),
113 protocol_parameters: ProtocolParameters {
114 k: 0,
115 m: 0,
116 phi_f: 0.0,
117 },
118 protocol_message: Default::default(),
119 signers: vec![],
120 initiated_at: DateTime::parse_from_rfc3339("2024-02-12T13:11:47Z")
121 .unwrap()
122 .with_timezone(&Utc),
123 sealed_at: DateTime::parse_from_rfc3339("2024-02-12T13:12:57Z")
124 .unwrap()
125 .with_timezone(&Utc),
126 }
127 }
128}
129
130impl From<Certificate> for CertificateRecord {
131 fn from(other: Certificate) -> Self {
132 let signed_entity_type = other.signed_entity_type();
133 let (signature, parent_certificate_id) = match other.signature {
134 CertificateSignature::GenesisSignature(signature) => (signature.to_bytes_hex(), None),
135 CertificateSignature::MultiSignature(_, signature) => {
136 (signature.to_json_hex().unwrap(), Some(other.previous_hash))
137 }
138 };
139
140 CertificateRecord {
141 certificate_id: other.hash,
142 parent_certificate_id,
143 message: other.signed_message,
144 signature,
145 aggregate_verification_key: other.aggregate_verification_key.to_json_hex().unwrap(),
146 epoch: other.epoch,
147 network: other.metadata.network,
148 signed_entity_type,
149 protocol_version: other.metadata.protocol_version,
150 protocol_parameters: other.metadata.protocol_parameters,
151 protocol_message: other.protocol_message,
152 signers: other.metadata.signers,
153 initiated_at: other.metadata.initiated_at,
154 sealed_at: other.metadata.sealed_at,
155 }
156 }
157}
158
159impl From<CertificateRecord> for Certificate {
160 fn from(other: CertificateRecord) -> Self {
161 let certificate_metadata = CertificateMetadata::new(
162 other.network,
163 other.protocol_version,
164 other.protocol_parameters,
165 other.initiated_at,
166 other.sealed_at,
167 other.signers,
168 );
169 let (previous_hash, signature) = match other.parent_certificate_id {
170 None => (
171 String::new(),
172 CertificateSignature::GenesisSignature(other.signature.try_into().unwrap()),
173 ),
174 Some(parent_certificate_id) => (
175 parent_certificate_id,
176 CertificateSignature::MultiSignature(
177 other.signed_entity_type,
178 other.signature.try_into().unwrap(),
179 ),
180 ),
181 };
182
183 Certificate {
184 hash: other.certificate_id,
185 previous_hash,
186 epoch: other.epoch,
187 metadata: certificate_metadata,
188 signed_message: other.protocol_message.compute_hash(),
189 protocol_message: other.protocol_message,
190 aggregate_verification_key: other.aggregate_verification_key.try_into().unwrap(),
191 signature,
192 }
193 }
194}
195
196impl From<CertificateRecord> for CertificateMessage {
197 fn from(value: CertificateRecord) -> Self {
198 let metadata = CertificateMetadataMessagePart {
199 network: value.network,
200 protocol_version: value.protocol_version,
201 protocol_parameters: value.protocol_parameters,
202 initiated_at: value.initiated_at,
203 sealed_at: value.sealed_at,
204 signers: value.signers,
205 };
206 let (multi_signature, genesis_signature) = if value.parent_certificate_id.is_none() {
207 (String::new(), value.signature)
208 } else {
209 (value.signature, String::new())
210 };
211
212 CertificateMessage {
213 hash: value.certificate_id,
214 previous_hash: value.parent_certificate_id.unwrap_or_default(),
215 epoch: value.epoch,
216 signed_entity_type: value.signed_entity_type,
217 metadata,
218 protocol_message: value.protocol_message,
219 signed_message: value.message,
220 aggregate_verification_key: value.aggregate_verification_key,
221 multi_signature,
222 genesis_signature,
223 }
224 }
225}
226
227impl From<CertificateRecord> for CertificateListItemMessage {
228 fn from(value: CertificateRecord) -> Self {
229 let metadata = CertificateListItemMessageMetadata {
230 network: value.network,
231 protocol_version: value.protocol_version,
232 protocol_parameters: value.protocol_parameters,
233 initiated_at: value.initiated_at,
234 sealed_at: value.sealed_at,
235 total_signers: value.signers.len(),
236 };
237
238 CertificateListItemMessage {
239 hash: value.certificate_id,
240 previous_hash: value.parent_certificate_id.unwrap_or_default(),
241 epoch: value.epoch,
242 signed_entity_type: value.signed_entity_type,
243 metadata,
244 protocol_message: value.protocol_message,
245 signed_message: value.message,
246 aggregate_verification_key: value.aggregate_verification_key,
247 }
248 }
249}
250
251impl SqLiteEntity for CertificateRecord {
252 fn hydrate(row: sqlite::Row) -> Result<Self, HydrationError>
253 where
254 Self: Sized,
255 {
256 let certificate_id = row.read::<&str, _>(0).to_string();
257 let parent_certificate_id = row.read::<Option<&str>, _>(1).map(|s| s.to_owned());
258 let message = row.read::<&str, _>(2).to_string();
259 let signature = row.read::<&str, _>(3).to_string();
260 let aggregate_verification_key = row.read::<&str, _>(4).to_string();
261 let epoch_int = row.read::<i64, _>(5);
262 let network = row.read::<&str, _>(6).to_string();
263 let signed_entity_type_id = row.read::<i64, _>(7);
264 let signed_entity_beacon_string = Hydrator::read_signed_entity_beacon_column(&row, 8);
265 let protocol_version = row.read::<&str, _>(9).to_string();
266 let protocol_parameters_string = row.read::<&str, _>(10);
267 let protocol_message_string = row.read::<&str, _>(11);
268 let signers_string = row.read::<&str, _>(12);
269 let initiated_at = row.read::<&str, _>(13);
270 let sealed_at = row.read::<&str, _>(14);
271
272 let certificate_record = Self {
273 certificate_id,
274 parent_certificate_id,
275 message,
276 signature,
277 aggregate_verification_key,
278 epoch: Epoch(epoch_int.try_into().map_err(|e| {
279 HydrationError::InvalidData(format!(
280 "Could not cast i64 ({epoch_int}) to u64. Error: '{e}'"
281 ))
282 })?),
283 network,
284 signed_entity_type: Hydrator::hydrate_signed_entity_type(
285 signed_entity_type_id.try_into().map_err(|e| {
286 HydrationError::InvalidData(format!(
287 "Could not cast i64 ({signed_entity_type_id}) to u64. Error: '{e}'"
288 ))
289 })?,
290 &signed_entity_beacon_string,
291 )?,
292 protocol_version,
293 protocol_parameters: serde_json::from_str(protocol_parameters_string).map_err(
294 |e| {
295 HydrationError::InvalidData(format!(
296 "Could not turn string '{protocol_parameters_string}' to ProtocolParameters. Error: {e}"
297 ))
298 },
299 )?,
300 protocol_message: serde_json::from_str(protocol_message_string).map_err(
301 |e| {
302 HydrationError::InvalidData(format!(
303 "Could not turn string '{protocol_message_string}' to ProtocolMessage. Error: {e}"
304 ))
305 },
306 )?,
307 signers: serde_json::from_str(signers_string).map_err(
308 |e| {
309 HydrationError::InvalidData(format!(
310 "Could not turn string '{signers_string}' to Vec<StakeDistributionParty>. Error: {e}"
311 ))
312 },
313 )?,
314 initiated_at: DateTime::parse_from_rfc3339(initiated_at).map_err(
315 |e| {
316 HydrationError::InvalidData(format!(
317 "Could not turn string '{initiated_at}' to rfc3339 Datetime. Error: {e}"
318 ))
319 },
320 )?.with_timezone(&Utc),
321 sealed_at: DateTime::parse_from_rfc3339(sealed_at).map_err(
322 |e| {
323 HydrationError::InvalidData(format!(
324 "Could not turn string '{sealed_at}' to rfc3339 Datetime. Error: {e}"
325 ))
326 },
327 )?.with_timezone(&Utc),
328 };
329
330 Ok(certificate_record)
331 }
332
333 fn get_projection() -> Projection {
334 let mut projection = Projection::default();
335 projection.add_field("certificate_id", "{:certificate:}.certificate_id", "text");
336 projection.add_field(
337 "parent_certificate_id",
338 "{:certificate:}.parent_certificate_id",
339 "text",
340 );
341 projection.add_field("message", "{:certificate:}.message", "text");
342 projection.add_field("signature", "{:certificate:}.signature", "text");
343 projection.add_field(
344 "aggregate_verification_key",
345 "{:certificate:}.aggregate_verification_key",
346 "text",
347 );
348 projection.add_field("epoch", "{:certificate:}.epoch", "integer");
349 projection.add_field("network", "{:certificate:}.network", "text");
350 projection.add_field(
351 "signed_entity_type_id",
352 "{:certificate:}.signed_entity_type_id",
353 "integer",
354 );
355 projection.add_field(
356 "signed_entity_beacon",
357 "{:certificate:}.signed_entity_beacon",
358 "text",
359 );
360 projection.add_field(
361 "protocol_version",
362 "{:certificate:}.protocol_version",
363 "text",
364 );
365 projection.add_field(
366 "protocol_parameters",
367 "{:certificate:}.protocol_parameters",
368 "text",
369 );
370 projection.add_field(
371 "protocol_message",
372 "{:certificate:}.protocol_message",
373 "text",
374 );
375 projection.add_field("signers", "{:certificate:}.signers", "text");
376 projection.add_field("initiated_at", "{:certificate:}.initiated_at", "text");
377 projection.add_field("sealed_at", "{:certificate:}.sealed_at", "text");
378
379 projection
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use mithril_common::crypto_helper::tests_setup::setup_certificate_chain;
386
387 use super::*;
388
389 #[test]
390 fn test_convert_certificates() {
391 let (certificates, _) = setup_certificate_chain(20, 3);
392 let mut certificate_records: Vec<CertificateRecord> = Vec::new();
393 for certificate in certificates.clone() {
394 certificate_records.push(certificate.into());
395 }
396 let mut certificates_new: Vec<Certificate> = Vec::new();
397 for certificate_record in certificate_records {
398 certificates_new.push(certificate_record.into());
399 }
400 assert_eq!(certificates, certificates_new);
401 }
402
403 #[test]
404 fn converting_certificate_record_to_certificate_should_not_recompute_hash() {
405 let expected_hash = "my_hash";
406 let record = CertificateRecord::dummy_genesis(expected_hash, Epoch(1));
407 let certificate: Certificate = record.into();
408
409 assert_eq!(expected_hash, &certificate.hash);
410 }
411}