Configurable maximum validators (#2586)

* guide: max_validators

* guide: new approach

* restrict validators based on configurable max

* add some tests

* fix wasm build (Vec)

* clean up warnings

* add logging

* set validator indices in tests as well

* fix common tests

* Update runtime/parachains/src/util.rs

Co-authored-by: Andronik Ordian <write@reusable.software>

Co-authored-by: Andronik Ordian <write@reusable.software>
This commit is contained in:
Robert Habermeier
2021-03-09 12:38:55 -06:00
committed by GitHub
parent 30e4a67f0c
commit 8b4f46d2df
17 changed files with 300 additions and 83 deletions
@@ -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();
+6 -1
View File
@@ -1164,7 +1164,12 @@ mod tests {
};
// NOTE: this is in initialization order.
Shared::initializer_on_new_session(&notification);
Shared::initializer_on_new_session(
notification.session_index,
notification.random_seed,
&notification.new_config,
notification.validators.clone(),
);
let outgoing_paras = Paras::initializer_on_new_session(&notification);
Hrmp::initializer_on_new_session(&notification, &outgoing_paras);
}
+20 -22
View File
@@ -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(&notification);
Shared::initializer_on_new_session(
notification.session_index,
notification.random_seed,
&notification.new_config,
notification.validators.clone(),
);
Paras::initializer_on_new_session(&notification);
Inclusion::initializer_on_new_session(&notification);
}
@@ -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());
@@ -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(&notification);
let outgoing_paras = paras::Module::<T>::initializer_on_new_session(&notification);
scheduler::Module::<T>::initializer_on_new_session(&notification);
inclusion::Module::<T>::initializer_on_new_session(&notification);
+6 -1
View File
@@ -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);
@@ -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.
+20 -23
View File
@@ -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 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<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()
);
}
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<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);
}
@@ -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(
&notification.validators,
&notification.queued,
&notification.session_index,
);
Shared::initializer_on_new_session(&notification);
Shared::initializer_on_new_session(
notification.session_index,
notification.random_seed,
&notification.new_config,
notification.validators.clone(),
);
SessionInfo::initializer_on_new_session(&notification);
}
+160 -4
View File
@@ -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),
]
);
});
}
}
+19 -1
View File
@@ -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
}