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