1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Serialize};
4
5use crate::StdResult;
6use crate::entities::{
7 BlockNumber, BlockRange, CardanoDbBeacon, SignedEntityType, SignedEntityTypeDiscriminants,
8 TimePoint,
9};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct SignedEntityConfig {
15 pub allowed_discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
17 pub cardano_transactions_signing_config: Option<CardanoTransactionsSigningConfig>,
19 pub cardano_blocks_transactions_signing_config: Option<CardanoBlocksTransactionsSigningConfig>,
21}
22
23impl SignedEntityConfig {
24 pub const DEFAULT_ALLOWED_DISCRIMINANTS: [SignedEntityTypeDiscriminants; 1] =
28 [SignedEntityTypeDiscriminants::MithrilStakeDistribution];
29
30 pub fn append_allowed_signed_entity_types_discriminants(
33 discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
34 ) -> BTreeSet<SignedEntityTypeDiscriminants> {
35 let mut discriminants = discriminants;
36 discriminants.append(&mut BTreeSet::from(Self::DEFAULT_ALLOWED_DISCRIMINANTS));
37 discriminants
38 }
39
40 pub fn list_allowed_signed_entity_types_discriminants(
45 &self,
46 ) -> BTreeSet<SignedEntityTypeDiscriminants> {
47 let discriminants = self.allowed_discriminants.clone();
48 Self::append_allowed_signed_entity_types_discriminants(discriminants)
49 }
50
51 pub fn time_point_to_signed_entity<D: Into<SignedEntityTypeDiscriminants>>(
53 &self,
54 discriminant: D,
55 time_point: &TimePoint,
56 ) -> StdResult<SignedEntityType> {
57 let signed_entity_type = match discriminant.into() {
58 SignedEntityTypeDiscriminants::MithrilStakeDistribution => {
59 SignedEntityType::MithrilStakeDistribution(time_point.epoch)
60 }
61 SignedEntityTypeDiscriminants::CardanoStakeDistribution => {
62 SignedEntityType::CardanoStakeDistribution(time_point.epoch.previous()?)
63 }
64 SignedEntityTypeDiscriminants::CardanoImmutableFilesFull => {
65 SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(
66 *time_point.epoch,
67 time_point.immutable_file_number,
68 ))
69 }
70 SignedEntityTypeDiscriminants::CardanoTransactions => {
71 match &self.cardano_transactions_signing_config {
72 Some(config) => SignedEntityType::CardanoTransactions(
73 time_point.epoch,
74 config
75 .compute_block_number_to_be_signed(time_point.chain_point.block_number),
76 ),
77 None => {
78 anyhow::bail!(
79 "Can't derive a `CardanoTransactions` signed entity type from a time point without a `CardanoTransactionsSigningConfig`"
80 )
81 }
82 }
83 }
84 SignedEntityTypeDiscriminants::CardanoBlocksTransactions => {
85 match &self.cardano_blocks_transactions_signing_config {
86 Some(config) => SignedEntityType::CardanoBlocksTransactions(
87 time_point.epoch,
88 config
89 .compute_block_number_to_be_signed(time_point.chain_point.block_number),
90 ),
91 None => {
92 anyhow::bail!(
93 "Can't derive a `CardanoBlocksTransactions` signed entity type from a time point without a `CardanoBlocksTransactionsSigningConfig`"
94 )
95 }
96 }
97 }
98 SignedEntityTypeDiscriminants::CardanoDatabase => SignedEntityType::CardanoDatabase(
99 CardanoDbBeacon::new(*time_point.epoch, time_point.immutable_file_number),
100 ),
101 };
102
103 Ok(signed_entity_type)
104 }
105
106 pub fn list_allowed_signed_entity_types(
111 &self,
112 time_point: &TimePoint,
113 ) -> StdResult<Vec<SignedEntityType>> {
114 self.list_allowed_signed_entity_types_discriminants()
115 .into_iter()
116 .map(|discriminant| self.time_point_to_signed_entity(discriminant, time_point))
117 .collect()
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126pub struct CardanoTransactionsSigningConfig {
127 pub security_parameter: BlockNumber,
129
130 pub step: BlockNumber,
135}
136
137impl CardanoTransactionsSigningConfig {
138 pub fn compute_block_number_to_be_signed(&self, block_number: BlockNumber) -> BlockNumber {
141 compute_block_number_to_be_signed(block_number, self.security_parameter, self.step)
142 }
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
147pub struct CardanoBlocksTransactionsSigningConfig {
148 pub security_parameter: BlockNumber,
150
151 pub step: BlockNumber,
156}
157
158impl CardanoBlocksTransactionsSigningConfig {
159 pub fn compute_block_number_to_be_signed(&self, block_number: BlockNumber) -> BlockNumber {
162 compute_block_number_to_be_signed(block_number, self.security_parameter, self.step)
163 }
164}
165
166fn compute_block_number_to_be_signed(
183 block_number: BlockNumber,
184 security_parameter: BlockNumber,
185 step: BlockNumber,
186) -> BlockNumber {
187 let adjusted_step = BlockRange::from_block_number(step).start;
190 let adjusted_step = std::cmp::max(adjusted_step, BlockRange::LENGTH);
192
193 let block_number_to_be_signed =
194 (block_number - security_parameter) / adjusted_step * adjusted_step;
195 block_number_to_be_signed - 1
196}
197
198#[cfg(test)]
199mod tests {
200 use crate::entities::{
201 CardanoDbBeacon, ChainPoint, Epoch, SignedEntityType, SlotNumber, TimePoint,
202 };
203 use crate::test::{double::Dummy, double::fake_data};
204
205 use super::*;
206
207 #[test]
208 fn given_discriminant_convert_to_signed_entity() {
209 let time_point = TimePoint {
210 epoch: Epoch(1),
211 immutable_file_number: 5,
212 chain_point: ChainPoint {
213 slot_number: SlotNumber(73),
214 block_number: BlockNumber(20),
215 block_hash: "block_hash-20".to_string(),
216 },
217 };
218 let config = SignedEntityConfig {
219 allowed_discriminants: SignedEntityTypeDiscriminants::all(),
220 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
221 security_parameter: BlockNumber(0),
222 step: BlockNumber(15),
223 }),
224 cardano_blocks_transactions_signing_config: Some(
225 CardanoBlocksTransactionsSigningConfig {
226 security_parameter: BlockNumber(1),
227 step: BlockNumber(30),
228 },
229 ),
230 };
231
232 assert_eq!(
233 SignedEntityType::MithrilStakeDistribution(Epoch(1)),
234 config
235 .time_point_to_signed_entity(
236 SignedEntityTypeDiscriminants::MithrilStakeDistribution,
237 &time_point
238 )
239 .unwrap()
240 );
241
242 assert_eq!(
244 SignedEntityType::CardanoStakeDistribution(Epoch(0)),
245 config
246 .time_point_to_signed_entity(
247 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
248 &time_point
249 )
250 .unwrap()
251 );
252
253 assert_eq!(
254 SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(1, 5)),
255 config
256 .time_point_to_signed_entity(
257 SignedEntityTypeDiscriminants::CardanoImmutableFilesFull,
258 &time_point
259 )
260 .unwrap()
261 );
262
263 assert_eq!(
267 SignedEntityType::CardanoTransactions(Epoch(1), BlockNumber(14)),
268 config
269 .time_point_to_signed_entity(
270 SignedEntityTypeDiscriminants::CardanoTransactions,
271 &time_point
272 )
273 .unwrap()
274 );
275
276 assert_eq!(
277 SignedEntityType::CardanoDatabase(CardanoDbBeacon::new(1, 5)),
278 config
279 .time_point_to_signed_entity(
280 SignedEntityTypeDiscriminants::CardanoDatabase,
281 &time_point
282 )
283 .unwrap()
284 );
285 }
286
287 #[test]
288 fn can_not_convert_time_point_to_cardano_transaction_without_the_associated_config() {
289 let time_point = TimePoint {
290 epoch: Epoch(1),
291 immutable_file_number: 5,
292 chain_point: ChainPoint {
293 slot_number: SlotNumber(73),
294 block_number: BlockNumber(20),
295 block_hash: "block_hash-20".to_string(),
296 },
297 };
298 let config = SignedEntityConfig {
299 allowed_discriminants: SignedEntityTypeDiscriminants::all(),
300 cardano_transactions_signing_config: None,
301 cardano_blocks_transactions_signing_config: None,
302 };
303
304 let error = config
305 .time_point_to_signed_entity(
306 SignedEntityTypeDiscriminants::CardanoTransactions,
307 &time_point,
308 )
309 .unwrap_err();
310
311 let expected_error = "Can't derive a `CardanoTransactions` signed entity type from a time point without a `CardanoTransactionsSigningConfig`";
312 assert!(
313 error.to_string().contains(expected_error),
314 "Error message: {error:?}\nshould contains: {expected_error}\n"
315 );
316 }
317
318 #[test]
319 fn computing_block_number_to_be_signed() {
320 let block_number = BlockNumber(105);
322 let security_parameter = BlockNumber(0);
323 let step = BlockNumber(15);
324 assert_eq!(
325 compute_block_number_to_be_signed(block_number, security_parameter, step),
326 104
327 );
328
329 let block_number = BlockNumber(100);
330 let security_parameter = BlockNumber(5);
331 let step = BlockNumber(15);
332 assert_eq!(
333 compute_block_number_to_be_signed(block_number, security_parameter, step),
334 89
335 );
336
337 let block_number = BlockNumber(100);
338 let security_parameter = BlockNumber(85);
339 let step = BlockNumber(15);
340 assert_eq!(
341 compute_block_number_to_be_signed(block_number, security_parameter, step),
342 14
343 );
344
345 let block_number = BlockNumber(29);
346 let security_parameter = BlockNumber(0);
347 let step = BlockNumber(30);
348 assert_eq!(
349 compute_block_number_to_be_signed(block_number, security_parameter, step),
350 0
351 );
352 }
353
354 #[test]
355 fn computing_block_number_to_be_signed_should_not_overlow_on_security_parameter() {
356 let block_number = BlockNumber(50);
357 let security_parameter = BlockNumber(100);
358 let step = BlockNumber(30);
359 assert_eq!(
360 compute_block_number_to_be_signed(block_number, security_parameter, step),
361 0
362 );
363 }
364
365 #[test]
366 fn computing_block_number_to_be_signed_round_step_to_a_block_range_start() {
367 let block_number = BlockRange::LENGTH * 5 + 1;
368 let security_parameter = BlockNumber(0);
369 let step = BlockRange::LENGTH * 2 - 1;
370 assert_eq!(
371 compute_block_number_to_be_signed(block_number, security_parameter, step),
372 BlockRange::LENGTH * 5 - 1
373 );
374
375 let block_number = BlockRange::LENGTH * 5 + 1;
376 let security_parameter = BlockNumber(0);
377 let step = BlockRange::LENGTH * 2 + 1;
378 assert_eq!(
379 compute_block_number_to_be_signed(block_number, security_parameter, step),
380 BlockRange::LENGTH * 4 - 1
381 );
382
383 let block_number = BlockRange::LENGTH * 10 - 1;
385 let security_parameter = BlockNumber(0);
386 let step = BlockRange::LENGTH - 1;
387 assert_eq!(
388 compute_block_number_to_be_signed(block_number, security_parameter, step),
389 BlockRange::LENGTH * 9 - 1
390 );
391
392 let block_number = BlockRange::LENGTH - 1;
394 let security_parameter = BlockNumber(0);
395 let step = BlockRange::LENGTH - 1;
396 assert_eq!(
397 compute_block_number_to_be_signed(block_number, security_parameter, step),
398 0
399 );
400 }
401
402 #[test]
403 fn test_list_allowed_signed_entity_types_discriminant_without_specific_configuration() {
404 let config = SignedEntityConfig {
405 allowed_discriminants: BTreeSet::new(),
406 ..SignedEntityConfig::dummy()
407 };
408
409 let discriminants = config.list_allowed_signed_entity_types_discriminants();
410
411 assert_eq!(
412 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
413 discriminants
414 );
415 }
416
417 #[test]
418 fn test_list_allowed_signed_entity_types_discriminant_should_not_duplicate_a_signed_entity_discriminant_type_already_in_default_ones()
419 {
420 let config = SignedEntityConfig {
421 allowed_discriminants: BTreeSet::from([
422 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS[0],
423 ]),
424 ..SignedEntityConfig::dummy()
425 };
426
427 let discriminants = config.list_allowed_signed_entity_types_discriminants();
428
429 assert_eq!(
430 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
431 discriminants
432 );
433 }
434
435 #[test]
436 fn test_list_allowed_signed_entity_types_discriminants_should_add_configured_discriminants() {
437 let config = SignedEntityConfig {
438 allowed_discriminants: BTreeSet::from([
439 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
440 SignedEntityTypeDiscriminants::CardanoTransactions,
441 SignedEntityTypeDiscriminants::CardanoDatabase,
442 ]),
443 ..SignedEntityConfig::dummy()
444 };
445
446 let discriminants = config.list_allowed_signed_entity_types_discriminants();
447
448 assert_eq!(
449 BTreeSet::from_iter(
450 [
451 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
452 [
453 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
454 SignedEntityTypeDiscriminants::CardanoTransactions,
455 SignedEntityTypeDiscriminants::CardanoDatabase
456 ]
457 .as_slice()
458 ]
459 .concat()
460 ),
461 discriminants
462 );
463 }
464
465 #[test]
466 fn test_list_allowed_signed_entity_types_discriminants_with_multiple_identical_signed_entity_types_in_configuration_should_not_be_added_several_times()
467 {
468 let config = SignedEntityConfig {
469 allowed_discriminants: BTreeSet::from([
470 SignedEntityTypeDiscriminants::CardanoTransactions,
471 SignedEntityTypeDiscriminants::CardanoTransactions,
472 SignedEntityTypeDiscriminants::CardanoTransactions,
473 ]),
474 ..SignedEntityConfig::dummy()
475 };
476
477 let discriminants = config.list_allowed_signed_entity_types_discriminants();
478
479 assert_eq!(
480 BTreeSet::from_iter(
481 [
482 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
483 [SignedEntityTypeDiscriminants::CardanoTransactions].as_slice()
484 ]
485 .concat()
486 ),
487 discriminants
488 );
489 }
490
491 #[test]
492 fn test_list_allowed_signed_entity_types_with_specific_configuration() {
493 let beacon = fake_data::beacon();
494 let chain_point = ChainPoint {
495 block_number: BlockNumber(45),
496 ..ChainPoint::dummy()
497 };
498 let time_point = TimePoint::new(
499 *beacon.epoch,
500 beacon.immutable_file_number,
501 chain_point.clone(),
502 );
503 let config = SignedEntityConfig {
504 allowed_discriminants: BTreeSet::from([
505 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
506 SignedEntityTypeDiscriminants::CardanoTransactions,
507 SignedEntityTypeDiscriminants::CardanoBlocksTransactions,
508 ]),
509 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
510 security_parameter: BlockNumber(0),
511 step: BlockNumber(15),
512 }),
513 cardano_blocks_transactions_signing_config: Some(
514 CardanoBlocksTransactionsSigningConfig {
515 security_parameter: BlockNumber(0),
516 step: BlockNumber(15),
517 },
518 ),
519 };
520
521 let signed_entity_types = config.list_allowed_signed_entity_types(&time_point).unwrap();
522
523 assert_eq!(
524 vec![
525 SignedEntityType::MithrilStakeDistribution(beacon.epoch),
526 SignedEntityType::CardanoStakeDistribution(beacon.epoch - 1),
527 SignedEntityType::CardanoTransactions(beacon.epoch, chain_point.block_number - 1),
528 SignedEntityType::CardanoBlocksTransactions(
529 beacon.epoch,
530 chain_point.block_number - 1
531 ),
532 ],
533 signed_entity_types
534 );
535 }
536}