diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs index 2f02ca5f1932..4dc45cf7aecb 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs @@ -19,7 +19,7 @@ use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; use sp_consensus_beefy::ecdsa_crypto::AuthorityId as BeefyId; use sp_core::storage::Storage; -use sp_runtime::Perbill; +use sp_runtime::{BoundedVec, Perbill}; // Polkadot use polkadot_primitives::{AssignmentId, ValidatorId}; @@ -87,7 +87,13 @@ pub fn genesis() -> Storage { .iter() .map(|x| (x.0.clone(), x.1.clone(), STASH, pallet_staking::StakerStatus::Validator)) .collect(), - invulnerables: validators::initial_authorities().iter().map(|x| x.0.clone()).collect(), + invulnerables: BoundedVec::try_from( + validators::initial_authorities() + .iter() + .map(|x| x.0.clone()) + .collect::>(), + ) + .expect("Limit for staking invulnerables must be less than initial authorities."), force_era: pallet_staking::Forcing::ForceNone, slash_reward_fraction: Perbill::from_percent(10), ..Default::default() diff --git a/polkadot/runtime/common/src/try_runtime.rs b/polkadot/runtime/common/src/try_runtime.rs index b22e17032920..795249dde20b 100644 --- a/polkadot/runtime/common/src/try_runtime.rs +++ b/polkadot/runtime/common/src/try_runtime.rs @@ -36,7 +36,7 @@ where let all_stakers = Ledger::::iter().map(|(ctrl, l)| (ctrl, l.stash)).collect::>(); let mut all_exposed = BTreeSet::new(); - ErasStakers::::iter().for_each(|(_, val, expo)| { + ErasStakersPaged::::iter().for_each(|((_era, val, _page), expo)| { all_exposed.insert(val); all_exposed.extend(expo.others.iter().map(|ie| ie.who.clone())) }); diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index c44c0e3abadd..99c990210028 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -403,6 +403,8 @@ impl pallet_staking::Config for Runtime { type WeightInfo = (); type DisablingStrategy = pallet_staking::UpToLimitWithReEnablingDisablingStrategy; type MaxValidatorSet = MaxAuthorities; + type MaxInvulnerables = ConstU32<20>; + type MaxDisabledValidators = ConstU32<100>; } parameter_types! { diff --git a/polkadot/runtime/westend/src/genesis_config_presets.rs b/polkadot/runtime/westend/src/genesis_config_presets.rs index 76c0ce015c0d..af5e3607df48 100644 --- a/polkadot/runtime/westend/src/genesis_config_presets.rs +++ b/polkadot/runtime/westend/src/genesis_config_presets.rs @@ -33,7 +33,7 @@ use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_core::{crypto::get_public_from_string_or_panic, sr25519}; use sp_genesis_builder::PresetId; use sp_keyring::Sr25519Keyring; -use sp_runtime::Perbill; +use sp_runtime::{BoundedVec, Perbill}; use westend_runtime_constants::currency::UNITS as WND; /// Helper function to generate stash, controller and session key from seed @@ -202,7 +202,10 @@ fn westend_testnet_genesis( .iter() .map(|x| (x.0.clone(), x.0.clone(), STASH, StakerStatus::::Validator)) .collect::>(), - invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect::>(), + invulnerables: BoundedVec::try_from( + initial_authorities.iter().map(|x| x.0.clone()).collect::>() + ) + .expect("Too many invulnerable validators: upper limit is MaxInvulnerables from pallet staking config"), force_era: Forcing::NotForcing, slash_reward_fraction: Perbill::from_percent(10), }, @@ -373,7 +376,10 @@ fn westend_staging_testnet_config_genesis() -> serde_json::Value { .iter() .map(|x| (x.0.clone(), x.0.clone(), STASH, StakerStatus::::Validator)) .collect::>(), - invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect::>(), + invulnerables: BoundedVec::try_from( + initial_authorities.iter().map(|x| x.0.clone()).collect::>() + ) + .expect("Too many invulnerable validators: upper limit is MaxInvulnerables from pallet staking config"), force_era: Forcing::ForceNone, slash_reward_fraction: Perbill::from_percent(10), }, diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index f4b0d60d898b..4b37bb513c78 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -769,6 +769,8 @@ impl pallet_staking::Config for Runtime { type EventListeners = (NominationPools, DelegatedStaking); type WeightInfo = weights::pallet_staking::WeightInfo; type DisablingStrategy = pallet_staking::UpToLimitWithReEnablingDisablingStrategy; + type MaxInvulnerables = frame_support::traits::ConstU32<20>; + type MaxDisabledValidators = ConstU32<100>; } impl pallet_fast_unstake::Config for Runtime { @@ -1867,7 +1869,6 @@ pub mod migrations { parachains_shared::migration::MigrateToV1, parachains_scheduler::migration::MigrateV2ToV3, pallet_staking::migrations::v16::MigrateV15ToV16, - pallet_staking::migrations::v17::MigrateV16ToV17, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, ); diff --git a/polkadot/runtime/westend/src/weights/pallet_fast_unstake.rs b/polkadot/runtime/westend/src/weights/pallet_fast_unstake.rs index 8c061688fc66..dafac66f9d77 100644 --- a/polkadot/runtime/westend/src/weights/pallet_fast_unstake.rs +++ b/polkadot/runtime/westend/src/weights/pallet_fast_unstake.rs @@ -108,8 +108,6 @@ impl pallet_fast_unstake::WeightInfo for WeightInfo /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) /// Storage: Staking CurrentEra (r:1 w:0) /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: Staking ErasStakers (r:257 w:0) - /// Proof Skipped: Staking ErasStakers (max_values: None, max_size: None, mode: Measured) /// The range of component `v` is `[1, 256]`. /// The range of component `b` is `[1, 64]`. fn on_idle_check(v: u32, b: u32, ) -> Weight { diff --git a/polkadot/runtime/westend/src/weights/pallet_staking.rs b/polkadot/runtime/westend/src/weights/pallet_staking.rs index eca023c8b42f..b2356ea9d3d4 100644 --- a/polkadot/runtime/westend/src/weights/pallet_staking.rs +++ b/polkadot/runtime/westend/src/weights/pallet_staking.rs @@ -500,8 +500,6 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:65 w:65) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasStakersClipped` (r:1 w:0) - /// Proof: `Staking::ErasStakersClipped` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Staking::ErasStakersOverview` (r:1 w:0) /// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `Staking::ClaimedRewards` (r:1 w:1) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 72361c2de4b8..fdb54406ca83 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -893,6 +893,8 @@ impl pallet_staking::Config for Runtime { type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; type DisablingStrategy = pallet_staking::UpToLimitWithReEnablingDisablingStrategy; + type MaxInvulnerables = ConstU32<20>; + type MaxDisabledValidators = ConstU32<100>; } impl pallet_fast_unstake::Config for Runtime { diff --git a/substrate/bin/node/testing/src/genesis.rs b/substrate/bin/node/testing/src/genesis.rs index 624b00b4d6c2..aaa19e15d07d 100644 --- a/substrate/bin/node/testing/src/genesis.rs +++ b/substrate/bin/node/testing/src/genesis.rs @@ -24,7 +24,7 @@ use kitchensink_runtime::{ RuntimeGenesisConfig, SessionConfig, SocietyConfig, StakerStatus, StakingConfig, }; use sp_keyring::Ed25519Keyring; -use sp_runtime::Perbill; +use sp_runtime::{BoundedVec, Perbill}; /// Create genesis runtime configuration for tests. pub fn config() -> RuntimeGenesisConfig { @@ -65,7 +65,8 @@ pub fn config_endowed(extra_endowed: Vec) -> RuntimeGenesisConfig { validator_count: 3, minimum_validator_count: 0, slash_reward_fraction: Perbill::from_percent(10), - invulnerables: vec![alice(), bob(), charlie()], + invulnerables: BoundedVec::try_from(vec![alice(), bob(), charlie()]) + .expect("Too many invulnerable validators: upper limit is MaxInvulnerables from pallet staking config"), ..Default::default() }, society: SocietyConfig { pot: 0 }, diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs index 79ea24f0feba..1e4f51d51430 100644 --- a/substrate/frame/babe/src/mock.rs +++ b/substrate/frame/babe/src/mock.rs @@ -39,7 +39,7 @@ use sp_runtime::{ impl_opaque_keys, testing::{Digest, DigestItem, Header, TestXt}, traits::{Header as _, OpaqueKeys}, - BuildStorage, Perbill, + BoundedVec, BuildStorage, Perbill, }; use sp_staking::{EraIndex, SessionIndex}; @@ -345,7 +345,7 @@ pub fn new_test_ext_raw_authorities(authorities: Vec) -> sp_io::Tes validator_count: 8, force_era: pallet_staking::Forcing::ForceNew, minimum_validator_count: 0, - invulnerables: vec![], + invulnerables: BoundedVec::new(), ..Default::default() }; diff --git a/substrate/frame/beefy/src/mock.rs b/substrate/frame/beefy/src/mock.rs index eb4fcfda1408..a63b82e92d9e 100644 --- a/substrate/frame/beefy/src/mock.rs +++ b/substrate/frame/beefy/src/mock.rs @@ -36,7 +36,7 @@ use sp_runtime::{ impl_opaque_keys, testing::TestXt, traits::{Header as HeaderT, OpaqueKeys}, - BuildStorage, Perbill, + BoundedVec, BuildStorage, Perbill, }; use sp_staking::{EraIndex, SessionIndex}; use sp_state_machine::BasicExternalities; @@ -315,7 +315,7 @@ impl ExtBuilder { validator_count: 2, force_era: pallet_staking::Forcing::ForceNew, minimum_validator_count: 0, - invulnerables: vec![], + invulnerables: BoundedVec::new(), ..Default::default() }; diff --git a/substrate/frame/delegated-staking/src/mock.rs b/substrate/frame/delegated-staking/src/mock.rs index 1d83e99b64ab..4c8adbf368c1 100644 --- a/substrate/frame/delegated-staking/src/mock.rs +++ b/substrate/frame/delegated-staking/src/mock.rs @@ -24,7 +24,7 @@ use frame_support::{ PalletId, }; -use sp_runtime::{traits::IdentityLookup, BuildStorage, Perbill}; +use sp_runtime::{traits::IdentityLookup, BoundedVec, BuildStorage, Perbill}; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, @@ -221,7 +221,7 @@ impl ExtBuilder { // ideal validator count validator_count: 2, minimum_validator_count: 1, - invulnerables: vec![], + invulnerables: BoundedVec::new(), slash_reward_fraction: Perbill::from_percent(10), min_nominator_bond: ExistentialDeposit::get(), min_validator_bond: ExistentialDeposit::get(), diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index bf8bb74700f3..28ccf3852210 100644 --- a/substrate/frame/grandpa/src/mock.rs +++ b/substrate/frame/grandpa/src/mock.rs @@ -39,7 +39,7 @@ use sp_runtime::{ impl_opaque_keys, testing::{TestXt, UintAuthorityId}, traits::OpaqueKeys, - BuildStorage, DigestItem, Perbill, + BoundedVec, BuildStorage, DigestItem, Perbill, }; use sp_staking::{EraIndex, SessionIndex}; @@ -261,7 +261,7 @@ pub fn new_test_ext_raw_authorities(authorities: AuthorityList) -> sp_io::TestEx validator_count: 8, force_era: pallet_staking::Forcing::ForceNew, minimum_validator_count: 0, - invulnerables: vec![], + invulnerables: BoundedVec::new(), ..Default::default() }; diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs index 8f498f9b746a..bc72b78df933 100644 --- a/substrate/frame/root-offences/src/mock.rs +++ b/substrate/frame/root-offences/src/mock.rs @@ -183,7 +183,7 @@ impl Config for Test { pub struct ExtBuilder { validator_count: u32, minimum_validator_count: u32, - invulnerables: Vec, + invulnerables: BoundedVec::MaxInvulnerables>, balance_factor: Balance, } @@ -192,7 +192,7 @@ impl Default for ExtBuilder { Self { validator_count: 2, minimum_validator_count: 0, - invulnerables: vec![], + invulnerables: BoundedVec::new(), balance_factor: 1, } } diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 0cf95dcb5511..3b773d1e22cf 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -725,7 +725,7 @@ mod benchmarks { #[benchmark] // Worst case scenario, the list of invulnerables is very long. - fn set_invulnerables(v: Linear<0, { BenchMaxValidators::::get() }>) { + fn set_invulnerables(v: Linear<0, { T::MaxInvulnerables::get() }>) { let mut invulnerables = Vec::new(); for i in 0..v { invulnerables.push(account("invulnerable", i, SEED)); diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 690731bf8586..f97b4ed30b8f 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -1109,44 +1109,12 @@ where pub struct EraInfo(core::marker::PhantomData); impl EraInfo { /// Returns true if validator has one or more page of era rewards not claimed yet. - // Also looks at legacy storage that can be cleaned up after #433. pub fn pending_rewards(era: EraIndex, validator: &T::AccountId) -> bool { - let page_count = if let Some(overview) = >::get(&era, validator) { - overview.page_count - } else { - if >::contains_key(era, validator) { - // this means non paged exposure, and we treat them as single paged. - 1 - } else { - // if no exposure, then no rewards to claim. - return false - } - }; - - // check if era is marked claimed in legacy storage. - if >::get(validator) - .map(|l| l.legacy_claimed_rewards.contains(&era)) - .unwrap_or_default() - { - return false - } - - ClaimedRewards::::get(era, validator).len() < page_count as usize - } - - /// Temporary function which looks at both (1) passed param `T::StakingLedger` for legacy - /// non-paged rewards, and (2) `T::ClaimedRewards` for paged rewards. This function can be - /// removed once `T::HistoryDepth` eras have passed and none of the older non-paged rewards - /// are relevant/claimable. - // Refer tracker issue for cleanup: https://github.com/paritytech/polkadot-sdk/issues/433 - pub(crate) fn is_rewards_claimed_with_legacy_fallback( - era: EraIndex, - ledger: &StakingLedger, - validator: &T::AccountId, - page: Page, - ) -> bool { - ledger.legacy_claimed_rewards.binary_search(&era).is_ok() || - Self::is_rewards_claimed(era, validator, page) + >::get(&era, validator) + .map(|overview| { + ClaimedRewards::::get(era, validator).len() < overview.page_count as usize + }) + .unwrap_or(false) } /// Check if the rewards for the given era and page index have been claimed. @@ -1167,20 +1135,7 @@ impl EraInfo { validator: &T::AccountId, page: Page, ) -> Option>> { - let overview = >::get(&era, validator); - - // return clipped exposure if page zero and paged exposure does not exist - // exists for backward compatibility and can be removed as part of #13034 - if overview.is_none() && page == 0 { - return Some(PagedExposure::from_clipped(>::get(era, validator))) - } - - // no exposure for this validator - if overview.is_none() { - return None - } - - let overview = overview.expect("checked above; qed"); + let overview = >::get(&era, validator)?; // validator stake is added only in page zero let validator_stake = if page == 0 { overview.own } else { Zero::zero() }; @@ -1201,13 +1156,9 @@ impl EraInfo { era: EraIndex, validator: &T::AccountId, ) -> Exposure> { - let overview = >::get(&era, validator); - - if overview.is_none() { - return ErasStakers::::get(era, validator) - } - - let overview = overview.expect("checked above; qed"); + let Some(overview) = >::get(&era, validator) else { + return Exposure::default(); + }; let mut others = Vec::with_capacity(overview.nominator_count as usize); for page in 0..overview.page_count { @@ -1238,20 +1189,7 @@ impl EraInfo { } /// Returns the next page that can be claimed or `None` if nothing to claim. - pub(crate) fn get_next_claimable_page( - era: EraIndex, - validator: &T::AccountId, - ledger: &StakingLedger, - ) -> Option { - if Self::is_non_paged_exposure(era, validator) { - return match ledger.legacy_claimed_rewards.binary_search(&era) { - // already claimed - Ok(_) => None, - // Non-paged exposure is considered as a single page - Err(_) => Some(0), - } - } - + pub(crate) fn get_next_claimable_page(era: EraIndex, validator: &T::AccountId) -> Option { // Find next claimable page of paged exposure. let page_count = Self::get_page_count(era, validator); let all_claimable_pages: Vec = (0..page_count).collect(); @@ -1260,11 +1198,6 @@ impl EraInfo { all_claimable_pages.into_iter().find(|p| !claimed_pages.contains(p)) } - /// Checks if exposure is paged or not. - fn is_non_paged_exposure(era: EraIndex, validator: &T::AccountId) -> bool { - >::contains_key(&era, validator) - } - /// Returns validator commission for this era and page. pub(crate) fn get_validator_commission( era: EraIndex, diff --git a/substrate/frame/staking/src/migrations.rs b/substrate/frame/staking/src/migrations.rs index c616ce5ee330..08667dd61767 100644 --- a/substrate/frame/staking/src/migrations.rs +++ b/substrate/frame/staking/src/migrations.rs @@ -60,71 +60,42 @@ impl Default for ObsoleteReleases { #[storage_alias] type StorageVersion = StorageValue, ObsoleteReleases, ValueQuery>; -/// Migrates to multi-page election support. -/// -/// See: -/// -/// Important note: this migration should be released with the election provider configured by this -/// pallet supporting up to 1 page. Thus, -/// * `VoterSnapshotStatus` does not need migration, as it will always be `Status::Waiting` when -/// the number of election pages is 1. -/// * `ElectableStashes` must be populated iif there are collected exposures for a future era (i.e. -/// exposures have been collected but `fn try_plan_new_era` was not called). -pub mod v17 { +/// Migrating `DisabledValidators` from `Vec` to `Vec<(u32, OffenceSeverity)>` to track offense +/// severity for re-enabling purposes. +pub mod v16 { use super::*; + use frame_support::Twox64Concat; + use sp_staking::offence::OffenceSeverity; - pub struct VersionedMigrateV16ToV17(core::marker::PhantomData); - impl UncheckedOnRuntimeUpgrade for VersionedMigrateV16ToV17 { - fn on_runtime_upgrade() -> Weight { - // Populates the `ElectableStashes` with the exposures of the next planning era if it - // is initialized (i.e. if the there are exposures collected for the next planning - // era). - - // note: we expect the migration to be released with a single page config. - debug_assert!(Pallet::::election_pages() == 1); - - let next_era = CurrentEra::::get().defensive_unwrap_or_default().saturating_add(1); - let prepared_exposures = ErasStakersOverview::::iter_prefix(next_era) - .map(|(v, _)| v) - .collect::>(); - let migrated_stashes = prepared_exposures.len() as u32; - - let result = Pallet::::add_electables(prepared_exposures.into_iter()); - debug_assert!(result.is_ok()); - - log!(info, "v17 applied successfully, migrated {:?}.", migrated_stashes); - T::DbWeight::get().reads_writes( - // 1x read per history depth and current era read. - (T::HistoryDepth::get() + 1u32).into(), - // 1x write per exposure migrated. - migrated_stashes.into(), - ) - } + #[frame_support::storage_alias] + pub(crate) type Invulnerables = + StorageValue, Vec<::AccountId>, ValueQuery>; - #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { - frame_support::ensure!( - Pallet::::on_chain_storage_version() >= 17, - "v17 not applied" - ); - Ok(()) - } - } + #[frame_support::storage_alias] + pub(crate) type DisabledValidators = + StorageValue, Vec<(u32, OffenceSeverity)>, ValueQuery>; - pub type MigrateV16ToV17 = VersionedMigration< - 16, - 17, - VersionedMigrateV16ToV17, + #[frame_support::storage_alias] + pub(crate) type ErasStakers = StorageDoubleMap< Pallet, - ::DbWeight, + Twox64Concat, + EraIndex, + Twox64Concat, + ::AccountId, + Exposure<::AccountId, BalanceOf>, + ValueQuery, >; -} -/// Migrating `DisabledValidators` from `Vec` to `Vec<(u32, OffenceSeverity)>` to track offense -/// severity for re-enabling purposes. -pub mod v16 { - use super::*; - use sp_staking::offence::OffenceSeverity; + #[frame_support::storage_alias] + pub(crate) type ErasStakersClipped = StorageDoubleMap< + Pallet, + Twox64Concat, + EraIndex, + Twox64Concat, + ::AccountId, + Exposure<::AccountId, BalanceOf>, + ValueQuery, + >; pub struct VersionUncheckedMigrateV15ToV16(core::marker::PhantomData); impl UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV15ToV16 { @@ -146,7 +117,7 @@ pub mod v16 { .map(|v| (v, max_offence)) .collect::>(); - DisabledValidators::::set(migrated); + v16::DisabledValidators::::set(migrated); log!(info, "v16 applied successfully."); T::DbWeight::get().reads_writes(1, 1) diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 9dbec7cb24d5..0229937f5594 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -364,6 +364,8 @@ impl crate::pallet::pallet::Config for Test { type EventListeners = EventListenerMock; type DisablingStrategy = pallet_staking::UpToLimitWithReEnablingDisablingStrategy; + type MaxInvulnerables = ConstU32<20>; + type MaxDisabledValidators = ConstU32<100>; } pub struct WeightedNominationsQuota; @@ -397,7 +399,7 @@ pub struct ExtBuilder { nominate: bool, validator_count: u32, minimum_validator_count: u32, - invulnerables: Vec, + invulnerables: BoundedVec::MaxInvulnerables>, has_stakers: bool, initialize_first_session: bool, pub min_nominator_bond: Balance, @@ -415,7 +417,7 @@ impl Default for ExtBuilder { validator_count: 2, minimum_validator_count: 0, balance_factor: 1, - invulnerables: vec![], + invulnerables: BoundedVec::new(), has_stakers: true, initialize_first_session: true, min_nominator_bond: ExistentialDeposit::get(), @@ -449,7 +451,8 @@ impl ExtBuilder { self } pub fn invulnerables(mut self, invulnerables: Vec) -> Self { - self.invulnerables = invulnerables; + self.invulnerables = BoundedVec::try_from(invulnerables) + .expect("Too many invulnerable validators: upper limit is MaxInvulnerables"); self } pub fn session_per_era(self, length: SessionIndex) -> Self { diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index e9c695e03454..348a6a2584e2 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -244,13 +244,8 @@ impl Pallet { validator_stash: T::AccountId, era: EraIndex, ) -> DispatchResultWithPostInfo { - let controller = Self::bonded(&validator_stash).ok_or_else(|| { - Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) - })?; - - let ledger = Self::ledger(StakingAccount::Controller(controller))?; - let page = EraInfo::::get_next_claimable_page(era, &validator_stash, &ledger) - .ok_or_else(|| { + let page = + EraInfo::::get_next_claimable_page(era, &validator_stash).ok_or_else(|| { Error::::AlreadyClaimed .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) })?; @@ -306,13 +301,13 @@ impl Pallet { let stash = ledger.stash.clone(); - if EraInfo::::is_rewards_claimed_with_legacy_fallback(era, &ledger, &stash, page) { + if EraInfo::::is_rewards_claimed(era, &stash, page) { return Err(Error::::AlreadyClaimed .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))) - } else { - EraInfo::::set_rewards_as_claimed(era, &stash, page); } + EraInfo::::set_rewards_as_claimed(era, &stash, page); + let exposure = EraInfo::::get_paged_exposure(era, &stash, page).ok_or_else(|| { Error::::InvalidEraToReward .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) @@ -362,7 +357,7 @@ impl Pallet { era_index: era, validator_stash: stash.clone(), page, - next: EraInfo::::get_next_claimable_page(era, &stash, &ledger), + next: EraInfo::::get_next_claimable_page(era, &stash), }); let mut total_imbalance = PositiveImbalanceOf::::zero(); @@ -955,11 +950,7 @@ impl Pallet { pub(crate) fn clear_era_information(era_index: EraIndex) { // FIXME: We can possibly set a reasonable limit since we do this only once per era and // clean up state across multiple blocks. - let mut cursor = >::clear_prefix(era_index, u32::MAX, None); - debug_assert!(cursor.maybe_cursor.is_none()); - cursor = >::clear_prefix(era_index, u32::MAX, None); - debug_assert!(cursor.maybe_cursor.is_none()); - cursor = >::clear_prefix(era_index, u32::MAX, None); + let mut cursor = >::clear_prefix(era_index, u32::MAX, None); debug_assert!(cursor.maybe_cursor.is_none()); cursor = >::clear_prefix(era_index, u32::MAX, None); debug_assert!(cursor.maybe_cursor.is_none()); @@ -1314,9 +1305,10 @@ impl Pallet { /// Returns full exposure of a validator for a given era. /// - /// History note: This used to be a getter for old storage item `ErasStakers` deprecated in v14. - /// Since this function is used in the codebase at various places, we kept it as a custom getter - /// that takes care of getting the full exposure of the validator in a backward compatible way. + /// History note: This used to be a getter for old storage item `ErasStakers` deprecated in v14 + /// and deleted in v17. Since this function is used in the codebase at various places, we kept + /// it as a custom getter that takes care of getting the full exposure of the validator in a + /// backward compatible way. pub fn eras_stakers( era: EraIndex, account: &T::AccountId, @@ -2190,13 +2182,6 @@ impl StakingInterface for Pallet { } fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool { - // look in the non paged exposures - // FIXME: Can be cleaned up once non paged exposures are cleared (https://github.com/paritytech/polkadot-sdk/issues/433) - ErasStakers::::iter_prefix(era).any(|(validator, exposures)| { - validator == *who || exposures.others.iter().any(|i| i.who == *who) - }) - || - // look in the paged exposures ErasStakersPaged::::iter_prefix((era,)).any(|((validator, _), exposure_page)| { validator == *who || exposure_page.others.iter().any(|i| i.who == *who) }) @@ -2324,7 +2309,6 @@ impl Pallet { Self::check_bonded_consistency()?; Self::check_payees()?; Self::check_nominators()?; - Self::check_exposures()?; Self::check_paged_exposures()?; Self::check_count()?; Self::ensure_disabled_validators_sorted() @@ -2516,27 +2500,6 @@ impl Pallet { Ok(()) } - /// Invariants: - /// * For each era exposed validator, check if the exposure total is sane (exposure.total = - /// exposure.own + exposure.own). - fn check_exposures() -> Result<(), TryRuntimeError> { - let era = ActiveEra::::get().unwrap().index; - ErasStakers::::iter_prefix_values(era) - .map(|expo| { - ensure!( - expo.total == - expo.own + - expo.others - .iter() - .map(|e| e.value) - .fold(Zero::zero(), |acc, x| acc + x), - "wrong total exposure.", - ); - Ok(()) - }) - .collect::>() - } - /// Invariants: /// * For each paged era exposed validator, check if the exposure total is sane (exposure.total /// = exposure.own + exposure.own). diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 6c14233db744..69ff513b8e2e 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -59,10 +59,10 @@ pub use impls::*; use crate::{ asset, slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, - DisablingStrategy, EraPayout, EraRewardPoints, Exposure, ExposurePage, Forcing, - LedgerIntegrityState, MaxNominationsOf, NegativeImbalanceOf, Nominations, NominationsQuota, - PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, - UnlockChunk, ValidatorPrefs, + DisablingStrategy, EraPayout, EraRewardPoints, ExposurePage, Forcing, LedgerIntegrityState, + MaxNominationsOf, NegativeImbalanceOf, Nominations, NominationsQuota, PositiveImbalanceOf, + RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, + ValidatorPrefs, }; // The speculative number of spans are used as an input of the weight annotation of @@ -172,10 +172,9 @@ pub mod pallet { /// Number of eras to keep in history. /// /// Following information is kept for eras in `[current_era - - /// HistoryDepth, current_era]`: `ErasStakers`, `ErasStakersClipped`, - /// `ErasValidatorPrefs`, `ErasValidatorReward`, `ErasRewardPoints`, - /// `ErasTotalStake`, `ErasStartSessionIndex`, `ClaimedRewards`, `ErasStakersPaged`, - /// `ErasStakersOverview`. + /// HistoryDepth, current_era]`: `ErasValidatorPrefs`, `ErasValidatorReward`, + /// `ErasRewardPoints`, `ErasTotalStake`, `ErasStartSessionIndex`, `ClaimedRewards`, + /// `ErasStakersPaged`, `ErasStakersOverview`. /// /// Must be more than the number of eras delayed by session. /// I.e. active era must always be in history. I.e. `active_era > @@ -330,6 +329,14 @@ pub mod pallet { #[pallet::no_default_bounds] type DisablingStrategy: DisablingStrategy; + /// Maximum number of invulnerable validators. + #[pallet::constant] + type MaxInvulnerables: Get; + + /// Maximum number of disabled validators. + #[pallet::constant] + type MaxDisabledValidators: Get; + /// Some parameters of the benchmarking. #[cfg(feature = "std")] type BenchmarkingConfig: BenchmarkingConfig; @@ -386,6 +393,8 @@ pub mod pallet { type MaxUnlockingChunks = ConstU32<32>; type MaxValidatorSet = ConstU32<100>; type MaxControllersInDeprecationBatch = ConstU32<100>; + type MaxInvulnerables = ConstU32<20>; + type MaxDisabledValidators = ConstU32<100>; type EventListeners = (); type DisablingStrategy = crate::UpToLimitDisablingStrategy; #[cfg(feature = "std")] @@ -406,8 +415,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 = StorageValue<_, Vec, ValueQuery>; + pub type Invulnerables = + StorageValue<_, BoundedVec, ValueQuery>; /// Map from all locked "stash" accounts to the controller account. /// @@ -519,26 +528,6 @@ pub mod pallet { #[pallet::storage] pub type ErasStartSessionIndex = StorageMap<_, Twox64Concat, EraIndex, SessionIndex>; - /// Exposure of validator at era. - /// - /// This is keyed first by the era index to allow bulk deletion and then the stash account. - /// - /// Is it removed after [`Config::HistoryDepth`] eras. - /// If stakers hasn't been set or has been removed then empty exposure is returned. - /// - /// Note: Deprecated since v14. Use `EraInfo` instead to work with exposures. - #[pallet::storage] - #[pallet::unbounded] - pub type ErasStakers = StorageDoubleMap< - _, - Twox64Concat, - EraIndex, - Twox64Concat, - T::AccountId, - Exposure>, - ValueQuery, - >; - /// Summary of validator exposure at a given era. /// /// This contains the total stake in support of the validator and their own stake. In addition, @@ -562,34 +551,6 @@ pub mod pallet { OptionQuery, >; - /// Clipped Exposure of validator at era. - /// - /// Note: This is deprecated, should be used as read-only and will be removed in the future. - /// New `Exposure`s are stored in a paged manner in `ErasStakersPaged` instead. - /// - /// This is similar to [`ErasStakers`] but number of nominators exposed is reduced to the - /// `T::MaxExposurePageSize` biggest stakers. - /// (Note: the field `total` and `own` of the exposure remains unchanged). - /// This is used to limit the i/o cost for the nominator payout. - /// - /// This is keyed fist by the era index to allow bulk deletion and then the stash account. - /// - /// It is removed after [`Config::HistoryDepth`] eras. - /// If stakers hasn't been set or has been removed then empty exposure is returned. - /// - /// Note: Deprecated since v14. Use `EraInfo` instead to work with exposures. - #[pallet::storage] - #[pallet::unbounded] - pub type ErasStakersClipped = StorageDoubleMap< - _, - Twox64Concat, - EraIndex, - Twox64Concat, - T::AccountId, - Exposure>, - ValueQuery, - >; - /// Paginated exposure of a validator at given era. /// /// This is keyed first by the era index to allow bulk deletion, then stash account and finally @@ -627,7 +588,7 @@ pub mod pallet { ValueQuery, >; - /// Similar to `ErasStakers`, this holds the preferences of validators. + /// Exposure of validator at era with the preferences of validators. /// /// This is keyed first by the era index to allow bulk deletion and then the stash account. /// @@ -755,9 +716,8 @@ pub mod pallet { /// Additionally, each disabled validator is associated with an `OffenceSeverity` which /// represents how severe is the offence that got the validator disabled. #[pallet::storage] - #[pallet::unbounded] pub type DisabledValidators = - StorageValue<_, Vec<(u32, OffenceSeverity)>, ValueQuery>; + StorageValue<_, BoundedVec<(u32, OffenceSeverity), T::MaxDisabledValidators>, 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 @@ -792,7 +752,7 @@ pub mod pallet { pub struct GenesisConfig { pub validator_count: u32, pub minimum_validator_count: u32, - pub invulnerables: Vec, + pub invulnerables: BoundedVec, pub force_era: Forcing, pub slash_reward_fraction: Perbill, pub canceled_payout: BalanceOf, @@ -842,7 +802,11 @@ pub mod pallet { fn build(&self) { ValidatorCount::::put(self.validator_count); MinimumValidatorCount::::put(self.minimum_validator_count); - Invulnerables::::put(&self.invulnerables); + assert!( + self.invulnerables.len() as u32 <= T::MaxInvulnerables::get(), + "Too many invulnerable validators at genesis." + ); + >::put(&self.invulnerables); ForceEra::::put(self.force_era); CanceledSlashPayout::::put(self.canceled_payout); SlashRewardFraction::::put(self.slash_reward_fraction); @@ -1240,7 +1204,7 @@ pub mod pallet { } /// Get the validators that may never be slashed or forcibly kicked out. - pub fn invulnerables() -> Vec { + pub fn invulnerables() -> BoundedVec { Invulnerables::::get() } @@ -1283,18 +1247,6 @@ pub mod pallet { ErasStartSessionIndex::::get(era_index) } - /// Get the clipped exposure of a given validator at an era. - pub fn eras_stakers_clipped( - era_index: EncodeLikeEraIndex, - account_id: EncodeLikeAccountId, - ) -> Exposure> - where - EncodeLikeEraIndex: codec::EncodeLike, - EncodeLikeAccountId: codec::EncodeLike, - { - ErasStakersClipped::::get(era_index, account_id) - } - /// Get the paged history of claimed rewards by era for given validator. pub fn claimed_rewards( era_index: EncodeLikeEraIndex, @@ -1917,6 +1869,8 @@ pub mod pallet { invulnerables: Vec, ) -> DispatchResult { ensure_root(origin)?; + let invulnerables = + BoundedVec::try_from(invulnerables).map_err(|_| Error::::BoundNotMet)?; >::put(invulnerables); Ok(()) } diff --git a/substrate/frame/staking/src/slashing.rs b/substrate/frame/staking/src/slashing.rs index ae76b0707dcb..98a6424fe7ac 100644 --- a/substrate/frame/staking/src/slashing.rs +++ b/substrate/frame/staking/src/slashing.rs @@ -340,13 +340,15 @@ fn add_offending_validator(params: &SlashParams) { }, Err(index) => { // Offender is not disabled, add to `DisabledValidators` and disable it - disabled.insert(index, (offender_idx, new_severity)); - // Propagate disablement to session level - T::SessionInterface::disable_validator(offender_idx); - // Emit event that a validator got disabled - >::deposit_event(super::Event::::ValidatorDisabled { - stash: params.stash.clone(), - }); + if disabled.try_insert(index, (offender_idx, new_severity)).defensive().is_ok() + { + // Propagate disablement to session level + T::SessionInterface::disable_validator(offender_idx); + // Emit event that a validator got disabled + >::deposit_event(super::Event::::ValidatorDisabled { + stash: params.stash.clone(), + }); + } }, } } diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 152d623330e2..836b6706f5a3 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -32,6 +32,7 @@ use frame_support::{ fungible::Inspect, Currency, Get, InspectLockableCurrency, LockableCurrency, ReservableCurrency, WithdrawReasons, }, + BoundedVec, }; use mock::*; @@ -4159,17 +4160,8 @@ fn test_multi_page_payout_stakers_by_page() { ); // verify rewards are tracked to prevent double claims - let ledger = Staking::ledger(11.into()); for page in 0..EraInfo::::get_page_count(1, &11) { - assert_eq!( - EraInfo::::is_rewards_claimed_with_legacy_fallback( - 1, - ledger.as_ref().unwrap(), - &11, - page - ), - true - ); + assert_eq!(EraInfo::::is_rewards_claimed(1, &11, page), true); } for i in 3..16 { @@ -4191,15 +4183,7 @@ fn test_multi_page_payout_stakers_by_page() { // verify we track rewards for each era and page for page in 0..EraInfo::::get_page_count(i - 1, &11) { - assert_eq!( - EraInfo::::is_rewards_claimed_with_legacy_fallback( - i - 1, - Staking::ledger(11.into()).as_ref().unwrap(), - &11, - page - ), - true - ); + assert_eq!(EraInfo::::is_rewards_claimed(i - 1, &11, page), true); } } @@ -4358,7 +4342,6 @@ fn test_multi_page_payout_stakers_backward_compatible() { } // verify we no longer track rewards in `legacy_claimed_rewards` vec - let ledger = Staking::ledger(11.into()); assert_eq!( Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { @@ -4372,15 +4355,7 @@ fn test_multi_page_payout_stakers_backward_compatible() { // verify rewards are tracked to prevent double claims for page in 0..EraInfo::::get_page_count(1, &11) { - assert_eq!( - EraInfo::::is_rewards_claimed_with_legacy_fallback( - 1, - ledger.as_ref().unwrap(), - &11, - page - ), - true - ); + assert_eq!(EraInfo::::is_rewards_claimed(1, &11, page), true); } for i in 3..16 { @@ -4402,15 +4377,7 @@ fn test_multi_page_payout_stakers_backward_compatible() { // verify we track rewards for each era and page for page in 0..EraInfo::::get_page_count(i - 1, &11) { - assert_eq!( - EraInfo::::is_rewards_claimed_with_legacy_fallback( - i - 1, - Staking::ledger(11.into()).as_ref().unwrap(), - &11, - page - ), - true - ); + assert_eq!(EraInfo::::is_rewards_claimed(i - 1, &11, page), true); } } @@ -6767,218 +6734,6 @@ fn should_retain_era_info_only_upto_history_depth() { }); } -#[test] -fn test_legacy_claimed_rewards_is_checked_at_reward_payout() { - ExtBuilder::default().has_stakers(false).build_and_execute(|| { - // Create a validator: - bond_validator(11, 1000); - - // reward validator for next 2 eras - mock::start_active_era(1); - Pallet::::reward_by_ids(vec![(11, 1)]); - mock::start_active_era(2); - Pallet::::reward_by_ids(vec![(11, 1)]); - mock::start_active_era(3); - - //verify rewards are not claimed - assert_eq!( - EraInfo::::is_rewards_claimed_with_legacy_fallback( - 1, - Staking::ledger(11.into()).as_ref().unwrap(), - &11, - 0 - ), - false - ); - assert_eq!( - EraInfo::::is_rewards_claimed_with_legacy_fallback( - 2, - Staking::ledger(11.into()).as_ref().unwrap(), - &11, - 0 - ), - false - ); - - // assume reward claim for era 1 was stored in legacy storage - Ledger::::insert( - 11, - StakingLedgerInspect { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - legacy_claimed_rewards: bounded_vec![1], - }, - ); - - // verify rewards for era 1 cannot be claimed - assert_noop!( - Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 1, 0), - Error::::AlreadyClaimed - .with_weight(::WeightInfo::payout_stakers_alive_staked(0)), - ); - assert_eq!( - EraInfo::::is_rewards_claimed_with_legacy_fallback( - 1, - Staking::ledger(11.into()).as_ref().unwrap(), - &11, - 0 - ), - true - ); - - // verify rewards for era 2 can be claimed - assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 2, 0)); - assert_eq!( - EraInfo::::is_rewards_claimed_with_legacy_fallback( - 2, - Staking::ledger(11.into()).as_ref().unwrap(), - &11, - 0 - ), - true - ); - // but the new claimed rewards for era 2 is not stored in legacy storage - assert_eq!( - Ledger::::get(11).unwrap(), - StakingLedgerInspect { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - legacy_claimed_rewards: bounded_vec![1], - }, - ); - // instead it is kept in `ClaimedRewards` - assert_eq!(ClaimedRewards::::get(2, 11), vec![0]); - }); -} - -#[test] -fn test_validator_exposure_is_backward_compatible_with_non_paged_rewards_payout() { - ExtBuilder::default().has_stakers(false).build_and_execute(|| { - // case 1: exposure exist in clipped. - // set page cap to 10 - MaxExposurePageSize::set(10); - bond_validator(11, 1000); - let mut expected_individual_exposures: Vec> = vec![]; - let mut total_exposure: Balance = 0; - // 1st exposure page - for i in 0..10 { - let who = 1000 + i; - let value = 1000 + i as Balance; - bond_nominator(who, value, vec![11]); - expected_individual_exposures.push(IndividualExposure { who, value }); - total_exposure += value; - } - - for i in 10..15 { - let who = 1000 + i; - let value = 1000 + i as Balance; - bond_nominator(who, value, vec![11]); - expected_individual_exposures.push(IndividualExposure { who, value }); - total_exposure += value; - } - - mock::start_active_era(1); - // reward validator for current era - Pallet::::reward_by_ids(vec![(11, 1)]); - - // start new era - mock::start_active_era(2); - // verify exposure for era 1 is stored in paged storage, that each exposure is stored in - // one and only one page, and no exposure is repeated. - let actual_exposure_page_0 = ErasStakersPaged::::get((1, 11, 0)).unwrap(); - let actual_exposure_page_1 = ErasStakersPaged::::get((1, 11, 1)).unwrap(); - expected_individual_exposures.iter().for_each(|exposure| { - assert!( - actual_exposure_page_0.others.contains(exposure) || - actual_exposure_page_1.others.contains(exposure) - ); - }); - assert_eq!( - expected_individual_exposures.len(), - actual_exposure_page_0.others.len() + actual_exposure_page_1.others.len() - ); - // verify `EraInfo` returns page from paged storage - assert_eq!( - EraInfo::::get_paged_exposure(1, &11, 0).unwrap().others(), - &actual_exposure_page_0.others - ); - assert_eq!( - EraInfo::::get_paged_exposure(1, &11, 1).unwrap().others(), - &actual_exposure_page_1.others - ); - assert_eq!(EraInfo::::get_page_count(1, &11), 2); - - // validator is exposed - assert!(::is_exposed_in_era(&11, &1)); - // nominators are exposed - for i in 10..15 { - let who: AccountId = 1000 + i; - assert!(::is_exposed_in_era(&who, &1)); - } - - // case 2: exposure exist in ErasStakers and ErasStakersClipped (legacy). - // delete paged storage and add exposure to clipped storage - >::remove((1, 11, 0)); - >::remove((1, 11, 1)); - >::remove(1, 11); - - >::insert( - 1, - 11, - Exposure { - total: total_exposure, - own: 1000, - others: expected_individual_exposures.clone(), - }, - ); - let mut clipped_exposure = expected_individual_exposures.clone(); - clipped_exposure.sort_by(|a, b| b.who.cmp(&a.who)); - clipped_exposure.truncate(10); - >::insert( - 1, - 11, - Exposure { total: total_exposure, own: 1000, others: clipped_exposure.clone() }, - ); - - // verify `EraInfo` returns exposure from clipped storage - let actual_exposure_paged = EraInfo::::get_paged_exposure(1, &11, 0).unwrap(); - assert_eq!(actual_exposure_paged.others(), &clipped_exposure); - assert_eq!(actual_exposure_paged.own(), 1000); - assert_eq!(actual_exposure_paged.exposure_metadata.page_count, 1); - - let actual_exposure_full = EraInfo::::get_full_exposure(1, &11); - assert_eq!(actual_exposure_full.others, expected_individual_exposures); - assert_eq!(actual_exposure_full.own, 1000); - assert_eq!(actual_exposure_full.total, total_exposure); - - // validator is exposed - assert!(::is_exposed_in_era(&11, &1)); - // nominators are exposed - for i in 10..15 { - let who: AccountId = 1000 + i; - assert!(::is_exposed_in_era(&who, &1)); - } - - // for pages other than 0, clipped storage returns empty exposure - assert_eq!(EraInfo::::get_paged_exposure(1, &11, 1), None); - // page size is 1 for clipped storage - assert_eq!(EraInfo::::get_page_count(1, &11), 1); - - // payout for page 0 works - assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 0, 0)); - // payout for page 1 fails - assert_noop!( - Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 0, 1), - Error::::InvalidPage - .with_weight(::WeightInfo::payout_stakers_alive_staked(0)) - ); - }); -} - #[test] fn test_runtime_api_pending_rewards() { ExtBuilder::default().build_and_execute(|| { @@ -7019,70 +6774,36 @@ fn test_runtime_api_pending_rewards() { others: individual_exposures, }; - // add non-paged exposure for one and two. - >::insert(0, validator_one, exposure.clone()); - >::insert(0, validator_two, exposure.clone()); - // add paged exposure for third validator - EraInfo::::upsert_exposure(0, &validator_three, exposure); + // add exposure for validators + EraInfo::::upsert_exposure(0, &validator_one, exposure.clone()); + EraInfo::::upsert_exposure(0, &validator_two, exposure.clone()); // add some reward to be distributed ErasValidatorReward::::insert(0, 1000); - // mark rewards claimed for validator_one in legacy claimed rewards - >::insert( - validator_one, - StakingLedgerInspect { - stash: validator_one, - total: stake, - active: stake, - unlocking: Default::default(), - legacy_claimed_rewards: bounded_vec![0], - }, - ); - - // SCENARIO ONE: rewards already marked claimed in legacy storage. - // runtime api should return false for pending rewards for validator_one. + // SCENARIO: Validator with paged exposure (two pages). + // validators have not claimed rewards, so pending rewards is true. + assert!(EraInfo::::pending_rewards(0, &validator_one)); + assert!(EraInfo::::pending_rewards(0, &validator_two)); + // and payout works + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_one, 0)); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_two, 0)); + // validators have two pages of exposure, so pending rewards is still true. + assert!(EraInfo::::pending_rewards(0, &validator_one)); + assert!(EraInfo::::pending_rewards(0, &validator_two)); + // payout again only for validator one + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_one, 0)); + // now pending rewards is false for validator one assert!(!EraInfo::::pending_rewards(0, &validator_one)); - // and if we try to pay, we get an error. + // and payout fails for validator one assert_noop!( Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_one, 0), Error::::AlreadyClaimed.with_weight(err_weight) ); - - // SCENARIO TWO: non-paged exposure - // validator two has not claimed rewards, so pending rewards is true. + // while pending reward is true for validator two assert!(EraInfo::::pending_rewards(0, &validator_two)); - // and payout works + // and payout works again for validator two. assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_two, 0)); - // now pending rewards is false. - assert!(!EraInfo::::pending_rewards(0, &validator_two)); - // and payout fails - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_two, 0), - Error::::AlreadyClaimed.with_weight(err_weight) - ); - - // SCENARIO THREE: validator with paged exposure (two pages). - // validator three has not claimed rewards, so pending rewards is true. - assert!(EraInfo::::pending_rewards(0, &validator_three)); - // and payout works - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_three, 0)); - // validator three has two pages of exposure, so pending rewards is still true. - assert!(EraInfo::::pending_rewards(0, &validator_three)); - // payout again - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_three, 0)); - // now pending rewards is false. - assert!(!EraInfo::::pending_rewards(0, &validator_three)); - // and payout fails - assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_three, 0), - Error::::AlreadyClaimed.with_weight(err_weight) - ); - - // for eras with no exposure, pending rewards is false. - assert!(!EraInfo::::pending_rewards(0, &validator_one)); - assert!(!EraInfo::::pending_rewards(0, &validator_two)); - assert!(!EraInfo::::pending_rewards(0, &validator_three)); }); } @@ -8853,12 +8574,12 @@ mod getters { slashing, tests::{Staking, Test}, ActiveEra, ActiveEraInfo, BalanceOf, CanceledSlashPayout, ClaimedRewards, CurrentEra, - CurrentPlannedSession, EraRewardPoints, ErasRewardPoints, ErasStakersClipped, - ErasStartSessionIndex, ErasTotalStake, ErasValidatorPrefs, ErasValidatorReward, ForceEra, - Forcing, Nominations, Nominators, Perbill, SlashRewardFraction, SlashingSpans, - ValidatorPrefs, Validators, + CurrentPlannedSession, EraRewardPoints, ErasRewardPoints, ErasStartSessionIndex, + ErasTotalStake, ErasValidatorPrefs, ErasValidatorReward, ForceEra, Forcing, Nominations, + Nominators, Perbill, SlashRewardFraction, SlashingSpans, ValidatorPrefs, Validators, }; - use sp_staking::{EraIndex, Exposure, IndividualExposure, Page, SessionIndex}; + use frame_support::BoundedVec; + use sp_staking::{EraIndex, Page, SessionIndex}; #[test] fn get_validator_count_returns_value_from_storage() { @@ -8895,7 +8616,9 @@ mod getters { sp_io::TestExternalities::default().execute_with(|| { // given let v: Vec = vec![1, 2, 3]; - Invulnerables::::put(v.clone()); + Invulnerables::::put( + BoundedVec::try_from(v.clone()).expect("Too many invulnerable validators!"), + ); // when let result = Staking::invulnerables(); @@ -8994,27 +8717,6 @@ mod getters { }); } - #[test] - fn get_eras_stakers_clipped_returns_value_from_storage() { - sp_io::TestExternalities::default().execute_with(|| { - // given - let era: EraIndex = 12; - let account_id: mock::AccountId = 1; - let exposure: Exposure> = Exposure { - total: 1125, - own: 1000, - others: vec![IndividualExposure { who: 101, value: 125 }], - }; - ErasStakersClipped::::insert(era, account_id, exposure.clone()); - - // when - let result = Staking::eras_stakers_clipped(era, &account_id); - - // then - assert_eq!(result, exposure); - }); - } - #[test] fn get_claimed_rewards_returns_value_from_storage() { sp_io::TestExternalities::default().execute_with(|| { diff --git a/substrate/primitives/staking/src/offence.rs b/substrate/primitives/staking/src/offence.rs index e73e8efe5839..9e3c0e5a1946 100644 --- a/substrate/primitives/staking/src/offence.rs +++ b/substrate/primitives/staking/src/offence.rs @@ -19,7 +19,7 @@ //! that use staking. use alloc::vec::Vec; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use sp_core::Get; use sp_runtime::{transaction_validity::TransactionValidityError, DispatchError, Perbill}; @@ -252,7 +252,15 @@ impl OffenceReportSystem for () { /// For instance used for the purposes of distinguishing who should be /// prioritized for disablement. #[derive( - Clone, Copy, PartialEq, Eq, Encode, Decode, sp_runtime::RuntimeDebug, scale_info::TypeInfo, + Clone, + Copy, + PartialEq, + Eq, + Encode, + Decode, + MaxEncodedLen, + sp_runtime::RuntimeDebug, + scale_info::TypeInfo, )] pub struct OffenceSeverity(pub Perbill);