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::CardanoDatabase => SignedEntityType::CardanoDatabase(
83 CardanoDbBeacon::new(*time_point.epoch, time_point.immutable_file_number),
84 ),
85 };
86
87 Ok(signed_entity_type)
88 }
89
90 pub fn list_allowed_signed_entity_types(
95 &self,
96 time_point: &TimePoint,
97 ) -> StdResult<Vec<SignedEntityType>> {
98 self.list_allowed_signed_entity_types_discriminants()
99 .into_iter()
100 .map(|discriminant| self.time_point_to_signed_entity(discriminant, time_point))
101 .collect()
102 }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110pub struct CardanoTransactionsSigningConfig {
111 pub security_parameter: BlockNumber,
113
114 pub step: BlockNumber,
119}
120
121impl CardanoTransactionsSigningConfig {
122 pub fn compute_block_number_to_be_signed(&self, block_number: BlockNumber) -> BlockNumber {
139 let adjusted_step = BlockRange::from_block_number(self.step).start;
142 let adjusted_step = std::cmp::max(adjusted_step, BlockRange::LENGTH);
144
145 let block_number_to_be_signed =
146 (block_number - self.security_parameter) / adjusted_step * adjusted_step;
147 block_number_to_be_signed - 1
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use crate::entities::{
154 CardanoDbBeacon, ChainPoint, Epoch, SignedEntityType, SlotNumber, TimePoint,
155 };
156 use crate::test::{double::Dummy, double::fake_data};
157
158 use super::*;
159
160 #[test]
161 fn given_discriminant_convert_to_signed_entity() {
162 let time_point = TimePoint {
163 epoch: Epoch(1),
164 immutable_file_number: 5,
165 chain_point: ChainPoint {
166 slot_number: SlotNumber(73),
167 block_number: BlockNumber(20),
168 block_hash: "block_hash-20".to_string(),
169 },
170 };
171 let config = SignedEntityConfig {
172 allowed_discriminants: SignedEntityTypeDiscriminants::all(),
173 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
174 security_parameter: BlockNumber(0),
175 step: BlockNumber(15),
176 }),
177 };
178
179 assert_eq!(
180 SignedEntityType::MithrilStakeDistribution(Epoch(1)),
181 config
182 .time_point_to_signed_entity(
183 SignedEntityTypeDiscriminants::MithrilStakeDistribution,
184 &time_point
185 )
186 .unwrap()
187 );
188
189 assert_eq!(
191 SignedEntityType::CardanoStakeDistribution(Epoch(0)),
192 config
193 .time_point_to_signed_entity(
194 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
195 &time_point
196 )
197 .unwrap()
198 );
199
200 assert_eq!(
201 SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(1, 5)),
202 config
203 .time_point_to_signed_entity(
204 SignedEntityTypeDiscriminants::CardanoImmutableFilesFull,
205 &time_point
206 )
207 .unwrap()
208 );
209
210 assert_eq!(
214 SignedEntityType::CardanoTransactions(Epoch(1), BlockNumber(14)),
215 config
216 .time_point_to_signed_entity(
217 SignedEntityTypeDiscriminants::CardanoTransactions,
218 &time_point
219 )
220 .unwrap()
221 );
222
223 assert_eq!(
224 SignedEntityType::CardanoDatabase(CardanoDbBeacon::new(1, 5)),
225 config
226 .time_point_to_signed_entity(
227 SignedEntityTypeDiscriminants::CardanoDatabase,
228 &time_point
229 )
230 .unwrap()
231 );
232 }
233
234 #[test]
235 fn can_not_convert_time_point_to_cardano_transaction_without_the_associated_config() {
236 let time_point = TimePoint {
237 epoch: Epoch(1),
238 immutable_file_number: 5,
239 chain_point: ChainPoint {
240 slot_number: SlotNumber(73),
241 block_number: BlockNumber(20),
242 block_hash: "block_hash-20".to_string(),
243 },
244 };
245 let config = SignedEntityConfig {
246 allowed_discriminants: SignedEntityTypeDiscriminants::all(),
247 cardano_transactions_signing_config: None,
248 };
249
250 let error = config
251 .time_point_to_signed_entity(
252 SignedEntityTypeDiscriminants::CardanoTransactions,
253 &time_point,
254 )
255 .unwrap_err();
256
257 let expected_error = "Can't derive a CardanoTransactions signed entity type from a time point without a `CardanoTransactionsSigningConfig`";
258 assert!(
259 error.to_string().contains(expected_error),
260 "Error message: {error:?}\nshould contains: {expected_error}\n"
261 );
262 }
263
264 #[test]
265 fn computing_block_number_to_be_signed() {
266 assert_eq!(
268 CardanoTransactionsSigningConfig {
269 security_parameter: BlockNumber(0),
270 step: BlockNumber(15),
271 }
272 .compute_block_number_to_be_signed(BlockNumber(105)),
273 104
274 );
275
276 assert_eq!(
277 CardanoTransactionsSigningConfig {
278 security_parameter: BlockNumber(5),
279 step: BlockNumber(15),
280 }
281 .compute_block_number_to_be_signed(BlockNumber(100)),
282 89
283 );
284
285 assert_eq!(
286 CardanoTransactionsSigningConfig {
287 security_parameter: BlockNumber(85),
288 step: BlockNumber(15),
289 }
290 .compute_block_number_to_be_signed(BlockNumber(100)),
291 14
292 );
293
294 assert_eq!(
295 CardanoTransactionsSigningConfig {
296 security_parameter: BlockNumber(0),
297 step: BlockNumber(30),
298 }
299 .compute_block_number_to_be_signed(BlockNumber(29)),
300 0
301 );
302 }
303
304 #[test]
305 fn computing_block_number_to_be_signed_should_not_overlow_on_security_parameter() {
306 assert_eq!(
307 CardanoTransactionsSigningConfig {
308 security_parameter: BlockNumber(100),
309 step: BlockNumber(30),
310 }
311 .compute_block_number_to_be_signed(BlockNumber(50)),
312 0
313 );
314 }
315
316 #[test]
317 fn computing_block_number_to_be_signed_round_step_to_a_block_range_start() {
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 * 5 - 1
325 );
326
327 assert_eq!(
328 CardanoTransactionsSigningConfig {
329 security_parameter: BlockNumber(0),
330 step: BlockRange::LENGTH * 2 + 1,
331 }
332 .compute_block_number_to_be_signed(BlockRange::LENGTH * 5 + 1),
333 BlockRange::LENGTH * 4 - 1
334 );
335
336 assert_eq!(
338 CardanoTransactionsSigningConfig {
339 security_parameter: BlockNumber(0),
340 step: BlockRange::LENGTH - 1,
341 }
342 .compute_block_number_to_be_signed(BlockRange::LENGTH * 10 - 1),
343 BlockRange::LENGTH * 9 - 1
344 );
345
346 assert_eq!(
347 CardanoTransactionsSigningConfig {
348 security_parameter: BlockNumber(0),
349 step: BlockRange::LENGTH - 1,
350 }
351 .compute_block_number_to_be_signed(BlockRange::LENGTH - 1),
352 0
353 );
354 }
355
356 #[test]
357 fn test_list_allowed_signed_entity_types_discriminant_without_specific_configuration() {
358 let config = SignedEntityConfig {
359 allowed_discriminants: BTreeSet::new(),
360 ..SignedEntityConfig::dummy()
361 };
362
363 let discriminants = config.list_allowed_signed_entity_types_discriminants();
364
365 assert_eq!(
366 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
367 discriminants
368 );
369 }
370
371 #[test]
372 fn test_list_allowed_signed_entity_types_discriminant_should_not_duplicate_a_signed_entity_discriminant_type_already_in_default_ones()
373 {
374 let config = SignedEntityConfig {
375 allowed_discriminants: BTreeSet::from([
376 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS[0],
377 ]),
378 ..SignedEntityConfig::dummy()
379 };
380
381 let discriminants = config.list_allowed_signed_entity_types_discriminants();
382
383 assert_eq!(
384 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
385 discriminants
386 );
387 }
388
389 #[test]
390 fn test_list_allowed_signed_entity_types_discriminants_should_add_configured_discriminants() {
391 let config = SignedEntityConfig {
392 allowed_discriminants: BTreeSet::from([
393 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
394 SignedEntityTypeDiscriminants::CardanoTransactions,
395 SignedEntityTypeDiscriminants::CardanoDatabase,
396 ]),
397 ..SignedEntityConfig::dummy()
398 };
399
400 let discriminants = config.list_allowed_signed_entity_types_discriminants();
401
402 assert_eq!(
403 BTreeSet::from_iter(
404 [
405 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
406 [
407 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
408 SignedEntityTypeDiscriminants::CardanoTransactions,
409 SignedEntityTypeDiscriminants::CardanoDatabase
410 ]
411 .as_slice()
412 ]
413 .concat()
414 ),
415 discriminants
416 );
417 }
418
419 #[test]
420 fn test_list_allowed_signed_entity_types_discriminants_with_multiple_identical_signed_entity_types_in_configuration_should_not_be_added_several_times()
421 {
422 let config = SignedEntityConfig {
423 allowed_discriminants: BTreeSet::from([
424 SignedEntityTypeDiscriminants::CardanoTransactions,
425 SignedEntityTypeDiscriminants::CardanoTransactions,
426 SignedEntityTypeDiscriminants::CardanoTransactions,
427 ]),
428 ..SignedEntityConfig::dummy()
429 };
430
431 let discriminants = config.list_allowed_signed_entity_types_discriminants();
432
433 assert_eq!(
434 BTreeSet::from_iter(
435 [
436 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
437 [SignedEntityTypeDiscriminants::CardanoTransactions].as_slice()
438 ]
439 .concat()
440 ),
441 discriminants
442 );
443 }
444
445 #[test]
446 fn test_list_allowed_signed_entity_types_with_specific_configuration() {
447 let beacon = fake_data::beacon();
448 let chain_point = ChainPoint {
449 block_number: BlockNumber(45),
450 ..ChainPoint::dummy()
451 };
452 let time_point = TimePoint::new(
453 *beacon.epoch,
454 beacon.immutable_file_number,
455 chain_point.clone(),
456 );
457 let config = SignedEntityConfig {
458 allowed_discriminants: BTreeSet::from([
459 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
460 SignedEntityTypeDiscriminants::CardanoTransactions,
461 ]),
462 cardano_transactions_signing_config: Some(CardanoTransactionsSigningConfig {
463 security_parameter: BlockNumber(0),
464 step: BlockNumber(15),
465 }),
466 };
467
468 let signed_entity_types = config.list_allowed_signed_entity_types(&time_point).unwrap();
469
470 assert_eq!(
471 vec![
472 SignedEntityType::MithrilStakeDistribution(beacon.epoch),
473 SignedEntityType::CardanoStakeDistribution(beacon.epoch - 1),
474 SignedEntityType::CardanoTransactions(beacon.epoch, chain_point.block_number - 1),
475 ],
476 signed_entity_types
477 );
478 }
479}