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
//! # Collator Assignment Pallet
18
//!
19
//! This pallet assigns a list of collators to:
20
//!    - the orchestrator chain
21
//!    - a set of container chains
22
//!
23
//! The set of container chains is retrieved thanks to the GetContainerChains trait
24
//! The number of collators to assign to the orchestrator chain and the number
25
//! of collators to assign to each container chain is retrieved through the GetHostConfiguration
26
//! trait.
27
//!  
28
//! The pallet uses the following approach:
29
//!
30
//! - First, it aims at filling the necessary collators to serve the orchestrator chain
31
//! - Second, it aims at filling in-order (FIFO) the existing containerChains
32
//!
33
//! Upon new session, this pallet takes whatever assignation was in the PendingCollatorContainerChain
34
//! storage, and assigns it as the current CollatorContainerChain. In addition, it takes the next
35
//! queued set of parachains and collators and calculates the assignment for the next session, storing
36
//! it in the PendingCollatorContainerChain storage item.
37
//!
38
//! The reason for the collator-assignment pallet to work with a one-session delay assignment is because
39
//! we want collators to know at least one session in advance the container chain/orchestrator that they
40
//! are assigned to.
41

            
42
#![cfg_attr(not(feature = "std"), no_std)]
43
extern crate alloc;
44

            
45
use {
46
    crate::assignment::{Assignment, ChainNumCollators},
47
    alloc::{collections::btree_set::BTreeSet, fmt::Debug, vec, vec::Vec},
48
    core::ops::Mul,
49
    frame_support::{pallet_prelude::*, traits::Currency},
50
    frame_system::pallet_prelude::BlockNumberFor,
51
    rand::{seq::SliceRandom, SeedableRng},
52
    rand_chacha::ChaCha20Rng,
53
    sp_runtime::{
54
        traits::{AtLeast32BitUnsigned, One, Zero},
55
        Perbill, Saturating,
56
    },
57
    tp_traits::{
58
        CollatorAssignmentTip, ForSession, FullRotationModes, GetContainerChainAuthor,
59
        GetContainerChainsWithCollators, GetHostConfiguration, GetSessionContainerChains, ParaId,
60
        ParaIdAssignmentHooks, RemoveInvulnerables, ShouldRotateAllCollators, Slot,
61
    },
62
};
63
pub use {dp_collator_assignment::AssignedCollators, pallet::*};
64

            
65
mod assignment;
66
#[cfg(feature = "runtime-benchmarks")]
67
mod benchmarking;
68
pub mod weights;
69
pub use weights::WeightInfo;
70

            
71
#[cfg(test)]
72
mod mock;
73

            
74
#[cfg(test)]
75
mod tests;
76

            
77
#[derive(Encode, Decode, Debug, TypeInfo)]
78
pub struct CoreAllocationConfiguration {
79
    pub core_count: u32,
80
    pub max_parachain_percentage: Perbill,
81
}
82

            
83
#[frame_support::pallet]
84
pub mod pallet {
85
    use super::*;
86

            
87
    #[pallet::pallet]
88
    pub struct Pallet<T>(_);
89

            
90
    /// Configure the pallet by specifying the parameters and types on which it depends.
91
    #[pallet::config]
92
    pub trait Config: frame_system::Config {
93
        type SessionIndex: parity_scale_codec::FullCodec
94
            + TypeInfo
95
            + Copy
96
            + AtLeast32BitUnsigned
97
            + Debug;
98
        // `SESSION_DELAY` is used to delay any changes to Paras registration or configurations.
99
        // Wait until the session index is 2 larger then the current index to apply any changes,
100
        // which guarantees that at least one full session has passed before any changes are applied.
101
        type HostConfiguration: GetHostConfiguration<Self::SessionIndex>;
102
        type ContainerChains: GetSessionContainerChains<Self::SessionIndex>;
103
        type SelfParaId: Get<ParaId>;
104
        type ShouldRotateAllCollators: ShouldRotateAllCollators<Self::SessionIndex>;
105
        type Randomness: CollatorAssignmentRandomness<BlockNumberFor<Self>>;
106
        type RemoveInvulnerables: RemoveInvulnerables<Self::AccountId>;
107
        type ParaIdAssignmentHooks: ParaIdAssignmentHooks<BalanceOf<Self>, Self::AccountId>;
108
        type Currency: Currency<Self::AccountId>;
109
        type CollatorAssignmentTip: CollatorAssignmentTip<BalanceOf<Self>>;
110
        type ForceEmptyOrchestrator: Get<bool>;
111
        type CoreAllocationConfiguration: Get<Option<CoreAllocationConfiguration>>;
112
        /// The weight information of this pallet.
113
        type WeightInfo: WeightInfo;
114
    }
115

            
116
    #[pallet::event]
117
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
118
    pub enum Event<T: Config> {
119
        NewPendingAssignment {
120
            random_seed: [u8; 32],
121
            full_rotation: bool,
122
            target_session: T::SessionIndex,
123
            full_rotation_mode: FullRotationModes,
124
        },
125
    }
126

            
127
    #[pallet::storage]
128
    #[pallet::unbounded]
129
    pub(crate) type CollatorContainerChain<T: Config> =
130
        StorageValue<_, AssignedCollators<T::AccountId>, ValueQuery>;
131

            
132
    /// Pending configuration changes.
133
    ///
134
    /// This is a list of configuration changes, each with a session index at which it should
135
    /// be applied.
136
    ///
137
    /// The list is sorted ascending by session index. Also, this list can only contain at most
138
    /// 2 items: for the next session and for the `scheduled_session`.
139
    #[pallet::storage]
140
    #[pallet::unbounded]
141
    pub(crate) type PendingCollatorContainerChain<T: Config> =
142
        StorageValue<_, Option<AssignedCollators<T::AccountId>>, ValueQuery>;
143

            
144
    /// Randomness from previous block. Used to shuffle collators on session change.
145
    /// Should only be set on the last block of each session and should be killed on the on_initialize of the next block.
146
    /// The default value of [0; 32] disables randomness in the pallet.
147
    #[pallet::storage]
148
    pub(crate) type Randomness<T: Config> = StorageValue<_, [u8; 32], ValueQuery>;
149

            
150
    /// Ratio of assigned collators to max collators.
151
    #[pallet::storage]
152
    pub type CollatorFullnessRatio<T: Config> = StorageValue<_, Perbill, OptionQuery>;
153

            
154
    #[pallet::call]
155
    impl<T: Config> Pallet<T> {}
156

            
157
    /// A struct that holds the assignment that is active after the session change and optionally
158
    /// the assignment that becomes active after the next session change.
159
    pub struct SessionChangeOutcome<T: Config> {
160
        /// New active assignment.
161
        pub active_assignment: AssignedCollators<T::AccountId>,
162
        /// Next session active assignment.
163
        pub next_assignment: AssignedCollators<T::AccountId>,
164
        /// Total number of registered parachains before filtering them out, used as a weight hint
165
        pub num_total_registered_paras: u32,
166
    }
167

            
168
    impl<T: Config> Pallet<T> {
169
6175
        pub(crate) fn enough_collators_for_all_chains(
170
6175
            bulk_paras: &[ChainNumCollators],
171
6175
            pool_paras: &[ChainNumCollators],
172
6175
            target_session_index: T::SessionIndex,
173
6175
            number_of_collators: u32,
174
6175
            collators_per_container: u32,
175
6175
            collators_per_parathread: u32,
176
6175
        ) -> bool {
177
6175
            number_of_collators
178
6175
                >= T::HostConfiguration::min_collators_for_orchestrator(target_session_index)
179
6175
                    .saturating_add(collators_per_container.saturating_mul(bulk_paras.len() as u32))
180
6175
                    .saturating_add(
181
6175
                        collators_per_parathread.saturating_mul(pool_paras.len() as u32),
182
6175
                    )
183
6175
        }
184

            
185
        /// Takes the bulk paras (parachains) and pool paras (parathreads)
186
        /// and checks if we if a) Do we have enough collators? b) Do we have enough cores?
187
        /// If either of the answer is yes. We  separately sort bulk_paras and pool_paras and
188
        /// then append the two vectors.
189
1439
        pub(crate) fn order_paras_with_core_config(
190
1439
            mut bulk_paras: Vec<ChainNumCollators>,
191
1439
            mut pool_paras: Vec<ChainNumCollators>,
192
1439
            core_allocation_configuration: &CoreAllocationConfiguration,
193
1439
            target_session_index: T::SessionIndex,
194
1439
            number_of_collators: u32,
195
1439
            collators_per_container: u32,
196
1439
            collators_per_parathread: u32,
197
1439
        ) -> (Vec<ChainNumCollators>, bool) {
198
1439
            let core_count = core_allocation_configuration.core_count;
199
1439
            let max_number_of_bulk_paras = core_allocation_configuration
200
1439
                .max_parachain_percentage
201
1439
                .mul(core_count);
202

            
203
1439
            let enough_cores_for_bulk_paras = bulk_paras.len() <= max_number_of_bulk_paras as usize;
204

            
205
1439
            let enough_collators = Self::enough_collators_for_all_chains(
206
1439
                &bulk_paras,
207
1439
                &pool_paras,
208
1439
                target_session_index,
209
1439
                number_of_collators,
210
1439
                collators_per_container,
211
1439
                collators_per_parathread,
212
            );
213

            
214
            // We should charge tip if parachain demand exceeds the `max_number_of_bulk_paras` OR
215
            // if `num_collators` is not enough to satisfy  collation need of all paras.
216
1439
            let should_charge_tip = !enough_cores_for_bulk_paras || !enough_collators;
217

            
218
            // Currently, we are sorting both bulk and pool paras by tip, even when for example
219
            // only number of bulk paras are restricted due to core availability since we deduct tip from
220
            // all paras.
221
            // We need to sort both separately as we have fixed space for parachains at the moment
222
            // which means even when we have some parathread cores empty we cannot schedule parachain there.
223
1439
            if should_charge_tip {
224
1149
                bulk_paras.sort_by(|a, b| {
225
458
                    T::CollatorAssignmentTip::get_para_tip(b.para_id)
226
458
                        .cmp(&T::CollatorAssignmentTip::get_para_tip(a.para_id))
227
458
                });
228

            
229
1121
                pool_paras.sort_by(|a, b| {
230
106
                    T::CollatorAssignmentTip::get_para_tip(b.para_id)
231
106
                        .cmp(&T::CollatorAssignmentTip::get_para_tip(a.para_id))
232
106
                });
233
347
            }
234

            
235
1439
            bulk_paras.truncate(max_number_of_bulk_paras as usize);
236
            // We are not truncating pool paras, since their workload is not continuous one core
237
            // can be shared by many paras during the session.
238

            
239
1439
            let chains: Vec<_> = bulk_paras.into_iter().chain(pool_paras).collect();
240

            
241
1439
            (chains, should_charge_tip)
242
1439
        }
243

            
244
4736
        pub(crate) fn order_paras(
245
4736
            bulk_paras: Vec<ChainNumCollators>,
246
4736
            pool_paras: Vec<ChainNumCollators>,
247
4736
            target_session_index: T::SessionIndex,
248
4736
            number_of_collators: u32,
249
4736
            collators_per_container: u32,
250
4736
            collators_per_parathread: u32,
251
4736
        ) -> (Vec<ChainNumCollators>, bool) {
252
            // Are there enough collators to satisfy the minimum demand?
253
4736
            let enough_collators_for_all_chain = Self::enough_collators_for_all_chains(
254
4736
                &bulk_paras,
255
4736
                &pool_paras,
256
4736
                target_session_index,
257
4736
                number_of_collators,
258
4736
                collators_per_container,
259
4736
                collators_per_parathread,
260
            );
261

            
262
4736
            let mut chains: Vec<_> = bulk_paras.into_iter().chain(pool_paras).collect();
263

            
264
            // Prioritize paras by tip on congestion
265
            // As of now this doesn't distinguish between bulk paras and pool paras
266
4736
            if !enough_collators_for_all_chain {
267
2203
                chains.sort_by(|a, b| {
268
2202
                    T::CollatorAssignmentTip::get_para_tip(b.para_id)
269
2202
                        .cmp(&T::CollatorAssignmentTip::get_para_tip(a.para_id))
270
2202
                });
271
2609
            }
272

            
273
4736
            (chains, !enough_collators_for_all_chain)
274
4736
        }
275

            
276
        /// Assign new collators
277
        /// collators should be queued collators
278
6167
        pub fn assign_collators(
279
6167
            current_session_index: &T::SessionIndex,
280
6167
            random_seed: [u8; 32],
281
6167
            collators: Vec<T::AccountId>,
282
6167
        ) -> SessionChangeOutcome<T> {
283
6167
            let maybe_core_allocation_configuration = T::CoreAllocationConfiguration::get();
284
            // We work with one session delay to calculate assignments
285
6167
            let session_delay = T::SessionIndex::one();
286
6167
            let target_session_index = current_session_index.saturating_add(session_delay);
287

            
288
6167
            let collators_per_container =
289
6167
                T::HostConfiguration::collators_per_container(target_session_index);
290
6167
            let collators_per_parathread =
291
6167
                T::HostConfiguration::collators_per_parathread(target_session_index);
292

            
293
            // We get the containerChains that we will have at the target session
294
6167
            let container_chains =
295
6167
                T::ContainerChains::session_container_chains(target_session_index);
296
6167
            let num_total_registered_paras = container_chains
297
6167
                .parachains
298
6167
                .len()
299
6167
                .saturating_add(container_chains.parathreads.len())
300
6167
                as u32;
301
6167
            let mut container_chain_ids = container_chains.parachains;
302
6167
            let mut parathreads: Vec<_> = container_chains
303
6167
                .parathreads
304
6167
                .into_iter()
305
6167
                .map(|(para_id, _)| para_id)
306
6167
                .collect();
307

            
308
            // We read current assigned collators
309
6167
            let old_assigned = Self::read_assigned_collators();
310
6167
            let old_assigned_para_ids: BTreeSet<ParaId> =
311
6167
                old_assigned.container_chains.keys().cloned().collect();
312

            
313
            // Remove the containerChains that do not have enough credits for block production
314
6167
            T::ParaIdAssignmentHooks::pre_assignment(
315
6167
                &mut container_chain_ids,
316
6167
                &old_assigned_para_ids,
317
            );
318
            // TODO: parathreads should be treated a bit differently, they don't need to have the same amount of credits
319
            // as parathreads because they will not be producing blocks on every slot.
320
6167
            T::ParaIdAssignmentHooks::pre_assignment(&mut parathreads, &old_assigned_para_ids);
321

            
322
6167
            let mut shuffle_collators = None;
323
            // If the random_seed is all zeros, we don't shuffle the list of collators nor the list
324
            // of container chains.
325
            // This should only happen in tests_without_core_config, and in the genesis block.
326
6167
            if random_seed != [0; 32] {
327
295
                let mut rng: ChaCha20Rng = SeedableRng::from_seed(random_seed);
328
295
                container_chain_ids.shuffle(&mut rng);
329
295
                parathreads.shuffle(&mut rng);
330
367
                shuffle_collators = Some(move |collators: &mut Vec<T::AccountId>| {
331
367
                    collators.shuffle(&mut rng);
332
367
                })
333
5872
            }
334

            
335
6167
            let orchestrator_chain: ChainNumCollators = if T::ForceEmptyOrchestrator::get() {
336
1431
                ChainNumCollators {
337
1431
                    para_id: T::SelfParaId::get(),
338
1431
                    min_collators: 0u32,
339
1431
                    max_collators: 0u32,
340
1431
                    parathread: false,
341
1431
                }
342
            } else {
343
4736
                ChainNumCollators {
344
4736
                    para_id: T::SelfParaId::get(),
345
4736
                    min_collators: T::HostConfiguration::min_collators_for_orchestrator(
346
4736
                        target_session_index,
347
4736
                    ),
348
4736
                    max_collators: T::HostConfiguration::max_collators_for_orchestrator(
349
4736
                        target_session_index,
350
4736
                    ),
351
4736
                    parathread: false,
352
4736
                }
353
            };
354

            
355
            // Initialize list of chains as `[container1, container2, parathread1, parathread2]`.
356
            // The order means priority: the first chain in the list will be the first one to get assigned collators.
357
            // Chains will not be assigned less than `min_collators`, except the orchestrator chain.
358
            // First all chains will be assigned `min_collators`, and then the first one will be assigned up to `max`,
359
            // then the second one, and so on.
360
6167
            let mut bulk_paras = vec![];
361
6167
            let mut pool_paras = vec![];
362

            
363
11905
            for para_id in &container_chain_ids {
364
5738
                bulk_paras.push(ChainNumCollators {
365
5738
                    para_id: *para_id,
366
5738
                    min_collators: collators_per_container,
367
5738
                    max_collators: collators_per_container,
368
5738
                    parathread: false,
369
5738
                });
370
5738
            }
371
6683
            for para_id in &parathreads {
372
516
                pool_paras.push(ChainNumCollators {
373
516
                    para_id: *para_id,
374
516
                    min_collators: collators_per_parathread,
375
516
                    max_collators: collators_per_parathread,
376
516
                    parathread: true,
377
516
                });
378
516
            }
379

            
380
6167
            let (chains, need_to_charge_tip) =
381
6167
                if let Some(core_allocation_configuration) = maybe_core_allocation_configuration {
382
1431
                    Self::order_paras_with_core_config(
383
1431
                        bulk_paras,
384
1431
                        pool_paras,
385
1431
                        &core_allocation_configuration,
386
1431
                        target_session_index,
387
1431
                        collators.len() as u32,
388
1431
                        collators_per_container,
389
1431
                        collators_per_parathread,
390
                    )
391
                } else {
392
4736
                    Self::order_paras(
393
4736
                        bulk_paras,
394
4736
                        pool_paras,
395
4736
                        target_session_index,
396
4736
                        collators.len() as u32,
397
4736
                        collators_per_container,
398
4736
                        collators_per_parathread,
399
                    )
400
                };
401

            
402
            // We assign new collators
403
            // we use the config scheduled at the target_session_index
404
6167
            let full_rotation =
405
6167
                T::ShouldRotateAllCollators::should_rotate_all_collators(target_session_index);
406
6167
            if full_rotation {
407
52
                log::info!(
408
                    "Collator assignment: rotating collators. Session {:?}, Seed: {:?}",
409
                    current_session_index.encode(),
410
                    random_seed
411
                );
412
            } else {
413
6115
                log::info!(
414
                    "Collator assignment: keep old assigned. Session {:?}, Seed: {:?}",
415
                    current_session_index.encode(),
416
                    random_seed
417
                );
418
            }
419

            
420
6167
            let full_rotation_mode = if full_rotation {
421
52
                T::HostConfiguration::full_rotation_mode(target_session_index)
422
            } else {
423
                // On sessions where there is no rotation, we try to keep all collators assigned to the same chains
424
6115
                FullRotationModes::keep_all()
425
            };
426

            
427
6167
            Self::deposit_event(Event::NewPendingAssignment {
428
6167
                random_seed,
429
6167
                full_rotation,
430
6167
                target_session: target_session_index,
431
6167
                full_rotation_mode: full_rotation_mode.clone(),
432
6167
            });
433

            
434
6167
            let new_assigned = Assignment::<T>::assign_collators_always_keep_old(
435
6167
                collators,
436
6167
                orchestrator_chain,
437
6167
                chains,
438
6167
                old_assigned.clone(),
439
6167
                shuffle_collators,
440
6167
                full_rotation_mode,
441
            );
442

            
443
6167
            let mut new_assigned = match new_assigned {
444
6163
                Ok(x) => x,
445
4
                Err(e) => {
446
4
                    log::error!(
447
4
                        "Error in collator assignment, will keep previous assignment. {:?}",
448
                        e
449
                    );
450

            
451
4
                    old_assigned.clone()
452
                }
453
            };
454

            
455
6167
            let mut assigned_containers = new_assigned.container_chains.clone();
456
6750
            assigned_containers.retain(|_, v| !v.is_empty());
457

            
458
            // On congestion, prioritized chains need to pay the minimum tip of the prioritized chains
459
6167
            let maybe_tip: Option<BalanceOf<T>> = if !need_to_charge_tip {
460
2955
                None
461
            } else {
462
3212
                assigned_containers
463
3212
                    .into_keys()
464
3212
                    .filter_map(T::CollatorAssignmentTip::get_para_tip)
465
3212
                    .min()
466
            };
467

            
468
            // TODO: this probably is asking for a refactor
469
            // only apply the onCollatorAssignedHook if sufficient collators
470
6167
            T::ParaIdAssignmentHooks::post_assignment(
471
6167
                &old_assigned_para_ids,
472
6167
                &mut new_assigned.container_chains,
473
6167
                &maybe_tip,
474
            );
475

            
476
6167
            Self::store_collator_fullness(
477
6167
                &new_assigned,
478
6167
                T::HostConfiguration::max_collators(target_session_index),
479
            );
480

            
481
6167
            let mut pending = PendingCollatorContainerChain::<T>::get();
482

            
483
6167
            let old_assigned_changed = old_assigned != new_assigned;
484
6167
            let mut pending_changed = false;
485
            // Update CollatorContainerChain using last entry of pending, if needed
486
6167
            if let Some(current) = pending.take() {
487
1687
                pending_changed = true;
488
1687
                CollatorContainerChain::<T>::put(current);
489
4506
            }
490
6167
            if old_assigned_changed {
491
3080
                pending = Some(new_assigned.clone());
492
3080
                pending_changed = true;
493
4084
            }
494
            // Update PendingCollatorContainerChain, if it changed
495
6167
            if pending_changed {
496
4066
                PendingCollatorContainerChain::<T>::put(pending);
497
4834
            }
498

            
499
            // Only applies to session index 0
500
6167
            if current_session_index == &T::SessionIndex::zero() {
501
2860
                CollatorContainerChain::<T>::put(new_assigned.clone());
502
2860
                return SessionChangeOutcome {
503
2860
                    active_assignment: new_assigned.clone(),
504
2860
                    next_assignment: new_assigned,
505
2860
                    num_total_registered_paras,
506
2860
                };
507
3307
            }
508

            
509
3307
            SessionChangeOutcome {
510
3307
                active_assignment: old_assigned,
511
3307
                next_assignment: new_assigned,
512
3307
                num_total_registered_paras,
513
3307
            }
514
6167
        }
515

            
516
        /// Count number of collators assigned to any chain, divide that by `max_collators` and store
517
        /// in pallet storage.
518
6167
        fn store_collator_fullness(
519
6167
            new_assigned: &AssignedCollators<T::AccountId>,
520
6167
            max_collators: u32,
521
6167
        ) {
522
            // Count number of assigned collators
523
6167
            let mut num_collators = 0;
524
6167
            num_collators.saturating_accrue(new_assigned.orchestrator_chain.len());
525
6750
            for collators in new_assigned.container_chains.values() {
526
6222
                num_collators.saturating_accrue(collators.len());
527
6222
            }
528

            
529
6167
            let mut num_collators = num_collators as u32;
530
6167
            if num_collators > max_collators {
531
148
                // Shouldn't happen but just in case
532
148
                num_collators = max_collators;
533
6167
            }
534

            
535
6167
            let ratio = Perbill::from_rational(num_collators, max_collators);
536

            
537
6167
            CollatorFullnessRatio::<T>::put(ratio);
538
6167
        }
539

            
540
        // Returns the assigned collators as read from storage.
541
        // If there is any item in PendingCollatorContainerChain, returns that element.
542
        // Otherwise, reads and returns the current CollatorContainerChain
543
6167
        fn read_assigned_collators() -> AssignedCollators<T::AccountId> {
544
6167
            let mut pending_collator_list = PendingCollatorContainerChain::<T>::get();
545

            
546
6167
            if let Some(assigned_collators) = pending_collator_list.take() {
547
1687
                assigned_collators
548
            } else {
549
                // Read current
550
4480
                CollatorContainerChain::<T>::get()
551
            }
552
6167
        }
553

            
554
6167
        pub fn initializer_on_new_session(
555
6167
            session_index: &T::SessionIndex,
556
6167
            collators: Vec<T::AccountId>,
557
6167
        ) -> SessionChangeOutcome<T> {
558
6167
            let random_seed = T::Randomness::take_randomness();
559
6167
            let num_collators = collators.len();
560
6167
            let assigned_collators = Self::assign_collators(session_index, random_seed, collators);
561
6167
            let num_total_registered_paras = assigned_collators.num_total_registered_paras;
562

            
563
6167
            frame_system::Pallet::<T>::register_extra_weight_unchecked(
564
6167
                T::WeightInfo::new_session(num_collators as u32, num_total_registered_paras),
565
6167
                DispatchClass::Mandatory,
566
            );
567

            
568
6167
            assigned_collators
569
6167
        }
570

            
571
91264
        pub fn collator_container_chain() -> AssignedCollators<T::AccountId> {
572
91264
            CollatorContainerChain::<T>::get()
573
91264
        }
574

            
575
45
        pub fn pending_collator_container_chain() -> Option<AssignedCollators<T::AccountId>> {
576
45
            PendingCollatorContainerChain::<T>::get()
577
45
        }
578
    }
579

            
580
    impl<T: Config> GetContainerChainAuthor<T::AccountId> for Pallet<T> {
581
82041
        fn author_for_slot(slot: Slot, para_id: ParaId) -> Option<T::AccountId> {
582
82041
            let assigned_collators = Pallet::<T>::collator_container_chain();
583
82041
            let collators = if para_id == T::SelfParaId::get() {
584
81904
                Some(&assigned_collators.orchestrator_chain)
585
            } else {
586
137
                assigned_collators.container_chains.get(&para_id)
587
            }?;
588

            
589
82041
            if collators.is_empty() {
590
                // Avoid division by zero below
591
                return None;
592
82041
            }
593
82041
            let author_index = u64::from(slot) % collators.len() as u64;
594
82041
            collators.get(author_index as usize).cloned()
595
82041
        }
596

            
597
        #[cfg(feature = "runtime-benchmarks")]
598
        fn set_authors_for_para_id(para_id: ParaId, authors: Vec<T::AccountId>) {
599
            let mut assigned_collators = Pallet::<T>::collator_container_chain();
600
            assigned_collators.container_chains.insert(para_id, authors);
601
            CollatorContainerChain::<T>::put(assigned_collators);
602
        }
603
    }
604

            
605
    #[pallet::hooks]
606
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
607
14452
        fn on_initialize(n: BlockNumberFor<T>) -> Weight {
608
14452
            let mut weight = Weight::zero();
609

            
610
            // Account reads and writes for on_finalize
611
14452
            weight.saturating_accrue(T::Randomness::prepare_randomness_weight(n));
612

            
613
14452
            weight
614
14452
        }
615

            
616
13731
        fn on_finalize(n: BlockNumberFor<T>) {
617
            // If the next block is a session change, read randomness and store in pallet storage
618
13731
            T::Randomness::prepare_randomness(n);
619
13731
        }
620
    }
621

            
622
    impl<T: Config> GetContainerChainsWithCollators<T::AccountId> for Pallet<T> {
623
6322
        fn container_chains_with_collators(
624
6322
            for_session: ForSession,
625
6322
        ) -> Vec<(ParaId, Vec<T::AccountId>)> {
626
            // If next session has None then current session data will stay.
627
6322
            let chains = (for_session == ForSession::Next)
628
6322
                .then(PendingCollatorContainerChain::<T>::get)
629
6322
                .flatten()
630
6322
                .unwrap_or_else(CollatorContainerChain::<T>::get);
631

            
632
6322
            chains.container_chains.into_iter().collect()
633
6322
        }
634

            
635
3092
        fn get_all_collators_assigned_to_chains(for_session: ForSession) -> BTreeSet<T::AccountId> {
636
3092
            let mut all_chains: Vec<T::AccountId> =
637
3092
                Self::container_chains_with_collators(for_session)
638
3092
                    .iter()
639
3842
                    .flat_map(|(_para_id, collators)| collators.iter())
640
3092
                    .cloned()
641
3092
                    .collect();
642
3092
            all_chains.extend(
643
3092
                Self::collator_container_chain()
644
3092
                    .orchestrator_chain
645
3092
                    .iter()
646
3092
                    .cloned(),
647
            );
648
3092
            all_chains.into_iter().collect()
649
3092
        }
650

            
651
        #[cfg(feature = "runtime-benchmarks")]
652
        fn set_container_chains_with_collators(
653
            for_session: ForSession,
654
            container_chains: &[(ParaId, Vec<T::AccountId>)],
655
        ) {
656
            match for_session {
657
                ForSession::Current => {
658
                    let mut collators = CollatorContainerChain::<T>::get();
659
                    collators.container_chains = container_chains.iter().cloned().collect();
660
                    CollatorContainerChain::<T>::put(collators);
661
                }
662
                ForSession::Next => {
663
                    let mut collators =
664
                        PendingCollatorContainerChain::<T>::get().unwrap_or_default();
665
                    collators.container_chains = container_chains.iter().cloned().collect();
666
                    PendingCollatorContainerChain::<T>::put(Some(collators));
667
                }
668
            }
669
        }
670
    }
671
}
672

            
673
/// Balance used by this pallet
674
pub type BalanceOf<T> =
675
    <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
676

            
677
pub struct RotateCollatorsEveryNSessions<Period>(PhantomData<Period>);
678

            
679
impl<Period> ShouldRotateAllCollators<u32> for RotateCollatorsEveryNSessions<Period>
680
where
681
    Period: Get<u32>,
682
{
683
6000
    fn should_rotate_all_collators(session_index: u32) -> bool {
684
6000
        let period = Period::get();
685

            
686
6000
        if period == 0 {
687
            // A period of 0 disables rotation
688
2051
            false
689
        } else {
690
3949
            session_index % Period::get() == 0
691
        }
692
6000
    }
693
}
694

            
695
/// Only works on parachains because in relaychains it is not possible to know for sure if the next
696
/// block will be in the same session as the current one, as it depends on slots and validators can
697
/// skip slots.
698
pub trait GetRandomnessForNextBlock<BlockNumber> {
699
    fn should_end_session(block_number: BlockNumber) -> bool;
700
    fn get_randomness() -> [u8; 32];
701
}
702

            
703
impl<BlockNumber> GetRandomnessForNextBlock<BlockNumber> for () {
704
    fn should_end_session(_block_number: BlockNumber) -> bool {
705
        false
706
    }
707

            
708
    fn get_randomness() -> [u8; 32] {
709
        [0; 32]
710
    }
711
}
712

            
713
pub trait CollatorAssignmentRandomness<BlockNumber> {
714
    /// Called in on_initialize, returns weight needed by prepare_randomness call.
715
    fn prepare_randomness_weight(n: BlockNumber) -> Weight;
716
    /// Called in on_finalize.
717
    /// Prepares randomness for the next block if the next block is a new session start.
718
    fn prepare_randomness(n: BlockNumber);
719
    /// Called once at the start of each session in on_initialize of pallet_initializer
720
    fn take_randomness() -> [u8; 32];
721
}
722

            
723
impl<BlockNumber> CollatorAssignmentRandomness<BlockNumber> for () {
724
1057
    fn prepare_randomness_weight(_n: BlockNumber) -> Weight {
725
1057
        Weight::zero()
726
1057
    }
727
987
    fn prepare_randomness(_n: BlockNumber) {}
728
167
    fn take_randomness() -> [u8; 32] {
729
167
        [0; 32]
730
167
    }
731
}
732

            
733
/// Parachain randomness impl.
734
///
735
/// Reads relay chain randomness in the last block of the session and stores it in pallet storage.
736
/// When new session starts, takes that value from storage removing it.
737
/// Relay randomness cannot be accessed in `on_initialize`, so `prepare_randomness` is executed in
738
/// `on_finalize`, with `prepare_randomness_weight` reserving the weight needed.
739
pub struct ParachainRandomness<T, Runtime>(PhantomData<(T, Runtime)>);
740

            
741
impl<BlockNumber, T, Runtime> CollatorAssignmentRandomness<BlockNumber>
742
    for ParachainRandomness<T, Runtime>
743
where
744
    BlockNumber: Saturating + One,
745
    T: GetRandomnessForNextBlock<BlockNumber>,
746
    Runtime: frame_system::Config + crate::Config,
747
{
748
5811
    fn prepare_randomness_weight(n: BlockNumber) -> Weight {
749
5811
        let mut weight = Weight::zero();
750

            
751
5811
        if T::should_end_session(n.saturating_add(One::one())) {
752
605
            weight.saturating_accrue(Runtime::DbWeight::get().reads_writes(1, 1));
753
5206
        }
754

            
755
5811
        weight
756
5811
    }
757

            
758
5555
    fn prepare_randomness(n: BlockNumber) {
759
5555
        if T::should_end_session(n.saturating_add(One::one())) {
760
598
            let random_seed = T::get_randomness();
761
598
            Randomness::<Runtime>::put(random_seed);
762
4957
        }
763
5555
    }
764

            
765
4569
    fn take_randomness() -> [u8; 32] {
766
4569
        Randomness::<Runtime>::take()
767
4569
    }
768
}
769

            
770
/// Solochain randomness.
771
///
772
/// Uses current block randomness. This randomness exists in `on_initialize` so we don't need to
773
/// `prepare_randomness` in the previous block.
774
pub struct SolochainRandomness<T>(PhantomData<T>);
775

            
776
impl<BlockNumber, T> CollatorAssignmentRandomness<BlockNumber> for SolochainRandomness<T>
777
where
778
    T: Get<[u8; 32]>,
779
{
780
7584
    fn prepare_randomness_weight(_n: BlockNumber) -> Weight {
781
7584
        Weight::zero()
782
7584
    }
783

            
784
7189
    fn prepare_randomness(_n: BlockNumber) {}
785

            
786
1431
    fn take_randomness() -> [u8; 32] {
787
        #[cfg(feature = "runtime-benchmarks")]
788
        if let Some(x) =
789
            frame_support::storage::unhashed::take(b"__bench_collator_assignment_randomness")
790
        {
791
            return x;
792
        }
793

            
794
1431
        T::get()
795
1431
    }
796
}