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
const LOG_TARGET: &str = "collator-assignment";
78

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

            
85
#[frame_support::pallet]
86
pub mod pallet {
87
    use super::*;
88

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

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

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

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

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

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

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

            
156
    #[pallet::call]
157
    impl<T: Config> Pallet<T> {}
158

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

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

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

            
206
1764
            let enough_cores_for_bulk_paras = bulk_paras.len() <= max_number_of_bulk_paras as usize;
207

            
208
1764
            let enough_collators = Self::enough_collators_for_all_chains(
209
1764
                &bulk_paras,
210
1764
                &pool_paras,
211
1764
                target_session_index,
212
1764
                number_of_collators,
213
1764
                collators_per_container,
214
1764
                collators_per_parathread,
215
            );
216

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

            
221
            // Currently, we are sorting both bulk and pool paras by tip, even when for example
222
            // only number of bulk paras are restricted due to core availability since we deduct tip from
223
            // all paras.
224
            // We need to sort both separately as we have fixed space for parachains at the moment
225
            // which means even when we have some parathread cores empty we cannot schedule parachain there.
226
1764
            if should_charge_tip {
227
1474
                bulk_paras.sort_by(|a, b| {
228
454
                    order_old_assigned_first_then_by_max_tip::<T>(
229
454
                        a.para_id,
230
454
                        b.para_id,
231
454
                        old_assigned_para_ids,
232
                    )
233
454
                });
234

            
235
                // Parathreads are not long-lived chains and block production is much more sporadic,
236
                // so for them it makes sense to "battle for existing spots".
237
1446
                pool_paras.sort_by(|a, b| {
238
62
                    T::CollatorAssignmentTip::get_para_max_tip(b.para_id)
239
62
                        .cmp(&T::CollatorAssignmentTip::get_para_max_tip(a.para_id))
240
62
                });
241
347
            }
242

            
243
1764
            bulk_paras.truncate(max_number_of_bulk_paras as usize);
244

            
245
            // We are not truncating pool paras, since their workload is not continuous one core
246
            // can be shared by many paras during the session.
247

            
248
1764
            let chains: Vec<_> = bulk_paras.into_iter().chain(pool_paras).collect();
249

            
250
1764
            (chains, should_charge_tip)
251
1764
        }
252

            
253
4760
        pub(crate) fn order_paras(
254
4760
            bulk_paras: Vec<ChainNumCollators>,
255
4760
            pool_paras: Vec<ChainNumCollators>,
256
4760
            old_assigned_para_ids: &BTreeSet<ParaId>,
257
4760
            target_session_index: T::SessionIndex,
258
4760
            number_of_collators: u32,
259
4760
            collators_per_container: u32,
260
4760
            collators_per_parathread: u32,
261
4760
        ) -> (Vec<ChainNumCollators>, bool) {
262
            // Are there enough collators to satisfy the minimum demand?
263
4760
            let enough_collators_for_all_chain = Self::enough_collators_for_all_chains(
264
4760
                &bulk_paras,
265
4760
                &pool_paras,
266
4760
                target_session_index,
267
4760
                number_of_collators,
268
4760
                collators_per_container,
269
4760
                collators_per_parathread,
270
            );
271

            
272
4760
            let mut chains: Vec<_> = bulk_paras.into_iter().chain(pool_paras).collect();
273

            
274
            // Prioritize paras by tip on congestion
275
            // As of now this doesn't distinguish between bulk paras and pool paras
276
4760
            if !enough_collators_for_all_chain {
277
2021
                chains.sort_by(|a, b| {
278
2019
                    order_old_assigned_first_then_by_max_tip::<T>(
279
2019
                        a.para_id,
280
2019
                        b.para_id,
281
2019
                        old_assigned_para_ids,
282
                    )
283
2019
                });
284
2769
            }
285

            
286
4760
            (chains, !enough_collators_for_all_chain)
287
4760
        }
288

            
289
        /// Assign new collators
290
        /// collators should be queued collators
291
6516
        pub fn assign_collators(
292
6516
            current_session_index: &T::SessionIndex,
293
6516
            random_seed: [u8; 32],
294
6516
            collators: Vec<T::AccountId>,
295
6516
        ) -> SessionChangeOutcome<T> {
296
6516
            let maybe_core_allocation_configuration = T::CoreAllocationConfiguration::get();
297
            // We work with one session delay to calculate assignments
298
6516
            let session_delay = T::SessionIndex::one();
299
6516
            let target_session_index = current_session_index.saturating_add(session_delay);
300

            
301
6516
            let collators_per_container =
302
6516
                T::HostConfiguration::collators_per_container(target_session_index);
303
6516
            let collators_per_parathread =
304
6516
                T::HostConfiguration::collators_per_parathread(target_session_index);
305

            
306
            // We get the containerChains that we will have at the target session
307
6516
            let container_chains =
308
6516
                T::ContainerChains::session_container_chains(target_session_index);
309
6516
            let num_total_registered_paras = container_chains
310
6516
                .parachains
311
6516
                .len()
312
6516
                .saturating_add(container_chains.parathreads.len())
313
6516
                as u32;
314
6516
            let mut container_chain_ids = container_chains.parachains;
315
6516
            let mut parathreads: Vec<_> = container_chains
316
6516
                .parathreads
317
6516
                .into_iter()
318
6516
                .map(|(para_id, _)| para_id)
319
6516
                .collect();
320

            
321
            // We read current assigned collators
322
6516
            let old_assigned = Self::read_assigned_collators();
323
6516
            let old_assigned_para_ids: BTreeSet<ParaId> =
324
6516
                old_assigned.container_chains.keys().cloned().collect();
325

            
326
6516
            let old_assigned_para_ids_with_collators: BTreeSet<_> = old_assigned
327
6516
                .container_chains
328
6516
                .iter()
329
6725
                .filter_map(|(k, v)| (!v.is_empty()).then_some(k))
330
6516
                .cloned()
331
6516
                .collect();
332

            
333
6516
            let collators_len = collators.len();
334

            
335
6516
            log::trace!(
336
                target: LOG_TARGET,
337
                "assign_collators: start: {} collators, {} parachains, {} parathreads",
338
                collators_len,
339
                container_chain_ids.len(),
340
                parathreads.len(),
341
            );
342

            
343
6516
            log::trace!(
344
                target: LOG_TARGET,
345
                "assign_collators: old: {} parachains (including {} with assigned collators)",
346
                old_assigned_para_ids.len(),
347
                old_assigned_para_ids_with_collators.len(),
348
            );
349

            
350
            // Remove the containerChains that do not have enough credits for block production
351
6516
            T::ParaIdAssignmentHooks::pre_assignment(
352
6516
                &mut container_chain_ids,
353
6516
                &old_assigned_para_ids,
354
            );
355

            
356
            // TODO: parathreads should be treated a bit differently, they don't need to have the same amount of credits
357
            // as parathreads because they will not be producing blocks on every slot.
358
6516
            T::ParaIdAssignmentHooks::pre_assignment(&mut parathreads, &old_assigned_para_ids);
359

            
360
6516
            log::trace!(
361
                target: LOG_TARGET,
362
                "assign_collators: after pre_assignment: {} collators, {} parachains, {} parathreads",
363
                collators.len(),
364
                container_chain_ids.len(),
365
                parathreads.len(),
366
            );
367

            
368
6516
            let mut shuffle_collators = None;
369
            // If the random_seed is all zeros, we don't shuffle the list of collators nor the list
370
            // of container chains.
371
            // This should only happen in tests_without_core_config, and in the genesis block.
372
6516
            if random_seed != [0; 32] {
373
295
                let mut rng: ChaCha20Rng = SeedableRng::from_seed(random_seed);
374
295
                container_chain_ids.shuffle(&mut rng);
375
295
                parathreads.shuffle(&mut rng);
376
367
                shuffle_collators = Some(move |collators: &mut Vec<T::AccountId>| {
377
367
                    collators.shuffle(&mut rng);
378
367
                })
379
6221
            }
380

            
381
6516
            let orchestrator_chain: ChainNumCollators = if T::ForceEmptyOrchestrator::get() {
382
1756
                ChainNumCollators {
383
1756
                    para_id: T::SelfParaId::get(),
384
1756
                    min_collators: 0u32,
385
1756
                    max_collators: 0u32,
386
1756
                    parathread: false,
387
1756
                }
388
            } else {
389
4760
                ChainNumCollators {
390
4760
                    para_id: T::SelfParaId::get(),
391
4760
                    min_collators: T::HostConfiguration::min_collators_for_orchestrator(
392
4760
                        target_session_index,
393
4760
                    ),
394
4760
                    max_collators: T::HostConfiguration::max_collators_for_orchestrator(
395
4760
                        target_session_index,
396
4760
                    ),
397
4760
                    parathread: false,
398
4760
                }
399
            };
400

            
401
            // Initialize list of chains as `[container1, container2, parathread1, parathread2]`.
402
            // The order means priority: the first chain in the list will be the first one to get assigned collators.
403
            // Chains will not be assigned less than `min_collators`, except the orchestrator chain.
404
            // First all chains will be assigned `min_collators`, and then the first one will be assigned up to `max`,
405
            // then the second one, and so on.
406
6516
            let mut bulk_paras = vec![];
407
6516
            let mut pool_paras = vec![];
408

            
409
12342
            for para_id in &container_chain_ids {
410
5826
                bulk_paras.push(ChainNumCollators {
411
5826
                    para_id: *para_id,
412
5826
                    min_collators: collators_per_container,
413
5826
                    max_collators: collators_per_container,
414
5826
                    parathread: false,
415
5826
                });
416
5826
            }
417
6986
            for para_id in &parathreads {
418
470
                pool_paras.push(ChainNumCollators {
419
470
                    para_id: *para_id,
420
470
                    min_collators: collators_per_parathread,
421
470
                    max_collators: collators_per_parathread,
422
470
                    parathread: true,
423
470
                });
424
470
            }
425

            
426
6516
            let (chains, need_to_charge_tip) =
427
6516
                if let Some(core_allocation_configuration) = maybe_core_allocation_configuration {
428
1756
                    Self::order_paras_with_core_config(
429
1756
                        bulk_paras,
430
1756
                        pool_paras,
431
1756
                        &old_assigned_para_ids_with_collators,
432
1756
                        &core_allocation_configuration,
433
1756
                        target_session_index,
434
1756
                        collators.len() as u32,
435
1756
                        collators_per_container,
436
1756
                        collators_per_parathread,
437
                    )
438
                } else {
439
4760
                    Self::order_paras(
440
4760
                        bulk_paras,
441
4760
                        pool_paras,
442
4760
                        &old_assigned_para_ids_with_collators,
443
4760
                        target_session_index,
444
4760
                        collators.len() as u32,
445
4760
                        collators_per_container,
446
4760
                        collators_per_parathread,
447
                    )
448
                };
449

            
450
6516
            log::trace!(
451
                target: LOG_TARGET,
452
                "assign_collators: after order_paras: {} collators, {} chains, need to charge tip: {}",
453
                collators.len(),
454
                chains.len(),
455
                need_to_charge_tip,
456
            );
457

            
458
            // We assign new collators
459
            // we use the config scheduled at the target_session_index
460
6516
            let full_rotation =
461
6516
                T::ShouldRotateAllCollators::should_rotate_all_collators(target_session_index);
462
6516
            if full_rotation {
463
52
                log::debug!(
464
                    target: LOG_TARGET,
465
                    "assign_collators: rotating collators. Session {:?}, Seed: {:?}",
466
                    current_session_index.encode(),
467
                    random_seed
468
                );
469
            } else {
470
6464
                log::debug!(
471
                    target: LOG_TARGET,
472
                    "assign_collators: keep old assigned. Session {:?}, Seed: {:?}",
473
                    current_session_index.encode(),
474
                    random_seed
475
                );
476
            }
477

            
478
6516
            let full_rotation_mode = if full_rotation {
479
52
                T::HostConfiguration::full_rotation_mode(target_session_index)
480
            } else {
481
                // On sessions where there is no rotation, we try to keep all collators assigned to the same chains
482
6464
                FullRotationModes::keep_all()
483
            };
484

            
485
6516
            Self::deposit_event(Event::NewPendingAssignment {
486
6516
                random_seed,
487
6516
                full_rotation,
488
6516
                target_session: target_session_index,
489
6516
                full_rotation_mode: full_rotation_mode.clone(),
490
6516
            });
491

            
492
6516
            let new_assigned = Assignment::<T>::assign_collators_always_keep_old(
493
6516
                collators,
494
6516
                orchestrator_chain,
495
6516
                chains,
496
6516
                old_assigned.clone(),
497
6516
                shuffle_collators,
498
6516
                full_rotation_mode,
499
            );
500

            
501
6516
            let mut new_assigned = match new_assigned {
502
6512
                Ok(x) => x,
503
4
                Err(e) => {
504
4
                    log::error!(
505
4
                        "Error in collator assignment, will keep previous assignment. {:?}",
506
                        e
507
                    );
508

            
509
4
                    old_assigned.clone()
510
                }
511
            };
512

            
513
            // Containers only with assigned collators to call post_assignment (for payment).
514
6516
            let mut assigned_containers = new_assigned.container_chains.clone();
515
7133
            assigned_containers.retain(|_, v| !v.is_empty());
516

            
517
            // Helpers for logs
518
6516
            let count_collators = |new_assigned: &AssignedCollators<_>| {
519
                new_assigned.orchestrator_chain.len().saturating_add(
520
                    new_assigned
521
                        .container_chains
522
                        .values()
523
                        .map(|x| x.len())
524
                        .sum(),
525
                )
526
            };
527

            
528
6516
            log::trace!(
529
                target: LOG_TARGET,
530
                "assign_collators: after assign. {} collators, {} chains",
531
                count_collators(&new_assigned),
532
                new_assigned.container_chains.len(),
533
            );
534

            
535
            // On congestion, prioritized chains need to pay the minimum tip of the prioritized chains
536
6516
            let maybe_tip: Option<BalanceOf<T>> = if !need_to_charge_tip {
537
3115
                None
538
            } else {
539
3401
                assigned_containers
540
3401
                    .into_keys()
541
3401
                    .filter_map(T::CollatorAssignmentTip::get_para_max_tip)
542
3401
                    .min()
543
            };
544

            
545
6516
            log::trace!(
546
                target: LOG_TARGET,
547
                "Tip to be charged: {maybe_tip:?}",
548
            );
549

            
550
            // TODO: this probably is asking for a refactor
551
            // only apply the onCollatorAssignedHook if sufficient collators
552
6516
            T::ParaIdAssignmentHooks::post_assignment(
553
6516
                &old_assigned_para_ids,
554
6516
                &mut new_assigned.container_chains,
555
6516
                &maybe_tip,
556
            );
557

            
558
6516
            log::trace!(
559
                target: LOG_TARGET,
560
                "assign_collators: post_assignment summary. {}/{} collators, {}/{} chains",
561
                count_collators(&new_assigned),
562
                collators_len,
563
                new_assigned.container_chains.len(),
564
                num_total_registered_paras,
565
            );
566

            
567
6516
            Self::store_collator_fullness(
568
6516
                &new_assigned,
569
6516
                T::HostConfiguration::max_collators(target_session_index),
570
            );
571

            
572
6516
            let mut pending = PendingCollatorContainerChain::<T>::get();
573

            
574
6516
            let old_assigned_changed = old_assigned != new_assigned;
575
6516
            let mut pending_changed = false;
576
            // Update CollatorContainerChain using last entry of pending, if needed
577
6516
            if let Some(current) = pending.take() {
578
1650
                pending_changed = true;
579
1650
                CollatorContainerChain::<T>::put(current);
580
4890
            }
581
6516
            if old_assigned_changed {
582
3056
                pending = Some(new_assigned.clone());
583
3056
                pending_changed = true;
584
4389
            }
585
            // Update PendingCollatorContainerChain, if it changed
586
6516
            log::trace!("assign_collators: pending_changed? {}", pending_changed);
587
6516
            if pending_changed {
588
4057
                PendingCollatorContainerChain::<T>::put(pending);
589
5146
            }
590

            
591
            // Only applies to session index 0
592
6516
            if current_session_index == &T::SessionIndex::zero() {
593
3161
                CollatorContainerChain::<T>::put(new_assigned.clone());
594
3161
                return SessionChangeOutcome {
595
3161
                    active_assignment: new_assigned.clone(),
596
3161
                    next_assignment: new_assigned,
597
3161
                    num_total_registered_paras,
598
3161
                };
599
3355
            }
600

            
601
3355
            SessionChangeOutcome {
602
3355
                active_assignment: old_assigned,
603
3355
                next_assignment: new_assigned,
604
3355
                num_total_registered_paras,
605
3355
            }
606
6516
        }
607

            
608
        /// Count number of collators assigned to any chain, divide that by `max_collators` and store
609
        /// in pallet storage.
610
6516
        fn store_collator_fullness(
611
6516
            new_assigned: &AssignedCollators<T::AccountId>,
612
6516
            max_collators: u32,
613
6516
        ) {
614
            // Count number of assigned collators
615
6516
            let mut num_collators = 0;
616
6516
            num_collators.saturating_accrue(new_assigned.orchestrator_chain.len());
617
7133
            for collators in new_assigned.container_chains.values() {
618
6274
                num_collators.saturating_accrue(collators.len());
619
6274
            }
620

            
621
6516
            let mut num_collators = num_collators as u32;
622
6516
            if num_collators > max_collators {
623
150
                // Shouldn't happen but just in case
624
150
                num_collators = max_collators;
625
6516
            }
626

            
627
6516
            let ratio = Perbill::from_rational(num_collators, max_collators);
628

            
629
6516
            CollatorFullnessRatio::<T>::put(ratio);
630
6516
        }
631

            
632
        // Returns the assigned collators as read from storage.
633
        // If there is any item in PendingCollatorContainerChain, returns that element.
634
        // Otherwise, reads and returns the current CollatorContainerChain
635
6516
        fn read_assigned_collators() -> AssignedCollators<T::AccountId> {
636
6516
            let mut pending_collator_list = PendingCollatorContainerChain::<T>::get();
637

            
638
6516
            if let Some(assigned_collators) = pending_collator_list.take() {
639
1650
                assigned_collators
640
            } else {
641
                // Read current
642
4866
                CollatorContainerChain::<T>::get()
643
            }
644
6516
        }
645

            
646
6516
        pub fn initializer_on_new_session(
647
6516
            session_index: &T::SessionIndex,
648
6516
            collators: Vec<T::AccountId>,
649
6516
        ) -> SessionChangeOutcome<T> {
650
6516
            let random_seed = T::Randomness::take_randomness();
651
6516
            let num_collators = collators.len();
652
6516
            let assigned_collators = Self::assign_collators(session_index, random_seed, collators);
653
6516
            let num_total_registered_paras = assigned_collators.num_total_registered_paras;
654

            
655
6516
            frame_system::Pallet::<T>::register_extra_weight_unchecked(
656
6516
                T::WeightInfo::new_session(num_collators as u32, num_total_registered_paras),
657
6516
                DispatchClass::Mandatory,
658
            );
659

            
660
6516
            assigned_collators
661
6516
        }
662

            
663
92027
        pub fn collator_container_chain() -> AssignedCollators<T::AccountId> {
664
92027
            CollatorContainerChain::<T>::get()
665
92027
        }
666

            
667
45
        pub fn pending_collator_container_chain() -> Option<AssignedCollators<T::AccountId>> {
668
45
            PendingCollatorContainerChain::<T>::get()
669
45
        }
670
    }
671

            
672
    impl<T: Config> GetContainerChainAuthor<T::AccountId> for Pallet<T> {
673
82207
        fn author_for_slot(slot: Slot, para_id: ParaId) -> Option<T::AccountId> {
674
82207
            let assigned_collators = Pallet::<T>::collator_container_chain();
675
82207
            let collators = if para_id == T::SelfParaId::get() {
676
82064
                Some(&assigned_collators.orchestrator_chain)
677
            } else {
678
143
                assigned_collators.container_chains.get(&para_id)
679
            }?;
680

            
681
82207
            if collators.is_empty() {
682
                // Avoid division by zero below
683
                return None;
684
82207
            }
685
82207
            let author_index = u64::from(slot) % collators.len() as u64;
686
82207
            collators.get(author_index as usize).cloned()
687
82207
        }
688

            
689
        #[cfg(feature = "runtime-benchmarks")]
690
        fn set_authors_for_para_id(para_id: ParaId, authors: Vec<T::AccountId>) {
691
            let mut assigned_collators = Pallet::<T>::collator_container_chain();
692
            assigned_collators.container_chains.insert(para_id, authors);
693
            CollatorContainerChain::<T>::put(assigned_collators);
694
        }
695
    }
696

            
697
    #[pallet::hooks]
698
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
699
15030
        fn on_initialize(n: BlockNumberFor<T>) -> Weight {
700
15030
            let mut weight = Weight::zero();
701

            
702
            // Account reads and writes for on_finalize
703
15030
            weight.saturating_accrue(T::Randomness::prepare_randomness_weight(n));
704

            
705
15030
            weight
706
15030
        }
707

            
708
14252
        fn on_finalize(n: BlockNumberFor<T>) {
709
            // If the next block is a session change, read randomness and store in pallet storage
710
14252
            T::Randomness::prepare_randomness(n);
711
14252
        }
712
    }
713

            
714
    impl<T: Config> GetContainerChainsWithCollators<T::AccountId> for Pallet<T> {
715
20809
        fn container_chains_with_collators(
716
20809
            for_session: ForSession,
717
20809
        ) -> Vec<(ParaId, Vec<T::AccountId>)> {
718
            // If next session has None then current session data will stay.
719
20809
            let chains = (for_session == ForSession::Next)
720
20809
                .then(PendingCollatorContainerChain::<T>::get)
721
20809
                .flatten()
722
20809
                .unwrap_or_else(CollatorContainerChain::<T>::get);
723

            
724
20809
            chains.container_chains.into_iter().collect()
725
20809
        }
726

            
727
3138
        fn get_all_collators_assigned_to_chains(for_session: ForSession) -> BTreeSet<T::AccountId> {
728
3138
            let mut all_chains: Vec<T::AccountId> =
729
3138
                Self::container_chains_with_collators(for_session)
730
3138
                    .iter()
731
3888
                    .flat_map(|(_para_id, collators)| collators.iter())
732
3138
                    .cloned()
733
3138
                    .collect();
734
3138
            all_chains.extend(
735
3138
                Self::collator_container_chain()
736
3138
                    .orchestrator_chain
737
3138
                    .iter()
738
3138
                    .cloned(),
739
            );
740
3138
            all_chains.into_iter().collect()
741
3138
        }
742

            
743
        #[cfg(feature = "runtime-benchmarks")]
744
        fn set_container_chains_with_collators(
745
            for_session: ForSession,
746
            container_chains: &[(ParaId, Vec<T::AccountId>)],
747
        ) {
748
            match for_session {
749
                ForSession::Current => {
750
                    let mut collators = CollatorContainerChain::<T>::get();
751
                    collators.container_chains = container_chains.iter().cloned().collect();
752
                    CollatorContainerChain::<T>::put(collators);
753
                }
754
                ForSession::Next => {
755
                    let mut collators =
756
                        PendingCollatorContainerChain::<T>::get().unwrap_or_default();
757
                    collators.container_chains = container_chains.iter().cloned().collect();
758
                    PendingCollatorContainerChain::<T>::put(Some(collators));
759
                }
760
            }
761
        }
762
    }
763
}
764

            
765
/// Balance used by this pallet
766
pub type BalanceOf<T> =
767
    <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
768

            
769
pub struct RotateCollatorsEveryNSessions<Period>(PhantomData<Period>);
770

            
771
impl<Period> ShouldRotateAllCollators<u32> for RotateCollatorsEveryNSessions<Period>
772
where
773
    Period: Get<u32>,
774
{
775
6347
    fn should_rotate_all_collators(session_index: u32) -> bool {
776
6347
        let period = Period::get();
777

            
778
6347
        if period == 0 {
779
            // A period of 0 disables rotation
780
2160
            false
781
        } else {
782
4187
            session_index % Period::get() == 0
783
        }
784
6347
    }
785
}
786

            
787
/// Only works on parachains because in relaychains it is not possible to know for sure if the next
788
/// block will be in the same session as the current one, as it depends on slots and validators can
789
/// skip slots.
790
pub trait GetRandomnessForNextBlock<BlockNumber> {
791
    fn should_end_session(block_number: BlockNumber) -> bool;
792
    fn get_randomness() -> [u8; 32];
793
}
794

            
795
impl<BlockNumber> GetRandomnessForNextBlock<BlockNumber> for () {
796
    fn should_end_session(_block_number: BlockNumber) -> bool {
797
        false
798
    }
799

            
800
    fn get_randomness() -> [u8; 32] {
801
        [0; 32]
802
    }
803
}
804

            
805
pub trait CollatorAssignmentRandomness<BlockNumber> {
806
    /// Called in on_initialize, returns weight needed by prepare_randomness call.
807
    fn prepare_randomness_weight(n: BlockNumber) -> Weight;
808
    /// Called in on_finalize.
809
    /// Prepares randomness for the next block if the next block is a new session start.
810
    fn prepare_randomness(n: BlockNumber);
811
    /// Called once at the start of each session in on_initialize of pallet_initializer
812
    fn take_randomness() -> [u8; 32];
813
}
814

            
815
impl<BlockNumber> CollatorAssignmentRandomness<BlockNumber> for () {
816
1062
    fn prepare_randomness_weight(_n: BlockNumber) -> Weight {
817
1062
        Weight::zero()
818
1062
    }
819
990
    fn prepare_randomness(_n: BlockNumber) {}
820
169
    fn take_randomness() -> [u8; 32] {
821
169
        [0; 32]
822
169
    }
823
}
824

            
825
/// Parachain randomness impl.
826
///
827
/// Reads relay chain randomness in the last block of the session and stores it in pallet storage.
828
/// When new session starts, takes that value from storage removing it.
829
/// Relay randomness cannot be accessed in `on_initialize`, so `prepare_randomness` is executed in
830
/// `on_finalize`, with `prepare_randomness_weight` reserving the weight needed.
831
pub struct ParachainRandomness<T, Runtime>(PhantomData<(T, Runtime)>);
832

            
833
impl<BlockNumber, T, Runtime> CollatorAssignmentRandomness<BlockNumber>
834
    for ParachainRandomness<T, Runtime>
835
where
836
    BlockNumber: Saturating + One,
837
    T: GetRandomnessForNextBlock<BlockNumber>,
838
    Runtime: frame_system::Config + crate::Config,
839
{
840
5831
    fn prepare_randomness_weight(n: BlockNumber) -> Weight {
841
5831
        let mut weight = Weight::zero();
842

            
843
5831
        if T::should_end_session(n.saturating_add(One::one())) {
844
607
            weight.saturating_accrue(Runtime::DbWeight::get().reads_writes(1, 1));
845
5224
        }
846

            
847
5831
        weight
848
5831
    }
849

            
850
5571
    fn prepare_randomness(n: BlockNumber) {
851
5571
        if T::should_end_session(n.saturating_add(One::one())) {
852
600
            let random_seed = T::get_randomness();
853
600
            Randomness::<Runtime>::put(random_seed);
854
4971
        }
855
5571
    }
856

            
857
4591
    fn take_randomness() -> [u8; 32] {
858
4591
        Randomness::<Runtime>::take()
859
4591
    }
860
}
861

            
862
/// Solochain randomness.
863
///
864
/// Uses current block randomness. This randomness exists in `on_initialize` so we don't need to
865
/// `prepare_randomness` in the previous block.
866
pub struct SolochainRandomness<T>(PhantomData<T>);
867

            
868
impl<BlockNumber, T> CollatorAssignmentRandomness<BlockNumber> for SolochainRandomness<T>
869
where
870
    T: Get<[u8; 32]>,
871
{
872
8137
    fn prepare_randomness_weight(_n: BlockNumber) -> Weight {
873
8137
        Weight::zero()
874
8137
    }
875

            
876
7691
    fn prepare_randomness(_n: BlockNumber) {}
877

            
878
1756
    fn take_randomness() -> [u8; 32] {
879
        #[cfg(feature = "runtime-benchmarks")]
880
        if let Some(x) =
881
            frame_support::storage::unhashed::take(b"__bench_collator_assignment_randomness")
882
        {
883
            return x;
884
        }
885

            
886
1756
        T::get()
887
1756
    }
888
}
889

            
890
2486
fn order_old_assigned_first_then_by_max_tip<T: Config>(
891
2486
    a: ParaId,
892
2486
    b: ParaId,
893
2486
    old_assigned_para_ids: &BTreeSet<ParaId>,
894
2486
) -> core::cmp::Ordering {
895
    // old assigned comes first
896
2486
    let old_assigned_first = old_assigned_para_ids
897
2486
        .contains(&a)
898
2486
        .cmp(&old_assigned_para_ids.contains(&b))
899
2486
        .reverse();
900

            
901
    // higher tip comes first
902
2486
    let higher_tip_first = || {
903
1387
        T::CollatorAssignmentTip::get_para_max_tip(a)
904
1387
            .cmp(&T::CollatorAssignmentTip::get_para_max_tip(b))
905
1387
            .reverse()
906
1387
    };
907

            
908
    // order first by old assigned, if equal order by higher tip
909
2486
    old_assigned_first.then_with(higher_tip_first)
910
2486
}