mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 05:51:02 +00:00
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:
committed by
GitHub
parent
30e4a67f0c
commit
8b4f46d2df
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
¬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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user