near_primitives/validator_mandates/
mod.rs

1use crate::types::{ValidatorId, validator_stake::ValidatorStake};
2use borsh::{BorshDeserialize, BorshSerialize};
3use near_primitives_core::types::Balance;
4use near_schema_checker_lib::ProtocolSchema;
5
6mod compute_price;
7
8/// Represents the configuration of [`ValidatorMandates`]. Its parameters are expected to remain
9/// valid for one epoch.
10#[derive(
11    BorshSerialize,
12    BorshDeserialize,
13    Default,
14    Copy,
15    Clone,
16    Debug,
17    PartialEq,
18    Eq,
19    serde::Serialize,
20    ProtocolSchema,
21)]
22pub struct ValidatorMandatesConfig {
23    /// The desired number of mandates required per shard.
24    target_mandates_per_shard: usize,
25    /// The number of shards for the referenced epoch.
26    num_shards: usize,
27}
28
29impl ValidatorMandatesConfig {
30    /// Constructs a new configuration.
31    ///
32    /// # Panics
33    ///
34    /// Panics in the following cases:
35    ///
36    /// - If `stake_per_mandate` is 0 as this would lead to division by 0.
37    /// - If `num_shards` is zero.
38    pub fn new(target_mandates_per_shard: usize, num_shards: usize) -> Self {
39        assert!(num_shards > 0, "there should be at least one shard");
40        Self { target_mandates_per_shard, num_shards }
41    }
42}
43
44/// The mandates for a set of validators given a [`ValidatorMandatesConfig`].
45///
46/// A mandate is a liability for a validator to validate a shard. Depending on its stake and the
47/// `stake_per_mandate` specified in `ValidatorMandatesConfig`, a validator may hold multiple
48/// mandates. Each mandate may be assigned to a different shard. The assignment of mandates to
49/// shards is calculated with [`Self::sample`], typically at every height.
50///
51/// See #9983 for context and links to resources that introduce mandates.
52#[derive(
53    BorshSerialize,
54    BorshDeserialize,
55    Default,
56    Clone,
57    Debug,
58    PartialEq,
59    Eq,
60    serde::Serialize,
61    ProtocolSchema,
62)]
63pub struct ValidatorMandates {
64    /// The configuration applied to the mandates.
65    config: ValidatorMandatesConfig,
66    /// The amount of stake a whole mandate is worth.
67    stake_per_mandate: Balance,
68    /// Each element represents a validator mandate held by the validator with the given id.
69    ///
70    /// The id of a validator who holds `n >= 0` mandates occurs `n` times in the vector.
71    mandates: Vec<ValidatorId>,
72    /// Each element represents a partial validator mandate held by the validator with the given id.
73    /// For example, an element `(1, 42)` represents the partial mandate of the validator with id 1
74    /// which has a weight of 42.
75    ///
76    /// Validators whose stake can be distributed across mandates without remainder are not
77    /// represented in this vector.
78    partials: Vec<(ValidatorId, Balance)>,
79}
80
81impl ValidatorMandates {
82    /// Initiates mandates corresponding to the provided `validators`. The validators must be sorted
83    /// by id in ascending order, so the validator with `ValidatorId` equal to `i` is given by
84    /// `validators[i]`.
85    ///
86    /// Only full mandates are assigned, partial mandates are dropped. For example, when the stake
87    /// required for a mandate is 5 and a validator has staked 12, then it will obtain 2 mandates.
88    pub fn new(config: ValidatorMandatesConfig, validators: &[ValidatorStake]) -> Self {
89        let stakes: Vec<Balance> = validators.iter().map(|v| v.stake()).collect();
90        let stake_per_mandate = compute_price::compute_mandate_price(config, &stakes);
91        let num_mandates_per_validator: Vec<u16> =
92            validators.iter().map(|v| v.num_mandates(stake_per_mandate)).collect();
93        let num_total_mandates =
94            num_mandates_per_validator.iter().map(|&num| usize::from(num)).sum();
95        let mut mandates: Vec<ValidatorId> = Vec::with_capacity(num_total_mandates);
96
97        for i in 0..validators.len() {
98            for _ in 0..num_mandates_per_validator[i] {
99                // Each validator's position corresponds to its id.
100                mandates.push(i as ValidatorId);
101            }
102        }
103
104        // Not counting partials towards `required_mandates` as the weight of partials and its
105        // distribution across shards may vary widely.
106        //
107        // Construct vector with capacity as most likely some validators' stake will not be evenly
108        // divided by `config.stake_per_mandate`, i.e. some validators will have partials.
109        let mut partials = Vec::with_capacity(validators.len());
110        for i in 0..validators.len() {
111            let partial_weight = validators[i].partial_mandate_weight(stake_per_mandate);
112            if partial_weight > Balance::ZERO {
113                partials.push((i as ValidatorId, partial_weight));
114            }
115        }
116
117        Self { config, stake_per_mandate, mandates, partials }
118    }
119}
120
121#[cfg(feature = "rand")]
122mod validator_mandates_sample {
123    use super::*;
124    use itertools::Itertools;
125    use rand::{Rng, seq::SliceRandom};
126
127    impl ValidatorMandates {
128        /// Returns a validator assignment obtained by shuffling mandates and assigning them to shards.
129        /// Shard ids are shuffled as well in this process to avoid a bias lower shard ids, see
130        /// [`ShuffledShardIds`].
131        ///
132        /// It clones mandates since [`ValidatorMandates`] is supposed to be valid for an epoch, while a
133        /// new assignment is calculated at every height.
134        pub fn sample<R>(&self, rng: &mut R) -> ChunkValidatorStakeAssignment
135        where
136            R: Rng + ?Sized,
137        {
138            // Shuffling shard ids to avoid a bias towards lower ids, see [`ShuffledShardIds`]. We
139            // do two separate shuffles for full and partial mandates to reduce the likelihood of
140            // assigning fewer full _and_ partial mandates to the _same_ shard.
141            let shard_ids_for_mandates = ShuffledShardIds::new(rng, self.config.num_shards);
142            let shard_ids_for_partials = ShuffledShardIds::new(rng, self.config.num_shards);
143
144            let shuffled_mandates = self.shuffled_mandates(rng);
145            let shuffled_partials = self.shuffled_partials(rng);
146
147            // Distribute shuffled mandates and partials across shards. For each shard with `shard_id`
148            // in `[0, num_shards)`, we take the elements of the vector with index `i` such that `i %
149            // num_shards == shard_id`.
150            //
151            // Assume, for example, there are 10 mandates and 4 shards. Then for `shard_id = 1` we
152            // collect the mandates with indices 1, 5, and 9.
153            let stake_per_mandate = self.stake_per_mandate;
154            let mut stake_assignment_per_shard =
155                vec![std::collections::HashMap::new(); self.config.num_shards];
156            for shard_id in 0..self.config.num_shards {
157                // Achieve shard id shuffling by writing to the position of the alias of `shard_id`.
158                let mandates_assignment =
159                    &mut stake_assignment_per_shard[shard_ids_for_mandates.get_alias(shard_id)];
160
161                // For the current `shard_id`, collect mandates with index `i` such that
162                // `i % num_shards == shard_id`.
163                for idx in (shard_id..shuffled_mandates.len()).step_by(self.config.num_shards) {
164                    let validator_id = shuffled_mandates[idx];
165                    let current_shard_validator_stake: &mut Balance =
166                        mandates_assignment.entry(validator_id).or_default();
167                    *current_shard_validator_stake =
168                        current_shard_validator_stake.checked_add(stake_per_mandate).unwrap();
169                }
170
171                // Achieve shard id shuffling by writing to the position of the alias of `shard_id`.
172                let partials_assignment =
173                    &mut stake_assignment_per_shard[shard_ids_for_partials.get_alias(shard_id)];
174
175                // For the current `shard_id`, collect partials with index `i` such that
176                // `i % num_shards == shard_id`.
177                for idx in (shard_id..shuffled_partials.len()).step_by(self.config.num_shards) {
178                    let (validator_id, partial_weight) = shuffled_partials[idx];
179                    let current_shard_validator_stake: &mut Balance =
180                        partials_assignment.entry(validator_id).or_default();
181                    *current_shard_validator_stake =
182                        current_shard_validator_stake.checked_add(partial_weight).unwrap();
183                }
184            }
185
186            // Deterministically shuffle the validator order for each shard
187            let mut ordered_stake_assignment_per_shard = Vec::with_capacity(self.config.num_shards);
188            for shard_id in 0..self.config.num_shards {
189                // first sort the validators by id then shuffle using rng
190                let stake_assignment = &stake_assignment_per_shard[shard_id];
191                let mut ordered_validator_ids = stake_assignment.keys().sorted().collect_vec();
192                ordered_validator_ids.shuffle(rng);
193                let ordered_mandate_assignment = ordered_validator_ids
194                    .into_iter()
195                    .map(|validator_id| (*validator_id, stake_assignment[validator_id]))
196                    .collect_vec();
197                ordered_stake_assignment_per_shard.push(ordered_mandate_assignment);
198            }
199
200            ordered_stake_assignment_per_shard
201        }
202
203        /// Clones the contained mandates and shuffles them. Cloning is required as a shuffle happens at
204        /// every height while the `ValidatorMandates` are to be valid for an epoch.
205        pub(super) fn shuffled_mandates<R>(&self, rng: &mut R) -> Vec<ValidatorId>
206        where
207            R: Rng + ?Sized,
208        {
209            let mut shuffled_mandates = self.mandates.clone();
210            shuffled_mandates.shuffle(rng);
211            shuffled_mandates
212        }
213
214        /// Clones the contained partials and shuffles them. Cloning is required as a shuffle happens at
215        /// every height while the `ValidatorMandates` are to be valid for an epoch.
216        pub(super) fn shuffled_partials<R>(&self, rng: &mut R) -> Vec<(ValidatorId, Balance)>
217        where
218            R: Rng + ?Sized,
219        {
220            let mut shuffled_partials = self.partials.clone();
221            shuffled_partials.shuffle(rng);
222            shuffled_partials
223        }
224    }
225}
226
227/// Represents an assignment of [`ValidatorMandates`] for a specific height.
228///
229/// Contains one vec per shard, with the position in the vector corresponding to `shard_id` in
230/// `0..num_shards`. Each element is a tuple of `ValidatorId`, total stake they have in the
231/// corresponding shards. A validator whose id is not in any vec has not been assigned to the shard.
232///
233/// For example, `mandates_per_shard[0]` gives us the entries of shard with id 0.
234/// Elements of `mandates_per_shard[0]` can be [(validator3, stake), (validator7, stake)]
235pub type ChunkValidatorStakeAssignment = Vec<Vec<(ValidatorId, Balance)>>;
236
237/// When assigning mandates first to shards with lower ids, the shards with higher ids might end up
238/// with fewer assigned mandates.
239///
240/// Assumes shard ids are in `[0, num_shards)`.
241///
242/// # Example
243///
244/// Assume there are 3 shards and 5 mandates. Assigning to shards with lower ids first, the first
245/// two shards get 2 mandates each. For the third shard only 1 mandate remains.
246///
247/// # Shuffling to avoid bias
248///
249/// When mandates cannot be distributed evenly across shards, some shards will be assigned one
250/// mandate less than others. Shuffling shard ids prevents a bias towards lower shard ids, as it is
251/// no longer predictable which shard(s) will be assigned one mandate less.
252#[cfg(feature = "rand")]
253#[derive(Default, Clone, Debug, PartialEq, Eq)]
254struct ShuffledShardIds {
255    /// Contains the shard ids `[0, num_shards)` in shuffled order.
256    shuffled_ids: Vec<usize>,
257}
258
259#[cfg(feature = "rand")]
260impl ShuffledShardIds {
261    fn new<R>(rng: &mut R, num_shards: usize) -> Self
262    where
263        R: rand::Rng + ?Sized,
264    {
265        use rand::seq::SliceRandom;
266
267        let mut shuffled_ids = (0..num_shards).collect::<Vec<_>>();
268        shuffled_ids.shuffle(rng);
269        Self { shuffled_ids }
270    }
271
272    /// Gets the alias of `shard_id` corresponding to the current shuffling.
273    ///
274    /// # Panics
275    ///
276    /// Panics if `shard_id >= num_shards`.
277    fn get_alias(&self, shard_id: usize) -> usize {
278        self.shuffled_ids[shard_id]
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use near_crypto::PublicKey;
285    use near_primitives_core::types::Balance;
286    use rand::SeedableRng;
287    use rand_chacha::ChaCha8Rng;
288
289    use crate::{
290        types::ValidatorId, types::validator_stake::ValidatorStake,
291        validator_mandates::ValidatorMandatesConfig,
292    };
293
294    use super::{ChunkValidatorStakeAssignment, ShuffledShardIds, ValidatorMandates};
295
296    /// Returns a new, fixed RNG to be used only in tests. Using a fixed RNG facilitates testing as
297    /// it makes outcomes based on that RNG deterministic.
298    fn new_fixed_rng() -> ChaCha8Rng {
299        ChaCha8Rng::seed_from_u64(42)
300    }
301
302    #[test]
303    fn test_validator_mandates_config_new() {
304        let target_mandates_per_shard = 400;
305        let num_shards = 4;
306        assert_eq!(
307            ValidatorMandatesConfig::new(target_mandates_per_shard, num_shards),
308            ValidatorMandatesConfig { target_mandates_per_shard, num_shards },
309        )
310    }
311
312    /// Constructs some `ValidatorStakes` for usage in tests.
313    ///
314    /// # Properties of the corresponding `ValidatorMandates`
315    ///
316    /// The mandates are (verified in [`test_validator_mandates_new`]):
317    /// `vec![0, 0, 0, 1, 1, 3, 4, 4, 4]`
318    ///
319    /// The partials are (verified in [`test_validator_mandates_new`]):
320    /// `vec![(1, 7), (2, 9), (3, 2), (4, 5), (5, 4), (6, 6)]`
321    fn new_validator_stakes() -> Vec<ValidatorStake> {
322        let new_vs = |account_id: &str, balance: Balance| -> ValidatorStake {
323            ValidatorStake::new(
324                account_id.parse().unwrap(),
325                PublicKey::empty(near_crypto::KeyType::ED25519),
326                balance,
327            )
328        };
329
330        vec![
331            new_vs("account_0", Balance::from_yoctonear(30)),
332            new_vs("account_1", Balance::from_yoctonear(27)),
333            new_vs("account_2", Balance::from_yoctonear(9)),
334            new_vs("account_3", Balance::from_yoctonear(12)),
335            new_vs("account_4", Balance::from_yoctonear(35)),
336            new_vs("account_5", Balance::from_yoctonear(4)),
337            new_vs("account_6", Balance::from_yoctonear(6)),
338        ]
339    }
340
341    #[test]
342    fn test_validator_mandates_new() {
343        let validators = new_validator_stakes();
344        let config = ValidatorMandatesConfig::new(3, 4);
345        let mandates = ValidatorMandates::new(config, &validators);
346
347        // With 3 mandates per shard and 4 shards, we are looking for around 12 total mandates.
348        // The total stake in `new_validator_stakes` is 123, so to get 12 mandates we need a price
349        // close to 10. But the algorithm for computing price tries to make the number of _whole_
350        // mandates equal to 12, and there are validators with partial mandates in the distribution,
351        // therefore the price is set a little lower than 10.
352        assert_eq!(mandates.stake_per_mandate, Balance::from_yoctonear(8));
353
354        // At 8 stake per mandate, the first validator holds three mandates, and so on.
355        // Note that "account_5" and "account_6" hold no mandate as both their stakes are below the threshold.
356        let expected_mandates: Vec<ValidatorId> = vec![0, 0, 0, 1, 1, 1, 2, 3, 4, 4, 4, 4];
357        assert_eq!(mandates.mandates, expected_mandates);
358
359        // The number of whole mandates is exactly equal to our target
360        assert_eq!(mandates.mandates.len(), config.num_shards * config.target_mandates_per_shard);
361
362        // At 8 stake per mandate, the first validator a partial mandate with weight 6, the second
363        // validator holds a partial mandate with weight 3, and so on.
364        let expected_partials: Vec<(ValidatorId, Balance)> = vec![
365            (0, Balance::from_yoctonear(6)),
366            (1, Balance::from_yoctonear(3)),
367            (2, Balance::from_yoctonear(1)),
368            (3, Balance::from_yoctonear(4)),
369            (4, Balance::from_yoctonear(3)),
370            (5, Balance::from_yoctonear(4)),
371            (6, Balance::from_yoctonear(6)),
372        ];
373        assert_eq!(mandates.partials, expected_partials);
374    }
375
376    #[test]
377    fn test_validator_mandates_shuffled_mandates() {
378        // Testing with different `num_shards` values to verify the shuffles used in other tests.
379        assert_validator_mandates_shuffled_mandates(3, vec![0, 1, 4, 4, 3, 1, 4, 0, 0]);
380        assert_validator_mandates_shuffled_mandates(4, vec![0, 0, 2, 1, 3, 4, 1, 1, 0, 4, 4, 4]);
381    }
382
383    fn assert_validator_mandates_shuffled_mandates(
384        num_shards: usize,
385        expected_assignment: Vec<ValidatorId>,
386    ) {
387        let validators = new_validator_stakes();
388        let config = ValidatorMandatesConfig::new(3, num_shards);
389        let mandates = ValidatorMandates::new(config, &validators);
390        let mut rng = new_fixed_rng();
391
392        // Call methods that modify `rng` before shuffling mandates to emulate what happens in
393        // [`ValidatorMandates::sample`]. Then `expected_assignment` below equals the shuffled
394        // mandates assigned to shards in `test_validator_mandates_sample_*`.
395        let _shard_ids_for_mandates = ShuffledShardIds::new(&mut rng, config.num_shards);
396        let _shard_ids_for_partials = ShuffledShardIds::new(&mut rng, config.num_shards);
397        let assignment = mandates.shuffled_mandates(&mut rng);
398        assert_eq!(
399            assignment, expected_assignment,
400            "Unexpected shuffling for num_shards = {num_shards}"
401        );
402    }
403
404    #[test]
405    fn test_validator_mandates_shuffled_partials() {
406        // Testing with different `num_shards` values to verify the shuffles used in other tests.
407        assert_validator_mandates_shuffled_partials(
408            3,
409            vec![
410                (3, Balance::from_yoctonear(2)),
411                (4, Balance::from_yoctonear(5)),
412                (1, Balance::from_yoctonear(7)),
413                (2, Balance::from_yoctonear(9)),
414                (5, Balance::from_yoctonear(4)),
415                (6, Balance::from_yoctonear(6)),
416            ],
417        );
418        assert_validator_mandates_shuffled_partials(
419            4,
420            vec![
421                (5, Balance::from_yoctonear(4)),
422                (3, Balance::from_yoctonear(4)),
423                (0, Balance::from_yoctonear(6)),
424                (2, Balance::from_yoctonear(1)),
425                (1, Balance::from_yoctonear(3)),
426                (4, Balance::from_yoctonear(3)),
427                (6, Balance::from_yoctonear(6)),
428            ],
429        );
430    }
431
432    fn assert_validator_mandates_shuffled_partials(
433        num_shards: usize,
434        expected_assignment: Vec<(ValidatorId, Balance)>,
435    ) {
436        let validators = new_validator_stakes();
437        let config = ValidatorMandatesConfig::new(3, num_shards);
438        let mandates = ValidatorMandates::new(config, &validators);
439        let mut rng = new_fixed_rng();
440
441        // Call methods that modify `rng` before shuffling mandates to emulate what happens in
442        // [`ValidatorMandates::sample`]. Then `expected_assignment` below equals the shuffled
443        // partials assigned to shards in `test_validator_mandates_sample_*`.
444        let _shard_ids_for_mandates = ShuffledShardIds::new(&mut rng, config.num_shards);
445        let _shard_ids_for_partials = ShuffledShardIds::new(&mut rng, config.num_shards);
446        let _ = mandates.shuffled_mandates(&mut rng);
447        let assignment = mandates.shuffled_partials(&mut rng);
448        assert_eq!(
449            assignment, expected_assignment,
450            "Unexpected shuffling for num_shards = {num_shards}"
451        );
452    }
453
454    /// Test mandates per shard are collected correctly for `num_mandates % num_shards == 0` and
455    /// `num_partials % num_shards == 0`.
456    #[test]
457    fn test_validator_mandates_sample_even() {
458        // Choosing `num_shards` such that mandates and partials are distributed evenly.
459        // Assignments in `test_validator_mandates_shuffled_*` can be used to construct
460        // `expected_assignment` below.
461        // Note that shard ids are shuffled too, see `test_shuffled_shard_ids_new`.
462        let config = ValidatorMandatesConfig::new(3, 3);
463        let expected_assignment = vec![
464            vec![
465                (1, Balance::from_yoctonear(17)),
466                (4, Balance::from_yoctonear(10)),
467                (6, Balance::from_yoctonear(6)),
468                (0, Balance::from_yoctonear(10)),
469            ],
470            vec![
471                (4, Balance::from_yoctonear(5)),
472                (5, Balance::from_yoctonear(4)),
473                (0, Balance::from_yoctonear(10)),
474                (1, Balance::from_yoctonear(10)),
475                (3, Balance::from_yoctonear(10)),
476            ],
477            vec![
478                (0, Balance::from_yoctonear(10)),
479                (2, Balance::from_yoctonear(9)),
480                (4, Balance::from_yoctonear(20)),
481                (3, Balance::from_yoctonear(2)),
482            ],
483        ];
484        assert_validator_mandates_sample(config, expected_assignment);
485    }
486
487    /// Test mandates per shard are collected correctly for `num_mandates % num_shards != 0` and
488    /// `num_partials % num_shards != 0`.
489    #[test]
490    fn test_validator_mandates_sample_uneven() {
491        // Choosing `num_shards` such that mandates and partials are distributed unevenly.
492        // Assignments in `test_validator_mandates_shuffled_*` can be used to construct
493        // `expected_assignment` below.
494        // Note that shard ids are shuffled too, see `test_shuffled_shard_ids_new`.
495        let config = ValidatorMandatesConfig::new(3, 4);
496        let expected_mandates_per_shards = vec![
497            vec![
498                (3, Balance::from_yoctonear(8)),
499                (6, Balance::from_yoctonear(6)),
500                (0, Balance::from_yoctonear(22)),
501            ],
502            vec![
503                (4, Balance::from_yoctonear(8)),
504                (2, Balance::from_yoctonear(9)),
505                (1, Balance::from_yoctonear(8)),
506            ],
507            vec![
508                (0, Balance::from_yoctonear(8)),
509                (3, Balance::from_yoctonear(4)),
510                (4, Balance::from_yoctonear(19)),
511            ],
512            vec![
513                (4, Balance::from_yoctonear(8)),
514                (5, Balance::from_yoctonear(4)),
515                (1, Balance::from_yoctonear(19)),
516            ],
517        ];
518        assert_validator_mandates_sample(config, expected_mandates_per_shards);
519    }
520
521    /// Asserts mandates per shard are collected correctly.
522    fn assert_validator_mandates_sample(
523        config: ValidatorMandatesConfig,
524        expected_assignment: ChunkValidatorStakeAssignment,
525    ) {
526        let validators = new_validator_stakes();
527        let mandates = ValidatorMandates::new(config, &validators);
528
529        let mut rng = new_fixed_rng();
530        let assignment = mandates.sample(&mut rng);
531
532        assert_eq!(assignment, expected_assignment);
533    }
534
535    #[test]
536    fn test_shuffled_shard_ids_new() {
537        // Testing with different `num_shards` values to verify the shuffles used in other tests.
538        // Doing two shuffles for each `num_shards` with the same RNG since shard ids are shuffled
539        // twice (once for full and once for partial mandates).
540        let mut rng_3_shards = new_fixed_rng();
541        assert_shuffled_shard_ids(&mut rng_3_shards, 3, vec![2, 1, 0], "3 shards, 1st shuffle");
542        assert_shuffled_shard_ids(&mut rng_3_shards, 3, vec![2, 1, 0], "3 shards, 2nd shuffle");
543        let mut rng_4_shards = new_fixed_rng();
544        assert_shuffled_shard_ids(&mut rng_4_shards, 4, vec![0, 2, 1, 3], "4 shards, 1st shuffle");
545        assert_shuffled_shard_ids(&mut rng_4_shards, 4, vec![3, 2, 0, 1], "4 shards, 2nd shuffle");
546    }
547
548    fn assert_shuffled_shard_ids(
549        rng: &mut ChaCha8Rng,
550        num_shards: usize,
551        expected_shuffling: Vec<usize>,
552        test_descriptor: &str,
553    ) {
554        let shuffled_ids_full_mandates = ShuffledShardIds::new(rng, num_shards);
555        assert_eq!(
556            shuffled_ids_full_mandates,
557            ShuffledShardIds { shuffled_ids: expected_shuffling },
558            "Unexpected shuffling for {test_descriptor}",
559        );
560    }
561
562    #[test]
563    fn test_shuffled_shard_ids_get_alias() {
564        let mut rng = new_fixed_rng();
565        let shuffled_ids = ShuffledShardIds::new(&mut rng, 4);
566        // See [`test_shuffled_shard_ids_new`] for the result of this shuffling.
567        assert_eq!(shuffled_ids.get_alias(1), 2);
568    }
569
570    #[test]
571    fn test_deterministic_shuffle() {
572        let config = ValidatorMandatesConfig::new(3, 4);
573        let validators = new_validator_stakes();
574        let mandates = ValidatorMandates::new(config, &validators);
575
576        let mut rng1 = new_fixed_rng();
577        let assignment1 = mandates.sample(&mut rng1);
578
579        let mut rng2 = new_fixed_rng();
580        let assignment2 = mandates.sample(&mut rng2);
581
582        // Two assignments with the same RNG should be equal.
583        assert_eq!(assignment1, assignment2);
584    }
585}