diff --git a/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md b/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md index ab8089a647..ead981b6d6 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md @@ -175,11 +175,12 @@ Actions: 1. Set `SessionStartBlock` to current block number. 1. Clear all `Some` members of `AvailabilityCores`. Return all parathread claims to queue with retries un-incremented. 1. Set `configuration = Configuration::configuration()` (see [`HostConfiguration`](../types/runtime.md#host-configuration)) -1. Resize `AvailabilityCores` to have length `Paras::parachains().len() + configuration.parathread_cores with all`None` entries. +1. Resize `AvailabilityCores` to have length `Paras::parachains().len() + configuration.parathread_cores with all `None` entries. 1. Compute new validator groups by shuffling using a secure randomness beacon - We need a total of `N = Paras::parachains().len() + configuration.parathread_cores` validator groups. - - The total number of validators `V` in the `SessionChangeNotification`'s `validators` may not be evenly divided by `V`. - First, we obtain "shuffled validators" `SV` by shuffling the validators using the `SessionChangeNotification`'s random seed. + - Then, we truncate `SV` to have at most `configuration.max_validators_per_core * N` members, if `configuration.max_validators_per_core` is `Some`. + - Note that the total number of validators `V` in `SV` may not be evenly divided by `N`. - The groups are selected by partitioning `SV`. The first V % N groups will have (V / N) + 1 members, while the remaining groups will have (V / N) members each. 1. Prune the parathread queue to remove all retries beyond `configuration.parathread_retries`. - Also prune all parathread claims corresponding to de-registered parathreads. diff --git a/polkadot/roadmap/implementers-guide/src/types/runtime.md b/polkadot/roadmap/implementers-guide/src/types/runtime.md index 7900a6edab..332860d470 100644 --- a/polkadot/roadmap/implementers-guide/src/types/runtime.md +++ b/polkadot/roadmap/implementers-guide/src/types/runtime.md @@ -34,6 +34,8 @@ struct HostConfiguration { pub thread_availability_period: BlockNumber, /// The amount of blocks ahead to schedule parathreads. pub scheduling_lookahead: u32, + /// The maximum number of validators to have per core. `None` means no maximum. + pub max_validators_per_core: Option, /// The amount of sessions to keep for disputes. pub dispute_period: SessionIndex, /// The amount of consensus slots that must pass between submitting an assignment and diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index 159caadabb..2c413b7d61 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -61,6 +61,8 @@ pub struct HostConfiguration { pub thread_availability_period: BlockNumber, /// The amount of blocks ahead to schedule parachains and parathreads. pub scheduling_lookahead: u32, + /// The maximum number of validators to have per core. `None` means no maximum. + pub max_validators_per_core: Option, /// The amount of sessions to keep for disputes. pub dispute_period: SessionIndex, /// The amount of consensus slots that must pass between submitting an assignment and @@ -271,6 +273,16 @@ decl_module! { Ok(()) } + /// Set the maximum number of validators to assign to any core. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_max_validators_per_core(origin, new: Option) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.max_validators_per_core, new) != new + }); + Ok(()) + } + /// Set the dispute period, in number of sessions to keep for disputes. #[weight = (1_000, DispatchClass::Operational)] pub fn set_dispute_period(origin, new: SessionIndex) -> DispatchResult { @@ -582,6 +594,7 @@ mod tests { chain_availability_period: 10, thread_availability_period: 8, scheduling_lookahead: 3, + max_validators_per_core: None, dispute_period: 239, no_show_slots: 240, n_delay_tranches: 241, @@ -645,6 +658,9 @@ mod tests { Configuration::set_scheduling_lookahead( Origin::root(), new_config.scheduling_lookahead, ).unwrap(); + Configuration::set_max_validators_per_core( + Origin::root(), new_config.max_validators_per_core, + ).unwrap(); Configuration::set_dispute_period( Origin::root(), new_config.dispute_period, ).unwrap(); diff --git a/polkadot/runtime/parachains/src/scheduler.rs b/polkadot/runtime/parachains/src/scheduler.rs index 8f4b0d55ef..139e2dfa6a 100644 --- a/polkadot/runtime/parachains/src/scheduler.rs +++ b/polkadot/runtime/parachains/src/scheduler.rs @@ -273,8 +273,17 @@ impl Module { shuffled_indices.shuffle(&mut rng); - let group_base_size = validators.len() / n_cores as usize; - let n_larger_groups = validators.len() % n_cores as usize; + // trim to max per cores. do this after shuffling. + { + if let Some(max_per_core) = config.max_validators_per_core { + let max_total = max_per_core * n_cores; + shuffled_indices.truncate(max_total as usize); + } + } + + let group_base_size = shuffled_indices.len() / n_cores as usize; + let n_larger_groups = shuffled_indices.len() % n_cores as usize; + let groups: Vec> = (0..n_cores).map(|core_id| { let n_members = if (core_id as usize) < n_larger_groups { group_base_size + 1 @@ -1055,6 +1064,68 @@ mod tests { }); } + #[test] + fn session_change_takes_only_max_per_core() { + let config = { + let mut config = default_config(); + config.parathread_cores = 0; + config.max_validators_per_core = Some(1); + config + }; + + let genesis_config = MockGenesisConfig { + configuration: crate::configuration::GenesisConfig { + config: config.clone(), + ..Default::default() + }, + ..Default::default() + }; + + new_test_ext(genesis_config).execute_with(|| { + let chain_a = ParaId::from(1); + let chain_b = ParaId::from(2); + + // ensure that we have 5 groups by registering 2 parachains. + Paras::schedule_para_initialize(chain_a, ParaGenesisArgs { + genesis_head: Vec::new().into(), + validation_code: Vec::new().into(), + parachain: true, + }); + Paras::schedule_para_initialize(chain_b, ParaGenesisArgs { + genesis_head: Vec::new().into(), + validation_code: Vec::new().into(), + parachain: true, + }); + + 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::get(); + assert_eq!(groups.len(), 2); + + // Even though there are 7 validators, only 1 validator per group + // due to the max. + for i in 0..2 { + assert_eq!(groups[i].len(), 1); + } + }); + } + #[test] fn schedule_schedules() { let genesis_config = MockGenesisConfig {