mithril_common/entities/
signed_entity_config.rs

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/// Convert [TimePoint] to [SignedEntityType] and list allowed signed entity types and
12/// discriminants.
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct SignedEntityConfig {
15    /// List of discriminants that the node is allowed to sign
16    pub allowed_discriminants: BTreeSet<SignedEntityTypeDiscriminants>,
17    /// Cardano transactions signing configuration
18    pub cardano_transactions_signing_config: CardanoTransactionsSigningConfig,
19}
20
21impl SignedEntityConfig {
22    cfg_test_tools! {
23        /// Create a dummy SignedEntityConfig
24        pub fn dummy() -> Self {
25            Self {
26                allowed_discriminants: SignedEntityTypeDiscriminants::all(),
27                cardano_transactions_signing_config: CardanoTransactionsSigningConfig::dummy(),
28            }
29        }
30    }
31
32    /// Default allowed discriminants
33    ///
34    /// Appended to the allowed discriminants in the configuration.
35    pub const DEFAULT_ALLOWED_DISCRIMINANTS: [SignedEntityTypeDiscriminants; 1] =
36        [SignedEntityTypeDiscriminants::MithrilStakeDistribution];
37
38    /// Append to the given list of allowed signed entity types discriminants the [Self::DEFAULT_ALLOWED_DISCRIMINANTS]
39    /// if not already present.
40    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    /// Create the deduplicated list of allowed signed entity types discriminants.
49    ///
50    /// The list is the aggregation of [Self::DEFAULT_ALLOWED_DISCRIMINANTS] and
51    /// `allowed_discriminants`.
52    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    /// Convert this time point to a signed entity type based on the given discriminant.
60    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    /// Create the deduplicated list of allowed signed entity types discriminants.
94    ///
95    /// The list is the aggregation of [Self::DEFAULT_ALLOWED_DISCRIMINANTS] and
96    /// `allowed_discriminants`.
97    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/// Configuration for the signing of Cardano transactions
109///
110/// Allow to compute the block number to be signed based on the chain tip block number.
111///
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113pub struct CardanoTransactionsSigningConfig {
114    /// Number of blocks to discard from the tip of the chain when importing transactions.
115    pub security_parameter: BlockNumber,
116
117    /// The number of blocks between signature of the transactions.
118    ///
119    /// *Note: The step is adjusted to be a multiple of the block range length in order
120    /// to guarantee that the block number signed in a certificate is effectively signed.*
121    pub step: BlockNumber,
122}
123
124impl CardanoTransactionsSigningConfig {
125    cfg_test_tools! {
126        /// Create a new CardanoTransactionsSigningConfig
127        pub fn new(security_parameter: BlockNumber, step: BlockNumber) -> Self {
128            Self {
129                security_parameter,
130                step,
131            }
132        }
133
134        /// Create a dummy config
135        pub fn dummy() -> Self {
136            Self {
137                security_parameter: BlockNumber(0),
138                step: BlockNumber(15),
139            }
140        }
141    }
142
143    /// Compute the block number to be signed based on the chain tip block number.
144    ///
145    /// The latest block number to be signed is the highest multiple of the step less or equal than the
146    /// block number minus the security parameter.
147    ///
148    /// The formula is as follows:
149    ///
150    /// `block_number = ⌊(tip.block_number - security_parameter) / step⌋ × step - 1`
151    ///
152    /// where `⌊x⌋` is the floor function which rounds to the greatest integer less than or equal to `x`.
153    ///
154    /// *Notes:*
155    /// * *The step is adjusted to be a multiple of the block range length in order
156    ///   to guarantee that the block number signed in a certificate is effectively signed.*
157    /// * *1 is subtracted to the result because block range end is exclusive (ie: a BlockRange over
158    ///   `30..45` finish at 44 included, 45 is included in the next block range).*
159    pub fn compute_block_number_to_be_signed(&self, block_number: BlockNumber) -> BlockNumber {
160        // TODO: See if we can remove this adjustment by including a "partial" block range in
161        // the signed data.
162        let adjusted_step = BlockRange::from_block_number(self.step).start;
163        // We can't have a step lower than the block range length.
164        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        // An offset of -1 is applied to the epoch of the time point to get the epoch of the stake distribution to be signed
211        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        // The block number to be signed is 14 because the step is 15, the block number is 20, and
232        // the security parameter is 0.
233        // This is further tested in the "computing_block_number_to_be_signed" tests below.
234        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        // **block_number = ((tip.block_number - k') / n) × n**
258        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        // Adjusted step is always at least BLOCK_RANGE_LENGTH.
328        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}