Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add storage bounds for pallet staking #6445

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions polkadot/runtime/westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,8 @@ impl pallet_staking::Config for Runtime {
type EventListeners = (NominationPools, DelegatedStaking);
type WeightInfo = weights::pallet_staking::WeightInfo<Runtime>;
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
type MaxInvulnerables = frame_support::traits::ConstU32<4>;
type MaxActiveValidators = MaxActiveValidators;
}

impl pallet_fast_unstake::Config for Runtime {
Expand Down
17 changes: 13 additions & 4 deletions substrate/frame/staking/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use testing_utils::*;
use codec::Decode;
use frame_election_provider_support::{bounds::DataProviderBounds, SortedListProvider};
use frame_support::{
assert_ok,
pallet_prelude::*,
storage::bounded_vec::BoundedVec,
traits::{Get, Imbalance, UnfilteredDispatchable},
Expand Down Expand Up @@ -122,10 +123,14 @@ pub fn create_validator_with_nominators<T: Config>(
assert_ne!(Validators::<T>::count(), 0);
assert_eq!(Nominators::<T>::count(), original_nominator_count + nominators.len() as u32);

let mut reward_map = BoundedBTreeMap::new();
for (validator, reward) in points_individual {
assert_ok!(reward_map.try_insert(validator, reward));
}
// Give Era Points
let reward = EraRewardPoints::<T::AccountId> {
let reward = EraRewardPoints::<T::AccountId, T::MaxActiveValidators> {
total: points_total,
individual: points_individual.into_iter().collect(),
individual: reward_map,
};

let current_era = CurrentEra::<T>::get().unwrap();
Expand Down Expand Up @@ -865,10 +870,14 @@ mod benchmarks {
payout_calls_arg.push((validator.clone(), current_era));
}

let mut reward_map = BoundedBTreeMap::new();
for (validator, reward) in points_individual {
assert_ok!(reward_map.try_insert(validator, reward));
}
// Give Era Points
let reward = EraRewardPoints::<T::AccountId> {
let reward = EraRewardPoints::<T::AccountId, T::MaxActiveValidators> {
total: points_total,
individual: points_individual.into_iter().collect(),
individual: reward_map,
};

ErasRewardPoints::<T>::insert(current_era, reward);
Expand Down
15 changes: 9 additions & 6 deletions substrate/frame/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ use scale_info::TypeInfo;
use sp_runtime::{
curve::PiecewiseLinear,
traits::{AtLeast32BitUnsigned, Convert, StaticLookup, Zero},
Perbill, Perquintill, Rounding, RuntimeDebug, Saturating,
BoundedBTreeMap, Perbill, Perquintill, Rounding, RuntimeDebug, Saturating,
};
use sp_staking::{
offence::{Offence, OffenceError, ReportOffence},
Expand Down Expand Up @@ -385,17 +385,20 @@ pub struct ActiveEraInfo {
/// Reward points of an era. Used to split era total payout between validators.
///
/// This points will be used to reward validators and their respective nominators.
#[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct EraRewardPoints<AccountId: Ord> {
#[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(MaxActiveValidators))]
pub struct EraRewardPoints<AccountId: Ord, MaxActiveValidators: Get<u32>> {
/// Total number of points. Equals the sum of reward points for each validator.
pub total: RewardPoint,
/// The reward points earned by a given validator.
pub individual: BTreeMap<AccountId, RewardPoint>,
pub individual: BoundedBTreeMap<AccountId, RewardPoint, MaxActiveValidators>,
}

impl<AccountId: Ord> Default for EraRewardPoints<AccountId> {
impl<AccountId: Ord, MaxActiveValidators: Get<u32>> Default
for EraRewardPoints<AccountId, MaxActiveValidators>
{
fn default() -> Self {
EraRewardPoints { total: Default::default(), individual: BTreeMap::new() }
EraRewardPoints { total: Default::default(), individual: BoundedBTreeMap::new() }
}
}

Expand Down
88 changes: 88 additions & 0 deletions substrate/frame/staking/src/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,97 @@ impl Default for ObsoleteReleases {
#[storage_alias]
type StorageVersion<T: Config> = StorageValue<Pallet<T>, ObsoleteReleases, ValueQuery>;

/// Migrating all unbounded storage items to bounded
pub mod v16 {
use super::*;

pub struct VersionUncheckedMigrateV15ToV16<T>(core::marker::PhantomData<T>);
impl<T: Config> UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV15ToV16<T> {
fn on_runtime_upgrade() -> Weight {
// BoundedVec with MaxActiveValidators limit, this should always work
let disabled_validators_maybe =
BoundedVec::try_from(v15::DisabledValidators::<T>::get());
match disabled_validators_maybe {
Ok(disabled_validators) => DisabledValidators::<T>::set(disabled_validators),
Err(_) => log!(warn, "Migration failed for DisabledValidators from v15 to v16."),
}

// BoundedVec with MaxActiveValidators limit, this should always work
let invulnerables_maybe = BoundedVec::try_from(v15::Invulnerables::<T>::get());
match invulnerables_maybe {
Ok(invulnerables) => Invulnerables::<T>::set(invulnerables),
Err(_) => log!(warn, "Migration failed for Invulnerables from v15 to v16."),
}

for (era_index, era_rewards) in v15::ErasRewardPoints::<T>::iter() {
let individual_rewards_maybe = BoundedBTreeMap::try_from(era_rewards.individual);
match individual_rewards_maybe {
Ok(individual_rewards) => {
let bounded_era_rewards = EraRewardPoints::<
<T as frame_system::Config>::AccountId,
<T as Config>::MaxActiveValidators,
> {
individual: individual_rewards,
total: era_rewards.total,
};
ErasRewardPoints::<T>::insert(era_index, bounded_era_rewards);
},
Err(_) => log!(warn, "Migration failed for ErasRewardPoints from v15 to v16."),
}
}

log!(info, "v16 applied successfully.");
T::DbWeight::get().reads_writes(1, 1)
}

// TODO
#[cfg(feature = "try-runtime")]
fn post_upgrade(_state: Vec<u8>) -> Result<(), TryRuntimeError> {
Ok(())
}
}

pub type MigrateV15ToV16<T> = VersionedMigration<
15,
16,
VersionUncheckedMigrateV15ToV16<T>,
Pallet<T>,
<T as frame_system::Config>::DbWeight,
>;
}

/// Migrating `OffendingValidators` from `Vec<(u32, bool)>` to `Vec<u32>`
pub mod v15 {
use super::*;
use frame_support::Twox64Concat;

#[frame_support::storage_alias]
pub(crate) type DisabledValidators<T: Config> = StorageValue<Pallet<T>, Vec<u32>, ValueQuery>;

#[frame_support::storage_alias]
pub(crate) type Invulnerables<T: Config> =
StorageValue<Pallet<T>, Vec<<T as frame_system::Config>::AccountId>, ValueQuery>;

#[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct EraRewardPoints<AccountId: Ord> {
pub total: u32,
pub individual: BTreeMap<AccountId, u32>,
}

impl<AccountId: Ord> Default for EraRewardPoints<AccountId> {
fn default() -> Self {
EraRewardPoints { total: Default::default(), individual: BTreeMap::new() }
}
}

#[frame_support::storage_alias]
pub(crate) type ErasRewardPoints<T: Config> = StorageMap<
Pallet<T>,
Twox64Concat,
u32,
EraRewardPoints<<T as frame_system::Config>::AccountId>,
ValueQuery,
>;

// The disabling strategy used by staking pallet
type DefaultDisablingStrategy = UpToLimitDisablingStrategy;
Expand Down
2 changes: 2 additions & 0 deletions substrate/frame/staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ impl crate::pallet::pallet::Config for Test {
type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch;
type EventListeners = EventListenerMock;
type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy<DISABLING_LIMIT_FACTOR>;
type MaxInvulnerables = ConstU32<4>;
type MaxActiveValidators = ConstU32<100>;
}

pub struct WeightedNominationsQuota<const MAX: u32>;
Expand Down
18 changes: 16 additions & 2 deletions substrate/frame/staking/src/pallet/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -857,8 +857,22 @@ impl<T: Config> Pallet<T> {
if let Some(active_era) = ActiveEra::<T>::get() {
<ErasRewardPoints<T>>::mutate(active_era.index, |era_rewards| {
for (validator, points) in validators_points.into_iter() {
*era_rewards.individual.entry(validator).or_default() += points;
era_rewards.total += points;
match era_rewards.individual.get_mut(&validator) {
Some(value) => *value += points,
None => {
if let Ok(_) =
era_rewards.individual.try_insert(validator.clone(), points)
{
era_rewards.total += points;
} else {
log!(
warn,
"Could not add reward points to {}: max numbers of rewarded validators reached",
validator
);
}
},
};
}
});
}
Expand Down
48 changes: 35 additions & 13 deletions substrate/frame/staking/src/pallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,14 @@ pub mod pallet {
#[pallet::no_default_bounds]
type DisablingStrategy: DisablingStrategy<Self>;

/// Maximum number of invulnerable validators.
#[pallet::constant]
type MaxInvulnerables: Get<u32>;

/// Maximum number of active validators in an era.
#[pallet::constant]
type MaxActiveValidators: Get<u32>;

/// Some parameters of the benchmarking.
#[cfg(feature = "std")]
type BenchmarkingConfig: BenchmarkingConfig;
Expand Down Expand Up @@ -343,6 +351,8 @@ pub mod pallet {
type MaxControllersInDeprecationBatch = ConstU32<100>;
type EventListeners = ();
type DisablingStrategy = crate::UpToLimitDisablingStrategy;
type MaxInvulnerables = ConstU32<4>;
type MaxActiveValidators = ConstU32<100>;
#[cfg(feature = "std")]
type BenchmarkingConfig = crate::TestBenchmarkingConfig;
type WeightInfo = ();
Expand All @@ -361,8 +371,8 @@ pub mod pallet {
/// easy to initialize and the performance hit is minimal (we expect no more than four
/// invulnerables) and restricted to testnets.
#[pallet::storage]
#[pallet::unbounded]
pub type Invulnerables<T: Config> = StorageValue<_, Vec<T::AccountId>, ValueQuery>;
pub type Invulnerables<T: Config> =
StorageValue<_, BoundedVec<T::AccountId, T::MaxInvulnerables>, ValueQuery>;

/// Map from all locked "stash" accounts to the controller account.
///
Expand Down Expand Up @@ -608,9 +618,13 @@ pub mod pallet {
/// Rewards for the last [`Config::HistoryDepth`] eras.
/// If reward hasn't been set or has been removed then 0 reward is returned.
#[pallet::storage]
#[pallet::unbounded]
pub type ErasRewardPoints<T: Config> =
StorageMap<_, Twox64Concat, EraIndex, EraRewardPoints<T::AccountId>, ValueQuery>;
pub type ErasRewardPoints<T: Config> = StorageMap<
_,
Twox64Concat,
EraIndex,
EraRewardPoints<T::AccountId, T::MaxActiveValidators>,
ValueQuery,
>;

/// The total amount staked for the last [`Config::HistoryDepth`] eras.
/// If total hasn't been set or has been removed then 0 stake is returned.
Expand Down Expand Up @@ -704,11 +718,11 @@ pub mod pallet {
/// implementor of [`DisablingStrategy`] defines if a validator should be disabled which
/// implicitly means that the implementor also controls the max number of disabled validators.
///
/// The vec is always kept sorted so that we can find whether a given validator has previously
/// offended using binary search.
/// The bounded vec is always kept sorted so that we can find whether a given validator has
/// previously offended using binary search.
#[pallet::storage]
#[pallet::unbounded]
pub type DisabledValidators<T: Config> = StorageValue<_, Vec<u32>, ValueQuery>;
pub type DisabledValidators<T: Config> =
StorageValue<_, BoundedVec<u32, T::MaxActiveValidators>, ValueQuery>;

/// The threshold for when users can start calling `chill_other` for other validators /
/// nominators. The threshold is compared to the actual number of validators / nominators
Expand Down Expand Up @@ -738,7 +752,11 @@ pub mod pallet {
fn build(&self) {
ValidatorCount::<T>::put(self.validator_count);
MinimumValidatorCount::<T>::put(self.minimum_validator_count);
Invulnerables::<T>::put(&self.invulnerables);
assert!(
self.invulnerables.len() as u32 <= T::MaxInvulnerables::get(),
"Too many invulnerable validators at genesis."
);
<Invulnerables<T>>::put(BoundedVec::truncate_from(self.invulnerables.clone()));
ForceEra::<T>::put(self.force_era);
CanceledSlashPayout::<T>::put(self.canceled_payout);
SlashRewardFraction::<T>::put(self.slash_reward_fraction);
Expand Down Expand Up @@ -984,7 +1002,7 @@ pub mod pallet {
}

/// Get the validators that may never be slashed or forcibly kicked out.
pub fn invulnerables() -> Vec<T::AccountId> {
pub fn invulnerables() -> BoundedVec<T::AccountId, T::MaxInvulnerables> {
Invulnerables::<T>::get()
}

Expand Down Expand Up @@ -1076,7 +1094,7 @@ pub mod pallet {
/// Get the rewards for the last [`Config::HistoryDepth`] eras.
pub fn eras_reward_points<EncodeLikeEraIndex>(
era_index: EncodeLikeEraIndex,
) -> EraRewardPoints<T::AccountId>
) -> EraRewardPoints<T::AccountId, T::MaxActiveValidators>
where
EncodeLikeEraIndex: codec::EncodeLike<EraIndex>,
{
Expand Down Expand Up @@ -1672,7 +1690,11 @@ pub mod pallet {
invulnerables: Vec<T::AccountId>,
) -> DispatchResult {
ensure_root(origin)?;
<Invulnerables<T>>::put(invulnerables);
ensure!(
invulnerables.len() as u32 <= T::MaxInvulnerables::get(),
Error::<T>::BoundNotMet
);
<Invulnerables<T>>::put(BoundedVec::truncate_from(invulnerables));
Ok(())
}

Expand Down
2 changes: 1 addition & 1 deletion substrate/frame/staking/src/slashing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ fn add_offending_validator<T: Config>(params: &SlashParams<T>) {
// Add the validator to `DisabledValidators` and disable it. Do nothing if it is
// already disabled.
if let Err(index) = disabled.binary_search_by_key(&offender, |index| *index) {
disabled.insert(index, offender);
disabled.try_insert(index, offender);
T::SessionInterface::disable_validator(offender);
}
}
Expand Down
Loading
Loading