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
    alloc::collections::btree_set::BTreeSet,
29
    frame_support::{
30
        dispatch::DispatchResult, dispatch::DispatchResultWithPostInfo, ensure,
31
        pallet_prelude::Weight,
32
    },
33
    parity_scale_codec::{Decode, Encode},
34
    scale_info::TypeInfo,
35
    serde::{Deserialize, Serialize},
36
    sp_core::{MaxEncodedLen, RuntimeDebug},
37
    sp_runtime::{traits::Get, BoundedBTreeSet},
38
    sp_staking::SessionIndex,
39
    tp_traits::{
40
        AuthorNotingHook, AuthorNotingInfo, ForSession, GetContainerChainsWithCollators,
41
        GetSessionIndex, InvulnerablesHelper, MaybeSelfChainBlockAuthor,
42
        NodeActivityTrackingHelper, ParaId, ParathreadHelper, StakingCandidateHelper,
43
    },
44
};
45

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

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

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

            
55
pub mod weights;
56

            
57
pub use weights::WeightInfo;
58

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
299
66622
    #[pallet::hooks]
300
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
301
30140
        fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
302
30140
            let mut total_weight = T::DbWeight::get().reads_writes(1, 0);
303
            // Process the orchestrator chain block author (if it exists) and activity tracking is enabled
304
30140
            if let Some(orchestrator_chain_author) = T::GetSelfChainBlockAuthor::get_block_author()
305
            {
306
26127
                total_weight.saturating_accrue(T::DbWeight::get().reads(1));
307
26127
                if let ActivityTrackingStatus::Enabled { start, end: _ } =
308
26127
                    <CurrentActivityTrackingStatus<T>>::get()
309
                {
310
26127
                    total_weight.saturating_accrue(T::DbWeight::get().reads(1));
311
26127
                    if start <= T::CurrentSessionIndex::session_index() {
312
26127
                        let authors: &[T::AccountId] = &[orchestrator_chain_author];
313
26127
                        total_weight
314
26127
                            .saturating_accrue(Self::on_authors_noted(authors.iter().cloned()));
315
26127
                    }
316
                }
317
4013
            }
318
30140
            total_weight
319
30140
        }
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
4637
        pub fn process_ended_session() {
350
4637
            let current_session_index = T::CurrentSessionIndex::session_index();
351
4637
            <ActiveCollatorsForCurrentSession<T>>::put(BoundedBTreeSet::new());
352
4637
            <ActiveContainerChainsForCurrentSession<T>>::put(BoundedBTreeSet::new());
353
4637

            
354
4637
            // Cleanup active collator info for sessions that are older than the maximum allowed
355
4637
            if current_session_index > T::MaxInactiveSessions::get() {
356
1595
                <crate::pallet::InactiveCollators<T>>::remove(
357
1595
                    current_session_index
358
1595
                        .saturating_sub(T::MaxInactiveSessions::get())
359
1595
                        .saturating_sub(1),
360
1595
                );
361
3582
            }
362
4637
        }
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
3392
        pub fn on_before_session_ending() {
367
3392
            let current_session_index = T::CurrentSessionIndex::session_index();
368
3392
            Self::process_inactive_chains_for_session();
369
3392
            match <CurrentActivityTrackingStatus<T>>::get() {
370
16
                ActivityTrackingStatus::Disabled { .. } => return,
371
3376
                ActivityTrackingStatus::Enabled { start, end: _ } => {
372
3376
                    if start > current_session_index {
373
5
                        return;
374
3371
                    }
375
                }
376
            }
377
3371
            if let Ok(inactive_collators) =
378
3371
                BoundedBTreeSet::<Collator<T>, T::MaxCollatorsPerSession>::try_from(
379
3371
                    T::CurrentCollatorsFetcher::get_all_collators_assigned_to_chains(
380
3371
                        ForSession::Current,
381
3371
                    )
382
3371
                    .difference(&<ActiveCollatorsForCurrentSession<T>>::get())
383
3371
                    .cloned()
384
3371
                    .collect::<BTreeSet<Collator<T>>>(),
385
3371
                )
386
3371
            {
387
3371
                InactiveCollators::<T>::insert(current_session_index, inactive_collators);
388
3371
            } 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
3392
        }
394

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

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

            
424
5511
                    for (para_id, collator_ids) in container_chains_with_collators.iter() {
425
5265
                        if !active_chains.contains(para_id) {
426
                            // Collators assigned to inactive chain are added
427
                            // to the current active collators storage
428
4447
                            for collator_id in collator_ids {
429
1327
                                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
1326
                                }
438
                            }
439
2144
                        }
440
                    }
441
3371
                    Ok(())
442
3372
                },
443
3372
            );
444
3392
        }
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
48804
        pub fn on_authors_noted(authors: impl IntoIterator<Item = T::AccountId>) -> Weight {
449
48804
            let authors: BTreeSet<_> = authors.into_iter().collect();
450
48804
            if authors.is_empty() {
451
504
                return Weight::zero();
452
48300
            }
453
48300

            
454
48300
            let mut total_weight = T::DbWeight::get().reads(1);
455
48300

            
456
48300
            let result = <ActiveCollatorsForCurrentSession<T>>::try_mutate(|active_collators| {
457
48300
                let mut temp_set: BTreeSet<Collator<T>> =
458
48300
                    core::mem::take(active_collators).into_inner();
459
48300

            
460
48300
                temp_set.extend(authors);
461
48300

            
462
48300
                match BoundedBTreeSet::<Collator<T>, T::MaxCollatorsPerSession>::try_from(temp_set)
463
                {
464
48299
                    Ok(bounded_set) => {
465
48299
                        *active_collators = bounded_set;
466
48299
                        Ok(())
467
                    }
468
1
                    Err(_) => Err(()),
469
                }
470
48300
            });
471
48300

            
472
48300
            match result {
473
                Ok(_) => {
474
48299
                    total_weight.saturating_accrue(T::DbWeight::get().writes(1));
475
48299
                    total_weight
476
                }
477
                Err(_) => {
478
1
                    log::error!(
479
1
                        "Limit of active collators per session reached. Disabling activity tracking."
480
                    );
481
                    // If we reach MaxCollatorsPerSession limit there must be a bug in the pallet
482
                    // so we disable the activity tracking
483
1
                    Self::set_inactivity_tracking_status_inner(
484
1
                        T::CurrentSessionIndex::session_index(),
485
1
                        false,
486
1
                    );
487
1
                    total_weight.saturating_accrue(T::DbWeight::get().writes(2));
488
1
                    total_weight
489
                }
490
            }
491
48804
        }
492

            
493
        /// Internal update the current session active chains records.
494
        /// This function is called when a container chain is noted.
495
22677
        pub fn on_chains_noted(chains: impl IntoIterator<Item = ParaId>) -> Weight {
496
22677
            let chains: BTreeSet<_> = chains.into_iter().collect();
497
22677
            if chains.is_empty() {
498
504
                return Weight::zero();
499
22173
            }
500
22173

            
501
22173
            let mut total_weight = T::DbWeight::get().reads(1);
502
22173

            
503
22173
            let result = <ActiveContainerChainsForCurrentSession<T>>::try_mutate(|active_chains| {
504
22173
                let mut temp_set: BTreeSet<ParaId> = core::mem::take(active_chains).into_inner();
505
22173

            
506
22173
                temp_set.extend(chains);
507
22173

            
508
22173
                match BoundedBTreeSet::<ParaId, T::MaxContainerChains>::try_from(temp_set) {
509
22172
                    Ok(bounded_set) => {
510
22172
                        *active_chains = bounded_set;
511
22172
                        Ok(())
512
                    }
513
1
                    Err(_) => Err(()),
514
                }
515
22173
            });
516
22173

            
517
22173
            match result {
518
22172
                Ok(_) => {
519
22172
                    total_weight.saturating_accrue(T::DbWeight::get().writes(1));
520
22172
                }
521
                Err(_) => {
522
                    // If we reach MaxContainerChains limit there must be a bug in the pallet
523
                    // so we disable the activity tracking
524
1
                    log::error!(
525
1
                        "Limit of active container chains reached. Disabling activity tracking."
526
                    );
527
1
                    Self::set_inactivity_tracking_status_inner(
528
1
                        T::CurrentSessionIndex::session_index(),
529
1
                        false,
530
1
                    );
531
1
                    total_weight.saturating_accrue(T::DbWeight::get().writes(2));
532
                }
533
            }
534

            
535
22173
            total_weight
536
22677
        }
537

            
538
        /// Internal function to mark a collator as offline.
539
22
        pub fn mark_collator_offline(collator: &Collator<T>) -> DispatchResultWithPostInfo {
540
22
            ensure!(
541
22
                <EnableMarkingOffline<T>>::get(),
542
2
                Error::<T>::MarkingOfflineNotEnabled
543
            );
544
20
            ensure!(
545
20
                T::CollatorStakeHelper::is_candidate_selected(collator),
546
2
                Error::<T>::CollatorNotEligibleCandidate
547
            );
548
18
            ensure!(
549
18
                !<OfflineCollators<T>>::get(collator.clone()),
550
2
                Error::<T>::CollatorNotOnline
551
            );
552
16
            ensure!(
553
16
                !T::InvulnerablesFilter::is_invulnerable(collator),
554
2
                Error::<T>::MarkingInvulnerableOfflineInvalid
555
            );
556
14
            <OfflineCollators<T>>::insert(collator.clone(), true);
557
14
            // Updates the SortedEligibleCandidates list. Has to be called after the collator is marked offline.
558
14
            T::CollatorStakeHelper::on_online_status_change(collator, false)?;
559
14
            Self::deposit_event(Event::<T>::CollatorStatusUpdated {
560
14
                collator: collator.clone(),
561
14
                is_offline: true,
562
14
            });
563
14
            Ok(().into())
564
22
        }
565

            
566
5
        pub fn mark_collator_online(collator: &Collator<T>) -> DispatchResultWithPostInfo {
567
5
            ensure!(
568
5
                <OfflineCollators<T>>::get(collator),
569
1
                Error::<T>::CollatorNotOffline
570
            );
571
4
            <OfflineCollators<T>>::remove(collator.clone());
572
4
            // Updates the SortedEligibleCandidates list. Has to be called after the collator is marked online.
573
4
            T::CollatorStakeHelper::on_online_status_change(collator, true)?;
574
4
            Self::deposit_event(Event::<T>::CollatorStatusUpdated {
575
4
                collator: collator.clone(),
576
4
                is_offline: false,
577
4
            });
578
4
            Ok(().into())
579
5
        }
580
    }
581
}
582

            
583
impl<T: Config> NodeActivityTrackingHelper<Collator<T>> for Pallet<T> {
584
55
    fn is_node_inactive(node: &Collator<T>) -> bool {
585
55
        // If inactivity tracking is not enabled all nodes are considered active.
586
55
        // We don't need to check the activity records and can return false
587
55
        // Inactivity tracking is not enabled if
588
55
        // - the status is disabled
589
55
        // - the CurrentSessionIndex < start session + MaxInactiveSessions index since there won't be
590
55
        // sufficient activity records to determine inactivity
591
55
        let current_session_index = T::CurrentSessionIndex::session_index();
592
55
        let minimum_sessions_required = T::MaxInactiveSessions::get();
593
55
        match <CurrentActivityTrackingStatus<T>>::get() {
594
2
            ActivityTrackingStatus::Disabled { .. } => return false,
595
53
            ActivityTrackingStatus::Enabled { start, end: _ } => {
596
53
                if start.saturating_add(minimum_sessions_required) > current_session_index {
597
21
                    return false;
598
32
                }
599
32
            }
600
32
        }
601
32

            
602
32
        let start_session_index = current_session_index.saturating_sub(minimum_sessions_required);
603
82
        for session_index in start_session_index..current_session_index {
604
82
            if !<InactiveCollators<T>>::get(session_index).contains(node) {
605
15
                return false;
606
67
            }
607
        }
608
17
        true
609
55
    }
610
221
    fn is_node_offline(node: &Collator<T>) -> bool {
611
221
        <OfflineCollators<T>>::get(node)
612
221
    }
613

            
614
    #[cfg(feature = "runtime-benchmarks")]
615
    fn make_node_online(node: &Collator<T>) {
616
        let _ = Self::mark_collator_online(node);
617
    }
618

            
619
    #[cfg(feature = "runtime-benchmarks")]
620
    fn make_node_inactive(node: &Collator<T>) {
621
        // First we need to make sure that eniugh sessions had pass
622
        // so the node can be marked as inactive
623
        let max_inactive_sessions = T::MaxInactiveSessions::get();
624
        if T::CurrentSessionIndex::session_index() < max_inactive_sessions {
625
            T::CurrentSessionIndex::skip_to_session(max_inactive_sessions)
626
        }
627

            
628
        // Now we can insert the node as inactive for all sessions in the current inactivity window
629
        let mut inactive_nodes_set: BoundedBTreeSet<
630
            Collator<T>,
631
            <T as Config>::MaxCollatorsPerSession,
632
        > = BoundedBTreeSet::new();
633
        let _ = inactive_nodes_set.try_insert(node.clone());
634
        for session_index in 0..max_inactive_sessions {
635
            <InactiveCollators<T>>::insert(session_index, inactive_nodes_set.clone());
636
        }
637
    }
638
}
639

            
640
impl<T: Config> AuthorNotingHook<Collator<T>> for Pallet<T> {
641
22681
    fn on_container_authors_noted(info: &[AuthorNotingInfo<Collator<T>>]) -> Weight {
642
22681
        let mut total_weight = T::DbWeight::get().reads_writes(1, 0);
643
22679
        if let ActivityTrackingStatus::Enabled { start, end: _ } =
644
22681
            <CurrentActivityTrackingStatus<T>>::get()
645
        {
646
22679
            if start <= T::CurrentSessionIndex::session_index() {
647
22677
                let (authors, chains): (BTreeSet<_>, BTreeSet<_>) = info
648
22677
                    .iter()
649
22677
                    .map(
650
22677
                        |AuthorNotingInfo {
651
                             author, para_id, ..
652
22812
                         }| (author.clone(), *para_id),
653
22677
                    )
654
22677
                    .unzip();
655
22677

            
656
22677
                total_weight.saturating_accrue(Self::on_authors_noted(authors));
657
22677
                total_weight.saturating_accrue(Self::on_chains_noted(chains));
658
22677
            }
659
2
        }
660
22681
        total_weight
661
22681
    }
662
    #[cfg(feature = "runtime-benchmarks")]
663
    fn prepare_worst_case_for_bench(_a: &Collator<T>, _b: BlockNumber, _para_id: ParaId) {}
664
}