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, RuntimeAppPublic, 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
7678
    fn default() -> Self {
92
7678
        Self {
93
7678
            max_collators: 100u32,
94
7678
            min_orchestrator_collators: 2u32,
95
7678
            max_orchestrator_collators: 5u32,
96
7678
            collators_per_container: 2u32,
97
7678
            full_rotation_period: 24u32,
98
7678
            collators_per_parathread: 1,
99
7678
            parathreads_per_collator: 1,
100
7678
            target_container_chain_fullness: Perbill::from_percent(80),
101
7678
        }
102
7678
    }
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
3920
    pub fn check_consistency(&self) -> Result<(), InconsistentError> {
125
3920
        if self.max_collators < 1 {
126
1
            return Err(InconsistentError::MaxCollatorsTooLow);
127
3919
        }
128
3919
        if self.min_orchestrator_collators < 1 {
129
2
            return Err(InconsistentError::MinOrchestratorCollatorsTooLow);
130
3917
        }
131
3917
        if self.max_orchestrator_collators < self.min_orchestrator_collators {
132
            return Err(InconsistentError::MaxCollatorsLowerThanMinCollators);
133
3917
        }
134
3917
        if self.parathreads_per_collator != 1 {
135
            return Err(InconsistentError::UnimplementedParameter);
136
3917
        }
137
3917
        if self.max_collators < self.min_orchestrator_collators {
138
2
            return Err(InconsistentError::MaxCollatorsLowerThanMinCollators);
139
3915
        }
140
3915
        Ok(())
141
3920
    }
142

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

            
155
#[frame_support::pallet]
156
pub mod pallet {
157
    use tp_traits::GetHostConfiguration;
158

            
159
    use super::*;
160

            
161
13612
    #[pallet::pallet]
162
    #[pallet::without_storage_info]
163
    pub struct Pallet<T>(_);
164

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

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

            
176
        type CurrentSessionIndex: GetSessionIndex<Self::SessionIndex>;
177

            
178
        /// The identifier type for an authority.
179
        type AuthorityId: Member
180
            + Parameter
181
            + RuntimeAppPublic
182
            + MaybeSerializeDeserialize
183
            + MaxEncodedLen;
184

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

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

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

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

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

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

            
223
490
    #[pallet::genesis_build]
224
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
225
258
        fn build(&self) {
226
258
            self.config.panic_if_not_consistent();
227
258
            ActiveConfig::<T>::put(&self.config);
228
258
        }
229
    }
230

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
405
104
            SessionChangeOutcome {
406
104
                prev_config,
407
104
                new_config,
408
104
            }
409
1855
        }
410

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

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

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

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

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

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

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

            
511
44
            let scheduled_session = Self::scheduled_session();
512

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

            
523
44
            <PendingConfigs<T>>::put(pending_configs);
524
44

            
525
44
            Ok(())
526
49
        }
527

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

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

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

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

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

            
556
1720
            let config = if let Some(last) = past_and_present.last() {
557
36
                last.1.clone()
558
            } else {
559
1684
                Pallet::<T>::config()
560
            };
561
1720
            config.collators_per_container
562
1720
        }
563

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

            
569
1720
            let config = if let Some(last) = past_and_present.last() {
570
36
                last.1.clone()
571
            } else {
572
1684
                Pallet::<T>::config()
573
            };
574
1720
            config.collators_per_parathread
575
1720
        }
576

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

            
582
3440
            let config = if let Some(last) = past_and_present.last() {
583
72
                last.1.clone()
584
            } else {
585
3368
                Pallet::<T>::config()
586
            };
587
3440
            config.min_orchestrator_collators
588
3440
        }
589

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

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