1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Serialize};
4
5use crate::StdResult;
6use crate::entities::{
7 BlockNumber, BlockNumberOffset, BlockRange, CardanoDbBeacon, SignedEntityType,
8 SignedEntityTypeDiscriminants, 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 config.security_parameter,
91 ),
92 None => {
93 anyhow::bail!(
94 "Can't derive a `CardanoBlocksTransactions` signed entity type from a time point without a `CardanoBlocksTransactionsSigningConfig`"
95 )
96 }
97 }
98 }
99 SignedEntityTypeDiscriminants::CardanoDatabase => SignedEntityType::CardanoDatabase(
100 CardanoDbBeacon::new(*time_point.epoch, time_point.immutable_file_number),
101 ),
102 };
103
104 Ok(signed_entity_type)
105 }
106
107 pub fn list_allowed_signed_entity_types(
112 &self,
113 time_point: &TimePoint,
114 ) -> StdResult<Vec<SignedEntityType>> {
115 self.list_allowed_signed_entity_types_discriminants()
116 .into_iter()
117 .map(|discriminant| self.time_point_to_signed_entity(discriminant, time_point))
118 .collect()
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127pub struct CardanoTransactionsSigningConfig {
128 pub security_parameter: BlockNumberOffset,
130
131 pub step: BlockNumber,
136}
137
138impl CardanoTransactionsSigningConfig {
139 pub fn compute_block_number_to_be_signed(&self, block_number: BlockNumber) -> BlockNumber {
156 let adjusted_step = BlockRange::from_block_number(self.step).start;
157 let adjusted_step = std::cmp::max(adjusted_step, BlockRange::LENGTH);
159
160 let block_number_to_be_signed =
161 compute_block_number_to_be_signed(block_number, self.security_parameter, adjusted_step);
162 block_number_to_be_signed - 1
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
168pub struct CardanoBlocksTransactionsSigningConfig {
169 pub security_parameter: BlockNumberOffset,
171
172 pub step: BlockNumber,
177}
178
179impl CardanoBlocksTransactionsSigningConfig {
180 pub fn compute_block_number_to_be_signed(&self, block_number: BlockNumber) -> BlockNumber {
191 compute_block_number_to_be_signed(block_number, self.security_parameter, self.step)
192 }
193}
194
195fn compute_block_number_to_be_signed(
197 block_number: BlockNumber,
198 security_parameter: BlockNumberOffset,
199 step: BlockNumber,
200) -> BlockNumber {
201 let adjusted_step = std::cmp::max(step, BlockNumber(1));
202 (block_number - security_parameter) / adjusted_step * adjusted_step
203}
204
205#[cfg(test)]
206mod tests {
207 use crate::entities::{
208 BlockNumberOffset, CardanoDbBeacon, ChainPoint, Epoch, SignedEntityType, SlotNumber,
209 TimePoint,
210 };
211 use crate::test::{double::Dummy, double::fake_data};
212
213 use super::*;
214
215 #[test]
216 fn given_discriminant_convert_to_signed_entity() {
217 let time_point = TimePoint {
218 epoch: Epoch(1),
219 immutable_file_number: 5,
220 chain_point: ChainPoint {
221 slot_number: SlotNumber(73),
222 block_number: BlockNumber(20),
223 block_hash: "block_hash-20".to_string(),
224 },
225 };
226 let config = SignedEntityConfig {
227 allowed_discriminants: SignedEntityTypeDiscriminants::all(),
228 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
229 security_parameter: BlockNumberOffset(0),
230 step: BlockNumber(15),
231 }),
232 cardano_blocks_transactions_signing_config: Some(
233 CardanoBlocksTransactionsSigningConfig {
234 security_parameter: BlockNumberOffset(5),
235 step: BlockNumber(15),
236 },
237 ),
238 };
239
240 assert_eq!(
241 SignedEntityType::MithrilStakeDistribution(Epoch(1)),
242 config
243 .time_point_to_signed_entity(
244 SignedEntityTypeDiscriminants::MithrilStakeDistribution,
245 &time_point
246 )
247 .unwrap()
248 );
249
250 assert_eq!(
252 SignedEntityType::CardanoStakeDistribution(Epoch(0)),
253 config
254 .time_point_to_signed_entity(
255 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
256 &time_point
257 )
258 .unwrap()
259 );
260
261 assert_eq!(
262 SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(1, 5)),
263 config
264 .time_point_to_signed_entity(
265 SignedEntityTypeDiscriminants::CardanoImmutableFilesFull,
266 &time_point
267 )
268 .unwrap()
269 );
270
271 assert_eq!(
275 SignedEntityType::CardanoTransactions(Epoch(1), BlockNumber(14)),
276 config
277 .time_point_to_signed_entity(
278 SignedEntityTypeDiscriminants::CardanoTransactions,
279 &time_point
280 )
281 .unwrap()
282 );
283
284 assert_eq!(
288 SignedEntityType::CardanoBlocksTransactions(
289 Epoch(1),
290 BlockNumber(15),
291 BlockNumberOffset(5)
292 ),
293 config
294 .time_point_to_signed_entity(
295 SignedEntityTypeDiscriminants::CardanoBlocksTransactions,
296 &time_point
297 )
298 .unwrap()
299 );
300
301 assert_eq!(
302 SignedEntityType::CardanoDatabase(CardanoDbBeacon::new(1, 5)),
303 config
304 .time_point_to_signed_entity(
305 SignedEntityTypeDiscriminants::CardanoDatabase,
306 &time_point
307 )
308 .unwrap()
309 );
310 }
311
312 #[test]
313 fn can_not_convert_time_point_to_cardano_transaction_without_the_associated_config() {
314 let time_point = TimePoint {
315 epoch: Epoch(1),
316 immutable_file_number: 5,
317 chain_point: ChainPoint {
318 slot_number: SlotNumber(73),
319 block_number: BlockNumber(20),
320 block_hash: "block_hash-20".to_string(),
321 },
322 };
323 let config = SignedEntityConfig {
324 allowed_discriminants: SignedEntityTypeDiscriminants::all(),
325 cardano_transactions_signing_config: None,
326 cardano_blocks_transactions_signing_config: None,
327 };
328
329 let error = config
330 .time_point_to_signed_entity(
331 SignedEntityTypeDiscriminants::CardanoTransactions,
332 &time_point,
333 )
334 .unwrap_err();
335
336 let expected_error = "Can't derive a `CardanoTransactions` signed entity type from a time point without a `CardanoTransactionsSigningConfig`";
337 assert!(
338 error.to_string().contains(expected_error),
339 "Error message: {error:?}\nshould contains: {expected_error}\n"
340 );
341 }
342
343 mod compute_block_number_to_be_signed_for_cardano_transactions {
344 use super::*;
345
346 #[test]
347 fn compute_without_a_security_parameter() {
348 let block_number = BlockNumber(105);
350 let signing_config = CardanoTransactionsSigningConfig {
351 security_parameter: BlockNumberOffset(0),
352 step: BlockNumber(15),
353 };
354 assert_eq!(
355 signing_config.compute_block_number_to_be_signed(block_number),
356 104
357 );
358 }
359
360 #[test]
361 fn compute_with_security_parameter_lower_than_block_number() {
362 let block_number = BlockNumber(100);
363 let signing_config = CardanoTransactionsSigningConfig {
364 security_parameter: BlockNumberOffset(5),
365 step: BlockNumber(15),
366 };
367 assert_eq!(
368 signing_config.compute_block_number_to_be_signed(block_number),
369 89
370 );
371 }
372
373 #[test]
374 fn when_security_parameter_plus_step_equal_to_block_number_return_step_minus_1() {
375 let block_number = BlockNumber(100);
376 let signing_config = CardanoTransactionsSigningConfig {
377 security_parameter: BlockNumberOffset(85),
378 step: BlockNumber(15),
379 };
380 assert_eq!(
381 signing_config.compute_block_number_to_be_signed(block_number),
382 14
383 );
384 }
385
386 #[test]
387 fn when_step_higher_than_block_number_return_0() {
388 let block_number = BlockNumber(29);
389 let signing_config = CardanoTransactionsSigningConfig {
390 security_parameter: BlockNumberOffset(0),
391 step: BlockNumber(30),
392 };
393 assert_eq!(
394 signing_config.compute_block_number_to_be_signed(block_number),
395 0
396 );
397 }
398
399 #[test]
400 fn should_not_overlow_on_security_parameter() {
401 let block_number = BlockNumber(50);
402 let signing_config = CardanoTransactionsSigningConfig {
403 security_parameter: BlockNumberOffset(100),
404 step: BlockNumber(30),
405 };
406 assert_eq!(
407 signing_config.compute_block_number_to_be_signed(block_number),
408 0
409 );
410 }
411
412 #[test]
413 fn round_step_to_previous_block_range_start_when_step_right_below_said_block_range_start() {
414 let block_number = BlockRange::LENGTH * 5 + 1;
415 let signing_config = CardanoTransactionsSigningConfig {
416 security_parameter: BlockNumberOffset(0),
417 step: BlockRange::LENGTH * 2 - 1,
418 };
419 assert_eq!(
420 signing_config.compute_block_number_to_be_signed(block_number),
421 BlockRange::LENGTH * 5 - 1
422 );
423 }
424
425 #[test]
426 fn round_step_to_block_range_start_when_step_right_after_said_block_range_start() {
427 let block_number = BlockRange::LENGTH * 5 + 1;
428 let signing_config = CardanoTransactionsSigningConfig {
429 security_parameter: BlockNumberOffset(0),
430 step: BlockRange::LENGTH * 2 + 1,
431 };
432 assert_eq!(
433 signing_config.compute_block_number_to_be_signed(block_number),
434 BlockRange::LENGTH * 4 - 1
435 );
436 }
437
438 #[test]
439 fn step_lower_than_block_range_length_is_adjusted_to_block_range_length() {
440 let block_number = BlockRange::LENGTH * 10 - 1;
442 let signing_config = CardanoTransactionsSigningConfig {
443 security_parameter: BlockNumberOffset(0),
444 step: BlockRange::LENGTH - 1,
445 };
446 assert_eq!(
447 signing_config.compute_block_number_to_be_signed(block_number),
448 BlockRange::LENGTH * 9 - 1
449 );
450 }
451
452 #[test]
453 fn step_and_block_number_below_block_range_length_returns_0() {
454 let block_number = BlockRange::LENGTH - 1;
456 let signing_config = CardanoTransactionsSigningConfig {
457 security_parameter: BlockNumberOffset(0),
458 step: BlockRange::LENGTH - 1,
459 };
460 assert_eq!(
461 signing_config.compute_block_number_to_be_signed(block_number),
462 0
463 );
464 }
465 }
466
467 mod compute_block_number_to_be_signed_for_cardano_blocks_transactions {
468 use super::*;
469
470 #[test]
471 fn compute_without_a_security_parameter() {
472 let block_number = BlockNumber(105);
474 let signing_config = CardanoBlocksTransactionsSigningConfig {
475 security_parameter: BlockNumberOffset(0),
476 step: BlockNumber(15),
477 };
478 assert_eq!(
480 signing_config.compute_block_number_to_be_signed(block_number),
481 105
482 );
483 }
484
485 #[test]
486 fn compute_with_security_parameter_lower_than_block_number() {
487 let block_number = BlockNumber(100);
488 let signing_config = CardanoBlocksTransactionsSigningConfig {
489 security_parameter: BlockNumberOffset(5),
490 step: BlockNumber(15),
491 };
492 assert_eq!(
494 signing_config.compute_block_number_to_be_signed(block_number),
495 90
496 );
497 }
498
499 #[test]
500 fn when_security_parameter_plus_step_equal_to_block_number_return_step_minus_1() {
501 let block_number = BlockNumber(100);
502 let signing_config = CardanoBlocksTransactionsSigningConfig {
503 security_parameter: BlockNumberOffset(85),
504 step: BlockNumber(15),
505 };
506 assert_eq!(
508 signing_config.compute_block_number_to_be_signed(block_number),
509 15
510 );
511 }
512
513 #[test]
514 fn when_step_higher_than_block_number_return_0() {
515 let block_number = BlockNumber(29);
516 let signing_config = CardanoBlocksTransactionsSigningConfig {
517 security_parameter: BlockNumberOffset(0),
518 step: BlockNumber(30),
519 };
520 assert_eq!(
521 signing_config.compute_block_number_to_be_signed(block_number),
522 0
523 );
524 }
525
526 #[test]
527 fn should_not_overlow_on_security_parameter() {
528 let block_number = BlockNumber(50);
529 let signing_config = CardanoBlocksTransactionsSigningConfig {
530 security_parameter: BlockNumberOffset(100),
531 step: BlockNumber(30),
532 };
533 assert_eq!(
534 signing_config.compute_block_number_to_be_signed(block_number),
535 0
536 );
537 }
538
539 #[test]
540 fn can_use_step_right_below_than_a_multiple_of_a_block_range_length() {
541 let block_number = BlockNumber(150);
542 let signing_config = CardanoBlocksTransactionsSigningConfig {
543 security_parameter: BlockNumberOffset(0),
544 step: BlockRange::LENGTH - 1,
545 };
546 assert_eq!(
548 signing_config.compute_block_number_to_be_signed(block_number),
549 140,
550 );
551 }
552
553 #[test]
554 fn can_use_step_right_after_a_multiple_of_a_block_range_length() {
555 let block_number = BlockNumber(150);
556 let signing_config = CardanoBlocksTransactionsSigningConfig {
557 security_parameter: BlockNumberOffset(0),
558 step: BlockRange::LENGTH + 1,
559 };
560 assert_eq!(
562 signing_config.compute_block_number_to_be_signed(block_number),
563 144,
564 );
565 }
566
567 #[test]
568 fn can_use_step_higher_than_a_block_range_length_and_out_not_equal_to_a_range_boundaries() {
569 let block_number = BlockNumber(150);
570 let signing_config = CardanoBlocksTransactionsSigningConfig {
571 security_parameter: BlockNumberOffset(0),
572 step: BlockRange::LENGTH + 6,
573 };
574 assert_eq!(
576 signing_config.compute_block_number_to_be_signed(block_number),
577 147,
578 );
579 }
580
581 #[test]
582 fn can_use_step_lower_than_a_block_range_length_and_out_not_equal_to_a_range_boundaries() {
583 let block_number = BlockNumber(150);
584 let signing_config = CardanoBlocksTransactionsSigningConfig {
585 security_parameter: BlockNumberOffset(0),
586 step: BlockRange::LENGTH - 6,
587 };
588 assert_eq!(
590 signing_config.compute_block_number_to_be_signed(block_number),
591 144,
592 );
593 }
594
595 #[test]
596 fn can_use_a_step_and_block_number_both_below_block_range_length() {
597 let block_number = BlockRange::LENGTH - 1;
598 let signing_config = CardanoBlocksTransactionsSigningConfig {
599 security_parameter: BlockNumberOffset(0),
600 step: BlockRange::LENGTH - 10,
601 };
602 assert_eq!(
604 signing_config.compute_block_number_to_be_signed(block_number),
605 10,
606 );
607 }
608 }
609
610 #[test]
611 fn test_list_allowed_signed_entity_types_discriminant_without_specific_configuration() {
612 let config = SignedEntityConfig {
613 allowed_discriminants: BTreeSet::new(),
614 ..SignedEntityConfig::dummy()
615 };
616
617 let discriminants = config.list_allowed_signed_entity_types_discriminants();
618
619 assert_eq!(
620 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
621 discriminants
622 );
623 }
624
625 #[test]
626 fn test_list_allowed_signed_entity_types_discriminant_should_not_duplicate_a_signed_entity_discriminant_type_already_in_default_ones()
627 {
628 let config = SignedEntityConfig {
629 allowed_discriminants: BTreeSet::from([
630 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS[0],
631 ]),
632 ..SignedEntityConfig::dummy()
633 };
634
635 let discriminants = config.list_allowed_signed_entity_types_discriminants();
636
637 assert_eq!(
638 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
639 discriminants
640 );
641 }
642
643 #[test]
644 fn test_list_allowed_signed_entity_types_discriminants_should_add_configured_discriminants() {
645 let config = SignedEntityConfig {
646 allowed_discriminants: BTreeSet::from([
647 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
648 SignedEntityTypeDiscriminants::CardanoTransactions,
649 SignedEntityTypeDiscriminants::CardanoDatabase,
650 ]),
651 ..SignedEntityConfig::dummy()
652 };
653
654 let discriminants = config.list_allowed_signed_entity_types_discriminants();
655
656 assert_eq!(
657 BTreeSet::from_iter(
658 [
659 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
660 [
661 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
662 SignedEntityTypeDiscriminants::CardanoTransactions,
663 SignedEntityTypeDiscriminants::CardanoDatabase
664 ]
665 .as_slice()
666 ]
667 .concat()
668 ),
669 discriminants
670 );
671 }
672
673 #[test]
674 fn test_list_allowed_signed_entity_types_discriminants_with_multiple_identical_signed_entity_types_in_configuration_should_not_be_added_several_times()
675 {
676 let config = SignedEntityConfig {
677 allowed_discriminants: BTreeSet::from([
678 SignedEntityTypeDiscriminants::CardanoTransactions,
679 SignedEntityTypeDiscriminants::CardanoTransactions,
680 SignedEntityTypeDiscriminants::CardanoTransactions,
681 ]),
682 ..SignedEntityConfig::dummy()
683 };
684
685 let discriminants = config.list_allowed_signed_entity_types_discriminants();
686
687 assert_eq!(
688 BTreeSet::from_iter(
689 [
690 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
691 [SignedEntityTypeDiscriminants::CardanoTransactions].as_slice()
692 ]
693 .concat()
694 ),
695 discriminants
696 );
697 }
698
699 #[test]
700 fn test_list_allowed_signed_entity_types_with_specific_configuration() {
701 let beacon = fake_data::beacon();
702 let chain_point = ChainPoint {
703 block_number: BlockNumber(45),
704 ..ChainPoint::dummy()
705 };
706 let time_point = TimePoint::new(
707 *beacon.epoch,
708 beacon.immutable_file_number,
709 chain_point.clone(),
710 );
711 let config = SignedEntityConfig {
712 allowed_discriminants: BTreeSet::from([
713 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
714 SignedEntityTypeDiscriminants::CardanoTransactions,
715 SignedEntityTypeDiscriminants::CardanoBlocksTransactions,
716 ]),
717 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
718 security_parameter: BlockNumberOffset(0),
719 step: BlockNumber(15),
720 }),
721 cardano_blocks_transactions_signing_config: Some(
722 CardanoBlocksTransactionsSigningConfig {
723 security_parameter: BlockNumberOffset(0),
724 step: BlockNumber(15),
725 },
726 ),
727 };
728
729 let signed_entity_types = config.list_allowed_signed_entity_types(&time_point).unwrap();
730
731 assert_eq!(
732 vec![
733 SignedEntityType::MithrilStakeDistribution(beacon.epoch),
734 SignedEntityType::CardanoStakeDistribution(beacon.epoch - 1),
735 SignedEntityType::CardanoTransactions(beacon.epoch, chain_point.block_number - 1),
736 SignedEntityType::CardanoBlocksTransactions(
737 beacon.epoch,
738 chain_point.block_number,
739 BlockNumberOffset(0)
740 ),
741 ],
742 signed_entity_types
743 );
744 }
745}