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 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 SignedEntityType::CardanoTransactions(
70 time_point.epoch,
71 self.cardano_transactions_signing_config
72 .compute_block_number_to_be_signed(time_point.chain_point.block_number),
73 )
74 }
75 SignedEntityTypeDiscriminants::CardanoDatabase => SignedEntityType::CardanoDatabase(
76 CardanoDbBeacon::new(*time_point.epoch, time_point.immutable_file_number),
77 ),
78 };
79
80 Ok(signed_entity_type)
81 }
82
83 pub fn list_allowed_signed_entity_types(
88 &self,
89 time_point: &TimePoint,
90 ) -> StdResult<Vec<SignedEntityType>> {
91 self.list_allowed_signed_entity_types_discriminants()
92 .into_iter()
93 .map(|discriminant| self.time_point_to_signed_entity(discriminant, time_point))
94 .collect()
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
103pub struct CardanoTransactionsSigningConfig {
104 pub security_parameter: BlockNumber,
106
107 pub step: BlockNumber,
112}
113
114impl CardanoTransactionsSigningConfig {
115 pub fn compute_block_number_to_be_signed(&self, block_number: BlockNumber) -> BlockNumber {
132 let adjusted_step = BlockRange::from_block_number(self.step).start;
135 let adjusted_step = std::cmp::max(adjusted_step, BlockRange::LENGTH);
137
138 let block_number_to_be_signed =
139 (block_number - self.security_parameter) / adjusted_step * adjusted_step;
140 block_number_to_be_signed - 1
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use crate::entities::{
147 CardanoDbBeacon, ChainPoint, Epoch, SignedEntityType, SlotNumber, TimePoint,
148 };
149 use crate::test::{double::Dummy, double::fake_data};
150
151 use super::*;
152
153 #[test]
154 fn given_discriminant_convert_to_signed_entity() {
155 let time_point = TimePoint {
156 epoch: Epoch(1),
157 immutable_file_number: 5,
158 chain_point: ChainPoint {
159 slot_number: SlotNumber(73),
160 block_number: BlockNumber(20),
161 block_hash: "block_hash-20".to_string(),
162 },
163 };
164 let config = SignedEntityConfig {
165 allowed_discriminants: SignedEntityTypeDiscriminants::all(),
166 cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
167 security_parameter: BlockNumber(0),
168 step: BlockNumber(15),
169 },
170 };
171
172 assert_eq!(
173 SignedEntityType::MithrilStakeDistribution(Epoch(1)),
174 config
175 .time_point_to_signed_entity(
176 SignedEntityTypeDiscriminants::MithrilStakeDistribution,
177 &time_point
178 )
179 .unwrap()
180 );
181
182 assert_eq!(
184 SignedEntityType::CardanoStakeDistribution(Epoch(0)),
185 config
186 .time_point_to_signed_entity(
187 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
188 &time_point
189 )
190 .unwrap()
191 );
192
193 assert_eq!(
194 SignedEntityType::CardanoImmutableFilesFull(CardanoDbBeacon::new(1, 5)),
195 config
196 .time_point_to_signed_entity(
197 SignedEntityTypeDiscriminants::CardanoImmutableFilesFull,
198 &time_point
199 )
200 .unwrap()
201 );
202
203 assert_eq!(
207 SignedEntityType::CardanoTransactions(Epoch(1), BlockNumber(14)),
208 config
209 .time_point_to_signed_entity(
210 SignedEntityTypeDiscriminants::CardanoTransactions,
211 &time_point
212 )
213 .unwrap()
214 );
215
216 assert_eq!(
217 SignedEntityType::CardanoDatabase(CardanoDbBeacon::new(1, 5)),
218 config
219 .time_point_to_signed_entity(
220 SignedEntityTypeDiscriminants::CardanoDatabase,
221 &time_point
222 )
223 .unwrap()
224 );
225 }
226
227 #[test]
228 fn computing_block_number_to_be_signed() {
229 assert_eq!(
231 CardanoTransactionsSigningConfig {
232 security_parameter: BlockNumber(0),
233 step: BlockNumber(15),
234 }
235 .compute_block_number_to_be_signed(BlockNumber(105)),
236 104
237 );
238
239 assert_eq!(
240 CardanoTransactionsSigningConfig {
241 security_parameter: BlockNumber(5),
242 step: BlockNumber(15),
243 }
244 .compute_block_number_to_be_signed(BlockNumber(100)),
245 89
246 );
247
248 assert_eq!(
249 CardanoTransactionsSigningConfig {
250 security_parameter: BlockNumber(85),
251 step: BlockNumber(15),
252 }
253 .compute_block_number_to_be_signed(BlockNumber(100)),
254 14
255 );
256
257 assert_eq!(
258 CardanoTransactionsSigningConfig {
259 security_parameter: BlockNumber(0),
260 step: BlockNumber(30),
261 }
262 .compute_block_number_to_be_signed(BlockNumber(29)),
263 0
264 );
265 }
266
267 #[test]
268 fn computing_block_number_to_be_signed_should_not_overlow_on_security_parameter() {
269 assert_eq!(
270 CardanoTransactionsSigningConfig {
271 security_parameter: BlockNumber(100),
272 step: BlockNumber(30),
273 }
274 .compute_block_number_to_be_signed(BlockNumber(50)),
275 0
276 );
277 }
278
279 #[test]
280 fn computing_block_number_to_be_signed_round_step_to_a_block_range_start() {
281 assert_eq!(
282 CardanoTransactionsSigningConfig {
283 security_parameter: BlockNumber(0),
284 step: BlockRange::LENGTH * 2 - 1,
285 }
286 .compute_block_number_to_be_signed(BlockRange::LENGTH * 5 + 1),
287 BlockRange::LENGTH * 5 - 1
288 );
289
290 assert_eq!(
291 CardanoTransactionsSigningConfig {
292 security_parameter: BlockNumber(0),
293 step: BlockRange::LENGTH * 2 + 1,
294 }
295 .compute_block_number_to_be_signed(BlockRange::LENGTH * 5 + 1),
296 BlockRange::LENGTH * 4 - 1
297 );
298
299 assert_eq!(
301 CardanoTransactionsSigningConfig {
302 security_parameter: BlockNumber(0),
303 step: BlockRange::LENGTH - 1,
304 }
305 .compute_block_number_to_be_signed(BlockRange::LENGTH * 10 - 1),
306 BlockRange::LENGTH * 9 - 1
307 );
308
309 assert_eq!(
310 CardanoTransactionsSigningConfig {
311 security_parameter: BlockNumber(0),
312 step: BlockRange::LENGTH - 1,
313 }
314 .compute_block_number_to_be_signed(BlockRange::LENGTH - 1),
315 0
316 );
317 }
318
319 #[test]
320 fn test_list_allowed_signed_entity_types_discriminant_without_specific_configuration() {
321 let config = SignedEntityConfig {
322 allowed_discriminants: BTreeSet::new(),
323 ..SignedEntityConfig::dummy()
324 };
325
326 let discriminants = config.list_allowed_signed_entity_types_discriminants();
327
328 assert_eq!(
329 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
330 discriminants
331 );
332 }
333
334 #[test]
335 fn test_list_allowed_signed_entity_types_discriminant_should_not_duplicate_a_signed_entity_discriminant_type_already_in_default_ones()
336 {
337 let config = SignedEntityConfig {
338 allowed_discriminants: BTreeSet::from([
339 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS[0],
340 ]),
341 ..SignedEntityConfig::dummy()
342 };
343
344 let discriminants = config.list_allowed_signed_entity_types_discriminants();
345
346 assert_eq!(
347 BTreeSet::from(SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS),
348 discriminants
349 );
350 }
351
352 #[test]
353 fn test_list_allowed_signed_entity_types_discriminants_should_add_configured_discriminants() {
354 let config = SignedEntityConfig {
355 allowed_discriminants: BTreeSet::from([
356 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
357 SignedEntityTypeDiscriminants::CardanoTransactions,
358 SignedEntityTypeDiscriminants::CardanoDatabase,
359 ]),
360 ..SignedEntityConfig::dummy()
361 };
362
363 let discriminants = config.list_allowed_signed_entity_types_discriminants();
364
365 assert_eq!(
366 BTreeSet::from_iter(
367 [
368 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
369 [
370 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
371 SignedEntityTypeDiscriminants::CardanoTransactions,
372 SignedEntityTypeDiscriminants::CardanoDatabase
373 ]
374 .as_slice()
375 ]
376 .concat()
377 ),
378 discriminants
379 );
380 }
381
382 #[test]
383 fn test_list_allowed_signed_entity_types_discriminants_with_multiple_identical_signed_entity_types_in_configuration_should_not_be_added_several_times()
384 {
385 let config = SignedEntityConfig {
386 allowed_discriminants: BTreeSet::from([
387 SignedEntityTypeDiscriminants::CardanoTransactions,
388 SignedEntityTypeDiscriminants::CardanoTransactions,
389 SignedEntityTypeDiscriminants::CardanoTransactions,
390 ]),
391 ..SignedEntityConfig::dummy()
392 };
393
394 let discriminants = config.list_allowed_signed_entity_types_discriminants();
395
396 assert_eq!(
397 BTreeSet::from_iter(
398 [
399 SignedEntityConfig::DEFAULT_ALLOWED_DISCRIMINANTS.as_slice(),
400 [SignedEntityTypeDiscriminants::CardanoTransactions].as_slice()
401 ]
402 .concat()
403 ),
404 discriminants
405 );
406 }
407
408 #[test]
409 fn test_list_allowed_signed_entity_types_with_specific_configuration() {
410 let beacon = fake_data::beacon();
411 let chain_point = ChainPoint {
412 block_number: BlockNumber(45),
413 ..ChainPoint::dummy()
414 };
415 let time_point = TimePoint::new(
416 *beacon.epoch,
417 beacon.immutable_file_number,
418 chain_point.clone(),
419 );
420 let config = SignedEntityConfig {
421 allowed_discriminants: BTreeSet::from([
422 SignedEntityTypeDiscriminants::CardanoStakeDistribution,
423 SignedEntityTypeDiscriminants::CardanoTransactions,
424 ]),
425 cardano_transactions_signing_config: CardanoTransactionsSigningConfig {
426 security_parameter: BlockNumber(0),
427 step: BlockNumber(15),
428 },
429 };
430
431 let signed_entity_types = config.list_allowed_signed_entity_types(&time_point).unwrap();
432
433 assert_eq!(
434 vec![
435 SignedEntityType::MithrilStakeDistribution(beacon.epoch),
436 SignedEntityType::CardanoStakeDistribution(beacon.epoch - 1),
437 SignedEntityType::CardanoTransactions(beacon.epoch, chain_point.block_number - 1),
438 ],
439 signed_entity_types
440 );
441 }
442}