1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Serialize};
4
5use crate::entities::{
6 BlockNumber, BlockRange, CardanoDbBeacon, SignedEntityType, SignedEntityTypeDiscriminants,
7 TimePoint,
8};
9use crate::StdResult;
10
11#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct SignedEntityConfig {
15 pub allowed_discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
17 pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
19}
20
21impl SignedEntityConfig {
22 cfg_test_tools! {
23 pub fn dummy() -> Self {
25 Self {
26 allowed_discriminants: SignedEntityTypeDiscriminants::all(),
27 cardano_transactions_signing_config: CardanoTransactionsSigningConfig::dummy(),
28 }
29 }
30 }
31
32 pub const DEFAULT_ALLOWED_DISCRIMINANTS: [SignedEntityTypeDiscriminants; 2] = [
36 SignedEntityTypeDiscriminants::MithrilStakeDistribution,
37 SignedEntityTypeDiscriminants::CardanoImmutableFilesFull,
38 ];
39
40 pub fn append_allowed_signed_entity_types_discriminants(
43 discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
44 ) -> BTreeSet<SignedEntityTypeDiscriminants> {
45 let mut discriminants = discriminants;
46 discriminants.append(&mut BTreeSet::from(Self::DEFAULT_ALLOWED_DISCRIMINANTS));
47 discriminants
48 }
49
50 pub fn list_allowed_signed_entity_types_discriminants(
55 &self,
56 ) -> BTreeSet<SignedEntityTypeDiscriminants> {
57 let discriminants = self.allowed_discriminants.clone();
58 Self::append_allowed_signed_entity_types_discriminants(discriminants)
59 }
60
61 pub fn time_point_to_signed_entity<D: Into<SignedEntityTypeDiscriminants>>(
63 &self,
64 discriminant: D,
65 time_point: &TimePoint,
66 ) -> StdResult<SignedEntityType> {
67 let signed_entity_type = match discriminant.into() {
68 SignedEntityTypeDiscriminants::MithrilStakeDistribution => {
69 SignedEntityType::MithrilStakeDistribution(time_point.epoch)
70 }
71 SignedEntityTypeDiscriminants::CardanoStakeDistribution => {
72 SignedEntityType::CardanoStakeDistribution(time_point.epoch.previous()?)
73 }
74 SignedEntityTypeDiscriminants::CardanoImmutableFilesFull => {
75 SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(
76 *time_point.epoch,
77 time_point.immutable_file_number,
78 ))
79 }
80 SignedEntityTypeDiscriminants::CardanoTransactions => {
81 SignedEntityType::CardanoTransactions(
82 time_point.epoch,
83 self.cardano_transactions_signing_config
84 .compute_block_number_to_be_signed(time_point.chain_point.block_number),
85 )
86 }
87 SignedEntityTypeDiscriminants::CardanoDatabase => SignedEntityType::CardanoDatabase(
88 CardanoDbBeacon::new(*time_point.epoch, time_point.immutable_file_number),
89 ),
90 };
91
92 Ok(signed_entity_type)
93 }
94
95 pub fn list_allowed_signed_entity_types(
100 &self,
101 time_point: &TimePoint,
102 ) -> StdResult<Vec<SignedEntityType>> {
103 self.list_allowed_signed_entity_types_discriminants()
104 .into_iter()
105 .map(|discriminant| self.time_point_to_signed_entity(discriminant, time_point))
106 .collect()
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115pub struct CardanoTransactionsSigningConfig {
116 pub security_parameter: BlockNumber,
118
119 pub step: BlockNumber,
124}
125
126impl CardanoTransactionsSigningConfig {
127 cfg_test_tools! {
128 pub fn new(security_parameter: BlockNumber, step: BlockNumber) -> Self {
130 Self {
131 security_parameter,
132 step,
133 }
134 }
135
136 pub fn dummy() -> Self {
138 Self {
139 security_parameter: BlockNumber(0),
140 step: BlockNumber(15),
141 }
142 }
143 }
144
145 pub fn compute_block_number_to_be_signed(&self, block_number: BlockNumber) -> BlockNumber {
162 let adjusted_step = BlockRange::from_block_number(self.step).start;
165 let adjusted_step = std::cmp::max(adjusted_step, BlockRange::LENGTH);
167
168 let block_number_to_be_signed =
169 (block_number - self.security_parameter) / adjusted_step * adjusted_step;
170 block_number_to_be_signed - 1
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use crate::entities::{
177 CardanoDbBeacon, ChainPoint, Epoch, SignedEntityType, SlotNumber, TimePoint,
178 };
179 use crate::test_utils::fake_data;
180
181 use super::*;
182
183 #[test]
184 fn given_discriminant_convert_to_signed_entity() {
185 let time_point = TimePoint {
186 epoch: Epoch(1),
187 immutable_file_number: 5,
188 chain_point: ChainPoint {
189 slot_number: SlotNumber(73),
190 block_number: BlockNumber(20),
191 block_hash: "block_hash-20".to_string(),
192 },
193 };
194 let config = SignedEntityConfig {
195 allowed_discriminants: SignedEntityTypeDiscriminants::all(),
196 cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
197 security_parameter: BlockNumber(0),
198 step: BlockNumber(15),
199 },
200 };
201
202 assert_eq!(
203 SignedEntityType::MithrilStakeDistribution(Epoch(1)),
204 config
205 .time_point_to_signed_entity(
206 SignedEntityTypeDiscriminants::MithrilStakeDistribution,
207 &time_point
208 )
209 .unwrap()
210 );
211
212 assert_eq!(
214 SignedEntityType::CardanoStakeDistribution(Epoch(0)),
215 config
216 .time_point_to_signed_entity(
217 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
218 &time_point
219 )
220 .unwrap()
221 );
222
223 assert_eq!(
224 SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(1, 5)),
225 config
226 .time_point_to_signed_entity(
227 SignedEntityTypeDiscriminants::CardanoImmutableFilesFull,
228 &time_point
229 )
230 .unwrap()
231 );
232
233 assert_eq!(
237 SignedEntityType::CardanoTransactions(Epoch(1), BlockNumber(14)),
238 config
239 .time_point_to_signed_entity(
240 SignedEntityTypeDiscriminants::CardanoTransactions,
241 &time_point
242 )
243 .unwrap()
244 );
245
246 assert_eq!(
247 SignedEntityType::CardanoDatabase(CardanoDbBeacon::new(1, 5)),
248 config
249 .time_point_to_signed_entity(
250 SignedEntityTypeDiscriminants::CardanoDatabase,
251 &time_point
252 )
253 .unwrap()
254 );
255 }
256
257 #[test]
258 fn computing_block_number_to_be_signed() {
259 assert_eq!(
261 CardanoTransactionsSigningConfig {
262 security_parameter: BlockNumber(0),
263 step: BlockNumber(15),
264 }
265 .compute_block_number_to_be_signed(BlockNumber(105)),
266 104
267 );
268
269 assert_eq!(
270 CardanoTransactionsSigningConfig {
271 security_parameter: BlockNumber(5),
272 step: BlockNumber(15),
273 }
274 .compute_block_number_to_be_signed(BlockNumber(100)),
275 89
276 );
277
278 assert_eq!(
279 CardanoTransactionsSigningConfig {
280 security_parameter: BlockNumber(85),
281 step: BlockNumber(15),
282 }
283 .compute_block_number_to_be_signed(BlockNumber(100)),
284 14
285 );
286
287 assert_eq!(
288 CardanoTransactionsSigningConfig {
289 security_parameter: BlockNumber(0),
290 step: BlockNumber(30),
291 }
292 .compute_block_number_to_be_signed(BlockNumber(29)),
293 0
294 );
295 }
296
297 #[test]
298 fn computing_block_number_to_be_signed_should_not_overlow_on_security_parameter() {
299 assert_eq!(
300 CardanoTransactionsSigningConfig {
301 security_parameter: BlockNumber(100),
302 step: BlockNumber(30),
303 }
304 .compute_block_number_to_be_signed(BlockNumber(50)),
305 0
306 );
307 }
308
309 #[test]
310 fn computing_block_number_to_be_signed_round_step_to_a_block_range_start() {
311 assert_eq!(
312 CardanoTransactionsSigningConfig {
313 security_parameter: BlockNumber(0),
314 step: BlockRange::LENGTH * 2 - 1,
315 }
316 .compute_block_number_to_be_signed(BlockRange::LENGTH * 5 + 1),
317 BlockRange::LENGTH * 5 - 1
318 );
319
320 assert_eq!(
321 CardanoTransactionsSigningConfig {
322 security_parameter: BlockNumber(0),
323 step: BlockRange::LENGTH * 2 + 1,
324 }
325 .compute_block_number_to_be_signed(BlockRange::LENGTH * 5 + 1),
326 BlockRange::LENGTH * 4 - 1
327 );
328
329 assert_eq!(
331 CardanoTransactionsSigningConfig {
332 security_parameter: BlockNumber(0),
333 step: BlockRange::LENGTH - 1,
334 }
335 .compute_block_number_to_be_signed(BlockRange::LENGTH * 10 - 1),
336 BlockRange::LENGTH * 9 - 1
337 );
338
339 assert_eq!(
340 CardanoTransactionsSigningConfig {
341 security_parameter: BlockNumber(0),
342 step: BlockRange::LENGTH - 1,
343 }
344 .compute_block_number_to_be_signed(BlockRange::LENGTH - 1),
345 0
346 );
347 }
348
349 #[test]
350 fn test_list_allowed_signed_entity_types_discriminant_without_specific_configuration() {
351 let config = SignedEntityConfig {
352 allowed_discriminants: BTreeSet::new(),
353 ..SignedEntityConfig::dummy()
354 };
355
356 let discriminants = config.list_allowed_signed_entity_types_discriminants();
357
358 assert_eq!(
359 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
360 discriminants
361 );
362 }
363
364 #[test]
365 fn test_list_allowed_signed_entity_types_discriminant_should_not_duplicate_a_signed_entity_discriminant_type_already_in_default_ones(
366 ) {
367 let config = SignedEntityConfig {
368 allowed_discriminants: BTreeSet::from([
369 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS[0],
370 ]),
371 ..SignedEntityConfig::dummy()
372 };
373
374 let discriminants = config.list_allowed_signed_entity_types_discriminants();
375
376 assert_eq!(
377 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
378 discriminants
379 );
380 }
381
382 #[test]
383 fn test_list_allowed_signed_entity_types_discriminants_should_add_configured_discriminants() {
384 let config = SignedEntityConfig {
385 allowed_discriminants: BTreeSet::from([
386 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
387 SignedEntityTypeDiscriminants::CardanoTransactions,
388 SignedEntityTypeDiscriminants::CardanoDatabase,
389 ]),
390 ..SignedEntityConfig::dummy()
391 };
392
393 let discriminants = config.list_allowed_signed_entity_types_discriminants();
394
395 assert_eq!(
396 BTreeSet::from_iter(
397 [
398 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
399 [
400 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
401 SignedEntityTypeDiscriminants::CardanoTransactions,
402 SignedEntityTypeDiscriminants::CardanoDatabase
403 ]
404 .as_slice()
405 ]
406 .concat()
407 ),
408 discriminants
409 );
410 }
411
412 #[test]
413 fn test_list_allowed_signed_entity_types_discriminants_with_multiple_identical_signed_entity_types_in_configuration_should_not_be_added_several_times(
414 ) {
415 let config = SignedEntityConfig {
416 allowed_discriminants: BTreeSet::from([
417 SignedEntityTypeDiscriminants::CardanoTransactions,
418 SignedEntityTypeDiscriminants::CardanoTransactions,
419 SignedEntityTypeDiscriminants::CardanoTransactions,
420 ]),
421 ..SignedEntityConfig::dummy()
422 };
423
424 let discriminants = config.list_allowed_signed_entity_types_discriminants();
425
426 assert_eq!(
427 BTreeSet::from_iter(
428 [
429 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
430 [SignedEntityTypeDiscriminants::CardanoTransactions].as_slice()
431 ]
432 .concat()
433 ),
434 discriminants
435 );
436 }
437
438 #[test]
439 fn test_list_allowed_signed_entity_types_with_specific_configuration() {
440 let beacon = fake_data::beacon();
441 let chain_point = ChainPoint {
442 block_number: BlockNumber(45),
443 ..ChainPoint::dummy()
444 };
445 let time_point = TimePoint::new(
446 *beacon.epoch,
447 beacon.immutable_file_number,
448 chain_point.clone(),
449 );
450 let config = SignedEntityConfig {
451 allowed_discriminants: BTreeSet::from([
452 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
453 SignedEntityTypeDiscriminants::CardanoTransactions,
454 ]),
455 cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
456 security_parameter: BlockNumber(0),
457 step: BlockNumber(15),
458 },
459 };
460
461 let signed_entity_types = config
462 .list_allowed_signed_entity_types(&time_point)
463 .unwrap();
464
465 assert_eq!(
466 vec![
467 SignedEntityType::MithrilStakeDistribution(beacon.epoch),
468 SignedEntityType::CardanoStakeDistribution(beacon.epoch - 1),
469 SignedEntityType::CardanoImmutableFilesFull(beacon.clone()),
470 SignedEntityType::CardanoTransactions(beacon.epoch, chain_point.block_number - 1),
471 ],
472 signed_entity_types
473 );
474 }
475}