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}
20
21impl SignedEntityConfig {
22 pub const DEFAULT_ALLOWED_DISCRIMINANTS: [SignedEntityTypeDiscriminants; 1] =
26 [SignedEntityTypeDiscriminants::MithrilStakeDistribution];
27
28 pub fn append_allowed_signed_entity_types_discriminants(
31 discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
32 ) -> BTreeSet<SignedEntityTypeDiscriminants> {
33 let mut discriminants = discriminants;
34 discriminants.append(&mut BTreeSet::from(Self::DEFAULT_ALLOWED_DISCRIMINANTS));
35 discriminants
36 }
37
38 pub fn list_allowed_signed_entity_types_discriminants(
43 &self,
44 ) -> BTreeSet<SignedEntityTypeDiscriminants> {
45 let discriminants = self.allowed_discriminants.clone();
46 Self::append_allowed_signed_entity_types_discriminants(discriminants)
47 }
48
49 pub fn time_point_to_signed_entity<D: Into<SignedEntityTypeDiscriminants>>(
51 &self,
52 discriminant: D,
53 time_point: &TimePoint,
54 ) -> StdResult<SignedEntityType> {
55 let signed_entity_type = match discriminant.into() {
56 SignedEntityTypeDiscriminants::MithrilStakeDistribution => {
57 SignedEntityType::MithrilStakeDistribution(time_point.epoch)
58 }
59 SignedEntityTypeDiscriminants::CardanoStakeDistribution => {
60 SignedEntityType::CardanoStakeDistribution(time_point.epoch.previous()?)
61 }
62 SignedEntityTypeDiscriminants::CardanoImmutableFilesFull => {
63 SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(
64 *time_point.epoch,
65 time_point.immutable_file_number,
66 ))
67 }
68 SignedEntityTypeDiscriminants::CardanoTransactions => {
69 match &self.cardano_transactions_signing_config {
70 Some(config) => SignedEntityType::CardanoTransactions(
71 time_point.epoch,
72 config
73 .compute_block_number_to_be_signed(time_point.chain_point.block_number),
74 ),
75 None => {
76 anyhow::bail!(
77 "Can't derive a `CardanoTransactions` signed entity type from a time point without a `CardanoTransactionsSigningConfig`"
78 )
79 }
80 }
81 }
82 SignedEntityTypeDiscriminants::CardanoBlocksTransactions => {
83 anyhow::bail!("Cardano blocks transactions is not supported yet")
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 pub fn compute_block_number_to_be_signed(&self, block_number: BlockNumber) -> BlockNumber {
142 let adjusted_step = BlockRange::from_block_number(self.step).start;
145 let adjusted_step = std::cmp::max(adjusted_step, BlockRange::LENGTH);
147
148 let block_number_to_be_signed =
149 (block_number - self.security_parameter) / adjusted_step * adjusted_step;
150 block_number_to_be_signed - 1
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use crate::entities::{
157 CardanoDbBeacon, ChainPoint, Epoch, SignedEntityType, SlotNumber, TimePoint,
158 };
159 use crate::test::{double::Dummy, double::fake_data};
160
161 use super::*;
162
163 #[test]
164 fn given_discriminant_convert_to_signed_entity() {
165 let time_point = TimePoint {
166 epoch: Epoch(1),
167 immutable_file_number: 5,
168 chain_point: ChainPoint {
169 slot_number: SlotNumber(73),
170 block_number: BlockNumber(20),
171 block_hash: "block_hash-20".to_string(),
172 },
173 };
174 let config = SignedEntityConfig {
175 allowed_discriminants: SignedEntityTypeDiscriminants::all(),
176 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
177 security_parameter: BlockNumber(0),
178 step: BlockNumber(15),
179 }),
180 };
181
182 assert_eq!(
183 SignedEntityType::MithrilStakeDistribution(Epoch(1)),
184 config
185 .time_point_to_signed_entity(
186 SignedEntityTypeDiscriminants::MithrilStakeDistribution,
187 &time_point
188 )
189 .unwrap()
190 );
191
192 assert_eq!(
194 SignedEntityType::CardanoStakeDistribution(Epoch(0)),
195 config
196 .time_point_to_signed_entity(
197 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
198 &time_point
199 )
200 .unwrap()
201 );
202
203 assert_eq!(
204 SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(1, 5)),
205 config
206 .time_point_to_signed_entity(
207 SignedEntityTypeDiscriminants::CardanoImmutableFilesFull,
208 &time_point
209 )
210 .unwrap()
211 );
212
213 assert_eq!(
217 SignedEntityType::CardanoTransactions(Epoch(1), BlockNumber(14)),
218 config
219 .time_point_to_signed_entity(
220 SignedEntityTypeDiscriminants::CardanoTransactions,
221 &time_point
222 )
223 .unwrap()
224 );
225
226 assert_eq!(
227 SignedEntityType::CardanoDatabase(CardanoDbBeacon::new(1, 5)),
228 config
229 .time_point_to_signed_entity(
230 SignedEntityTypeDiscriminants::CardanoDatabase,
231 &time_point
232 )
233 .unwrap()
234 );
235 }
236
237 #[test]
238 fn can_not_convert_time_point_to_cardano_transaction_without_the_associated_config() {
239 let time_point = TimePoint {
240 epoch: Epoch(1),
241 immutable_file_number: 5,
242 chain_point: ChainPoint {
243 slot_number: SlotNumber(73),
244 block_number: BlockNumber(20),
245 block_hash: "block_hash-20".to_string(),
246 },
247 };
248 let config = SignedEntityConfig {
249 allowed_discriminants: SignedEntityTypeDiscriminants::all(),
250 cardano_transactions_signing_config: None,
251 };
252
253 let error = config
254 .time_point_to_signed_entity(
255 SignedEntityTypeDiscriminants::CardanoTransactions,
256 &time_point,
257 )
258 .unwrap_err();
259
260 let expected_error = "Can't derive a `CardanoTransactions` signed entity type from a time point without a `CardanoTransactionsSigningConfig`";
261 assert!(
262 error.to_string().contains(expected_error),
263 "Error message: {error:?}\nshould contains: {expected_error}\n"
264 );
265 }
266
267 #[test]
268 fn computing_block_number_to_be_signed() {
269 assert_eq!(
271 CardanoTransactionsSigningConfig {
272 security_parameter: BlockNumber(0),
273 step: BlockNumber(15),
274 }
275 .compute_block_number_to_be_signed(BlockNumber(105)),
276 104
277 );
278
279 assert_eq!(
280 CardanoTransactionsSigningConfig {
281 security_parameter: BlockNumber(5),
282 step: BlockNumber(15),
283 }
284 .compute_block_number_to_be_signed(BlockNumber(100)),
285 89
286 );
287
288 assert_eq!(
289 CardanoTransactionsSigningConfig {
290 security_parameter: BlockNumber(85),
291 step: BlockNumber(15),
292 }
293 .compute_block_number_to_be_signed(BlockNumber(100)),
294 14
295 );
296
297 assert_eq!(
298 CardanoTransactionsSigningConfig {
299 security_parameter: BlockNumber(0),
300 step: BlockNumber(30),
301 }
302 .compute_block_number_to_be_signed(BlockNumber(29)),
303 0
304 );
305 }
306
307 #[test]
308 fn computing_block_number_to_be_signed_should_not_overlow_on_security_parameter() {
309 assert_eq!(
310 CardanoTransactionsSigningConfig {
311 security_parameter: BlockNumber(100),
312 step: BlockNumber(30),
313 }
314 .compute_block_number_to_be_signed(BlockNumber(50)),
315 0
316 );
317 }
318
319 #[test]
320 fn computing_block_number_to_be_signed_round_step_to_a_block_range_start() {
321 assert_eq!(
322 CardanoTransactionsSigningConfig {
323 security_parameter: BlockNumber(0),
324 step: BlockRange::LENGTH * 2 - 1,
325 }
326 .compute_block_number_to_be_signed(BlockRange::LENGTH * 5 + 1),
327 BlockRange::LENGTH * 5 - 1
328 );
329
330 assert_eq!(
331 CardanoTransactionsSigningConfig {
332 security_parameter: BlockNumber(0),
333 step: BlockRange::LENGTH * 2 + 1,
334 }
335 .compute_block_number_to_be_signed(BlockRange::LENGTH * 5 + 1),
336 BlockRange::LENGTH * 4 - 1
337 );
338
339 assert_eq!(
341 CardanoTransactionsSigningConfig {
342 security_parameter: BlockNumber(0),
343 step: BlockRange::LENGTH - 1,
344 }
345 .compute_block_number_to_be_signed(BlockRange::LENGTH * 10 - 1),
346 BlockRange::LENGTH * 9 - 1
347 );
348
349 assert_eq!(
350 CardanoTransactionsSigningConfig {
351 security_parameter: BlockNumber(0),
352 step: BlockRange::LENGTH - 1,
353 }
354 .compute_block_number_to_be_signed(BlockRange::LENGTH - 1),
355 0
356 );
357 }
358
359 #[test]
360 fn test_list_allowed_signed_entity_types_discriminant_without_specific_configuration() {
361 let config = SignedEntityConfig {
362 allowed_discriminants: BTreeSet::new(),
363 ..SignedEntityConfig::dummy()
364 };
365
366 let discriminants = config.list_allowed_signed_entity_types_discriminants();
367
368 assert_eq!(
369 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
370 discriminants
371 );
372 }
373
374 #[test]
375 fn test_list_allowed_signed_entity_types_discriminant_should_not_duplicate_a_signed_entity_discriminant_type_already_in_default_ones()
376 {
377 let config = SignedEntityConfig {
378 allowed_discriminants: BTreeSet::from([
379 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS[0],
380 ]),
381 ..SignedEntityConfig::dummy()
382 };
383
384 let discriminants = config.list_allowed_signed_entity_types_discriminants();
385
386 assert_eq!(
387 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
388 discriminants
389 );
390 }
391
392 #[test]
393 fn test_list_allowed_signed_entity_types_discriminants_should_add_configured_discriminants() {
394 let config = SignedEntityConfig {
395 allowed_discriminants: BTreeSet::from([
396 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
397 SignedEntityTypeDiscriminants::CardanoTransactions,
398 SignedEntityTypeDiscriminants::CardanoDatabase,
399 ]),
400 ..SignedEntityConfig::dummy()
401 };
402
403 let discriminants = config.list_allowed_signed_entity_types_discriminants();
404
405 assert_eq!(
406 BTreeSet::from_iter(
407 [
408 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
409 [
410 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
411 SignedEntityTypeDiscriminants::CardanoTransactions,
412 SignedEntityTypeDiscriminants::CardanoDatabase
413 ]
414 .as_slice()
415 ]
416 .concat()
417 ),
418 discriminants
419 );
420 }
421
422 #[test]
423 fn test_list_allowed_signed_entity_types_discriminants_with_multiple_identical_signed_entity_types_in_configuration_should_not_be_added_several_times()
424 {
425 let config = SignedEntityConfig {
426 allowed_discriminants: BTreeSet::from([
427 SignedEntityTypeDiscriminants::CardanoTransactions,
428 SignedEntityTypeDiscriminants::CardanoTransactions,
429 SignedEntityTypeDiscriminants::CardanoTransactions,
430 ]),
431 ..SignedEntityConfig::dummy()
432 };
433
434 let discriminants = config.list_allowed_signed_entity_types_discriminants();
435
436 assert_eq!(
437 BTreeSet::from_iter(
438 [
439 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
440 [SignedEntityTypeDiscriminants::CardanoTransactions].as_slice()
441 ]
442 .concat()
443 ),
444 discriminants
445 );
446 }
447
448 #[test]
449 fn test_list_allowed_signed_entity_types_with_specific_configuration() {
450 let beacon = fake_data::beacon();
451 let chain_point = ChainPoint {
452 block_number: BlockNumber(45),
453 ..ChainPoint::dummy()
454 };
455 let time_point = TimePoint::new(
456 *beacon.epoch,
457 beacon.immutable_file_number,
458 chain_point.clone(),
459 );
460 let config = SignedEntityConfig {
461 allowed_discriminants: BTreeSet::from([
462 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
463 SignedEntityTypeDiscriminants::CardanoTransactions,
464 ]),
465 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
466 security_parameter: BlockNumber(0),
467 step: BlockNumber(15),
468 }),
469 };
470
471 let signed_entity_types = config.list_allowed_signed_entity_types(&time_point).unwrap();
472
473 assert_eq!(
474 vec![
475 SignedEntityType::MithrilStakeDistribution(beacon.epoch),
476 SignedEntityType::CardanoStakeDistribution(beacon.epoch - 1),
477 SignedEntityType::CardanoTransactions(beacon.epoch, chain_point.block_number - 1),
478 ],
479 signed_entity_types
480 );
481 }
482}