diff --git a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md index e5ea1abdb6..fbfbb824f8 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md @@ -33,16 +33,12 @@ bitfields: map ValidatorIndex => AvailabilityBitfield; PendingAvailability: map ParaId => CandidatePendingAvailability; /// The commitments of candidates pending availability, by ParaId. PendingAvailabilityCommitments: map ParaId => CandidateCommitments; - -/// The current validators, by their parachain session keys. -Validators: Vec; ``` ## Session Change 1. Clear out all candidates pending availability. 1. Clear out all validator bitfields. -1. Update `Validators` with the validators from the session change notification. ## Routines diff --git a/polkadot/roadmap/implementers-guide/src/runtime/initializer.md b/polkadot/roadmap/implementers-guide/src/runtime/initializer.md index 05da0f8940..76178b3c52 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/initializer.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/initializer.md @@ -20,6 +20,7 @@ Before initializing modules, remove all changes from the `BufferedSessionChanges The other parachains modules are initialized in this order: 1. Configuration +1. Shared 1. Paras 1. Scheduler 1. Inclusion @@ -29,7 +30,7 @@ The other parachains modules are initialized in this order: 1. UMP 1. HRMP -The [Configuration Module](configuration.md) is first, since all other modules need to operate under the same configuration as each other. It would lead to inconsistency if, for example, the scheduler ran first and then the configuration was updated before the Inclusion module. +The [Configuration Module](configuration.md) is first, since all other modules need to operate under the same configuration as each other. Then the [Shared][shared.md] module is invoked, which determines the set of active validators. It would lead to inconsistency if, for example, the scheduler ran first and then the configuration was updated before the Inclusion module. Set `HasInitialized` to true. diff --git a/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md b/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md index a49ec978d1..68b1a8abb7 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/scheduler.md @@ -149,7 +149,7 @@ enum FreedReason { Storage layout: ```rust -/// All the validator groups. One for each core. +/// All the validator groups. One for each core. Indices are into the `ActiveValidators` storage. ValidatorGroups: Vec>; /// A queue of upcoming claims and which core they should be mapped onto. ParathreadQueue: ParathreadQueue; @@ -178,14 +178,15 @@ Actions: 1. Set `SessionStartBlock` to current block number + 1, as session changes are applied at the end of the block. 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. Fetch `Shared::ActiveValidators` as AV. 1. Determine the number of cores & validator groups as `n_cores`. This is the maximum of 1. `Paras::parachains().len() + configuration.parathread_cores` 1. `n_validators / max_validators_per_core` if `configuration.max_validators_per_core` is `Some` and non-zero. 1. Resize `AvailabilityCores` to have length `n_cores` with all `None` entries. 1. Compute new validator groups by shuffling using a secure randomness beacon - - We obtain "shuffled validators" `SV` by shuffling the validators using the `SessionChangeNotification`'s random seed. - - Note that the total number of validators `V` in `SV` may not be evenly divided by `n_cores`. - - The groups are selected by partitioning `SV`. The first V % N groups will have (V / n_cores) + 1 members, while the remaining groups will have (V / N) members each. + - Note that the total number of validators `V` in AV may not be evenly divided by `n_cores`. + - The groups are selected by partitioning AV. The first V % N groups will have (V / n_cores) + 1 members, while the remaining groups will have (V / N) members each. + - Instead of using the indices within AV, which point to the broader set, indices _into_ AV should be used. This implies that groups should have simply ascending validator indices. 1. Prune the parathread queue to remove all retries beyond `configuration.parathread_retries`. - Also prune all parathread claims corresponding to de-registered parathreads. - all pruned claims should have their entry removed from the parathread index. diff --git a/polkadot/roadmap/implementers-guide/src/runtime/session_info.md b/polkadot/roadmap/implementers-guide/src/runtime/session_info.md index d446a314cf..a37f61af08 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/session_info.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/session_info.md @@ -47,7 +47,7 @@ Sessions: map SessionIndex => Option, ## Session Change 1. Update `EarliestStoredSession` based on `config.dispute_period` and remove all entries from `Sessions` from the previous value up to the new value. -1. Create a new entry in `Sessions` with information about the current session. +1. Create a new entry in `Sessions` with information about the current session. Use `shared::ActiveValidators` to determine the indices into the broader validator sets (validation, assignment, discovery) which are actually used for parachain validation. Only these validators should appear in the `SessionInfo`. ## Routines diff --git a/polkadot/roadmap/implementers-guide/src/runtime/shared.md b/polkadot/roadmap/implementers-guide/src/runtime/shared.md index 7151e6acda..ae538928d5 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/shared.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/shared.md @@ -20,8 +20,14 @@ pub(crate) const SESSION_DELAY: SessionIndex = 2; ## Storage ```rust -// The current session index within the Parachains Runtime system. +/// The current session index within the Parachains Runtime system. CurrentSessionIndex: SessionIndex; +/// All the validators actively participating in parachain consensus. +/// Indices are into the broader validator set. +ActiveValidatorIndices: Vec, +/// The parachain attestation keys of the validators actively participating in parachain consensus. +/// This should be the same length as `ActiveValidatorIndices`. +ActiveValidatorKeys: Vec ``` ## Initialization @@ -35,8 +41,9 @@ them. ## Session Change -During a session change, the Shared Module receives and stores the current Session Index for that -block through the Session Change Notification. +During a session change, the Shared Module receives and stores the current Session Index directly from the initializer module, along with the broader validator set, and it returns the new list of validators. + +The list of validators should be first shuffled according to the chain's random seed and then truncated. The indices of these validators should be set to `ActiveValidatorIndices` and then returned back to the initializer. `ActiveValidatorKeys` should be set accordingly. This information is used in the: diff --git a/polkadot/roadmap/implementers-guide/src/types/runtime.md b/polkadot/roadmap/implementers-guide/src/types/runtime.md index ee0ed60107..fadc34def6 100644 --- a/polkadot/roadmap/implementers-guide/src/types/runtime.md +++ b/polkadot/roadmap/implementers-guide/src/types/runtime.md @@ -36,6 +36,8 @@ struct HostConfiguration { pub scheduling_lookahead: u32, /// The maximum number of validators to have per core. `None` means no maximum. pub max_validators_per_core: Option, + /// The maximum number of validators to use for parachains, in total. `None` means no maximum. + pub max_validators: 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/common/src/paras_registrar.rs b/polkadot/runtime/common/src/paras_registrar.rs index 760678e8a0..e91e43b52d 100644 --- a/polkadot/runtime/common/src/paras_registrar.rs +++ b/polkadot/runtime/common/src/paras_registrar.rs @@ -285,6 +285,7 @@ mod tests { System: frame_system::{Module, Call, Config, Storage, Event}, Balances: pallet_balances::{Module, Call, Storage, Config, Event}, Parachains: paras::{Module, Origin, Call, Storage, Config}, + Shared: shared::{Module, Call, Storage}, Inclusion: inclusion::{Module, Call, Storage, Event}, Registrar: paras_registrar::{Module, Call, Storage}, Staking: pallet_staking::{Module, Call, Config, Storage, Event, ValidateUnsigned}, @@ -466,7 +467,7 @@ mod tests { pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"test"); mod app { - use super::super::Inclusion; + use super::super::Shared; use sp_application_crypto::{app_crypto, sr25519}; app_crypto!(sr25519, super::KEY_TYPE); @@ -476,7 +477,7 @@ mod tests { fn into_account(self) -> Self::AccountId { let id = self.0.clone().into(); - Inclusion::validators().iter().position(|b| *b == id).unwrap() as u64 + Shared::active_validator_keys().iter().position(|b| *b == id).unwrap() as u64 } } } diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index d01e5fc872..b197363b16 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -19,7 +19,7 @@ //! Configuration can change only at session boundaries and is buffered until then. use sp_std::prelude::*; -use primitives::v1::{Balance, ValidatorId, SessionIndex}; +use primitives::v1::{Balance, SessionIndex}; use frame_support::{ decl_storage, decl_module, decl_error, ensure, @@ -146,6 +146,10 @@ pub struct HostConfiguration { /// /// `None` means no maximum. pub max_validators_per_core: Option, + /// The maximum number of valdiators to use for parachain consensus, period. + /// + /// `None` means no maximum. + pub max_validators: 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 @@ -181,6 +185,7 @@ impl> Default for HostConfiguration) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.max_validators, 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 { @@ -648,8 +663,6 @@ impl Module { /// Called by the initializer to note that a new session has started. pub(crate) fn initializer_on_new_session( - _validators: &[ValidatorId], - _queued: &[ValidatorId], session_index: &SessionIndex, ) { if let Some(pending) = ::PendingConfig::take(session_index) { @@ -700,12 +713,12 @@ mod tests { assert_eq!(Configuration::config(), old_config); assert_eq!(::PendingConfig::get(1), None); - Configuration::initializer_on_new_session(&[], &[], &1); + Configuration::initializer_on_new_session(&1); assert_eq!(Configuration::config(), old_config); assert_eq!(::PendingConfig::get(2), Some(config.clone())); - Configuration::initializer_on_new_session(&[], &[], &2); + Configuration::initializer_on_new_session(&2); assert_eq!(Configuration::config(), config); assert_eq!(::PendingConfig::get(3), None); @@ -729,6 +742,7 @@ mod tests { thread_availability_period: 8, scheduling_lookahead: 3, max_validators_per_core: None, + max_validators: None, dispute_period: 239, no_show_slots: 240, n_delay_tranches: 241, @@ -795,6 +809,9 @@ mod tests { Configuration::set_max_validators_per_core( Origin::root(), new_config.max_validators_per_core, ).unwrap(); + Configuration::set_max_validators( + Origin::root(), new_config.max_validators, + ).unwrap(); Configuration::set_dispute_period( Origin::root(), new_config.dispute_period, ).unwrap(); diff --git a/polkadot/runtime/parachains/src/hrmp.rs b/polkadot/runtime/parachains/src/hrmp.rs index e02051a7c2..78535b9c57 100644 --- a/polkadot/runtime/parachains/src/hrmp.rs +++ b/polkadot/runtime/parachains/src/hrmp.rs @@ -1164,7 +1164,12 @@ mod tests { }; // NOTE: this is in initialization order. - Shared::initializer_on_new_session(¬ification); + Shared::initializer_on_new_session( + notification.session_index, + notification.random_seed, + ¬ification.new_config, + notification.validators.clone(), + ); let outgoing_paras = Paras::initializer_on_new_session(¬ification); Hrmp::initializer_on_new_session(¬ification, &outgoing_paras); } diff --git a/polkadot/runtime/parachains/src/inclusion.rs b/polkadot/runtime/parachains/src/inclusion.rs index 275d6cf3d7..7430b5500c 100644 --- a/polkadot/runtime/parachains/src/inclusion.rs +++ b/polkadot/runtime/parachains/src/inclusion.rs @@ -22,7 +22,7 @@ use sp_std::prelude::*; use primitives::v1::{ - ValidatorId, CandidateCommitments, CandidateDescriptor, ValidatorIndex, Id as ParaId, + CandidateCommitments, CandidateDescriptor, ValidatorIndex, Id as ParaId, AvailabilityBitfield as AvailabilityBitfield, SignedAvailabilityBitfields, SigningContext, BackedCandidate, CoreIndex, GroupIndex, CommittedCandidateReceipt, CandidateReceipt, HeadData, CandidateHash, Hash, @@ -134,9 +134,6 @@ decl_storage! { /// The commitments of candidates pending availability, by ParaId. PendingAvailabilityCommitments: map hasher(twox_64_concat) ParaId => Option; - - /// The current validators, by their parachain session keys. - Validators get(fn validators) config(validators): Vec; } } @@ -224,15 +221,13 @@ impl Module { /// Handle an incoming session change. pub(crate) fn initializer_on_new_session( - notification: &crate::initializer::SessionChangeNotification + _notification: &crate::initializer::SessionChangeNotification ) { // unlike most drain methods, drained elements are not cleared on `Drop` of the iterator // and require consumption. for _ in ::drain() { } for _ in >::drain() { } for _ in >::drain() { } - - Validators::set(notification.validators.clone()); // substrate forces us to clone, stupidly. } /// Process a set of incoming bitfields. Return a vec of cores freed by candidates @@ -242,7 +237,7 @@ impl Module { signed_bitfields: SignedAvailabilityBitfields, core_lookup: impl Fn(CoreIndex) -> Option, ) -> Result, DispatchError> { - let validators = Validators::get(); + let validators = shared::Module::::active_validator_keys(); let session_index = shared::Module::::session_index(); let mut assigned_paras_record: Vec<_> = (0..expected_bits) @@ -394,7 +389,7 @@ impl Module { return Ok(Vec::new()); } - let validators = Validators::get(); + let validators = shared::Module::::active_validator_keys(); let parent_hash = >::parent_hash(); // At the moment we assume (and in fact enforce, below) that the relay-parent is always one @@ -900,7 +895,7 @@ mod tests { use primitives::v1::{BlockNumber, Hash}; use primitives::v1::{ SignedAvailabilityBitfield, CompactStatement as Statement, ValidityAttestation, CollatorId, - CandidateCommitments, SignedStatement, CandidateDescriptor, ValidationCode, + CandidateCommitments, SignedStatement, CandidateDescriptor, ValidationCode, ValidatorId, }; use sp_keystore::{SyncCryptoStorePtr, SyncCryptoStore}; use frame_support::traits::{OnFinalize, OnInitialize}; @@ -1039,7 +1034,12 @@ mod tests { Shared::initializer_finalize(); if let Some(notification) = new_session(b + 1) { - Shared::initializer_on_new_session(¬ification); + Shared::initializer_on_new_session( + notification.session_index, + notification.random_seed, + ¬ification.new_config, + notification.validators.clone(), + ); Paras::initializer_on_new_session(¬ification); Inclusion::initializer_on_new_session(¬ification); } @@ -1064,11 +1064,11 @@ mod tests { } fn default_availability_votes() -> BitVec { - bitvec::bitvec![BitOrderLsb0, u8; 0; Validators::get().len()] + bitvec::bitvec![BitOrderLsb0, u8; 0; Shared::active_validator_keys().len()] } fn default_backing_bitfield() -> BitVec { - bitvec::bitvec![BitOrderLsb0, u8; 0; Validators::get().len()] + bitvec::bitvec![BitOrderLsb0, u8; 0; Shared::active_validator_keys().len()] } fn backing_bitfield(v: &[usize]) -> BitVec { @@ -1213,7 +1213,7 @@ mod tests { let validator_public = validator_pubkeys(&validators); new_test_ext(genesis_config(paras)).execute_with(|| { - Validators::set(validator_public.clone()); + shared::Module::::set_active_validators(validator_public.clone()); shared::Module::::set_session_index(5); let signing_context = SigningContext { @@ -1446,7 +1446,7 @@ mod tests { let validator_public = validator_pubkeys(&validators); new_test_ext(genesis_config(paras)).execute_with(|| { - Validators::set(validator_public.clone()); + shared::Module::::set_active_validators(validator_public.clone()); shared::Module::::set_session_index(5); let signing_context = SigningContext { @@ -1611,7 +1611,7 @@ mod tests { let validator_public = validator_pubkeys(&validators); new_test_ext(genesis_config(paras)).execute_with(|| { - Validators::set(validator_public.clone()); + shared::Module::::set_active_validators(validator_public.clone()); shared::Module::::set_session_index(5); run_to_block(5, |_| None); @@ -2098,7 +2098,7 @@ mod tests { let validator_public = validator_pubkeys(&validators); new_test_ext(genesis_config(paras)).execute_with(|| { - Validators::set(validator_public.clone()); + shared::Module::::set_active_validators(validator_public.clone()); shared::Module::::set_session_index(5); run_to_block(5, |_| None); @@ -2295,7 +2295,7 @@ mod tests { let validator_public = validator_pubkeys(&validators); new_test_ext(genesis_config(paras)).execute_with(|| { - Validators::set(validator_public.clone()); + shared::Module::::set_active_validators(validator_public.clone()); shared::Module::::set_session_index(5); run_to_block(5, |_| None); @@ -2372,7 +2372,7 @@ mod tests { } #[test] - fn session_change_wipes_and_updates_session_info() { + fn session_change_wipes() { let chain_a = ParaId::from(1); let chain_b = ParaId::from(2); let thread_a = ParaId::from(3); @@ -2392,7 +2392,7 @@ mod tests { let validator_public = validator_pubkeys(&validators); new_test_ext(genesis_config(paras)).execute_with(|| { - Validators::set(validator_public.clone()); + shared::Module::::set_active_validators(validator_public.clone()); shared::Module::::set_session_index(5); let validators_new = vec![ @@ -2456,7 +2456,6 @@ mod tests { run_to_block(11, |_| None); - assert_eq!(Validators::get(), validator_public); assert_eq!(shared::Module::::session_index(), 5); assert!(>::get(&ValidatorIndex(0)).is_some()); @@ -2480,7 +2479,6 @@ mod tests { _ => None, }); - assert_eq!(Validators::get(), validator_public_new); assert_eq!(shared::Module::::session_index(), 6); assert!(>::get(&ValidatorIndex(0)).is_none()); diff --git a/polkadot/runtime/parachains/src/initializer.rs b/polkadot/runtime/parachains/src/initializer.rs index e0bd620510..8dc1365527 100644 --- a/polkadot/runtime/parachains/src/initializer.rs +++ b/polkadot/runtime/parachains/src/initializer.rs @@ -174,7 +174,7 @@ decl_module! { impl Module { fn apply_new_session( session_index: SessionIndex, - validators: Vec, + all_validators: Vec, queued: Vec, ) { let prev_config = >::config(); @@ -189,10 +189,17 @@ impl Module { // We can't pass the new config into the thing that determines the new config, // so we don't pass the `SessionChangeNotification` into this module. - configuration::Module::::initializer_on_new_session(&validators, &queued, &session_index); + configuration::Module::::initializer_on_new_session(&session_index); let new_config = >::config(); + let validators = shared::Module::::initializer_on_new_session( + session_index, + random_seed.clone(), + &new_config, + all_validators, + ); + let notification = SessionChangeNotification { validators, queued, @@ -202,7 +209,6 @@ impl Module { session_index, }; - shared::Module::::initializer_on_new_session(¬ification); let outgoing_paras = paras::Module::::initializer_on_new_session(¬ification); scheduler::Module::::initializer_on_new_session(¬ification); inclusion::Module::::initializer_on_new_session(¬ification); diff --git a/polkadot/runtime/parachains/src/paras.rs b/polkadot/runtime/parachains/src/paras.rs index f15284ea6c..b6f1b6166c 100644 --- a/polkadot/runtime/parachains/src/paras.rs +++ b/polkadot/runtime/parachains/src/paras.rs @@ -775,7 +775,12 @@ mod tests { if new_session.as_ref().map_or(false, |v| v.contains(&(b + 1))) { let mut session_change_notification = SessionChangeNotification::default(); session_change_notification.session_index = Shared::session_index() + 1; - Shared::initializer_on_new_session(&session_change_notification); + Shared::initializer_on_new_session( + session_change_notification.session_index, + session_change_notification.random_seed, + &session_change_notification.new_config, + session_change_notification.validators.clone(), + ); Paras::initializer_on_new_session(&session_change_notification); } System::on_finalize(b); diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs index ed5f787242..fd9b6ccfa3 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs @@ -32,7 +32,7 @@ use crate::{initializer, inclusion, scheduler, configuration, paras, session_inf /// Implementation for the `validators` function of the runtime API. pub fn validators() -> Vec { - >::validators() + >::active_validator_keys() } /// Implementation for the `validator_groups` function of the runtime API. diff --git a/polkadot/runtime/parachains/src/scheduler.rs b/polkadot/runtime/parachains/src/scheduler.rs index e690f91416..f142cef5be 100644 --- a/polkadot/runtime/parachains/src/scheduler.rs +++ b/polkadot/runtime/parachains/src/scheduler.rs @@ -48,9 +48,6 @@ use frame_support::{ use parity_scale_codec::{Encode, Decode}; use sp_runtime::traits::{One, Saturating}; -use rand::{SeedableRng, seq::SliceRandom}; -use rand_chacha::ChaCha20Rng; - use crate::{configuration, paras, initializer::SessionChangeNotification}; /// A queued parathread entry, pre-assigned to a core. @@ -157,7 +154,9 @@ pub trait Config: frame_system::Config + configuration::Config + paras::Config { decl_storage! { trait Store for Module as ParaScheduler { - /// All the validator groups. One for each core. + /// All the validator groups. One for each core. Indices are into `ActiveValidators` - not the + /// broader set of Polkadot validators, but instead just the subset used for parachains during + /// this session. /// /// Bound: The number of cores is the sum of the numbers of parachains and parathread multiplexers. /// Reasonably, 100-1000. The dominant factor is the number of validators: safe upper bound at 10k. @@ -223,7 +222,6 @@ impl Module { pub(crate) fn initializer_on_new_session(notification: &SessionChangeNotification) { let &SessionChangeNotification { ref validators, - ref random_seed, ref new_config, .. } = notification; @@ -259,27 +257,26 @@ impl Module { if n_cores == 0 || validators.is_empty() { ValidatorGroups::set(Vec::new()); } else { - let mut rng: ChaCha20Rng = SeedableRng::from_seed(*random_seed); + let group_base_size = validators.len() / n_cores as usize; + let n_larger_groups = validators.len() % n_cores as usize; - let mut shuffled_indices: Vec<_> = (0..validators.len()) - .enumerate() - .map(|(i, _)| ValidatorIndex(i as _)) - .collect(); + // Groups contain indices into the validators from the session change notification, + // which are already shuffled. - shuffled_indices.shuffle(&mut rng); + let mut groups: Vec> = Vec::new(); + for i in 0..n_larger_groups { + let offset = (group_base_size + 1) * i; + groups.push( + (0..group_base_size + 1).map(|j| offset + j).map(|j| ValidatorIndex(j as _)).collect() + ); + } - 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 - } else { - group_base_size - }; - - shuffled_indices.drain(shuffled_indices.len() - n_members ..).rev().collect() - }).collect(); + for i in 0..(n_cores as usize - n_larger_groups) { + let offset = (n_larger_groups * (group_base_size + 1)) + (i * group_base_size); + groups.push( + (0..group_base_size).map(|j| offset + j).map(|j| ValidatorIndex(j as _)).collect() + ); + } ValidatorGroups::set(groups); } diff --git a/polkadot/runtime/parachains/src/session_info.rs b/polkadot/runtime/parachains/src/session_info.rs index bc675c86ae..321c2cd0bb 100644 --- a/polkadot/runtime/parachains/src/session_info.rs +++ b/polkadot/runtime/parachains/src/session_info.rs @@ -24,12 +24,14 @@ use frame_support::{ decl_storage, decl_module, decl_error, traits::OneSessionHandler, weights::Weight, }; -use crate::{configuration, paras, scheduler}; +use crate::{configuration, paras, scheduler, shared}; +use crate::util::take_active_subset; use sp_std::vec::Vec; pub trait Config: frame_system::Config + configuration::Config + + shared::Config + paras::Config + scheduler::Config + AuthorityDiscoveryConfig @@ -88,6 +90,8 @@ impl Module { let validators = notification.validators.clone(); let discovery_keys = ::authorities(); let assignment_keys = AssignmentKeysUnsafe::get(); + + let active_set = >::active_validator_indices(); let validator_groups = >::validator_groups(); let n_cores = n_parachains + config.parathread_cores; let zeroth_delay_tranche_width = config.zeroth_delay_tranche_width; @@ -114,9 +118,9 @@ impl Module { } // create a new entry in `Sessions` with information about the current session let new_session_info = SessionInfo { - validators, - discovery_keys, - assignment_keys, + validators: take_active_subset(&active_set, &validators), + discovery_keys: take_active_subset(&active_set, &discovery_keys), + assignment_keys: take_active_subset(&active_set, &assignment_keys), validator_groups, n_cores, zeroth_delay_tranche_width, @@ -186,11 +190,14 @@ mod tests { if let Some(notification) = new_session(b + 1) { Configuration::initializer_on_new_session( - ¬ification.validators, - ¬ification.queued, ¬ification.session_index, ); - Shared::initializer_on_new_session(¬ification); + Shared::initializer_on_new_session( + notification.session_index, + notification.random_seed, + ¬ification.new_config, + notification.validators.clone(), + ); SessionInfo::initializer_on_new_session(¬ification); } diff --git a/polkadot/runtime/parachains/src/shared.rs b/polkadot/runtime/parachains/src/shared.rs index b7b874fbd4..3f89f17f54 100644 --- a/polkadot/runtime/parachains/src/shared.rs +++ b/polkadot/runtime/parachains/src/shared.rs @@ -19,12 +19,17 @@ //! To avoid cyclic dependencies, it is important that this module is not //! dependent on any of the other modules. -use primitives::v1::SessionIndex; +use primitives::v1::{SessionIndex, ValidatorId, ValidatorIndex}; use frame_support::{ decl_storage, decl_module, decl_error, weights::Weight, }; -use crate::initializer::SessionChangeNotification; +use sp_std::vec::Vec; + +use rand::{SeedableRng, seq::SliceRandom}; +use rand_chacha::ChaCha20Rng; + +use crate::configuration::HostConfiguration; pub trait Config: frame_system::Config { } @@ -37,6 +42,12 @@ decl_storage! { trait Store for Module as ParasShared { /// The current session index. CurrentSessionIndex get(fn session_index): SessionIndex; + /// All the validators actively participating in parachain consensus. + /// Indices are into the broader validator set. + ActiveValidatorIndices get(fn active_validator_indices): Vec; + /// The parachain attestation keys of the validators actively participating in parachain consensus. + /// This should be the same length as `ActiveValidatorIndices`. + ActiveValidatorKeys get(fn active_validator_keys): Vec; } } @@ -63,8 +74,35 @@ impl Module { /// Called by the initializer to note that a new session has started. /// /// Returns the list of outgoing paras from the actions queue. - pub(crate) fn initializer_on_new_session(notification: &SessionChangeNotification) { - CurrentSessionIndex::set(notification.session_index); + pub(crate) fn initializer_on_new_session( + session_index: SessionIndex, + random_seed: [u8; 32], + new_config: &HostConfiguration, + all_validators: Vec, + ) -> Vec { + CurrentSessionIndex::set(session_index); + let mut rng: ChaCha20Rng = SeedableRng::from_seed(random_seed); + + let mut shuffled_indices: Vec<_> = (0..all_validators.len()) + .enumerate() + .map(|(i, _)| ValidatorIndex(i as _)) + .collect(); + + shuffled_indices.shuffle(&mut rng); + + if let Some(max) = new_config.max_validators { + shuffled_indices.truncate(max as usize); + } + + let active_validator_keys = crate::util::take_active_subset( + &shuffled_indices, + &all_validators, + ); + + ActiveValidatorIndices::set(shuffled_indices); + ActiveValidatorKeys::set(active_validator_keys.clone()); + + active_validator_keys } /// Return the session index that should be used for any future scheduled changes. @@ -76,4 +114,122 @@ impl Module { pub(crate) fn set_session_index(index: SessionIndex) { CurrentSessionIndex::set(index); } + + #[cfg(test)] + pub(crate) fn set_active_validators(active: Vec) { + ActiveValidatorIndices::set( + (0..active.len()).map(|i| ValidatorIndex(i as _)).collect() + ); + ActiveValidatorKeys::set(active); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::configuration::HostConfiguration; + use crate::mock::{new_test_ext, MockGenesisConfig, Shared}; + use keyring::Sr25519Keyring; + + fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec { + val_ids.iter().map(|v| v.public().into()).collect() + } + + #[test] + fn sets_and_shuffles_validators() { + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Ferdie, + ]; + + let mut config = HostConfiguration::default(); + config.max_validators = None; + + let pubkeys = validator_pubkeys(&validators); + + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let validators = Shared::initializer_on_new_session( + 1, + [1; 32], + &config, + pubkeys, + ); + + assert_eq!( + validators, + validator_pubkeys(&[ + Sr25519Keyring::Ferdie, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Alice, + ]) + ); + + assert_eq!( + Shared::active_validator_keys(), + validators, + ); + + assert_eq!( + Shared::active_validator_indices(), + vec![ + ValidatorIndex(4), + ValidatorIndex(1), + ValidatorIndex(2), + ValidatorIndex(3), + ValidatorIndex(0), + ] + ); + }); + } + + #[test] + fn sets_truncates_and_shuffles_validators() { + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Ferdie, + ]; + + let mut config = HostConfiguration::default(); + config.max_validators = Some(2); + + let pubkeys = validator_pubkeys(&validators); + + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + let validators = Shared::initializer_on_new_session( + 1, + [1; 32], + &config, + pubkeys, + ); + + assert_eq!( + validators, + validator_pubkeys(&[ + Sr25519Keyring::Ferdie, + Sr25519Keyring::Bob, + ]) + ); + + assert_eq!( + Shared::active_validator_keys(), + validators, + ); + + assert_eq!( + Shared::active_validator_indices(), + vec![ + ValidatorIndex(4), + ValidatorIndex(1), + ] + ); + }); + } } diff --git a/polkadot/runtime/parachains/src/util.rs b/polkadot/runtime/parachains/src/util.rs index f504b42aa1..8721d3ed9c 100644 --- a/polkadot/runtime/parachains/src/util.rs +++ b/polkadot/runtime/parachains/src/util.rs @@ -17,7 +17,8 @@ //! Utilities that don't belong to any particular module but may draw //! on all modules. -use primitives::v1::{Id as ParaId, PersistedValidationData, Hash}; +use primitives::v1::{Id as ParaId, PersistedValidationData, Hash, ValidatorIndex}; +use sp_std::vec::Vec; use crate::{configuration, paras, hrmp}; @@ -39,3 +40,20 @@ pub fn make_persisted_validation_data( max_pov_size: config.max_pov_size, }) } + +/// Take the active subset of a set containing all validators. +pub fn take_active_subset(active_validators: &[ValidatorIndex], set: &[T]) -> Vec { + let subset: Vec<_> = active_validators.iter() + .filter_map(|i| set.get(i.0 as usize)) + .cloned() + .collect(); + + if subset.len() != active_validators.len() { + log::warn!( + target: "runtime::parachains", + "Took active validators from set with wrong size", + ); + } + + subset +}