Files
pezkuwi-subxt/polkadot/runtime/parachains/src/scheduler/tests.rs
T
eskimor 69434d9a32 Coretime Feature branch (relay chain) (#1694)
Also fixes: https://github.com/paritytech/polkadot-sdk/issues/1417

- [x] CoreIndex -> AssignmentProvider mapping will be able to change any
time.
- [x] Implement
- [x] Provide Migrations
- [x] Add and fix tests
- [x] Implement bulk assigner logic
- [x] bulk assigner tests
- [x] Port over current assigner to use bulk designer (+ share on-demand
with bulk): top-level assigner has core ranges: legacy, bulk
- [x] Adjust migrations to reflect new assigner structure
- [x] Move migration code to Assignment code directly and make it
recursive (make it possible to skip releases) -> follow up ticket.
- [x] Test migrations
- [x] Add migration PR to runtimes repo -> follow up ticket.
- [x] Wire up with actual UMP messages
- [x] Write PR docs

---------

Co-authored-by: eskimor <eskimor@no-such-url.com>
Co-authored-by: Bradley Olson <34992650+BradleyOlson64@users.noreply.github.com>
Co-authored-by: BradleyOlson64 <lotrftw9@gmail.com>
Co-authored-by: Anton Vilhelm Ásgeirsson <antonva@users.noreply.github.com>
Co-authored-by: antonva <anton.asgeirsson@parity.io>
Co-authored-by: Bastian Köcher <git@kchr.de>
Co-authored-by: Marcin S. <marcin@realemail.net>
Co-authored-by: Bastian Köcher <info@kchr.de>
Co-authored-by: command-bot <>
2023-12-21 19:06:58 +01:00

1193 lines
35 KiB
Rust

// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use frame_support::assert_ok;
use keyring::Sr25519Keyring;
use primitives::{BlockNumber, SessionIndex, ValidationCode, ValidatorId};
use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet};
use crate::{
configuration::HostConfiguration,
initializer::SessionChangeNotification,
mock::{
new_test_ext, MockAssigner, MockGenesisConfig, Paras, ParasShared, RuntimeOrigin,
Scheduler, System, Test,
},
paras::{ParaGenesisArgs, ParaKind},
scheduler::{common::Assignment, ClaimQueue},
};
fn schedule_blank_para(id: ParaId) {
let validation_code: ValidationCode = vec![1, 2, 3].into();
assert_ok!(Paras::schedule_para_initialize(
id,
ParaGenesisArgs {
genesis_head: Vec::new().into(),
validation_code: validation_code.clone(),
para_kind: ParaKind::Parathread, // This most closely mimics our test assigner
}
));
assert_ok!(Paras::add_trusted_validation_code(RuntimeOrigin::root(), validation_code));
}
fn run_to_block(
to: BlockNumber,
new_session: impl Fn(BlockNumber) -> Option<SessionChangeNotification<BlockNumber>>,
) {
while System::block_number() < to {
let b = System::block_number();
Scheduler::initializer_finalize();
Paras::initializer_finalize(b);
if let Some(notification) = new_session(b + 1) {
let mut notification_with_session_index = notification;
// We will make every session change trigger an action queue. Normally this may require
// 2 or more session changes.
if notification_with_session_index.session_index == SessionIndex::default() {
notification_with_session_index.session_index = ParasShared::scheduled_session();
}
Scheduler::pre_new_session();
Paras::initializer_on_new_session(&notification_with_session_index);
Scheduler::initializer_on_new_session(&notification_with_session_index);
}
System::on_finalize(b);
System::on_initialize(b + 1);
System::set_block_number(b + 1);
Paras::initializer_initialize(b + 1);
Scheduler::initializer_initialize(b + 1);
// In the real runtime this is expected to be called by the `InclusionInherent` pallet.
Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), b + 1);
}
}
fn run_to_end_of_block(
to: BlockNumber,
new_session: impl Fn(BlockNumber) -> Option<SessionChangeNotification<BlockNumber>>,
) {
run_to_block(to, &new_session);
Scheduler::initializer_finalize();
Paras::initializer_finalize(to);
if let Some(notification) = new_session(to + 1) {
Scheduler::pre_new_session();
Paras::initializer_on_new_session(&notification);
Scheduler::initializer_on_new_session(&notification);
}
System::on_finalize(to);
}
fn default_config() -> HostConfiguration<BlockNumber> {
HostConfiguration {
coretime_cores: 3,
group_rotation_frequency: 10,
paras_availability_period: 3,
scheduling_lookahead: 2,
// This field does not affect anything that scheduler does. However, `HostConfiguration`
// is still a subject to consistency test. It requires that
// `minimum_validation_upgrade_delay` is greater than `chain_availability_period` and
// `thread_availability_period`.
minimum_validation_upgrade_delay: 6,
..Default::default()
}
}
fn genesis_config(config: &HostConfiguration<BlockNumber>) -> MockGenesisConfig {
MockGenesisConfig {
configuration: crate::configuration::GenesisConfig { config: config.clone() },
..Default::default()
}
}
fn claimqueue_contains_para_ids<T: Config>(pids: Vec<ParaId>) -> bool {
let set: BTreeSet<ParaId> = ClaimQueue::<T>::get()
.into_iter()
.flat_map(|(_, paras_entries)| paras_entries.into_iter().map(|pe| pe.assignment.para_id()))
.collect();
pids.into_iter().all(|pid| set.contains(&pid))
}
fn availability_cores_contains_para_ids<T: Config>(pids: Vec<ParaId>) -> bool {
let set: BTreeSet<ParaId> = AvailabilityCores::<T>::get()
.into_iter()
.filter_map(|core| match core {
CoreOccupied::Free => None,
CoreOccupied::Paras(entry) => Some(entry.para_id()),
})
.collect();
pids.into_iter().all(|pid| set.contains(&pid))
}
/// Internal access to entries at the top of the claim queue.
fn scheduled_entries() -> impl Iterator<Item = (CoreIndex, ParasEntry<BlockNumberFor<Test>>)> {
let claimqueue = ClaimQueue::<Test>::get();
claimqueue
.into_iter()
.filter_map(|(core_idx, v)| v.front().map(|e| (core_idx, e.clone())))
}
#[test]
fn claimqueue_ttl_drop_fn_works() {
let mut config = default_config();
config.scheduling_lookahead = 3;
let genesis_config = genesis_config(&config);
let para_id = ParaId::from(100);
let core_idx = CoreIndex::from(0);
let mut now = 10;
new_test_ext(genesis_config).execute_with(|| {
let assignment_provider_ttl = MockAssigner::get_provider_config(CoreIndex::from(0)).ttl;
assert!(assignment_provider_ttl == 5);
// Register and run to a blockheight where the para is in a valid state.
schedule_blank_para(para_id);
run_to_block(now, |n| if n == now { Some(Default::default()) } else { None });
// Add a claim on core 0 with a ttl in the past.
let paras_entry = ParasEntry::new(Assignment::Bulk(para_id), now - 5 as u32);
Scheduler::add_to_claimqueue(core_idx, paras_entry.clone());
// Claim is in queue prior to call.
assert!(claimqueue_contains_para_ids::<Test>(vec![para_id]));
// Claim is dropped post call.
Scheduler::drop_expired_claims_from_claimqueue();
assert!(!claimqueue_contains_para_ids::<Test>(vec![para_id]));
// Add a claim on core 0 with a ttl in the future (15).
let paras_entry = ParasEntry::new(Assignment::Bulk(para_id), now + 5);
Scheduler::add_to_claimqueue(core_idx, paras_entry.clone());
// Claim is in queue post call.
Scheduler::drop_expired_claims_from_claimqueue();
assert!(claimqueue_contains_para_ids::<Test>(vec![para_id]));
now = now + 6;
run_to_block(now, |_| None);
// Claim is dropped
Scheduler::drop_expired_claims_from_claimqueue();
assert!(!claimqueue_contains_para_ids::<Test>(vec![para_id]));
// Add a claim on core 0 with a ttl == now (16)
let paras_entry = ParasEntry::new(Assignment::Bulk(para_id), now);
Scheduler::add_to_claimqueue(core_idx, paras_entry.clone());
// Claim is in queue post call.
Scheduler::drop_expired_claims_from_claimqueue();
assert!(claimqueue_contains_para_ids::<Test>(vec![para_id]));
now = now + 1;
run_to_block(now, |_| None);
// Drop expired claim.
Scheduler::drop_expired_claims_from_claimqueue();
// Add a claim on core 0 with a ttl == now (17)
let paras_entry_non_expired = ParasEntry::new(Assignment::Bulk(para_id), now);
let paras_entry_expired = ParasEntry::new(Assignment::Bulk(para_id), now - 2);
// ttls = [17, 15, 17]
Scheduler::add_to_claimqueue(core_idx, paras_entry_non_expired.clone());
Scheduler::add_to_claimqueue(core_idx, paras_entry_expired.clone());
Scheduler::add_to_claimqueue(core_idx, paras_entry_non_expired.clone());
let cq = Scheduler::claimqueue();
assert!(cq.get(&core_idx).unwrap().len() == 3);
// Add a claim to the test assignment provider.
let assignment = Assignment::Bulk(para_id);
MockAssigner::add_test_assignment(assignment.clone());
// Drop expired claim.
Scheduler::drop_expired_claims_from_claimqueue();
let cq = Scheduler::claimqueue();
let cqc = cq.get(&core_idx).unwrap();
// Same number of claims
assert!(cqc.len() == 3);
// The first 2 claims in the queue should have a ttl of 17,
// being the ones set up prior in this test as claims 1 and 3.
// The third claim is popped from the assignment provider and
// has a new ttl set by the scheduler of now +
// assignment_provider_ttl. ttls = [17, 17, 22]
assert!(cqc.iter().enumerate().all(|(index, entry)| {
match index {
0 | 1 => entry.clone().ttl == 17,
2 => entry.clone().ttl == 22,
_ => false,
}
}))
});
}
#[test]
fn session_change_shuffles_validators() {
let genesis_config = genesis_config(&default_config());
new_test_ext(genesis_config).execute_with(|| {
// Need five cores for this test
MockAssigner::set_core_count(5);
run_to_block(1, |number| match number {
1 => Some(SessionChangeNotification {
new_config: default_config(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Bob.public()),
ValidatorId::from(Sr25519Keyring::Charlie.public()),
ValidatorId::from(Sr25519Keyring::Dave.public()),
ValidatorId::from(Sr25519Keyring::Eve.public()),
ValidatorId::from(Sr25519Keyring::Ferdie.public()),
ValidatorId::from(Sr25519Keyring::One.public()),
],
random_seed: [99; 32],
..Default::default()
}),
_ => None,
});
let groups = ValidatorGroups::<Test>::get();
assert_eq!(groups.len(), 5);
// first two groups have the overflow.
for i in 0..2 {
assert_eq!(groups[i].len(), 2);
}
for i in 2..5 {
assert_eq!(groups[i].len(), 1);
}
});
}
#[test]
fn session_change_takes_only_max_per_core() {
let config = {
let mut config = default_config();
config.max_validators_per_core = Some(1);
config
};
let genesis_config = genesis_config(&config);
new_test_ext(genesis_config).execute_with(|| {
// Simulate 2 cores between all usage types
MockAssigner::set_core_count(2);
run_to_block(1, |number| match number {
1 => Some(SessionChangeNotification {
new_config: config.clone(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Bob.public()),
ValidatorId::from(Sr25519Keyring::Charlie.public()),
ValidatorId::from(Sr25519Keyring::Dave.public()),
ValidatorId::from(Sr25519Keyring::Eve.public()),
ValidatorId::from(Sr25519Keyring::Ferdie.public()),
ValidatorId::from(Sr25519Keyring::One.public()),
],
random_seed: [99; 32],
..Default::default()
}),
_ => None,
});
let groups = ValidatorGroups::<Test>::get();
assert_eq!(groups.len(), 7);
// Every validator gets its own group, even though there are 2 cores.
for i in 0..7 {
assert_eq!(groups[i].len(), 1);
}
});
}
#[test]
fn fill_claimqueue_fills() {
let genesis_config = genesis_config(&default_config());
let para_a = ParaId::from(3_u32);
let para_b = ParaId::from(4_u32);
let para_c = ParaId::from(5_u32);
let assignment_a = Assignment::Bulk(para_a);
let assignment_b = Assignment::Bulk(para_b);
let assignment_c = Assignment::Bulk(para_c);
new_test_ext(genesis_config).execute_with(|| {
MockAssigner::set_core_count(2);
let AssignmentProviderConfig { ttl: config_ttl, .. } =
MockAssigner::get_provider_config(CoreIndex(0));
// Add 3 paras
schedule_blank_para(para_a);
schedule_blank_para(para_b);
schedule_blank_para(para_c);
// start a new session to activate, 3 validators for 3 cores.
run_to_block(1, |number| match number {
1 => Some(SessionChangeNotification {
new_config: default_config(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Bob.public()),
ValidatorId::from(Sr25519Keyring::Charlie.public()),
],
..Default::default()
}),
_ => None,
});
// add some para assignments.
MockAssigner::add_test_assignment(assignment_a.clone());
MockAssigner::add_test_assignment(assignment_b.clone());
MockAssigner::add_test_assignment(assignment_c.clone());
run_to_block(2, |_| None);
{
assert_eq!(Scheduler::claimqueue_len(), 3);
let scheduled: BTreeMap<_, _> = scheduled_entries().collect();
// Was added a block later, note the TTL.
assert_eq!(
scheduled.get(&CoreIndex(0)).unwrap(),
&ParasEntry {
assignment: assignment_a.clone(),
availability_timeouts: 0,
ttl: 2 + config_ttl
},
);
// Sits on the same core as `para_a`
assert_eq!(
Scheduler::claimqueue().get(&CoreIndex(0)).unwrap()[1],
ParasEntry {
assignment: assignment_b.clone(),
availability_timeouts: 0,
ttl: 2 + config_ttl
}
);
assert_eq!(
scheduled.get(&CoreIndex(1)).unwrap(),
&ParasEntry {
assignment: assignment_c.clone(),
availability_timeouts: 0,
ttl: 2 + config_ttl
},
);
}
});
}
#[test]
fn schedule_schedules_including_just_freed() {
let mut config = default_config();
// NOTE: This test expects on demand cores to each get slotted on to a different core
// and not fill up the claimqueue of each core first.
config.scheduling_lookahead = 1;
let genesis_config = genesis_config(&config);
let para_a = ParaId::from(3_u32);
let para_b = ParaId::from(4_u32);
let para_c = ParaId::from(5_u32);
let para_d = ParaId::from(6_u32);
let para_e = ParaId::from(7_u32);
let assignment_a = Assignment::Bulk(para_a);
let assignment_b = Assignment::Bulk(para_b);
let assignment_c = Assignment::Bulk(para_c);
let assignment_d = Assignment::Bulk(para_d);
let assignment_e = Assignment::Bulk(para_e);
new_test_ext(genesis_config).execute_with(|| {
MockAssigner::set_core_count(3);
// add 5 paras
schedule_blank_para(para_a);
schedule_blank_para(para_b);
schedule_blank_para(para_c);
schedule_blank_para(para_d);
schedule_blank_para(para_e);
// start a new session to activate, 3 validators for 3 cores.
run_to_block(1, |number| match number {
1 => Some(SessionChangeNotification {
new_config: default_config(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Bob.public()),
ValidatorId::from(Sr25519Keyring::Charlie.public()),
],
..Default::default()
}),
_ => None,
});
// add a couple of para claims now that paras are live
MockAssigner::add_test_assignment(assignment_a.clone());
MockAssigner::add_test_assignment(assignment_c.clone());
let mut now = 2;
run_to_block(now, |_| None);
assert_eq!(Scheduler::scheduled_paras().collect::<Vec<_>>().len(), 2);
// cores 0, 1 should be occupied. mark them as such.
let mut occupied_map: BTreeMap<CoreIndex, ParaId> = BTreeMap::new();
occupied_map.insert(CoreIndex(0), para_a);
occupied_map.insert(CoreIndex(1), para_c);
Scheduler::occupied(occupied_map);
{
let cores = AvailabilityCores::<Test>::get();
// cores 0, 1 are `CoreOccupied::Paras(ParasEntry...)`
assert!(cores[0] != CoreOccupied::Free);
assert!(cores[1] != CoreOccupied::Free);
// core 2 is free
assert!(cores[2] == CoreOccupied::Free);
assert!(Scheduler::scheduled_paras().collect::<Vec<_>>().is_empty());
// All `core_queue`s should be empty
Scheduler::claimqueue()
.iter()
.for_each(|(_core_idx, core_queue)| assert!(core_queue.len() == 0))
}
// add a couple more para claims - the claim on `b` will go to the 3rd core
// (2) and the claim on `d` will go back to the 1st para core (0). The claim on `e`
// then will go for core `1`.
MockAssigner::add_test_assignment(assignment_b.clone());
MockAssigner::add_test_assignment(assignment_d.clone());
MockAssigner::add_test_assignment(assignment_e.clone());
now = 3;
run_to_block(now, |_| None);
{
let scheduled: BTreeMap<_, _> = scheduled_entries().collect();
// cores 0 and 1 are occupied by claims. core 2 was free.
assert_eq!(scheduled.len(), 1);
assert_eq!(
scheduled.get(&CoreIndex(2)).unwrap(),
&ParasEntry {
assignment: Assignment::Bulk(para_b),
availability_timeouts: 0,
ttl: 8
},
);
}
// now note that cores 0 and 1 were freed.
let just_updated: BTreeMap<CoreIndex, FreedReason> = vec![
(CoreIndex(0), FreedReason::Concluded),
(CoreIndex(1), FreedReason::TimedOut), // should go back on queue.
]
.into_iter()
.collect();
Scheduler::free_cores_and_fill_claimqueue(just_updated, now);
{
let scheduled: BTreeMap<_, _> = scheduled_entries().collect();
// 1 thing scheduled before, + 2 cores freed.
assert_eq!(scheduled.len(), 3);
assert_eq!(
scheduled.get(&CoreIndex(0)).unwrap(),
&ParasEntry {
assignment: Assignment::Bulk(para_d),
availability_timeouts: 0,
ttl: 8
},
);
// Although C was descheduled, the core `2` was occupied so C goes back to the queue.
assert_eq!(
scheduled.get(&CoreIndex(1)).unwrap(),
&ParasEntry {
assignment: Assignment::Bulk(para_c),
availability_timeouts: 1,
ttl: 8
},
);
assert_eq!(
scheduled.get(&CoreIndex(2)).unwrap(),
&ParasEntry {
assignment: Assignment::Bulk(para_b),
availability_timeouts: 0,
ttl: 8
},
);
// Para A claim should have been wiped, but para C claim should remain.
assert!(!claimqueue_contains_para_ids::<Test>(vec![para_a]));
assert!(claimqueue_contains_para_ids::<Test>(vec![para_c]));
assert!(!availability_cores_contains_para_ids::<Test>(vec![para_a, para_c]));
}
});
}
#[test]
fn schedule_clears_availability_cores() {
let mut config = default_config();
config.scheduling_lookahead = 1;
let genesis_config = genesis_config(&config);
let para_a = ParaId::from(1_u32);
let para_b = ParaId::from(2_u32);
let para_c = ParaId::from(3_u32);
let assignment_a = Assignment::Bulk(para_a);
let assignment_b = Assignment::Bulk(para_b);
let assignment_c = Assignment::Bulk(para_c);
new_test_ext(genesis_config).execute_with(|| {
MockAssigner::set_core_count(3);
// register 3 paras
schedule_blank_para(para_a);
schedule_blank_para(para_b);
schedule_blank_para(para_c);
// Adding assignments then running block to populate claim queue
MockAssigner::add_test_assignment(assignment_a.clone());
MockAssigner::add_test_assignment(assignment_b.clone());
MockAssigner::add_test_assignment(assignment_c.clone());
// start a new session to activate, 3 validators for 3 cores.
run_to_block(1, |number| match number {
1 => Some(SessionChangeNotification {
new_config: config.clone(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Bob.public()),
ValidatorId::from(Sr25519Keyring::Charlie.public()),
],
..Default::default()
}),
_ => None,
});
run_to_block(2, |_| None);
assert_eq!(Scheduler::claimqueue().len(), 3);
// cores 0, 1, and 2 should be occupied. mark them as such.
Scheduler::occupied(
vec![(CoreIndex(0), para_a), (CoreIndex(1), para_b), (CoreIndex(2), para_c)]
.into_iter()
.collect(),
);
{
let cores = AvailabilityCores::<Test>::get();
assert_eq!(cores[0].is_free(), false);
assert_eq!(cores[1].is_free(), false);
assert_eq!(cores[2].is_free(), false);
// All `core_queue`s should be empty
Scheduler::claimqueue()
.iter()
.for_each(|(_core_idx, core_queue)| assert!(core_queue.len() == 0))
}
// Add more assignments
MockAssigner::add_test_assignment(assignment_a.clone());
MockAssigner::add_test_assignment(assignment_c.clone());
run_to_block(3, |_| None);
// now note that cores 0 and 2 were freed.
Scheduler::free_cores_and_fill_claimqueue(
vec![(CoreIndex(0), FreedReason::Concluded), (CoreIndex(2), FreedReason::Concluded)]
.into_iter()
.collect::<Vec<_>>(),
3,
);
{
let claimqueue = ClaimQueue::<Test>::get();
let claimqueue_0 = claimqueue.get(&CoreIndex(0)).unwrap().clone();
let claimqueue_2 = claimqueue.get(&CoreIndex(2)).unwrap().clone();
let entry_ttl = 8;
assert_eq!(claimqueue_0.len(), 1);
assert_eq!(claimqueue_2.len(), 1);
let queue_0_expectation: VecDeque<ParasEntryType<Test>> =
vec![ParasEntry::new(assignment_a, entry_ttl as u32)].into_iter().collect();
let queue_2_expectation: VecDeque<ParasEntryType<Test>> =
vec![ParasEntry::new(assignment_c, entry_ttl as u32)].into_iter().collect();
assert_eq!(claimqueue_0, queue_0_expectation);
assert_eq!(claimqueue_2, queue_2_expectation);
// The freed cores should be `Free` in `AvailabilityCores`.
let cores = AvailabilityCores::<Test>::get();
assert!(cores[0].is_free());
assert!(cores[2].is_free());
}
});
}
#[test]
fn schedule_rotates_groups() {
let config = {
let mut config = default_config();
config.scheduling_lookahead = 1;
config
};
let rotation_frequency = config.group_rotation_frequency;
let on_demand_cores = 2;
let genesis_config = genesis_config(&config);
let para_a = ParaId::from(1_u32);
let para_b = ParaId::from(2_u32);
let assignment_a = Assignment::Bulk(para_a);
let assignment_b = Assignment::Bulk(para_b);
new_test_ext(genesis_config).execute_with(|| {
MockAssigner::set_core_count(on_demand_cores);
schedule_blank_para(para_a);
schedule_blank_para(para_b);
// start a new session to activate, 2 validators for 2 cores.
run_to_block(1, |number| match number {
1 => Some(SessionChangeNotification {
new_config: config.clone(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Eve.public()),
],
..Default::default()
}),
_ => None,
});
let session_start_block = Scheduler::session_start_block();
assert_eq!(session_start_block, 1);
MockAssigner::add_test_assignment(assignment_a.clone());
MockAssigner::add_test_assignment(assignment_b.clone());
let mut now = 2;
run_to_block(now, |_| None);
let assert_groups_rotated = |rotations: u32, now: &BlockNumberFor<Test>| {
let scheduled: BTreeMap<_, _> = Scheduler::scheduled_paras().collect();
assert_eq!(scheduled.len(), 2);
assert_eq!(
Scheduler::group_assigned_to_core(CoreIndex(0), *now).unwrap(),
GroupIndex((0u32 + rotations) % on_demand_cores)
);
assert_eq!(
Scheduler::group_assigned_to_core(CoreIndex(1), *now).unwrap(),
GroupIndex((1u32 + rotations) % on_demand_cores)
);
};
assert_groups_rotated(0, &now);
// one block before first rotation.
now = rotation_frequency;
run_to_block(rotation_frequency, |_| None);
assert_groups_rotated(0, &now);
// first rotation.
now = now + 1;
run_to_block(now, |_| None);
assert_groups_rotated(1, &now);
// one block before second rotation.
now = rotation_frequency * 2;
run_to_block(now, |_| None);
assert_groups_rotated(1, &now);
// second rotation.
now = now + 1;
run_to_block(now, |_| None);
assert_groups_rotated(2, &now);
});
}
#[test]
fn on_demand_claims_are_pruned_after_timing_out() {
let max_retries = 20;
let mut config = default_config();
config.scheduling_lookahead = 1;
let genesis_config = genesis_config(&config);
let para_a = ParaId::from(1_u32);
let assignment_a = Assignment::Bulk(para_a);
new_test_ext(genesis_config).execute_with(|| {
MockAssigner::set_core_count(2);
// Need more timeouts for this test
MockAssigner::set_assignment_provider_config(AssignmentProviderConfig {
max_availability_timeouts: max_retries,
ttl: BlockNumber::from(5u32),
});
schedule_blank_para(para_a);
// #1
let mut now = 1;
run_to_block(now, |number| match number {
1 => Some(SessionChangeNotification {
new_config: default_config(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Eve.public()),
],
..Default::default()
}),
_ => None,
});
MockAssigner::add_test_assignment(assignment_a.clone());
// #2
now += 1;
run_to_block(now, |_| None);
assert_eq!(Scheduler::claimqueue().len(), 1);
// ParaId a is in the claimqueue.
assert!(claimqueue_contains_para_ids::<Test>(vec![para_a]));
Scheduler::occupied(vec![(CoreIndex(0), para_a)].into_iter().collect());
// ParaId a is no longer in the claimqueue.
assert!(!claimqueue_contains_para_ids::<Test>(vec![para_a]));
// It is in availability cores.
assert!(availability_cores_contains_para_ids::<Test>(vec![para_a]));
// #3
now += 1;
// Run to block #n over the max_retries value.
// In this case, both validator groups with time out on availability and
// the assignment will be dropped.
for n in now..=(now + max_retries + 1) {
// #n
run_to_block(n, |_| None);
// Time out on core 0.
let just_updated: BTreeMap<CoreIndex, FreedReason> = vec![
(CoreIndex(0), FreedReason::TimedOut), // should go back on queue.
]
.into_iter()
.collect();
Scheduler::free_cores_and_fill_claimqueue(just_updated, now);
// ParaId a exists in the claim queue until max_retries is reached.
if n < max_retries + now {
assert!(claimqueue_contains_para_ids::<Test>(vec![para_a]));
} else {
assert!(!claimqueue_contains_para_ids::<Test>(vec![para_a]));
}
let core_assignments = Scheduler::scheduled_paras().collect();
Scheduler::occupied(core_assignments);
}
// ParaId a does not exist in the claimqueue/availability_cores after
// threshold has been reached.
assert!(!claimqueue_contains_para_ids::<Test>(vec![para_a]));
assert!(!availability_cores_contains_para_ids::<Test>(vec![para_a]));
// #25
now += max_retries + 2;
// Add assignment back to the mix.
MockAssigner::add_test_assignment(assignment_a.clone());
run_to_block(now, |_| None);
assert!(claimqueue_contains_para_ids::<Test>(vec![para_a]));
// #26
now += 1;
// Run to block #n but this time have group 1 conclude the availabilty.
for n in now..=(now + max_retries + 1) {
// #n
run_to_block(n, |_| None);
// Time out core 0 if group 0 is assigned to it, if group 1 is assigned, conclude.
let mut just_updated: BTreeMap<CoreIndex, FreedReason> = BTreeMap::new();
if let Some(group) = Scheduler::group_assigned_to_core(CoreIndex(0), n) {
match group {
GroupIndex(0) => {
just_updated.insert(CoreIndex(0), FreedReason::TimedOut); // should go back on queue.
},
GroupIndex(1) => {
just_updated.insert(CoreIndex(0), FreedReason::Concluded);
},
_ => panic!("Should only have 2 groups here"),
}
}
Scheduler::free_cores_and_fill_claimqueue(just_updated, now);
// ParaId a exists in the claim queue until groups are rotated.
if n < 31 {
assert!(claimqueue_contains_para_ids::<Test>(vec![para_a]));
} else {
assert!(!claimqueue_contains_para_ids::<Test>(vec![para_a]));
}
let core_assignments = Scheduler::scheduled_paras().collect();
Scheduler::occupied(core_assignments);
}
// ParaId a does not exist in the claimqueue/availability_cores after
// being concluded
assert!(!claimqueue_contains_para_ids::<Test>(vec![para_a]));
assert!(!availability_cores_contains_para_ids::<Test>(vec![para_a]));
});
}
#[test]
fn availability_predicate_works() {
let genesis_config = genesis_config(&default_config());
let HostConfiguration { group_rotation_frequency, paras_availability_period, .. } =
default_config();
assert!(paras_availability_period < group_rotation_frequency);
new_test_ext(genesis_config).execute_with(|| {
run_to_block(1 + paras_availability_period, |_| None);
assert!(!Scheduler::availability_timeout_check_required());
run_to_block(1 + group_rotation_frequency, |_| None);
{
let now = System::block_number();
assert!(Scheduler::availability_timeout_check_required());
let pred = Scheduler::availability_timeout_predicate();
let last_rotation = Scheduler::group_rotation_info(now).last_rotation_at();
let would_be_timed_out = now - paras_availability_period;
let should_not_be_timed_out = last_rotation;
assert!(pred(would_be_timed_out).timed_out);
assert!(!pred(should_not_be_timed_out).timed_out);
assert!(!pred(now).timed_out);
// check the threshold is exact.
assert!(!pred(would_be_timed_out + 1).timed_out);
}
});
}
#[test]
fn next_up_on_available_uses_next_scheduled_or_none() {
let genesis_config = genesis_config(&default_config());
let para_a = ParaId::from(1_u32);
let para_b = ParaId::from(2_u32);
new_test_ext(genesis_config).execute_with(|| {
MockAssigner::set_core_count(1);
schedule_blank_para(para_a);
schedule_blank_para(para_b);
// start a new session to activate, 2 validators for 2 cores.
run_to_block(1, |number| match number {
1 => Some(SessionChangeNotification {
new_config: default_config(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Eve.public()),
],
..Default::default()
}),
_ => None,
});
let entry_a = ParasEntry {
assignment: Assignment::Bulk(para_a),
availability_timeouts: 0 as u32,
ttl: 5 as u32,
};
let entry_b = ParasEntry {
assignment: Assignment::Bulk(para_b),
availability_timeouts: 0 as u32,
ttl: 5 as u32,
};
Scheduler::add_to_claimqueue(CoreIndex(0), entry_a.clone());
run_to_block(2, |_| None);
{
assert_eq!(Scheduler::claimqueue_len(), 1);
assert_eq!(Scheduler::availability_cores().len(), 1);
let mut map = BTreeMap::new();
map.insert(CoreIndex(0), para_a);
Scheduler::occupied(map);
let cores = Scheduler::availability_cores();
match &cores[0] {
CoreOccupied::Paras(entry) => assert_eq!(entry, &entry_a),
_ => panic!("There should only be one test assigner core"),
}
assert!(Scheduler::next_up_on_available(CoreIndex(0)).is_none());
Scheduler::add_to_claimqueue(CoreIndex(0), entry_b);
assert_eq!(
Scheduler::next_up_on_available(CoreIndex(0)).unwrap(),
ScheduledCore { para_id: para_b, collator: None }
);
}
});
}
#[test]
fn next_up_on_time_out_reuses_claim_if_nothing_queued() {
let genesis_config = genesis_config(&default_config());
let para_a = ParaId::from(1_u32);
let para_b = ParaId::from(2_u32);
let assignment_a = Assignment::Bulk(para_a);
let assignment_b = Assignment::Bulk(para_b);
new_test_ext(genesis_config).execute_with(|| {
MockAssigner::set_core_count(1);
schedule_blank_para(para_a);
schedule_blank_para(para_b);
// start a new session to activate, 2 validators for 2 cores.
run_to_block(1, |number| match number {
1 => Some(SessionChangeNotification {
new_config: default_config(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Eve.public()),
],
..Default::default()
}),
_ => None,
});
MockAssigner::add_test_assignment(assignment_a.clone());
run_to_block(2, |_| None);
{
assert_eq!(Scheduler::claimqueue().len(), 1);
assert_eq!(Scheduler::availability_cores().len(), 1);
let mut map = BTreeMap::new();
map.insert(CoreIndex(0), para_a);
Scheduler::occupied(map);
let cores = Scheduler::availability_cores();
match cores.get(0).unwrap() {
CoreOccupied::Paras(entry) => {
assert_eq!(entry.assignment, assignment_a.clone());
},
_ => panic!("There should only be a single test assigner core"),
}
// There's nothing more to pop for core 0 from the assignment provider.
assert!(MockAssigner::pop_assignment_for_core(CoreIndex(0)).is_none());
assert_eq!(
Scheduler::next_up_on_time_out(CoreIndex(0)).unwrap(),
ScheduledCore { para_id: para_a, collator: None }
);
MockAssigner::add_test_assignment(assignment_b.clone());
// Pop assignment_b into the claimqueue
Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), 2);
//// Now that there is an earlier next-up, we use that.
assert_eq!(
Scheduler::next_up_on_available(CoreIndex(0)).unwrap(),
ScheduledCore { para_id: para_b, collator: None }
);
}
});
}
#[test]
fn session_change_requires_reschedule_dropping_removed_paras() {
let mut config = default_config();
config.scheduling_lookahead = 1;
let genesis_config = genesis_config(&config);
let para_a = ParaId::from(1_u32);
let para_b = ParaId::from(2_u32);
let assignment_a = Assignment::Bulk(para_a);
let assignment_b = Assignment::Bulk(para_b);
new_test_ext(genesis_config).execute_with(|| {
// Setting explicit core count
MockAssigner::set_core_count(5);
let assignment_provider_ttl = MockAssigner::get_provider_config(CoreIndex::from(0)).ttl;
schedule_blank_para(para_a);
schedule_blank_para(para_b);
// Add assignments
MockAssigner::add_test_assignment(assignment_a.clone());
MockAssigner::add_test_assignment(assignment_b.clone());
run_to_block(1, |number| match number {
1 => Some(SessionChangeNotification {
new_config: default_config(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Bob.public()),
ValidatorId::from(Sr25519Keyring::Charlie.public()),
ValidatorId::from(Sr25519Keyring::Dave.public()),
ValidatorId::from(Sr25519Keyring::Eve.public()),
ValidatorId::from(Sr25519Keyring::Ferdie.public()),
ValidatorId::from(Sr25519Keyring::One.public()),
],
random_seed: [99; 32],
..Default::default()
}),
_ => None,
});
assert_eq!(Scheduler::claimqueue().len(), 2);
let groups = ValidatorGroups::<Test>::get();
assert_eq!(groups.len(), 5);
assert_ok!(Paras::schedule_para_cleanup(para_b));
// Add assignment
MockAssigner::add_test_assignment(assignment_a.clone());
run_to_end_of_block(2, |number| match number {
2 => Some(SessionChangeNotification {
new_config: default_config(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Bob.public()),
ValidatorId::from(Sr25519Keyring::Charlie.public()),
ValidatorId::from(Sr25519Keyring::Dave.public()),
ValidatorId::from(Sr25519Keyring::Eve.public()),
ValidatorId::from(Sr25519Keyring::Ferdie.public()),
ValidatorId::from(Sr25519Keyring::One.public()),
],
random_seed: [99; 32],
..Default::default()
}),
_ => None,
});
Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), 3);
assert_eq!(
Scheduler::claimqueue(),
vec![(
CoreIndex(0),
vec![ParasEntry::new(
Assignment::Bulk(para_a),
// At end of block 2
assignment_provider_ttl + 2
)]
.into_iter()
.collect()
)]
.into_iter()
.collect()
);
// Add para back
schedule_blank_para(para_b);
// Add assignments
MockAssigner::add_test_assignment(assignment_a.clone());
MockAssigner::add_test_assignment(assignment_b.clone());
run_to_block(3, |number| match number {
3 => Some(SessionChangeNotification {
new_config: default_config(),
validators: vec![
ValidatorId::from(Sr25519Keyring::Alice.public()),
ValidatorId::from(Sr25519Keyring::Bob.public()),
ValidatorId::from(Sr25519Keyring::Charlie.public()),
ValidatorId::from(Sr25519Keyring::Dave.public()),
ValidatorId::from(Sr25519Keyring::Eve.public()),
ValidatorId::from(Sr25519Keyring::Ferdie.public()),
ValidatorId::from(Sr25519Keyring::One.public()),
],
random_seed: [99; 32],
..Default::default()
}),
_ => None,
});
assert_eq!(Scheduler::claimqueue().len(), 2);
let groups = ValidatorGroups::<Test>::get();
assert_eq!(groups.len(), 5);
Scheduler::free_cores_and_fill_claimqueue(BTreeMap::new(), 4);
assert_eq!(
Scheduler::claimqueue(),
vec![
(
CoreIndex(0),
vec![ParasEntry::new(
Assignment::Bulk(para_a),
// At block 3
assignment_provider_ttl + 3
)]
.into_iter()
.collect()
),
(
CoreIndex(1),
vec![ParasEntry::new(
Assignment::Bulk(para_b),
// At block 3
assignment_provider_ttl + 3
)]
.into_iter()
.collect()
),
]
.into_iter()
.collect()
);
});
}