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
#![cfg(test)]
18

            
19
use std::collections::BTreeSet;
20
use {
21
    crate::tests::common::*,
22
    frame_support::{assert_ok, traits::Get, BoundedBTreeSet},
23
    pallet_inactivity_tracking::pallet::{ActiveCollatorsForCurrentSession, InactiveCollators},
24
    parity_scale_codec::Encode,
25
    sp_consensus_aura::AURA_ENGINE_ID,
26
    sp_runtime::{traits::BlakeTwo256, DigestItem},
27
    test_relay_sproof_builder::{HeaderAs, ParaHeaderSproofBuilder, ParaHeaderSproofBuilderItem},
28
    tp_traits::{MaybeSelfChainBlockAuthor, NodeActivityTrackingHelper, ParaId},
29
};
30

            
31
1
fn note_blocks_for_container_chain(para_id: ParaId, start_block: u32, end_block: u32, slot: u64) {
32
1
    // Simulate the inclusion of a block for a container chain
33
1
    let mut sproof = ParaHeaderSproofBuilder::default();
34

            
35
11
    for block_number in start_block..=end_block {
36
11
        let s = ParaHeaderSproofBuilderItem {
37
11
            para_id,
38
11
            author_id: HeaderAs::NonEncoded(sp_runtime::generic::Header::<u32, BlakeTwo256> {
39
11
                parent_hash: Default::default(),
40
11
                number: block_number,
41
11
                state_root: Default::default(),
42
11
                extrinsics_root: Default::default(),
43
11
                digest: sp_runtime::generic::Digest {
44
11
                    logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())],
45
11
                },
46
11
            }),
47
11
        };
48
11
        sproof.items.push(s);
49
11
    }
50
1
    set_author_noting_inherent_data(sproof.clone())
51
1
}
52

            
53
13
fn get_collators_set(
54
13
    collators: Vec<cumulus_primitives_core::relay_chain::AccountId>,
55
13
) -> BoundedBTreeSet<
56
13
    cumulus_primitives_core::relay_chain::AccountId,
57
13
    <Runtime as pallet_inactivity_tracking::Config>::MaxCollatorsPerSession,
58
13
> {
59
13
    let mut collator_set: BoundedBTreeSet<
60
13
        cumulus_primitives_core::relay_chain::AccountId,
61
13
        <Runtime as pallet_inactivity_tracking::Config>::MaxCollatorsPerSession,
62
13
    > = BoundedBTreeSet::new();
63
25
    collators.iter().for_each(|collator| {
64
25
        collator_set.try_insert(collator.clone()).ok();
65
25
    });
66
13
    collator_set
67
13
}
68

            
69
#[test]
70
1
fn inactivity_tracking_correctly_updates_storages_on_orchestrator_chains_author_noting() {
71
1
    ExtBuilder::default()
72
1
        .with_empty_parachains(vec![3000])
73
1
        .with_collators(vec![
74
1
            (AccountId::from(ALICE), 100_000),
75
1
            (AccountId::from(BOB), 100_000),
76
1
            (AccountId::from(CHARLIE), 100_000),
77
1
            (AccountId::from(DAVE), 100_000),
78
1
        ])
79
1
        .build()
80
1
        .execute_with(|| {
81
1
            assert_eq!(<Runtime as pallet_inactivity_tracking::Config>::GetSelfChainBlockAuthor::get_block_author(), Some(ALICE.into()));
82
1
            assert_eq!(
83
1
                <ActiveCollatorsForCurrentSession<Runtime>>::get(),
84
1
                get_collators_set(vec![ALICE.into()])
85
1
            );
86
1
            run_block();
87
1
            assert_eq!(<Runtime as pallet_inactivity_tracking::Config>::GetSelfChainBlockAuthor::get_block_author(), Some(BOB.into()));
88
1
            assert_eq!(
89
1
                <ActiveCollatorsForCurrentSession<Runtime>>::get(),
90
1
                get_collators_set(vec![ALICE.into(), BOB.into()])
91
1
            );
92
1
            run_block();
93
1
            assert_eq!(
94
1
                <ActiveCollatorsForCurrentSession<Runtime>>::get(),
95
1
                get_collators_set(vec![ALICE.into(), BOB.into()])
96
1
            );
97

            
98
1
            run_to_session(1);
99
1
            run_block();
100
1

            
101
1
            assert_eq!(
102
1
                <InactiveCollators<Runtime>>::get(0),
103
1
                get_collators_set(vec![CHARLIE.into(), DAVE.into()])
104
1
            );
105
1
            assert_eq!(
106
1
                <ActiveCollatorsForCurrentSession<Runtime>>::get(),
107
1
                get_collators_set(vec![ALICE.into(), BOB.into()])
108
1
            );
109

            
110
1
            run_to_session(2);
111
1
            run_block();
112
1

            
113
1
            assert_eq!(
114
1
                <InactiveCollators<Runtime>>::get(1),
115
1
                get_collators_set(vec![CHARLIE.into(), DAVE.into()])
116
1
            );
117

            
118
1
            assert_eq!(
119
1
                <ActiveCollatorsForCurrentSession<Runtime>>::get(),
120
1
                get_collators_set(vec![ALICE.into(), BOB.into()])
121
1
            );
122
1
            let max_inactive_sessions =
123
1
                <Runtime as pallet_inactivity_tracking::Config>::MaxInactiveSessions::get();
124
1
            run_to_session(max_inactive_sessions - 1);
125
1
            run_block();
126
1

            
127
1
            assert_eq!(
128
1
                InactivityTracking::is_node_inactive(
129
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(ALICE)
130
1
                ),
131
1
                false
132
1
            );
133
1
            assert_eq!(
134
1
                InactivityTracking::is_node_inactive(
135
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(BOB)
136
1
                ),
137
1
                false
138
1
            );
139
1
            assert_eq!(
140
1
                InactivityTracking::is_node_inactive(
141
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(CHARLIE)
142
1
                ),
143
1
                false
144
1
            );
145
1
            assert_eq!(
146
1
                InactivityTracking::is_node_inactive(
147
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(DAVE)
148
1
                ),
149
1
                false
150
1
            );
151
1
            run_to_session(max_inactive_sessions);
152
1
            assert_eq!(
153
1
                InactivityTracking::is_node_inactive(
154
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(ALICE)
155
1
                ),
156
1
                false
157
1
            );
158
1
            assert_eq!(
159
1
                InactivityTracking::is_node_inactive(
160
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(BOB)
161
1
                ),
162
1
                false
163
1
            );
164
1
            assert_eq!(
165
1
                InactivityTracking::is_node_inactive(
166
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(CHARLIE)
167
1
                ),
168
1
                true
169
1
            );
170
1
            assert_eq!(
171
1
                InactivityTracking::is_node_inactive(
172
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(DAVE)
173
1
                ),
174
1
                true
175
1
            );
176
1
            assert_eq!(<InactiveCollators<Runtime>>::get(0).is_empty(), false);
177
1
            run_to_session(max_inactive_sessions + 1);
178
1
            run_block();
179
1
            assert_eq!(<InactiveCollators<Runtime>>::get(0).is_empty(), true);
180
1
        });
181
1
}
182

            
183
#[test]
184
1
fn inactivity_tracking_correctly_updates_storages_on_container_chains_author_noting() {
185
1
    ExtBuilder::default()
186
1
        .with_empty_parachains(vec![3000])
187
1
        .with_collators(vec![
188
1
            (AccountId::from(ALICE), 100_000),
189
1
            (AccountId::from(BOB), 100_000),
190
1
            (AccountId::from(CHARLIE), 100_000),
191
1
            (AccountId::from(DAVE), 100_000),
192
1
        ])
193
1
        .build()
194
1
        .execute_with(|| {
195
1
            let max_inactive_sessions =
196
1
                <Runtime as pallet_inactivity_tracking::Config>::MaxInactiveSessions::get();
197
1
            assert_ok!(Configuration::set_max_orchestrator_collators(
198
1
                root_origin(),
199
1
                1
200
1
            ));
201
1
            note_blocks_for_container_chain(3000.into(), 1, session_to_block(1), 1);
202
1
            run_block();
203
1
            assert_eq!(
204
1
                <ActiveCollatorsForCurrentSession<Runtime>>::get(),
205
1
                get_collators_set(vec![ALICE.into(), BOB.into(), DAVE.into()])
206
1
            );
207
1
            run_block();
208
1
            assert_eq!(
209
1
                <ActiveCollatorsForCurrentSession<Runtime>>::get(),
210
1
                get_collators_set(vec![ALICE.into(), BOB.into(), DAVE.into()])
211
1
            );
212

            
213
1
            run_to_session(1);
214
1
            run_block();
215
1
            assert_eq!(
216
1
                <InactiveCollators<Runtime>>::get(0),
217
1
                get_collators_set(vec![CHARLIE.into()])
218
1
            );
219
1
            assert_eq!(
220
1
                <ActiveCollatorsForCurrentSession<Runtime>>::get(),
221
1
                get_collators_set(vec![ALICE.into(), BOB.into()])
222
1
            );
223

            
224
1
            run_to_session(2);
225
1
            run_block();
226
1

            
227
1
            assert_eq!(
228
1
                <InactiveCollators<Runtime>>::get(1),
229
1
                get_collators_set(vec![CHARLIE.into(), DAVE.into()])
230
1
            );
231
1
            assert_eq!(
232
1
                <ActiveCollatorsForCurrentSession<Runtime>>::get(),
233
1
                get_collators_set(vec![ALICE.into()])
234
1
            );
235

            
236
1
            run_to_session(max_inactive_sessions - 1);
237
1
            run_block();
238
1
            assert_eq!(
239
1
                InactivityTracking::is_node_inactive(
240
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(ALICE)
241
1
                ),
242
1
                false
243
1
            );
244
1
            assert_eq!(
245
1
                InactivityTracking::is_node_inactive(
246
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(BOB)
247
1
                ),
248
1
                false
249
1
            );
250
1
            assert_eq!(
251
1
                InactivityTracking::is_node_inactive(
252
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(CHARLIE)
253
1
                ),
254
1
                false
255
1
            );
256
1
            assert_eq!(
257
1
                InactivityTracking::is_node_inactive(
258
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(DAVE)
259
1
                ),
260
1
                false
261
1
            );
262
1
            run_to_session(max_inactive_sessions);
263
1
            assert_eq!(
264
1
                InactivityTracking::is_node_inactive(
265
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(ALICE)
266
1
                ),
267
1
                false
268
1
            );
269
1
            assert_eq!(
270
1
                InactivityTracking::is_node_inactive(
271
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(BOB)
272
1
                ),
273
1
                false
274
1
            );
275
1
            assert_eq!(
276
1
                InactivityTracking::is_node_inactive(
277
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(CHARLIE)
278
1
                ),
279
1
                true
280
1
            );
281
1
            assert_eq!(
282
1
                InactivityTracking::is_node_inactive(
283
1
                    &cumulus_primitives_core::relay_chain::AccountId::from(DAVE)
284
1
                ),
285
1
                false
286
1
            );
287
1
            assert_eq!(<InactiveCollators<Runtime>>::get(0).is_empty(), false);
288

            
289
1
            run_to_session(max_inactive_sessions + 1);
290
1
            run_block();
291
1
            assert_eq!(<InactiveCollators<Runtime>>::get(0).is_empty(), true);
292
1
        });
293
1
}
294

            
295
#[test]
296
1
fn inactivity_tracking_edge_case_one_block_per_collator() {
297
1
    // Check that all block authors are marked as active, even on edge cases such as the first or
298
1
    // last block of each session.
299
1

            
300
1
    // Skip test if not compiled with fast-runtime
301
1
    let session_period = crate::Period::get();
302
1
    if session_period > 10 {
303
        println!(
304
            "Skipping test because session period must be 10, is {:?}",
305
            session_period
306
        );
307
        return;
308
1
    }
309
1

            
310
1
    ExtBuilder::default()
311
1
        .with_config(pallet_configuration::HostConfiguration {
312
1
            max_collators: 100,
313
1
            min_orchestrator_collators: 2,
314
1
            max_orchestrator_collators: 100,
315
1
            ..Default::default()
316
1
        })
317
1
        .with_collators(vec![
318
1
            (AccountId::from(ALICE), 100_000),
319
1
            (AccountId::from(BOB), 100_000),
320
1
            (AccountId::from(CHARLIE), 100_000),
321
1
            (AccountId::from(DAVE), 100_000),
322
1
            (AccountId::from([8; 32]), 100_000),
323
1
            (AccountId::from([9; 32]), 100_000),
324
1
            (AccountId::from([10; 32]), 100_000),
325
1
            (AccountId::from([11; 32]), 100_000),
326
1
            (AccountId::from([12; 32]), 100_000),
327
1
            (AccountId::from([13; 32]), 100_000),
328
1
            (AccountId::from([14; 32]), 100_000),
329
1
            (AccountId::from([15; 32]), 100_000),
330
1
            (AccountId::from([16; 32]), 100_000),
331
1
            (AccountId::from([17; 32]), 100_000),
332
1
        ])
333
1
        .build()
334
1
        .execute_with(|| {
335
1
            run_to_session(2);
336
1

            
337
1
            // Confirm that all 14 collators have been assigned to orchestrator chain
338
1
            let collators =
339
1
                pallet_collator_assignment::Pallet::<Runtime>::collator_container_chain();
340
1
            let num_collators = collators.orchestrator_chain.len();
341
1
            assert_eq!(num_collators, 14);
342

            
343
            // Since 1 session = 10 blocks, at most 10 block authors will be marked as active per session.
344
            // In tests we use a simple round robin collator selection, so no collator will produce more
345
            // than one block per sessoin. Thus we can assert that we will see exactly 4 inactive collators
346
            // per session.
347
1
            let inactive_collators = InactiveCollators::<Runtime>::get(1);
348
1
            assert_eq!(
349
1
                inactive_collators.len(),
350
1
                num_collators - session_period as usize,
351
                "{:3}: {} inactive: {:?}",
352
                1,
353
                inactive_collators.len(),
354
                inactive_collators
355
            );
356
1
        });
357
1
}
358

            
359
#[test]
360
1
fn inactivity_tracking_edge_case_inactive_at_session_start() {
361
1
    // Check that an inactive collator can always be marked as inactive, even on edge cases such as the first or
362
1
    // last block of each session.
363
1

            
364
1
    // Skip test if not compiled with fast-runtime
365
1
    let session_period = crate::Period::get();
366
1
    if session_period > 10 {
367
        println!(
368
            "Skipping test because session period must be 10, is {:?}",
369
            session_period
370
        );
371
        return;
372
1
    }
373
1

            
374
1
    ExtBuilder::default()
375
1
        .with_config(pallet_configuration::HostConfiguration {
376
1
            max_collators: 100,
377
1
            min_orchestrator_collators: 2,
378
1
            max_orchestrator_collators: 100,
379
1
            ..Default::default()
380
1
        })
381
1
        .with_collators(vec![
382
1
            (AccountId::from(ALICE), 100_000),
383
1
            (AccountId::from(BOB), 100_000),
384
1
            (AccountId::from(CHARLIE), 100_000),
385
1
            (AccountId::from(DAVE), 100_000),
386
1
        ])
387
1
        .build()
388
1
        .execute_with(|| {
389
1
            let inactivity_period: u32 =
390
1
                <Runtime as pallet_inactivity_tracking::Config>::MaxInactiveSessions::get();
391
1
            run_to_session(2 + inactivity_period);
392

            
393
            // Mock inactive collator by writing directly into storage
394
5
            for s in 2..(2 + inactivity_period) {
395
5
                InactiveCollators::<Runtime>::insert(
396
5
                    s,
397
5
                    BoundedBTreeSet::try_from(BTreeSet::from([AccountId::from(BOB)])).unwrap(),
398
5
                );
399
5
            }
400

            
401
1
            let expected_session_index = Session::current_index();
402
1
            // Since run_block ends a block then starts one the last block of the session
403
1
            // we need to run the loop until session_period - 1
404
1
            for _ in 0..(session_period - 1) {
405
9
                assert_eq!(expected_session_index, Session::current_index());
406
9
                let bob_inactive = pallet_inactivity_tracking::Pallet::<Runtime>::is_node_inactive(
407
9
                    &AccountId::from(BOB),
408
9
                );
409
9
                assert!(bob_inactive);
410
9
                run_block();
411
            }
412

            
413
            // The next block is a new session
414
1
            run_block();
415
1
            let session_index = Session::current_index();
416
1
            assert_eq!(session_index, 8);
417
1
        });
418
1
}