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
//! # Configuration Pallet
18
//!
19
//! This pallet stores the configuration for an orchestration-collator assignation chain. In
20
//! particular stores:
21
//!
22
//!    - How many collators are taken.
23
//!    - How many of those collators should be serving the orchestrator chain
24
//!    - Howe many of those collators should be serving the containerChains
25
//!
26
//! All configuration changes are protected behind the root origin
27
//! CHanges to the configuration are not immeditaly applied, but rather we wait
28
//! T::SessionDelay to apply these changes
29

            
30
#![cfg_attr(not(feature = "std"), no_std)]
31

            
32
#[cfg(test)]
33
mod mock;
34

            
35
#[cfg(test)]
36
mod tests;
37
pub mod weights;
38

            
39
pub use weights::WeightInfo;
40

            
41
#[cfg(any(test, feature = "runtime-benchmarks"))]
42
mod benchmarks;
43

            
44
pub use pallet::*;
45
use {
46
    frame_support::pallet_prelude::*,
47
    frame_system::pallet_prelude::*,
48
    serde::{Deserialize, Serialize},
49
    sp_runtime::{traits::AtLeast32BitUnsigned, Perbill, Saturating},
50
    sp_std::prelude::*,
51
    tp_traits::GetSessionIndex,
52
};
53

            
54
const LOG_TARGET: &str = "pallet_configuration";
55

            
56
/// All configuration of the runtime with respect to parachains and parathreads.
57
#[derive(
58
    Clone,
59
    Encode,
60
    Decode,
61
    PartialEq,
62
    sp_core::RuntimeDebug,
63
7128
    scale_info::TypeInfo,
64
    Serialize,
65
    Deserialize,
66
)]
67
pub struct HostConfiguration {
68
    /// Maximum number of collators, in total, including orchestrator and containers
69
    pub max_collators: u32,
70
    /// Minimum number of collators to be assigned to orchestrator chain
71
    pub min_orchestrator_collators: u32,
72
    /// Maximum number of collators to be assigned to orchestrator chain after all the container chains have been
73
    /// assigned collators.
74
    pub max_orchestrator_collators: u32,
75
    /// How many collators to assign to one container chain
76
    pub collators_per_container: u32,
77
    /// Rotate all collators once every n sessions. If this value is 0 means that there is no rotation
78
    pub full_rotation_period: u32,
79
    /// How many collators to assign to one parathread
80
    // TODO: for now we only support 1 collator per parathread because using Aura for consensus conflicts with
81
    // the idea of being able to create blocks every n slots: if there are 2 collators and we create blocks
82
    // every 2 slots, 1 collator will create all the blocks.
83
    pub collators_per_parathread: u32,
84
    /// How many parathreads can be assigned to one collator
85
    pub parathreads_per_collator: u32,
86
    /// Ratio of collators that we expect to be assigned to container chains. Affects fees.
87
    pub target_container_chain_fullness: Perbill,
88
}
89

            
90
impl Default for HostConfiguration {
91
8452
    fn default() -> Self {
92
8452
        Self {
93
8452
            max_collators: 100u32,
94
8452
            min_orchestrator_collators: 2u32,
95
8452
            max_orchestrator_collators: 5u32,
96
8452
            collators_per_container: 2u32,
97
8452
            full_rotation_period: 24u32,
98
8452
            collators_per_parathread: 1,
99
8452
            parathreads_per_collator: 1,
100
8452
            target_container_chain_fullness: Perbill::from_percent(80),
101
8452
        }
102
8452
    }
103
}
104

            
105
/// Enumerates the possible inconsistencies of `HostConfiguration`.
106
#[derive(Debug)]
107
pub enum InconsistentError {
108
    /// `max_orchestrator_collators` is lower than `min_orchestrator_collators`
109
    MaxCollatorsLowerThanMinCollators,
110
    /// `min_orchestrator_collators` must be at least 1
111
    MinOrchestratorCollatorsTooLow,
112
    /// `max_collators` must be at least 1
113
    MaxCollatorsTooLow,
114
    /// Tried to modify an unimplemented parameter
115
    UnimplementedParameter,
116
}
117

            
118
impl HostConfiguration {
119
    /// Checks that this instance is consistent with the requirements on each individual member.
120
    ///
121
    /// # Errors
122
    ///
123
    /// This function returns an error if the configuration is inconsistent.
124
4308
    pub fn check_consistency(
125
4308
        &self,
126
4308
        allow_empty_orchestrator: bool,
127
4308
    ) -> Result<(), InconsistentError> {
128
4308
        if self.max_collators < 1 {
129
1
            return Err(InconsistentError::MaxCollatorsTooLow);
130
4307
        }
131
4307
        if self.min_orchestrator_collators < 1 && !allow_empty_orchestrator {
132
2
            return Err(InconsistentError::MinOrchestratorCollatorsTooLow);
133
4305
        }
134
4305
        if self.max_orchestrator_collators < self.min_orchestrator_collators {
135
            return Err(InconsistentError::MaxCollatorsLowerThanMinCollators);
136
4305
        }
137
4305
        if self.parathreads_per_collator != 1 {
138
            return Err(InconsistentError::UnimplementedParameter);
139
4305
        }
140
4305
        if self.max_collators < self.min_orchestrator_collators {
141
2
            return Err(InconsistentError::MaxCollatorsLowerThanMinCollators);
142
4303
        }
143
4303
        Ok(())
144
4308
    }
145

            
146
    /// Checks that this instance is consistent with the requirements on each individual member.
147
    ///
148
    /// # Panics
149
    ///
150
    /// This function panics if the configuration is inconsistent.
151
3552
    pub fn panic_if_not_consistent(&self, allow_empty_orchestrator: bool) {
152
3552
        if let Err(err) = self.check_consistency(allow_empty_orchestrator) {
153
            panic!("Host configuration is inconsistent: {:?}", err);
154
3552
        }
155
3552
    }
156
}
157

            
158
#[frame_support::pallet]
159
pub mod pallet {
160
    use tp_traits::GetHostConfiguration;
161

            
162
    use super::*;
163

            
164
13612
    #[pallet::pallet]
165
    #[pallet::without_storage_info]
166
    pub struct Pallet<T>(_);
167

            
168
    /// Configure the pallet by specifying the parameters and types on which it depends.
169
    #[pallet::config]
170
    pub trait Config: frame_system::Config {
171
        type SessionIndex: parity_scale_codec::FullCodec + TypeInfo + Copy + AtLeast32BitUnsigned;
172

            
173
        // `SESSION_DELAY` is used to delay any changes to Paras registration or configurations.
174
        // Wait until the session index is 2 larger then the current index to apply any changes,
175
        // which guarantees that at least one full session has passed before any changes are applied.
176
        #[pallet::constant]
177
        type SessionDelay: Get<Self::SessionIndex>;
178

            
179
        type CurrentSessionIndex: GetSessionIndex<Self::SessionIndex>;
180

            
181
        type ForceEmptyOrchestrator: Get<bool>;
182

            
183
        /// Weight information for extrinsics in this pallet.
184
        type WeightInfo: WeightInfo;
185
    }
186

            
187
10
    #[pallet::error]
188
    pub enum Error<T> {
189
        /// The new value for a configuration parameter is invalid.
190
        InvalidNewValue,
191
    }
192

            
193
    /// The active configuration for the current session.
194
32782
    #[pallet::storage]
195
    pub(crate) type ActiveConfig<T: Config> = StorageValue<_, HostConfiguration, ValueQuery>;
196

            
197
    /// Pending configuration changes.
198
    ///
199
    /// This is a list of configuration changes, each with a session index at which it should
200
    /// be applied.
201
    ///
202
    /// The list is sorted ascending by session index. Also, this list can only contain at most
203
    /// 2 items: for the next session and for the `scheduled_session`.
204
27526
    #[pallet::storage]
205
    pub(crate) type PendingConfigs<T: Config> =
206
        StorageValue<_, Vec<(T::SessionIndex, HostConfiguration)>, ValueQuery>;
207

            
208
    /// If this is set, then the configuration setters will bypass the consistency checks. This
209
    /// is meant to be used only as the last resort.
210
100
    #[pallet::storage]
211
    pub(crate) type BypassConsistencyCheck<T: Config> = StorageValue<_, bool, ValueQuery>;
212

            
213
    #[pallet::genesis_config]
214
    #[derive(frame_support::DefaultNoBound)]
215
    pub struct GenesisConfig<T: Config> {
216
        pub config: HostConfiguration,
217
        #[serde(skip)]
218
        pub _config: sp_std::marker::PhantomData<T>,
219
    }
220

            
221
500
    #[pallet::genesis_build]
222
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
223
263
        fn build(&self) {
224
263
            self.config
225
263
                .panic_if_not_consistent(T::ForceEmptyOrchestrator::get());
226
263
            ActiveConfig::<T>::put(&self.config);
227
263
        }
228
    }
229

            
230
113
    #[pallet::call]
231
    impl<T: Config> Pallet<T> {
232
        #[pallet::call_index(0)]
233
        #[pallet::weight((
234
			T::WeightInfo::set_config_with_u32(),
235
			DispatchClass::Operational,
236
		))]
237
15
        pub fn set_max_collators(origin: OriginFor<T>, new: u32) -> DispatchResult {
238
15
            ensure_root(origin)?;
239
13
            Self::schedule_config_update(|config| {
240
13
                config.max_collators = new;
241
13
            })
242
        }
243

            
244
        #[pallet::call_index(1)]
245
        #[pallet::weight((
246
			T::WeightInfo::set_config_with_u32(),
247
			DispatchClass::Operational,
248
		))]
249
14
        pub fn set_min_orchestrator_collators(origin: OriginFor<T>, new: u32) -> DispatchResult {
250
14
            ensure_root(origin)?;
251
12
            Self::schedule_config_update(|config| {
252
12
                if config.max_orchestrator_collators < new {
253
9
                    config.max_orchestrator_collators = new;
254
9
                }
255
12
                config.min_orchestrator_collators = new;
256
12
            })
257
        }
258

            
259
        #[pallet::call_index(2)]
260
        #[pallet::weight((
261
			T::WeightInfo::set_config_with_u32(),
262
			DispatchClass::Operational,
263
		))]
264
9
        pub fn set_max_orchestrator_collators(origin: OriginFor<T>, new: u32) -> DispatchResult {
265
9
            ensure_root(origin)?;
266
7
            Self::schedule_config_update(|config| {
267
7
                if config.min_orchestrator_collators > new {
268
3
                    config.min_orchestrator_collators = new;
269
5
                }
270
7
                config.max_orchestrator_collators = new;
271
7
            })
272
        }
273

            
274
        #[pallet::call_index(3)]
275
        #[pallet::weight((
276
			T::WeightInfo::set_config_with_u32(),
277
			DispatchClass::Operational,
278
		))]
279
15
        pub fn set_collators_per_container(origin: OriginFor<T>, new: u32) -> DispatchResult {
280
15
            ensure_root(origin)?;
281
13
            Self::schedule_config_update(|config| {
282
13
                config.collators_per_container = new;
283
13
            })
284
        }
285

            
286
        #[pallet::call_index(4)]
287
        #[pallet::weight((
288
			T::WeightInfo::set_config_with_u32(),
289
			DispatchClass::Operational,
290
		))]
291
3
        pub fn set_full_rotation_period(origin: OriginFor<T>, new: u32) -> DispatchResult {
292
3
            ensure_root(origin)?;
293
3
            Self::schedule_config_update(|config| {
294
3
                config.full_rotation_period = new;
295
3
            })
296
        }
297

            
298
        #[pallet::call_index(5)]
299
        #[pallet::weight((
300
        T::WeightInfo::set_config_with_u32(),
301
        DispatchClass::Operational,
302
        ))]
303
        pub fn set_collators_per_parathread(origin: OriginFor<T>, new: u32) -> DispatchResult {
304
            ensure_root(origin)?;
305
            Self::schedule_config_update(|config| {
306
                config.collators_per_parathread = new;
307
            })
308
        }
309

            
310
        #[pallet::call_index(6)]
311
        #[pallet::weight((
312
        T::WeightInfo::set_config_with_u32(),
313
        DispatchClass::Operational,
314
        ))]
315
        pub fn set_parathreads_per_collator(origin: OriginFor<T>, new: u32) -> DispatchResult {
316
            ensure_root(origin)?;
317
            Self::schedule_config_update(|config| {
318
                config.parathreads_per_collator = new;
319
            })
320
        }
321

            
322
        #[pallet::call_index(7)]
323
        #[pallet::weight((
324
        T::WeightInfo::set_config_with_u32(),
325
        DispatchClass::Operational,
326
        ))]
327
        pub fn set_target_container_chain_fullness(
328
            origin: OriginFor<T>,
329
            new: Perbill,
330
2
        ) -> DispatchResult {
331
2
            ensure_root(origin)?;
332
2
            Self::schedule_config_update(|config| {
333
2
                config.target_container_chain_fullness = new;
334
2
            })
335
        }
336

            
337
        /// Setting this to true will disable consistency checks for the configuration setters.
338
        /// Use with caution.
339
        #[pallet::call_index(44)]
340
        #[pallet::weight((
341
			T::DbWeight::get().writes(1),
342
			DispatchClass::Operational,
343
		))]
344
        pub fn set_bypass_consistency_check(origin: OriginFor<T>, new: bool) -> DispatchResult {
345
            ensure_root(origin)?;
346
            BypassConsistencyCheck::<T>::put(new);
347
            Ok(())
348
        }
349
    }
350

            
351
    /// A struct that holds the configuration that was active before the session change and optionally
352
    /// a configuration that became active after the session change.
353
    pub struct SessionChangeOutcome {
354
        /// Previously active configuration.
355
        pub prev_config: HostConfiguration,
356
        /// If new configuration was applied during the session change, this is the new configuration.
357
        pub new_config: Option<HostConfiguration>,
358
    }
359

            
360
    impl<T: Config> Pallet<T> {
361
        /// Called by the initializer to note that a new session has started.
362
        ///
363
        /// Returns the configuration that was actual before the session change and the configuration
364
        /// that became active after the session change. If there were no scheduled changes, both will
365
        /// be the same.
366
1991
        pub fn initializer_on_new_session(session_index: &T::SessionIndex) -> SessionChangeOutcome {
367
1991
            let pending_configs = <PendingConfigs<T>>::get();
368
1991
            let prev_config = ActiveConfig::<T>::get();
369
1991

            
370
1991
            // No pending configuration changes, so we're done.
371
1991
            if pending_configs.is_empty() {
372
1867
                return SessionChangeOutcome {
373
1867
                    prev_config,
374
1867
                    new_config: None,
375
1867
                };
376
124
            }
377
124

            
378
124
            // We partition those configs scheduled for the present
379
124
            // and those for the future
380
124
            let (mut past_and_present, future) = pending_configs
381
124
                .into_iter()
382
150
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| {
383
150
                    apply_at_session <= *session_index
384
150
                });
385
124

            
386
124
            if past_and_present.len() > 1 {
387
                // This should never happen since we schedule configuration changes only into the future
388
                // sessions and this handler called for each session change.
389
                log::error!(
390
                    target: LOG_TARGET,
391
                    "Skipping applying configuration changes scheduled sessions in the past",
392
                );
393
124
            }
394

            
395
124
            let new_config = past_and_present.pop().map(|(_, config)| config);
396
124
            if let Some(ref new_config) = new_config {
397
75
                // Apply the new configuration.
398
75
                ActiveConfig::<T>::put(new_config);
399
75
            }
400

            
401
            // We insert future as PendingConfig
402
124
            <PendingConfigs<T>>::put(future);
403
124

            
404
124
            SessionChangeOutcome {
405
124
                prev_config,
406
124
                new_config,
407
124
            }
408
1991
        }
409

            
410
        /// Return the session index that should be used for any future scheduled changes.
411
45
        fn scheduled_session() -> T::SessionIndex {
412
45
            T::CurrentSessionIndex::session_index().saturating_add(T::SessionDelay::get())
413
45
        }
414

            
415
        /// Forcibly set the active config. This should be used with extreme care, and typically
416
        /// only when enabling parachains runtime pallets for the first time on a chain which has
417
        /// been running without them.
418
        pub fn force_set_active_config(config: HostConfiguration) {
419
            ActiveConfig::<T>::set(config);
420
        }
421

            
422
        /// This function should be used to update members of the configuration.
423
        ///
424
        /// This function is used to update the configuration in a way that is safe. It will check the
425
        /// resulting configuration and ensure that the update is valid. If the update is invalid, it
426
        /// will check if the previous configuration was valid. If it was invalid, we proceed with
427
        /// updating the configuration, giving a chance to recover from such a condition.
428
        ///
429
        /// The actual configuration change take place after a couple of sessions have passed. In case
430
        /// this function is called more than once in a session, then the pending configuration change
431
        /// will be updated and the changes will be applied at once.
432
        // NOTE: Explicitly tell rustc not to inline this because otherwise heuristics note the incoming
433
        // closure making it's attractive to inline. However, in this case, we will end up with lots of
434
        // duplicated code (making this function to show up in the top of heaviest functions) only for
435
        // the sake of essentially avoiding an indirect call. Doesn't worth it.
436
        #[inline(never)]
437
50
        fn schedule_config_update(updater: impl FnOnce(&mut HostConfiguration)) -> DispatchResult {
438
50
            let mut pending_configs = <PendingConfigs<T>>::get();
439
50

            
440
50
            // 1. pending_configs = []
441
50
            //    No pending configuration changes.
442
50
            //
443
50
            //    That means we should use the active config as the base configuration. We will insert
444
50
            //    the new pending configuration as (cur+2, new_config) into the list.
445
50
            //
446
50
            // 2. pending_configs = [(cur+2, X)]
447
50
            //    There is a configuration that is pending for the scheduled session.
448
50
            //
449
50
            //    We will use X as the base configuration. We can update the pending configuration X
450
50
            //    directly.
451
50
            //
452
50
            // 3. pending_configs = [(cur+1, X)]
453
50
            //    There is a pending configuration scheduled and it will be applied in the next session.
454
50
            //
455
50
            //    We will use X as the base configuration. We need to schedule a new configuration change
456
50
            //    for the `scheduled_session` and use X as the base for the new configuration.
457
50
            //
458
50
            // 4. pending_configs = [(cur+1, X), (cur+2, Y)]
459
50
            //    There is a pending configuration change in the next session and for the scheduled
460
50
            //    session. Due to case â„–3, we can be sure that Y is based on top of X. This means we
461
50
            //    can use Y as the base configuration and update Y directly.
462
50
            //
463
50
            // There cannot be (cur, X) because those are applied in the session change handler for the
464
50
            // current session.
465
50

            
466
50
            // First, we need to decide what we should use as the base configuration.
467
50
            let mut base_config = pending_configs
468
50
                .last()
469
50
                .map(|(_, config)| config.clone())
470
50
                .unwrap_or_else(Self::config);
471
50
            let base_config_consistent = base_config
472
50
                .check_consistency(T::ForceEmptyOrchestrator::get())
473
50
                .is_ok();
474
50

            
475
50
            // Now, we need to decide what the new configuration should be.
476
50
            // We also move the `base_config` to `new_config` to empahsize that the base config was
477
50
            // destroyed by the `updater`.
478
50
            updater(&mut base_config);
479
50
            let new_config = base_config;
480
50

            
481
50
            if BypassConsistencyCheck::<T>::get() {
482
                // This will emit a warning each configuration update if the consistency check is
483
                // bypassed. This is an attempt to make sure the bypass is not accidentally left on.
484
                log::warn!(
485
                    target: LOG_TARGET,
486
                    "Bypassing the consistency check for the configuration change!",
487
                );
488
50
            } else if let Err(e) = new_config.check_consistency(T::ForceEmptyOrchestrator::get()) {
489
5
                if base_config_consistent {
490
                    // Base configuration is consistent and the new configuration is inconsistent.
491
                    // This means that the value set by the `updater` is invalid and we can return
492
                    // it as an error.
493
5
                    log::warn!(
494
                        target: LOG_TARGET,
495
                        "Configuration change rejected due to invalid configuration: {:?}",
496
                        e,
497
                    );
498
5
                    return Err(Error::<T>::InvalidNewValue.into());
499
                } else {
500
                    // The configuration was already broken, so we can as well proceed with the update.
501
                    // You cannot break something that is already broken.
502
                    //
503
                    // That will allow to call several functions and ultimately return the configuration
504
                    // into consistent state.
505
                    log::warn!(
506
                        target: LOG_TARGET,
507
                        "The new configuration is broken but the old is broken as well. Proceeding",
508
                    );
509
                }
510
45
            }
511

            
512
45
            let scheduled_session = Self::scheduled_session();
513

            
514
45
            if let Some(&mut (_, ref mut config)) = pending_configs
515
45
                .iter_mut()
516
45
                .find(|&&mut (apply_at_session, _)| apply_at_session >= scheduled_session)
517
10
            {
518
10
                *config = new_config;
519
35
            } else {
520
35
                // We are scheduling a new configuration change for the scheduled session.
521
35
                pending_configs.push((scheduled_session, new_config));
522
35
            }
523

            
524
45
            <PendingConfigs<T>>::put(pending_configs);
525
45

            
526
45
            Ok(())
527
50
        }
528

            
529
14062
        pub fn config() -> HostConfiguration {
530
14062
            ActiveConfig::<T>::get()
531
14062
        }
532

            
533
11548
        pub fn pending_configs() -> Vec<(T::SessionIndex, HostConfiguration)> {
534
11548
            PendingConfigs::<T>::get()
535
11548
        }
536
    }
537

            
538
    impl<T: Config> GetHostConfiguration<T::SessionIndex> for Pallet<T> {
539
2200
        fn max_collators(session_index: T::SessionIndex) -> u32 {
540
2200
            let (past_and_present, _) = Pallet::<T>::pending_configs()
541
2200
                .into_iter()
542
2200
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
543

            
544
2200
            let config = if let Some(last) = past_and_present.last() {
545
64
                last.1.clone()
546
            } else {
547
2136
                Pallet::<T>::config()
548
            };
549
2200
            config.max_collators
550
2200
        }
551

            
552
1969
        fn collators_per_container(session_index: T::SessionIndex) -> u32 {
553
1969
            let (past_and_present, _) = Pallet::<T>::pending_configs()
554
1969
                .into_iter()
555
1969
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
556

            
557
1969
            let config = if let Some(last) = past_and_present.last() {
558
68
                last.1.clone()
559
            } else {
560
1901
                Pallet::<T>::config()
561
            };
562
1969
            config.collators_per_container
563
1969
        }
564

            
565
1969
        fn collators_per_parathread(session_index: T::SessionIndex) -> u32 {
566
1969
            let (past_and_present, _) = Pallet::<T>::pending_configs()
567
1969
                .into_iter()
568
1969
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
569

            
570
1969
            let config = if let Some(last) = past_and_present.last() {
571
68
                last.1.clone()
572
            } else {
573
1901
                Pallet::<T>::config()
574
            };
575
1969
            config.collators_per_parathread
576
1969
        }
577

            
578
3689
        fn min_collators_for_orchestrator(session_index: T::SessionIndex) -> u32 {
579
3689
            let (past_and_present, _) = Pallet::<T>::pending_configs()
580
3689
                .into_iter()
581
3689
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
582

            
583
3689
            let config = if let Some(last) = past_and_present.last() {
584
104
                last.1.clone()
585
            } else {
586
3585
                Pallet::<T>::config()
587
            };
588
3689
            config.min_orchestrator_collators
589
3689
        }
590

            
591
1720
        fn max_collators_for_orchestrator(session_index: T::SessionIndex) -> u32 {
592
1720
            let (past_and_present, _) = Pallet::<T>::pending_configs()
593
1720
                .into_iter()
594
1720
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
595

            
596
1720
            let config = if let Some(last) = past_and_present.last() {
597
36
                last.1.clone()
598
            } else {
599
1684
                Pallet::<T>::config()
600
            };
601
1720
            config.max_orchestrator_collators
602
1720
        }
603
    }
604
}