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
//! # Inactivity Tracking Pallet
17
//!
18
//! This pallet tracks and stores the activity of container chain and orchestrator chain collators
19
//! for configurable number of sessions. It is used to determine if a collator is inactive
20
//! for that period of time.
21
//!
22
//! The tracking functionality can be enabled or disabled with root privileges.
23
//! By default, the tracking is enabled.
24
#![cfg_attr(not(feature = "std"), no_std)]
25
extern crate alloc;
26

            
27
use {
28
    frame_support::{
29
        dispatch::DispatchResult, dispatch::DispatchResultWithPostInfo, ensure,
30
        pallet_prelude::Weight,
31
    },
32
    parity_scale_codec::{Decode, Encode},
33
    scale_info::TypeInfo,
34
    serde::{Deserialize, Serialize},
35
    sp_core::{MaxEncodedLen, RuntimeDebug},
36
    sp_runtime::{traits::Get, BoundedBTreeSet},
37
    sp_staking::SessionIndex,
38
    tp_traits::{
39
        AuthorNotingHook, AuthorNotingInfo, ForSession, GetContainerChainsWithCollators,
40
        GetSessionIndex, InvulnerablesHelper, MaybeSelfChainBlockAuthor,
41
        NodeActivityTrackingHelper, ParaId, ParathreadHelper, StakingCandidateHelper,
42
    },
43
};
44

            
45
#[cfg(test)]
46
mod mock;
47

            
48
#[cfg(test)]
49
mod tests;
50

            
51
#[cfg(feature = "runtime-benchmarks")]
52
mod benchmarking;
53

            
54
pub mod weights;
55

            
56
pub use weights::WeightInfo;
57

            
58
#[cfg(feature = "runtime-benchmarks")]
59
use tp_traits::BlockNumber;
60

            
61
pub use pallet::*;
62
14976
#[frame_support::pallet]
63
pub mod pallet {
64
    use {
65
        super::*,
66
        crate::weights::WeightInfo,
67
        alloc::collections::btree_set::BTreeSet,
68
        core::marker::PhantomData,
69
        frame_support::{pallet_prelude::*, storage::types::StorageMap},
70
        frame_system::pallet_prelude::*,
71
    };
72

            
73
    pub type Collator<T> = <T as frame_system::Config>::AccountId;
74

            
75
    /// The status of collator activity tracking
76
    #[derive(
77
        Clone,
78
        PartialEq,
79
        Eq,
80
        Encode,
81
        DecodeWithMemTracking,
82
        Decode,
83
3744
        TypeInfo,
84
        Serialize,
85
        Deserialize,
86
        RuntimeDebug,
87
        MaxEncodedLen,
88
    )]
89
    pub enum ActivityTrackingStatus {
90
        Enabled {
91
            /// The session in which we will start recording the collator activity after enabling it
92
            start: SessionIndex,
93
            /// The session after which the activity tracking can be disabled
94
            end: SessionIndex,
95
        },
96
        Disabled {
97
            /// The session after which the activity tracking can be enabled
98
            end: SessionIndex,
99
        },
100
    }
101
    impl Default for ActivityTrackingStatus {
102
172812
        fn default() -> Self {
103
172812
            ActivityTrackingStatus::Enabled { start: 0, end: 0 }
104
172812
        }
105
    }
106

            
107
2652
    #[pallet::pallet]
108
    pub struct Pallet<T>(PhantomData<T>);
109
    #[pallet::config]
110
    pub trait Config: frame_system::Config {
111
        /// Overarching event type.
112
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
113

            
114
        /// The maximum number of sessions for which a collator can be inactive
115
        /// before being moved to the offline queue
116
        #[pallet::constant]
117
        type MaxInactiveSessions: Get<u32>;
118

            
119
        /// The maximum amount of collators that can be stored for a session
120
        #[pallet::constant]
121
        type MaxCollatorsPerSession: Get<u32>;
122

            
123
        /// The maximum amount of container chains that can be stored
124
        #[pallet::constant]
125
        type MaxContainerChains: Get<u32>;
126

            
127
        /// Helper that returns the current session index.
128
        type CurrentSessionIndex: GetSessionIndex<SessionIndex>;
129

            
130
        /// Helper that fetches a list of collators eligible to produce blocks for the current session
131
        type CurrentCollatorsFetcher: GetContainerChainsWithCollators<Collator<Self>>;
132

            
133
        /// Helper that returns the block author for the orchestrator chain (if it exists)
134
        type GetSelfChainBlockAuthor: MaybeSelfChainBlockAuthor<Collator<Self>>;
135

            
136
        /// Helper that checks if a ParaId is a parathread
137
        type ParaFilter: ParathreadHelper;
138

            
139
        /// Helper for dealing with invulnerables.
140
        type InvulnerablesFilter: InvulnerablesHelper<Collator<Self>>;
141

            
142
        /// Helper for dealing with collator's stake
143
        type CollatorStakeHelper: StakingCandidateHelper<Collator<Self>>;
144

            
145
        /// The weight information of this pallet.
146
        type WeightInfo: weights::WeightInfo;
147
    }
148

            
149
    /// Switch to enable/disable activity tracking
150
110594
    #[pallet::storage]
151
    pub type CurrentActivityTrackingStatus<T: Config> =
152
        StorageValue<_, ActivityTrackingStatus, ValueQuery>;
153

            
154
    /// A storage map of inactive collators for a session
155
5303
    #[pallet::storage]
156
    pub type InactiveCollators<T: Config> = StorageMap<
157
        _,
158
        Twox64Concat,
159
        SessionIndex,
160
        BoundedBTreeSet<Collator<T>, T::MaxCollatorsPerSession>,
161
        ValueQuery,
162
    >;
163

            
164
    /// A list of active collators for a session. Repopulated at the start of every session
165
222704
    #[pallet::storage]
166
    pub type ActiveCollatorsForCurrentSession<T: Config> =
167
        StorageValue<_, BoundedBTreeSet<Collator<T>, T::MaxCollatorsPerSession>, ValueQuery>;
168

            
169
    /// A list of active container chains for a session. Repopulated at the start of every session
170
106086
    #[pallet::storage]
171
    pub type ActiveContainerChainsForCurrentSession<T: Config> =
172
        StorageValue<_, BoundedBTreeSet<ParaId, T::MaxContainerChains>, ValueQuery>;
173

            
174
    /// Switch to enable/disable offline marking.
175
712
    #[pallet::storage]
176
    pub type EnableMarkingOffline<T: Config> = StorageValue<_, bool, ValueQuery>;
177

            
178
    /// Storage map indicating the offline status of a collator
179
860
    #[pallet::storage]
180
    pub type OfflineCollators<T: Config> =
181
        StorageMap<_, Blake2_128Concat, Collator<T>, bool, ValueQuery>;
182

            
183
624
    #[pallet::event]
184
32
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
185
    pub enum Event<T: Config> {
186
        /// Event emitted when the activity tracking status is updated
187
        ActivityTrackingStatusSet { status: ActivityTrackingStatus },
188
        /// Collator online status updated
189
        CollatorStatusUpdated {
190
            collator: Collator<T>,
191
            is_offline: bool,
192
        },
193
    }
194

            
195
624
    #[pallet::error]
196
    pub enum Error<T> {
197
        /// The size of a collator set for a session has already reached MaxCollatorsPerSession value
198
        MaxCollatorsPerSessionReached,
199
        /// The size of a chains set for a session has already reached MaxContainerChains value
200
        MaxContainerChainsReached,
201
        /// Error returned when the activity tracking status is attempted to be updated before the end session
202
        ActivityTrackingStatusUpdateSuspended,
203
        /// Error returned when the activity tracking status is attempted to be enabled when it is already enabled
204
        ActivityTrackingStatusAlreadyEnabled,
205
        /// Error returned when the activity tracking status is attempted to be disabled when it is already disabled
206
        ActivityTrackingStatusAlreadyDisabled,
207
        /// Error returned when the collator status is attempted to be set to offline when offline marking is disabled
208
        MarkingOfflineNotEnabled,
209
        /// Error returned when the collator is not part of the sorted eligible candidates list
210
        CollatorNotEligibleCandidate,
211
        /// Error returned when the collator status is attempted to be set to offline when it is already offline
212
        CollatorNotOnline,
213
        /// Error returned when the collator status is attempted to be set to online when it is already online
214
        CollatorNotOffline,
215
        /// Error returned when the collator attempted to be set offline is invulnerable
216
        MarkingInvulnerableOfflineInvalid,
217
        /// Error returned when the collator attempted to be set offline is not inactive
218
        CollatorCannotBeNotifiedAsInactive,
219
    }
220

            
221
648
    #[pallet::call]
222
    impl<T: Config> Pallet<T> {
223
        /// Enables or disables inactivity tracking.
224
        #[pallet::call_index(0)]
225
        #[pallet::weight(T::WeightInfo::set_inactivity_tracking_status())]
226
        pub fn set_inactivity_tracking_status(
227
            origin: OriginFor<T>,
228
            enable_inactivity_tracking: bool,
229
18
        ) -> DispatchResult {
230
18
            ensure_root(origin)?;
231
17
            let current_status_end_session_index = match <CurrentActivityTrackingStatus<T>>::get() {
232
11
                ActivityTrackingStatus::Enabled { start: _, end } => {
233
11
                    ensure!(
234
11
                        !enable_inactivity_tracking,
235
1
                        Error::<T>::ActivityTrackingStatusAlreadyEnabled
236
                    );
237
10
                    end
238
                }
239
6
                ActivityTrackingStatus::Disabled { end } => {
240
6
                    ensure!(
241
6
                        enable_inactivity_tracking,
242
1
                        Error::<T>::ActivityTrackingStatusAlreadyDisabled
243
                    );
244
5
                    end
245
                }
246
            };
247
15
            let current_session_index = T::CurrentSessionIndex::session_index();
248
15
            ensure!(
249
15
                current_session_index > current_status_end_session_index,
250
1
                Error::<T>::ActivityTrackingStatusUpdateSuspended
251
            );
252
14
            Self::set_inactivity_tracking_status_inner(
253
14
                current_session_index,
254
14
                enable_inactivity_tracking,
255
14
            );
256
14
            Ok(())
257
        }
258

            
259
        /// Enables or disables the marking of collators as offline.
260
        #[pallet::call_index(1)]
261
        #[pallet::weight(T::WeightInfo::enable_offline_marking())]
262
22
        pub fn enable_offline_marking(origin: OriginFor<T>, value: bool) -> DispatchResult {
263
22
            ensure_root(origin)?;
264
21
            <EnableMarkingOffline<T>>::set(value);
265
21
            Ok(())
266
        }
267

            
268
        /// Allows a collator to mark itself offline.
269
        #[pallet::call_index(2)]
270
        #[pallet::weight(T::WeightInfo::set_offline())]
271
15
        pub fn set_offline(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
272
15
            let collator = ensure_signed(origin)?;
273
15
            Self::mark_collator_offline(&collator)
274
        }
275

            
276
        /// Allows a collator to mark itself online.
277
        #[pallet::call_index(3)]
278
        #[pallet::weight(T::WeightInfo::set_online())]
279
4
        pub fn set_online(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
280
4
            let collator = ensure_signed(origin)?;
281
4
            Self::mark_collator_online(&collator)
282
        }
283

            
284
        /// Allows an account to notify inactive collator to be marked offline.
285
        #[pallet::call_index(4)]
286
        #[pallet::weight(T::WeightInfo::notify_inactive_collator())]
287
        pub fn notify_inactive_collator(
288
            origin: OriginFor<T>,
289
            collator: Collator<T>,
290
6
        ) -> DispatchResultWithPostInfo {
291
6
            ensure_signed(origin)?;
292
6
            ensure!(
293
6
                Self::is_node_inactive(&collator),
294
1
                Error::<T>::CollatorCannotBeNotifiedAsInactive
295
            );
296
5
            Self::mark_collator_offline(&collator)
297
        }
298
    }
299

            
300
66621
    #[pallet::hooks]
301
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
302
30137
        fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
303
30137
            let mut total_weight = T::DbWeight::get().reads_writes(1, 0);
304
            // Process the orchestrator chain block author (if it exists) and activity tracking is enabled
305
30137
            if let Some(orchestrator_chain_author) = T::GetSelfChainBlockAuthor::get_block_author()
306
            {
307
26127
                total_weight.saturating_accrue(T::DbWeight::get().reads(1));
308
26127
                if let ActivityTrackingStatus::Enabled { start, end: _ } =
309
26127
                    <CurrentActivityTrackingStatus<T>>::get()
310
                {
311
26127
                    total_weight.saturating_accrue(T::DbWeight::get().reads(1));
312
26127
                    if start <= T::CurrentSessionIndex::session_index() {
313
26127
                        total_weight
314
26127
                            .saturating_accrue(Self::on_author_noted(orchestrator_chain_author));
315
26127
                    }
316
                }
317
4010
            }
318
30137
            total_weight
319
30137
        }
320
    }
321

            
322
    impl<T: Config> Pallet<T> {
323
        /// Internal function to set the activity tracking status and
324
        /// clear ActiveCollatorsForCurrentSession if disabled
325
17
        fn set_inactivity_tracking_status_inner(
326
17
            current_session_index: SessionIndex,
327
17
            enable_inactivity_tracking: bool,
328
17
        ) {
329
17
            let new_status_end_session_index =
330
17
                current_session_index.saturating_add(T::MaxInactiveSessions::get());
331
17
            let new_status = if enable_inactivity_tracking {
332
5
                ActivityTrackingStatus::Enabled {
333
5
                    start: current_session_index.saturating_add(1),
334
5
                    end: new_status_end_session_index,
335
5
                }
336
            } else {
337
12
                <ActiveCollatorsForCurrentSession<T>>::put(BoundedBTreeSet::new());
338
12
                ActivityTrackingStatus::Disabled {
339
12
                    end: new_status_end_session_index,
340
12
                }
341
            };
342
17
            <CurrentActivityTrackingStatus<T>>::put(new_status.clone());
343
17
            Self::deposit_event(Event::<T>::ActivityTrackingStatusSet { status: new_status })
344
17
        }
345

            
346
        /// Internal function to clear the active collators for the current session
347
        /// and remove the collators records that are outside the activity period.
348
        /// Triggered at the beginning of each session.
349
4039
        pub fn process_ended_session() {
350
4039
            let current_session_index = T::CurrentSessionIndex::session_index();
351
4039
            <ActiveCollatorsForCurrentSession<T>>::put(BoundedBTreeSet::new());
352
4039
            <ActiveContainerChainsForCurrentSession<T>>::put(BoundedBTreeSet::new());
353
4039

            
354
4039
            // Cleanup active collator info for sessions that are older than the maximum allowed
355
4039
            if current_session_index > T::MaxInactiveSessions::get() {
356
1529
                <crate::pallet::InactiveCollators<T>>::remove(
357
1529
                    current_session_index
358
1529
                        .saturating_sub(T::MaxInactiveSessions::get())
359
1529
                        .saturating_sub(1),
360
1529
                );
361
3050
            }
362
4039
        }
363

            
364
        /// Internal function to populate the inactivity tracking storage used for marking collator
365
        /// as inactive. Triggered at the end of a session.
366
3044
        pub fn on_before_session_ending() {
367
3044
            let current_session_index = T::CurrentSessionIndex::session_index();
368
3044
            Self::process_inactive_chains_for_session();
369
3044
            match <CurrentActivityTrackingStatus<T>>::get() {
370
16
                ActivityTrackingStatus::Disabled { .. } => return,
371
3028
                ActivityTrackingStatus::Enabled { start, end: _ } => {
372
3028
                    if start > current_session_index {
373
5
                        return;
374
3023
                    }
375
                }
376
            }
377
3023
            if let Ok(inactive_collators) =
378
3023
                BoundedBTreeSet::<Collator<T>, T::MaxCollatorsPerSession>::try_from(
379
3023
                    T::CurrentCollatorsFetcher::get_all_collators_assigned_to_chains(
380
3023
                        ForSession::Current,
381
3023
                    )
382
3023
                    .difference(&<ActiveCollatorsForCurrentSession<T>>::get())
383
3023
                    .cloned()
384
3023
                    .collect::<BTreeSet<Collator<T>>>(),
385
3023
                )
386
3023
            {
387
3023
                InactiveCollators::<T>::insert(current_session_index, inactive_collators);
388
3023
            } else {
389
                // If we reach MaxCollatorsPerSession limit there must be a bug in the pallet
390
                // so we disable the activity tracking
391
                Self::set_inactivity_tracking_status_inner(current_session_index, false);
392
            }
393
3044
        }
394

            
395
        /// Internal function to populate the current session active collator records with collators
396
        /// part of inactive chains.
397
3044
        pub fn process_inactive_chains_for_session() {
398
3044
            match <CurrentActivityTrackingStatus<T>>::get() {
399
15
                ActivityTrackingStatus::Disabled { .. } => return,
400
3029
                ActivityTrackingStatus::Enabled { start, end: _ } => {
401
3029
                    if start > T::CurrentSessionIndex::session_index() {
402
5
                        return;
403
3024
                    }
404
3024
                }
405
3024
            }
406
3024
            let mut active_chains = <ActiveContainerChainsForCurrentSession<T>>::get().into_inner();
407
3024
            // Removing the parathreads for the current session from the active chains array.
408
3024
            // In this way we handle all parathreads as inactive chains.
409
3024
            // This solution would only work if a collator either:
410
3024
            // - is assigned to one chain only
411
3024
            // - is assigned to multiple chains but all of them are parathreads
412
3024
            active_chains = active_chains
413
3024
                .difference(&T::ParaFilter::get_parathreads_for_session())
414
3024
                .cloned()
415
3024
                .collect::<BTreeSet<ParaId>>();
416
3024

            
417
3024
            let _ = <ActiveCollatorsForCurrentSession<T>>::try_mutate(
418
3024
                |active_collators| -> DispatchResult {
419
3024
                    let container_chains_with_collators =
420
3024
                        T::CurrentCollatorsFetcher::container_chains_with_collators(
421
3024
                            ForSession::Current,
422
3024
                        );
423

            
424
5163
                    for (para_id, collator_ids) in container_chains_with_collators.iter() {
425
4956
                        if !active_chains.contains(para_id) {
426
                            // Collators assigned to inactive chain are added
427
                            // to the current active collators storage
428
3766
                            for collator_id in collator_ids {
429
948
                                if active_collators.try_insert(collator_id.clone()).is_err() {
430
                                    // If we reach MaxCollatorsPerSession limit there must be a bug in the pallet
431
                                    // so we disable the activity tracking
432
1
                                    Self::set_inactivity_tracking_status_inner(
433
1
                                        T::CurrentSessionIndex::session_index(),
434
1
                                        false,
435
1
                                    );
436
1
                                    return Err(Error::<T>::MaxCollatorsPerSessionReached.into());
437
947
                                }
438
                            }
439
2137
                        }
440
                    }
441
3023
                    Ok(())
442
3024
                },
443
3024
            );
444
3044
        }
445

            
446
        /// Internal update the current session active collator records.
447
        /// This function is called when a container chain or orchestrator chain collator is noted.
448
48929
        pub fn on_author_noted(author: Collator<T>) -> Weight {
449
48929
            let mut total_weight = T::DbWeight::get().reads_writes(1, 0);
450
48929
            let _ = <ActiveCollatorsForCurrentSession<T>>::try_mutate(
451
48929
                |active_collators| -> DispatchResult {
452
48929
                    if active_collators.try_insert(author.clone()).is_err() {
453
                        // If we reach MaxCollatorsPerSession limit there must be a bug in the pallet
454
                        // so we disable the activity tracking
455
1
                        Self::set_inactivity_tracking_status_inner(
456
1
                            T::CurrentSessionIndex::session_index(),
457
1
                            false,
458
1
                        );
459
1
                        total_weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
460
1
                        return Err(Error::<T>::MaxCollatorsPerSessionReached.into());
461
48928
                    } else {
462
48928
                        total_weight.saturating_accrue(T::DbWeight::get().writes(1));
463
48928
                    }
464
48928
                    Ok(())
465
48929
                },
466
48929
            );
467
48929
            total_weight
468
48929
        }
469

            
470
        /// Internal update the current session active chains records.
471
        /// This function is called when a container chain is noted.
472
22802
        pub fn on_chain_noted(chain_id: ParaId) -> Weight {
473
22802
            let mut total_weight = T::DbWeight::get().reads_writes(1, 0);
474
22802
            let _ = <ActiveContainerChainsForCurrentSession<T>>::try_mutate(
475
22802
                |active_chains| -> DispatchResult {
476
22802
                    if active_chains.try_insert(chain_id).is_err() {
477
                        // If we reach MaxContainerChains limit there must be a bug in the pallet
478
                        // so we disable the activity tracking
479
1
                        Self::set_inactivity_tracking_status_inner(
480
1
                            T::CurrentSessionIndex::session_index(),
481
1
                            false,
482
1
                        );
483
1
                        total_weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
484
1
                        return Err(Error::<T>::MaxContainerChainsReached.into());
485
22801
                    } else {
486
22801
                        total_weight += T::DbWeight::get().writes(1);
487
22801
                    }
488
22801
                    Ok(())
489
22802
                },
490
22802
            );
491
22802
            total_weight
492
22802
        }
493

            
494
        /// Internal function to mark a collator as offline.
495
20
        pub fn mark_collator_offline(collator: &Collator<T>) -> DispatchResultWithPostInfo {
496
20
            ensure!(
497
20
                <EnableMarkingOffline<T>>::get(),
498
2
                Error::<T>::MarkingOfflineNotEnabled
499
            );
500
18
            ensure!(
501
18
                T::CollatorStakeHelper::is_candidate_selected(collator),
502
2
                Error::<T>::CollatorNotEligibleCandidate
503
            );
504
16
            ensure!(
505
16
                !<OfflineCollators<T>>::get(collator.clone()),
506
2
                Error::<T>::CollatorNotOnline
507
            );
508
14
            ensure!(
509
14
                !T::InvulnerablesFilter::is_invulnerable(collator),
510
2
                Error::<T>::MarkingInvulnerableOfflineInvalid
511
            );
512
12
            <OfflineCollators<T>>::insert(collator.clone(), true);
513
12
            // Updates the SortedEligibleCandidates list. Has to be called after the collator is marked offline.
514
12
            T::CollatorStakeHelper::on_online_status_change(collator, false)?;
515
12
            Self::deposit_event(Event::<T>::CollatorStatusUpdated {
516
12
                collator: collator.clone(),
517
12
                is_offline: true,
518
12
            });
519
12
            Ok(().into())
520
20
        }
521

            
522
4
        pub fn mark_collator_online(collator: &Collator<T>) -> DispatchResultWithPostInfo {
523
4
            ensure!(
524
4
                <OfflineCollators<T>>::get(collator),
525
1
                Error::<T>::CollatorNotOffline
526
            );
527
3
            <OfflineCollators<T>>::remove(collator.clone());
528
3
            // Updates the SortedEligibleCandidates list. Has to be called after the collator is marked online.
529
3
            T::CollatorStakeHelper::on_online_status_change(collator, true)?;
530
3
            Self::deposit_event(Event::<T>::CollatorStatusUpdated {
531
3
                collator: collator.clone(),
532
3
                is_offline: false,
533
3
            });
534
3
            Ok(().into())
535
4
        }
536
    }
537
}
538

            
539
impl<T: Config> NodeActivityTrackingHelper<Collator<T>> for Pallet<T> {
540
47
    fn is_node_inactive(node: &Collator<T>) -> bool {
541
47
        // If inactivity tracking is not enabled all nodes are considered active.
542
47
        // We don't need to check the activity records and can return false
543
47
        // Inactivity tracking is not enabled if
544
47
        // - the status is disabled
545
47
        // - the CurrentSessionIndex < start session + MaxInactiveSessions index since there won't be
546
47
        // sufficient activity records to determine inactivity
547
47
        let current_session_index = T::CurrentSessionIndex::session_index();
548
47
        let minimum_sessions_required = T::MaxInactiveSessions::get();
549
47
        match <CurrentActivityTrackingStatus<T>>::get() {
550
2
            ActivityTrackingStatus::Disabled { .. } => return false,
551
45
            ActivityTrackingStatus::Enabled { start, end: _ } => {
552
45
                if start.saturating_add(minimum_sessions_required) > current_session_index {
553
17
                    return false;
554
28
                }
555
28
            }
556
28
        }
557
28

            
558
28
        let start_session_index = current_session_index.saturating_sub(minimum_sessions_required);
559
76
        for session_index in start_session_index..current_session_index {
560
76
            if !<InactiveCollators<T>>::get(session_index).contains(node) {
561
11
                return false;
562
65
            }
563
        }
564
17
        true
565
47
    }
566
189
    fn is_node_offline(node: &Collator<T>) -> bool {
567
189
        <OfflineCollators<T>>::get(node)
568
189
    }
569

            
570
    #[cfg(feature = "runtime-benchmarks")]
571
    fn make_node_online(node: &Collator<T>) {
572
        let _ = Self::mark_collator_online(node);
573
    }
574

            
575
    #[cfg(feature = "runtime-benchmarks")]
576
    fn make_node_inactive(node: &Collator<T>) {
577
        // First we need to make sure that eniugh sessions had pass
578
        // so the node can be marked as inactive
579
        let max_inactive_sessions = T::MaxInactiveSessions::get();
580
        if T::CurrentSessionIndex::session_index() < max_inactive_sessions {
581
            T::CurrentSessionIndex::skip_to_session(max_inactive_sessions)
582
        }
583

            
584
        // Now we can insert the node as inactive for all sessions in the current inactivity window
585
        let mut inactive_nodes_set: BoundedBTreeSet<
586
            Collator<T>,
587
            <T as Config>::MaxCollatorsPerSession,
588
        > = BoundedBTreeSet::new();
589
        let _ = inactive_nodes_set.try_insert(node.clone());
590
        for session_index in 0..max_inactive_sessions {
591
            <InactiveCollators<T>>::insert(session_index, inactive_nodes_set.clone());
592
        }
593
    }
594
}
595

            
596
impl<T: Config> AuthorNotingHook<Collator<T>> for Pallet<T> {
597
22672
    fn on_container_authors_noted(info: &[AuthorNotingInfo<Collator<T>>]) -> Weight {
598
22672
        let mut total_weight = T::DbWeight::get().reads_writes(1, 0);
599
22670
        if let ActivityTrackingStatus::Enabled { start, end: _ } =
600
22672
            <CurrentActivityTrackingStatus<T>>::get()
601
        {
602
22670
            if start <= T::CurrentSessionIndex::session_index() {
603
45470
                for author_info in info {
604
22802
                    total_weight
605
22802
                        .saturating_accrue(Self::on_author_noted(author_info.author.clone()));
606
22802
                    total_weight.saturating_accrue(Self::on_chain_noted(author_info.para_id));
607
22802
                }
608
2
            }
609
2
        }
610
22672
        total_weight
611
22672
    }
612
    #[cfg(feature = "runtime-benchmarks")]
613
    fn prepare_worst_case_for_bench(_a: &Collator<T>, _b: BlockNumber, _para_id: ParaId) {}
614
}