1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
use {
18
    dp_collator_assignment::AssignedCollators,
19
    frame_support::traits::Get,
20
    sp_std::{
21
        cmp,
22
        collections::{btree_map::BTreeMap, btree_set::BTreeSet},
23
        marker::PhantomData,
24
        mem,
25
        vec::Vec,
26
    },
27
    tp_traits::{ParaId, RemoveInvulnerables as RemoveInvulnerablesT},
28
};
29

            
30
// Separate import of `sp_std::vec!` macro, which cause issues with rustfmt if grouped
31
// with `sp_std::vec::Vec`.
32
use sp_std::vec;
33

            
34
/// Helper methods to implement collator assignment algorithm
35
pub struct Assignment<T>(PhantomData<T>);
36

            
37
impl<T> Assignment<T>
38
where
39
    T: crate::Config,
40
{
41
    /// Recompute collator assignment from scratch. If the list of collators and the list of
42
    /// container chains are shuffled, this returns a random assignment.
43
1197
    pub fn assign_collators_rotate_all<TShuffle>(
44
1197
        collators: Vec<T::AccountId>,
45
1197
        orchestrator_chain: ChainNumCollators,
46
1197
        chains: Vec<ChainNumCollators>,
47
1197
        shuffle: Option<TShuffle>,
48
1197
    ) -> Result<AssignedCollators<T::AccountId>, AssignmentError>
49
1197
    where
50
1197
        TShuffle: FnOnce(&mut Vec<T::AccountId>),
51
1197
    {
52
1197
        // This is just the "always_keep_old" algorithm but with an empty "old"
53
1197
        let old_assigned = Default::default();
54
1197

            
55
1197
        Self::assign_collators_always_keep_old(
56
1197
            collators,
57
1197
            orchestrator_chain,
58
1197
            chains,
59
1197
            old_assigned,
60
1197
            shuffle,
61
1197
        )
62
1197
    }
63

            
64
    /// Assign new collators to missing container_chains.
65
    /// Old collators always have preference to remain on the same chain.
66
    /// If there are no missing collators, nothing is changed.
67
    ///
68
    /// `chains` should be shuffled or at least rotated on every session to ensure
69
    /// a fair distribution, because the order of that list affects container chain priority:
70
    /// the first chain on that list will be the first one to get new collators.
71
    ///
72
    /// Similarly, in the `collators` list order means priority, the first collators will be more
73
    /// likely to get assigned. Unlike the list of `chains` which should already be shuffled,
74
    /// collators will be shuffled using the `shuffle` callback when needed. This allows the
75
    /// algorithm to truncate the list of collators and only shuffle the first N. This ensures that
76
    /// shuffling doesn't cause a collator with low priority to be assigned instead of a collator
77
    /// with higher priority.
78
135234
    pub fn assign_collators_always_keep_old<TShuffle>(
79
135234
        collators: Vec<T::AccountId>,
80
135234
        orchestrator_chain: ChainNumCollators,
81
135234
        mut chains: Vec<ChainNumCollators>,
82
135234
        mut old_assigned: AssignedCollators<T::AccountId>,
83
135234
        shuffle: Option<TShuffle>,
84
135234
    ) -> Result<AssignedCollators<T::AccountId>, AssignmentError>
85
135234
    where
86
135234
        TShuffle: FnOnce(&mut Vec<T::AccountId>),
87
135234
    {
88
135234
        if collators.is_empty() && !T::ForceEmptyOrchestrator::get() {
89
            return Err(AssignmentError::ZeroCollators);
90
135234
        }
91
135234
        // The rest of this function mostly treats orchestrator chain as another container chain, so move it into
92
135234
        // `old_assigned.container_chains`
93
135234
        let old_orchestrator_assigned = mem::take(&mut old_assigned.orchestrator_chain);
94
135234
        old_assigned
95
135234
            .container_chains
96
135234
            .insert(orchestrator_chain.para_id, old_orchestrator_assigned);
97
135234
        let mut old_assigned = old_assigned.container_chains;
98
135234
        // Orchestrator chain must be the first one in the list because it always has priority
99
135234
        chains.insert(0, orchestrator_chain);
100
180312
        let all_para_ids: Vec<ParaId> = chains.iter().map(|cc| cc.para_id).collect();
101
135234
        let collators_set = BTreeSet::from_iter(collators.iter().cloned());
102
135234
        let chains_with_collators =
103
135234
            Self::select_chains_with_collators(collators.len() as u32, &chains);
104
135234
        let chains_with_collators_set: BTreeSet<ParaId> = chains_with_collators
105
135234
            .iter()
106
180312
            .map(|(para_id, _num_collators)| *para_id)
107
135234
            .collect();
108
135234
        Self::retain_valid_old_assigned(
109
135234
            &mut old_assigned,
110
135234
            &chains_with_collators_set,
111
135234
            &collators_set,
112
135234
        );
113
135234

            
114
135234
        // Ensure the first `min_orchestrator_collators` of orchestrator chain are invulnerables
115
135234
        Self::prioritize_invulnerables(&collators, orchestrator_chain, &mut old_assigned);
116

            
117
135234
        let new_assigned_chains =
118
135234
            Self::assign_full(collators, chains_with_collators, old_assigned, shuffle)?;
119

            
120
135234
        let mut new_assigned = AssignedCollators {
121
135234
            container_chains: new_assigned_chains,
122
135234
            ..Default::default()
123
135234
        };
124

            
125
        // Add container chains with 0 collators so that they are shown in UI
126
270468
        for para_id in all_para_ids {
127
135234
            new_assigned.container_chains.entry(para_id).or_default();
128
135234
        }
129

            
130
        // The rest of this function mostly treats orchestrator chain as another container chain, remove it from
131
        // container chains before returning the final assignment.
132
135234
        let orchestrator_assigned = new_assigned
133
135234
            .container_chains
134
135234
            .remove(&orchestrator_chain.para_id)
135
135234
            .unwrap();
136
135234
        // Sanity check to avoid bricking orchestrator chain
137
135234
        if orchestrator_assigned.is_empty() && !T::ForceEmptyOrchestrator::get() {
138
            return Err(AssignmentError::EmptyOrchestrator);
139
135234
        }
140
135234
        new_assigned.orchestrator_chain = orchestrator_assigned;
141
135234

            
142
135234
        Ok(new_assigned)
143
135234
    }
144

            
145
    /// Select which container chains will be assigned collators and how many collators, but do not specify which
146
    /// collator goes to which chain.
147
    ///
148
    /// Each chain has a min and max number of collators. If the number of collators is not enough to reach the min,
149
    /// no collators are assigned to that chain.
150
    ///
151
    /// If the available number of collators is:
152
    /// * lower than the min of the first chain: we assign all the collators to the first chain. This is the
153
    ///   orchestrator chain and we always want it to have collators.
154
    /// * lower than the sum of all the min: we cannot assign collators to all the chains. So remove chains until
155
    ///   we can. The order is important, the first chains will be assigned collators and the last ones will not.
156
    /// * lower than the sum of all the max: we can assign the min value to all the chains, and have some leftover.
157
    ///   We use the same order to decide where this extra collators will go, by filling the max of the first chain,
158
    ///   then the max of the second chain, and so on.
159
    /// * greater than the sum of all the max: all the chains will be assigned their max number of collators.
160
    ///
161
    /// # Params
162
    ///
163
    /// The first item of `chains` should be the orchestrator chain, because it will be the first one to be assigned
164
    /// collators.
165
    ///
166
    /// # Returns
167
    ///
168
    /// A list of `(para_id, num_collators)`.
169
135234
    pub fn select_chains_with_collators(
170
135234
        num_collators: u32,
171
135234
        chains: &[ChainNumCollators],
172
135234
    ) -> Vec<(ParaId, u32)> {
173
135234
        if chains.is_empty() {
174
            // Avoid panic if chains is empty
175
            return vec![];
176
135234
        }
177
135234
        // Let's count how many container chains we can support with the current number of collators
178
135234
        let mut available_collators = num_collators;
179
135234
        // Handle orchestrator chain in a special way, we always want to assign collators to it, even if we don't
180
135234
        // reach the min.
181
135234
        let min_orchestrator_collators = chains[0].min_collators;
182
135234
        available_collators = available_collators.saturating_sub(min_orchestrator_collators);
183
135234

            
184
135234
        let mut container_chains_with_collators = vec![chains[0]];
185
        // Skipping orchestrator chain because it was handled above
186
135234
        for cc in chains.iter().skip(1) {
187
            if available_collators >= cc.min_collators {
188
                available_collators -= cc.min_collators;
189
                container_chains_with_collators.push(*cc);
190
            } else if available_collators == 0 {
191
                // Do not break if there are still some available collators. Even if they were not enough to reach the
192
                // `min` of this chain, it is possible that one of the chains with less priority has a lower `min`, so
193
                // that chain should be assigned collators.
194
                break;
195
            }
196
        }
197

            
198
135234
        let mut required_collators_min = 0;
199
270468
        for cc in &container_chains_with_collators {
200
135234
            required_collators_min += cc.min_collators;
201
135234
        }
202

            
203
135234
        if num_collators < min_orchestrator_collators {
204
            // Edge case: num collators less than min orchestrator collators: fill as much as we can
205
            vec![(chains[0].para_id, num_collators)]
206
        } else {
207
            // After assigning the min to all the chains we have this remainder. The remainder will be assigned until
208
            // all the chains reach the max value.
209
135234
            let mut required_collators_remainder = num_collators - required_collators_min;
210
135234
            let mut container_chains_variable = vec![];
211
270468
            for cc in &container_chains_with_collators {
212
135234
                // Each chain will have `min + extra` collators, where extra is capped so `min + extra <= max`.
213
135234
                let extra = cmp::min(
214
135234
                    required_collators_remainder,
215
135234
                    cc.max_collators.saturating_sub(cc.min_collators),
216
135234
                );
217
135234
                let num = cc.min_collators + extra;
218
135234
                required_collators_remainder -= extra;
219
135234
                container_chains_variable.push((cc.para_id, num));
220
135234
            }
221

            
222
135234
            container_chains_variable
223
        }
224
135234
    }
225

            
226
    /// Same as `prioritize_invulnerables` but return the invulnerables instead of inserting them into `old_assigned`.
227
    ///
228
    /// Mutates `old_assigned` by removing invulnerables from their old chain, even if they will later be assigned to
229
    /// the same chain.
230
135234
    pub fn remove_invulnerables(
231
135234
        collators: &[T::AccountId],
232
135234
        orchestrator_chain: ChainNumCollators,
233
135234
        old_assigned: &mut BTreeMap<ParaId, Vec<T::AccountId>>,
234
135234
    ) -> Vec<T::AccountId> {
235
135234
        // TODO: clean this up, maybe change remove_invulnerables trait into something more ergonomic
236
135234
        let min_orchestrator_collators = orchestrator_chain.min_collators as usize;
237
135234
        let invulnerables_already_assigned = T::RemoveInvulnerables::remove_invulnerables(
238
135234
            &mut old_assigned
239
135234
                .get(&orchestrator_chain.para_id)
240
135234
                .cloned()
241
135234
                .unwrap_or_default(),
242
135234
            min_orchestrator_collators,
243
135234
        );
244
135234
        let mut new_invulnerables = invulnerables_already_assigned;
245
135234
        if new_invulnerables.len() >= min_orchestrator_collators {
246
            // We already had invulnerables, we will just move them to the front of the list if they weren't already
247
135234
            return new_invulnerables;
248
        }
249

            
250
        // Not enough invulnerables currently assigned, get rest from new_collators
251
        let mut new_collators = collators.to_vec();
252
        for (_id, cs) in old_assigned.iter() {
253
            new_collators.retain(|c| !cs.contains(c));
254
        }
255
        let num_missing_invulnerables = min_orchestrator_collators - new_invulnerables.len();
256
        let invulnerables_not_assigned = T::RemoveInvulnerables::remove_invulnerables(
257
            &mut new_collators,
258
            num_missing_invulnerables,
259
        );
260
        new_invulnerables.extend(invulnerables_not_assigned);
261

            
262
        if new_invulnerables.len() >= min_orchestrator_collators {
263
            // Got invulnerables from new_collators, and maybe some were already assigned
264
            return new_invulnerables;
265
        }
266

            
267
        // Still not enough invulnerables, try to get an invulnerable that is currently assigned somewhere else
268
        let num_missing_invulnerables = min_orchestrator_collators - new_invulnerables.len();
269
        let mut collators = collators.to_vec();
270
        let new_invulnerables_set = BTreeSet::from_iter(new_invulnerables.iter().cloned());
271
        collators.retain(|c| {
272
            // Remove collators already selected
273
            !new_invulnerables_set.contains(c)
274
        });
275
        let invulnerables_assigned_elsewhere =
276
            T::RemoveInvulnerables::remove_invulnerables(&mut collators, num_missing_invulnerables);
277

            
278
        if invulnerables_assigned_elsewhere.is_empty() {
279
            // If at this point we still do not have enough invulnerables, it means that there are no
280
            // enough invulnerables, so no problem, but return the invulnerables
281
            return new_invulnerables;
282
        }
283

            
284
        new_invulnerables.extend(invulnerables_assigned_elsewhere.iter().cloned());
285

            
286
        // In this case we must delete the old assignment of the invulnerables
287
        let reassigned_invulnerables_set = BTreeSet::from_iter(invulnerables_assigned_elsewhere);
288
        // old_assigned.remove_collators_in_set
289
        for (_id, cs) in old_assigned.iter_mut() {
290
            cs.retain(|c| !reassigned_invulnerables_set.contains(c));
291
        }
292

            
293
        new_invulnerables
294
135234
    }
295

            
296
    /// Ensure orchestrator chain has `min_orchestrator` invulnerables. If that's not possible, it tries to add as
297
    /// many invulnerables as possible.
298
    ///
299
    /// Get invulnerables from:
300
    /// * old_assigned in orchestrator
301
    /// * new collators
302
    /// * old_assigned elsewhere
303
    ///
304
    /// In that order.
305
    ///
306
    /// Mutates `old_assigned` because invulnerables will be inserted there, and if invulnerables were already
307
    /// assigned to some other chain, they will be removed from that other chain as well.
308
    ///
309
    /// # Params
310
    ///
311
    /// * `old_assigned` must be a subset of `collators`
312
    /// * `old_assigned` must not have duplicate collators.
313
    ///
314
    /// # Returns
315
    ///
316
    /// The number of invulnerables assigned to the orchestrator chain, capped to `min_collators`.
317
135234
    pub fn prioritize_invulnerables(
318
135234
        collators: &[T::AccountId],
319
135234
        orchestrator_chain: ChainNumCollators,
320
135234
        old_assigned: &mut BTreeMap<ParaId, Vec<T::AccountId>>,
321
135234
    ) -> usize {
322
135234
        let new_invulnerables =
323
135234
            Self::remove_invulnerables(collators, orchestrator_chain, old_assigned);
324
135234

            
325
135234
        if !new_invulnerables.is_empty() {
326
            Self::insert_invulnerables(
327
                old_assigned.entry(orchestrator_chain.para_id).or_default(),
328
                &new_invulnerables,
329
            );
330
135234
        }
331

            
332
135234
        new_invulnerables.len()
333
135234
    }
334

            
335
    /// Assign collators assuming that the number of collators is greater than or equal to the required.
336
    /// The order of both container chains and collators is important to ensure randomness when `old_assigned` is
337
    /// empty.
338
    ///
339
    /// # Params
340
    ///
341
    /// * `old_assigned` does not need to be a subset of `collators`: collators are checked and removed.
342
    /// * `old_assigned` does not need to be a subset of `chains`, unused para ids are removed. Collators
343
    ///   assigned to a para_id not present in `chains` may be reassigned to another para_id.
344
    /// * `chains` `num_collators` can be 0. In that case an empty vec is returned for that para id.
345
    /// * `old_assigned` must not have duplicate collators.
346
    /// * `shuffle` is used to shuffle the list collators. The list will be truncated to only have
347
    ///   the number of required collators, to ensure that shuffling doesn't cause a collator with low
348
    ///   priority to be assigned instead of a collator with higher priority.
349
    ///
350
    /// # Returns
351
    ///
352
    /// The collator assigment, a map from `ParaId` to `Vec<T>`.
353
    ///
354
    /// Or an error if the number of collators is not enough to fill all the chains, or if the required number
355
    /// of collators overflows a `u32`.
356
135234
    pub fn assign_full<TShuffle>(
357
135234
        collators: Vec<T::AccountId>,
358
135234
        chains: Vec<(ParaId, u32)>,
359
135234
        mut old_assigned: BTreeMap<ParaId, Vec<T::AccountId>>,
360
135234
        shuffle: Option<TShuffle>,
361
135234
    ) -> Result<BTreeMap<ParaId, Vec<T::AccountId>>, AssignmentError>
362
135234
    where
363
135234
        TShuffle: FnOnce(&mut Vec<T::AccountId>),
364
135234
    {
365
135234
        let mut required_collators = 0usize;
366
135234
        for (_para_id, num_collators) in chains.iter() {
367
135234
            let num_collators =
368
135234
                usize::try_from(*num_collators).map_err(|_| AssignmentError::NotEnoughCollators)?;
369
135234
            required_collators = required_collators
370
135234
                .checked_add(num_collators)
371
135234
                .ok_or(AssignmentError::NotEnoughCollators)?;
372
        }
373

            
374
        // This check is necessary to ensure priority: if the number of collators is less than required, it is
375
        // possible that the chain with the least priority could be assigned collators (since they are in
376
        // old_assigned), while some chains with higher priority might have no collators.
377
135234
        if collators.len() < required_collators {
378
            return Err(AssignmentError::NotEnoughCollators);
379
135234
        }
380
135234
        // We checked that the sum of all `num_collators` fits in `usize`, so we can safely use `as usize`.
381
135234

            
382
135234
        // Remove invalid collators and para ids from `old_assigned`
383
135234
        let para_ids_set =
384
180312
            BTreeSet::from_iter(chains.iter().map(|(para_id, _num_collators)| *para_id));
385
135234
        let collators_set = BTreeSet::from_iter(collators.iter().cloned());
386
135234
        Self::retain_valid_old_assigned(&mut old_assigned, &para_ids_set, &collators_set);
387

            
388
        // Truncate num collators to required
389
135234
        for (para_id, num_collators) in chains.iter() {
390
135234
            let entry = old_assigned.entry(*para_id).or_default();
391
135234
            entry.truncate(*num_collators as usize);
392
135234
        }
393

            
394
        // Count number of needed new collators. This is equivalent to:
395
        // `required_collators - old_assigned.iter().map(|cs| cs.len()).sum()`.
396
135234
        let mut needed_new_collators = 0;
397
135234
        for (para_id, num_collators) in chains.iter() {
398
135234
            let cs = old_assigned.entry(*para_id).or_default();
399
135234
            needed_new_collators += (*num_collators as usize).saturating_sub(cs.len());
400
135234
        }
401

            
402
135234
        let assigned_collators: BTreeSet<T::AccountId> = old_assigned
403
135234
            .iter()
404
135234
            .flat_map(|(_para_id, para_collators)| para_collators.iter().cloned())
405
135234
            .collect();
406
135234

            
407
135234
        // Truncate list of new_collators to `needed_new_collators` and shuffle it.
408
135234
        // This has the effect of keeping collator priority (the first collator of that list is more
409
135234
        // likely to be assigned to a chain than the last collator of that list), while also
410
135234
        // ensuring randomness (the original order does not directly affect which chain the
411
135234
        // collators are assigned to).
412
135234
        let mut new_collators: Vec<_> = collators
413
135234
            .into_iter()
414
135234
            .filter(|x| {
415
                // Keep collators not already assigned
416
                !assigned_collators.contains(x)
417
135234
            })
418
135234
            .take(needed_new_collators)
419
135234
            .collect();
420
135234
        if let Some(shuffle) = shuffle {
421
            shuffle(&mut new_collators);
422
135234
        }
423
135234
        let mut new_collators = new_collators.into_iter();
424

            
425
        // Fill missing collators
426
135234
        for (para_id, num_collators) in chains.iter() {
427
135234
            let cs = old_assigned.entry(*para_id).or_default();
428

            
429
135234
            while cs.len() < *num_collators as usize {
430
                // This error should never happen because we calculated `needed_new_collators`
431
                // using the same algorithm
432
                let nc = new_collators
433
                    .next()
434
                    .ok_or(AssignmentError::NotEnoughCollators)?;
435
                cs.push(nc);
436
            }
437
        }
438

            
439
135234
        Ok(old_assigned)
440
135234
    }
441

            
442
    /// Insert invulnerables ensuring that they are always the first in the list.
443
    /// The order of both lists is preserved.
444
    /// `assigned` may already contain the invulnerables, in that case they are only moved to the front.
445
    ///
446
    /// Invulnerables need to be the first of the list because we may truncate the list of collators if the number of
447
    /// collators changes, and in that case we want invulnerables to stay assigned there.
448
    pub fn insert_invulnerables(assigned: &mut Vec<T::AccountId>, invulnerables: &[T::AccountId]) {
449
        assigned.retain(|item| !invulnerables.contains(item));
450

            
451
        let mut new_assigned = invulnerables.to_vec();
452
        new_assigned.extend(mem::take(assigned));
453

            
454
        *assigned = new_assigned;
455
    }
456

            
457
    /// Removes invalid entries from `old_assigned`:
458
    ///
459
    /// * para ids not in `chains_with_collators`
460
    /// * collators not in `collators`
461
270468
    pub fn retain_valid_old_assigned(
462
270468
        old_assigned: &mut BTreeMap<ParaId, Vec<T::AccountId>>,
463
270468
        chains_with_collators: &BTreeSet<ParaId>,
464
270468
        collators: &BTreeSet<T::AccountId>,
465
270468
    ) {
466
270468
        // old_assigned.remove_container_chains_not_in_set
467
360624
        old_assigned.retain(|id, _cs| chains_with_collators.contains(id));
468
        // old_assigned.remove_collators_not_in_set
469
270468
        for (_id, cs) in old_assigned.iter_mut() {
470
270468
            cs.retain(|c| collators.contains(c));
471
270468
        }
472
270468
    }
473
}
474

            
475
/// Errors than can happen during collator assignment
476
#[derive(Debug, Clone, PartialEq, Eq)]
477
pub enum AssignmentError {
478
    /// An empty list of collators was passed to `assign_collators_always_keep_old`
479
    ZeroCollators,
480
    /// The required number of collators for `assign_full` is greater than the provided number of collators.
481
    /// Also includes possible overflows in number of collators.
482
    NotEnoughCollators,
483
    /// No collators were assigned to orchestrator chain
484
    EmptyOrchestrator,
485
}
486

            
487
/// A `ParaId` and a range of collators that need to be assigned to it.
488
/// This can be a container chain, a parathread, or the orchestrator chain.
489
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
490
pub struct ChainNumCollators {
491
    pub para_id: ParaId,
492
    pub min_collators: u32,
493
    // This will only be filled if all the other min have been reached
494
    pub max_collators: u32,
495
}