diff --git a/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md b/polkadot/roadmap/implementers-guide/src/runtime/inclusion.md index e5ea1abdb64fcdac3d0e8a8b157926618413f089..fbfbb824f84b33ae8a09f50c283b72e1a339f184 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<ValidatorId>; ``` ## 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 05da0f8940654bdd88ba0f921bdf29fe3cb48885..76178b3c52a4234be4c73f5fb57ff7fdb254457b 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 a49ec978d1780a6325124720551a9d762ccdf451..68b1a8abb722dead6165b61e9f18cb50200ee00f 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<Vec<ValidatorIndex>>; /// 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 d446a314cf77312abd3eb0fa852b34f20511f7b3..a37f61af08d783334b6b290f0ce66e35a337b4ae 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<SessionInfo>, ## 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 7151e6acda7941050549d87d2a94082b728452dd..ae538928d5feb7367d6dee90a37ea8891aec54c5 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<ValidatorIndex>, +/// The parachain attestation keys of the validators actively participating in parachain consensus. +/// This should be the same length as `ActiveValidatorIndices`. +ActiveValidatorKeys: Vec<ValidatorId> ``` ## 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 ee0ed60107d757f16b43982892de027e7f3cdcce..fadc34def620eb12a063cfb6f95640be1708cfb3 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<u32>, + /// The maximum number of validators to use for parachains, in total. `None` means no maximum. + pub max_validators: Option<u32>, /// 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 760678e8a0a502326013dfefa82b4c192e5ae118..e91e43b52d878089b862ce6b298a01ee8e755669 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<T>}, Balances: pallet_balances::{Module, Call, Storage, Config<T>, Event<T>}, Parachains: paras::{Module, Origin, Call, Storage, Config<T>}, + Shared: shared::{Module, Call, Storage}, Inclusion: inclusion::{Module, Call, Storage, Event<T>}, Registrar: paras_registrar::{Module, Call, Storage}, Staking: pallet_staking::{Module, Call, Config<T>, Storage, Event<T>, 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 d01e5fc872dbd769c71fa3b916807097a8f82dd3..b197363b16112f83273f43aa24b1fac6c1dad020 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<BlockNumber> { /// /// `None` means no maximum. pub max_validators_per_core: Option<u32>, + /// The maximum number of valdiators to use for parachain consensus, period. + /// + /// `None` means no maximum. + pub max_validators: Option<u32>, /// 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<BlockNumber: Default + From<u32>> Default for HostConfiguration<BlockNumber parathread_retries: Default::default(), scheduling_lookahead: Default::default(), max_validators_per_core: Default::default(), + max_validators: None, dispute_period: Default::default(), n_delay_tranches: Default::default(), zeroth_delay_tranche_width: Default::default(), @@ -400,6 +405,16 @@ decl_module! { Ok(()) } + /// Set the maximum number of validators to use in parachain consensus. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_max_validators(origin, new: Option<u32>) -> 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<T: Config> Module<T> { /// 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) = <Self as Store>::PendingConfig::take(session_index) { @@ -700,12 +713,12 @@ mod tests { assert_eq!(Configuration::config(), old_config); assert_eq!(<Configuration as Store>::PendingConfig::get(1), None); - Configuration::initializer_on_new_session(&[], &[], &1); + Configuration::initializer_on_new_session(&1); assert_eq!(Configuration::config(), old_config); assert_eq!(<Configuration as Store>::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!(<Configuration as Store>::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 e02051a7c2dde4e88fa27a8afa3bf41bde9f5129..78535b9c5780e574e6093c91d864a07d3a79e601 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 275d6cf3d7c85e715be56f516ade6cc123af4dc2..7430b5500c50248c2630ba9db5aaaf1f648d1fb4 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<CandidateCommitments>; - - /// The current validators, by their parachain session keys. - Validators get(fn validators) config(validators): Vec<ValidatorId>; } } @@ -224,15 +221,13 @@ impl<T: Config> Module<T> { /// Handle an incoming session change. pub(crate) fn initializer_on_new_session( - notification: &crate::initializer::SessionChangeNotification<T::BlockNumber> + _notification: &crate::initializer::SessionChangeNotification<T::BlockNumber> ) { // unlike most drain methods, drained elements are not cleared on `Drop` of the iterator // and require consumption. for _ in <PendingAvailabilityCommitments>::drain() { } for _ in <PendingAvailability<T>>::drain() { } for _ in <AvailabilityBitfields<T>>::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<T: Config> Module<T> { signed_bitfields: SignedAvailabilityBitfields, core_lookup: impl Fn(CoreIndex) -> Option<ParaId>, ) -> Result<Vec<CoreIndex>, DispatchError> { - let validators = Validators::get(); + let validators = shared::Module::<T>::active_validator_keys(); let session_index = shared::Module::<T>::session_index(); let mut assigned_paras_record: Vec<_> = (0..expected_bits) @@ -394,7 +389,7 @@ impl<T: Config> Module<T> { return Ok(Vec::new()); } - let validators = Validators::get(); + let validators = shared::Module::<T>::active_validator_keys(); let parent_hash = <frame_system::Module<T>>::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<BitOrderLsb0, u8> { - bitvec::bitvec![BitOrderLsb0, u8; 0; Validators::get().len()] + bitvec::bitvec![BitOrderLsb0, u8; 0; Shared::active_validator_keys().len()] } fn default_backing_bitfield() -> BitVec<BitOrderLsb0, u8> { - bitvec::bitvec![BitOrderLsb0, u8; 0; Validators::get().len()] + bitvec::bitvec![BitOrderLsb0, u8; 0; Shared::active_validator_keys().len()] } fn backing_bitfield(v: &[usize]) -> BitVec<BitOrderLsb0, u8> { @@ -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::<Test>::set_active_validators(validator_public.clone()); shared::Module::<Test>::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::<Test>::set_active_validators(validator_public.clone()); shared::Module::<Test>::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::<Test>::set_active_validators(validator_public.clone()); shared::Module::<Test>::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::<Test>::set_active_validators(validator_public.clone()); shared::Module::<Test>::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::<Test>::set_active_validators(validator_public.clone()); shared::Module::<Test>::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::<Test>::set_active_validators(validator_public.clone()); shared::Module::<Test>::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::<Test>::session_index(), 5); assert!(<AvailabilityBitfields<Test>>::get(&ValidatorIndex(0)).is_some()); @@ -2480,7 +2479,6 @@ mod tests { _ => None, }); - assert_eq!(Validators::get(), validator_public_new); assert_eq!(shared::Module::<Test>::session_index(), 6); assert!(<AvailabilityBitfields<Test>>::get(&ValidatorIndex(0)).is_none()); diff --git a/polkadot/runtime/parachains/src/initializer.rs b/polkadot/runtime/parachains/src/initializer.rs index e0bd6205102082dd819bc7f767d3ef910fea901a..8dc13655272a7fc8c2a36a4960a31c14ea3a1d3a 100644 --- a/polkadot/runtime/parachains/src/initializer.rs +++ b/polkadot/runtime/parachains/src/initializer.rs @@ -174,7 +174,7 @@ decl_module! { impl<T: Config> Module<T> { fn apply_new_session( session_index: SessionIndex, - validators: Vec<ValidatorId>, + all_validators: Vec<ValidatorId>, queued: Vec<ValidatorId>, ) { let prev_config = <configuration::Module<T>>::config(); @@ -189,10 +189,17 @@ impl<T: Config> Module<T> { // 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::<T>::initializer_on_new_session(&validators, &queued, &session_index); + configuration::Module::<T>::initializer_on_new_session(&session_index); let new_config = <configuration::Module<T>>::config(); + let validators = shared::Module::<T>::initializer_on_new_session( + session_index, + random_seed.clone(), + &new_config, + all_validators, + ); + let notification = SessionChangeNotification { validators, queued, @@ -202,7 +209,6 @@ impl<T: Config> Module<T> { session_index, }; - shared::Module::<T>::initializer_on_new_session(¬ification); let outgoing_paras = paras::Module::<T>::initializer_on_new_session(¬ification); scheduler::Module::<T>::initializer_on_new_session(¬ification); inclusion::Module::<T>::initializer_on_new_session(¬ification); diff --git a/polkadot/runtime/parachains/src/paras.rs b/polkadot/runtime/parachains/src/paras.rs index f15284ea6c38dde2cbfe2d4cfa6205bc61da7ce6..b6f1b6166cf87ab3ed97ec575f7111974a87fa15 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 ed5f787242db1e3483e800bd5141a259876e7c3d..fd9b6ccfa3bfc78e1db90036609b09cfc25fa1c5 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<T: initializer::Config>() -> Vec<ValidatorId> { - <inclusion::Module<T>>::validators() + <shared::Module<T>>::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 e690f914167518fb4130b764bceb915632afa186..f142cef5be58bdab17da36d3b04914c2151d66c9 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<T: Config> 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<T: Config> Module<T> { pub(crate) fn initializer_on_new_session(notification: &SessionChangeNotification<T::BlockNumber>) { let &SessionChangeNotification { ref validators, - ref random_seed, ref new_config, .. } = notification; @@ -259,27 +257,26 @@ impl<T: Config> Module<T> { if n_cores == 0 || validators.is_empty() { ValidatorGroups::set(Vec::new()); } else { - let mut rng: ChaCha20Rng = SeedableRng::from_seed(*random_seed); - - let mut shuffled_indices: Vec<_> = (0..validators.len()) - .enumerate() - .map(|(i, _)| ValidatorIndex(i as _)) - .collect(); - - 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; - let group_base_size = shuffled_indices.len() / n_cores as usize; - let n_larger_groups = shuffled_indices.len() % n_cores as usize; + // Groups contain indices into the validators from the session change notification, + // which are already shuffled. - let groups: Vec<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 - }; + let mut groups: Vec<Vec<ValidatorIndex>> = 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() + ); + } - 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 bc675c86aec16e7cff2b2cc8352df2f3c59c8b01..321c2cd0bb1e072b648055b220dd54b96b3c1530 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<T: Config> Module<T> { let validators = notification.validators.clone(); let discovery_keys = <T as AuthorityDiscoveryConfig>::authorities(); let assignment_keys = AssignmentKeysUnsafe::get(); + + let active_set = <shared::Module<T>>::active_validator_indices(); let validator_groups = <scheduler::Module<T>>::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<T: Config> Module<T> { } // 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 b7b874fbd49704e77737cd10863c67ddf9162956..3f89f17f548166c40ef91b2367cb141954d3b401 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<T: Config> 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<ValidatorIndex>; + /// 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<ValidatorId>; } } @@ -63,8 +74,35 @@ impl<T: Config> Module<T> { /// 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<T::BlockNumber>) { - CurrentSessionIndex::set(notification.session_index); + pub(crate) fn initializer_on_new_session( + session_index: SessionIndex, + random_seed: [u8; 32], + new_config: &HostConfiguration<T::BlockNumber>, + all_validators: Vec<ValidatorId>, + ) -> Vec<ValidatorId> { + 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<T: Config> Module<T> { pub(crate) fn set_session_index(index: SessionIndex) { CurrentSessionIndex::set(index); } + + #[cfg(test)] + pub(crate) fn set_active_validators(active: Vec<ValidatorId>) { + 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<ValidatorId> { + 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 f504b42aa1b8dce0ab92e29c9d74c087f6f805e6..8721d3ed9c6ea8664f84c9e014fa3e69e3824e69 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<T: paras::Config + hrmp::Config>( max_pov_size: config.max_pov_size, }) } + +/// Take the active subset of a set containing all validators. +pub fn take_active_subset<T: Clone>(active_validators: &[ValidatorIndex], set: &[T]) -> Vec<T> { + 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 +}