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 f8d43cf4648d..9ebb5e210538 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}; @@ -86,7 +86,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/lib.rs b/polkadot/runtime/common/src/lib.rs index 41e1cdbab801..91dd1efa3274 100644 --- a/polkadot/runtime/common/src/lib.rs +++ b/polkadot/runtime/common/src/lib.rs @@ -230,7 +230,6 @@ impl OneSessionHandler /// A reasonable benchmarking config for staking pallet. pub struct StakingBenchmarkingConfig; impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig { - type MaxValidators = ConstU32<1000>; type MaxNominators = ConstU32<1000>; } 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/parachains/src/disputes/slashing.rs b/polkadot/runtime/parachains/src/disputes/slashing.rs index 95dbf2ba42bb..f17f21552808 100644 --- a/polkadot/runtime/parachains/src/disputes/slashing.rs +++ b/polkadot/runtime/parachains/src/disputes/slashing.rs @@ -83,17 +83,6 @@ const DEFENSIVE_PROOF: &'static str = "disputes module should bail on old sessio #[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; -/// The benchmarking configuration. -pub trait BenchmarkingConfiguration { - const MAX_VALIDATORS: u32; -} - -pub struct BenchConfig; - -impl BenchmarkingConfiguration for BenchConfig { - const MAX_VALIDATORS: u32 = M; -} - /// An offence that is filed when a series of validators lost a dispute. #[derive(TypeInfo)] #[cfg_attr(feature = "std", derive(Clone, PartialEq, Eq))] @@ -400,9 +389,6 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; - - /// Benchmarking configuration. - type BenchmarkingConfig: BenchmarkingConfiguration; } #[pallet::pallet] diff --git a/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs b/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs index bfd46d752438..2ecb3052ac89 100644 --- a/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs +++ b/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs @@ -29,11 +29,6 @@ use sp_session::MembershipProof; // Candidate hash of the disputed candidate. const CANDIDATE_HASH: CandidateHash = CandidateHash(Hash::zero()); -// Simplify getting the value in the benchmark -pub const fn max_validators_for() -> u32 { - <::BenchmarkingConfig as BenchmarkingConfiguration>::MAX_VALIDATORS -} - pub trait Config: pallet_session::Config + pallet_session::historical::Config @@ -148,7 +143,9 @@ mod benchmarks { use super::*; #[benchmark] - fn report_dispute_lost_unsigned(n: Linear<4, { max_validators_for::() }>) { + fn report_dispute_lost_unsigned( + n: Linear<4, { ::MaxValidatorsCount::get() - 1 }>, + ) { let (session_index, key_owner_proof, validator_id) = setup_validator_set::(n); // submit a single `ForInvalid` dispute for a past session. diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index da4f039624a3..c9300f411c4d 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -1184,7 +1184,6 @@ impl parachains_slashing::Config for Runtime { ReportLongevity, >; type WeightInfo = parachains_slashing::TestWeightInfo; - type BenchmarkingConfig = parachains_slashing::BenchConfig<200>; } parameter_types! { diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index d4031f7ac57a..5f2f29c9d2ff 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -339,12 +339,14 @@ parameter_types! { // Six sessions in an era (6 hours). pub storage SessionsPerEra: SessionIndex = 6; // 28 eras for unbonding (7 days). - pub storage BondingDuration: sp_staking::EraIndex = 28; + pub const BondingDuration: sp_staking::EraIndex = 28; + pub const MaxBondedEras: u32 = (BondingDuration::get() as u32) + 1; // 27 eras in which slashes can be cancelled (a bit less than 7 days). pub storage SlashDeferDuration: sp_staking::EraIndex = 27; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub const MaxExposurePageSize: u32 = 64; pub const MaxNominators: u32 = 256; + // This corresponds to the maximum number of validators pub const MaxAuthorities: u32 = 100_000; pub const OnChainMaxWinners: u32 = u32::MAX; // Unbounded number of election targets and voters. @@ -376,6 +378,7 @@ impl pallet_staking::Config for Runtime { type Reward = (); type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; + type MaxBondedEras = MaxBondedEras; type SlashDeferDuration = SlashDeferDuration; type AdminOrigin = frame_system::EnsureNever<()>; type SessionInterface = Self; @@ -396,6 +399,10 @@ impl pallet_staking::Config for Runtime { type EventListeners = (); type WeightInfo = (); type DisablingStrategy = pallet_staking::UpToLimitWithReEnablingDisablingStrategy; + type MaxInvulnerables = ConstU32<20>; + type MaxRewardPagesPerValidator = ConstU32<20>; + type MaxValidatorsCount = MaxAuthorities; + type MaxDisabledValidators = ConstU32<100>; } parameter_types! { @@ -550,7 +557,6 @@ impl parachains_slashing::Config for Runtime { ReportLongevity, >; type WeightInfo = parachains_disputes::slashing::TestWeightInfo; - type BenchmarkingConfig = parachains_slashing::BenchConfig<1000>; } impl parachains_paras_inherent::Config for Runtime { diff --git a/polkadot/runtime/westend/src/genesis_config_presets.rs b/polkadot/runtime/westend/src/genesis_config_presets.rs index ea5aff554e8c..b4b3e3427447 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 cbf2e02ce428..2f28487f71ee 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -287,6 +287,14 @@ pub mod dynamic_params { #[codec(index = 4)] pub static UseAuctionSlots: bool = false; } + + #[dynamic_pallet_params] + #[codec(index = 1)] + pub mod staking { + /// Maximum number of validators allowed. + #[codec(index = 0)] + pub static MaxValidatorsCount: u32 = 300; + } } #[cfg(feature = "runtime-benchmarks")] @@ -319,6 +327,7 @@ impl EnsureOriginWithArg for DynamicParamet match key { Inflation(_) => frame_system::ensure_root(origin.clone()), + Staking(_) => frame_system::ensure_root(origin.clone()), } .map_err(|_| origin) } @@ -716,6 +725,7 @@ parameter_types! { pub const SessionsPerEra: SessionIndex = prod_or_fast!(6, 1); // 2 eras for unbonding (12 hours). pub const BondingDuration: sp_staking::EraIndex = 2; + pub const MaxBondedEras: u32 = (BondingDuration::get() as u32) + 1; // 1 era in which slashes can be cancelled (6 hours). pub const SlashDeferDuration: sp_staking::EraIndex = 1; pub const MaxExposurePageSize: u32 = 64; @@ -738,6 +748,7 @@ impl pallet_staking::Config for Runtime { type Reward = (); type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; + type MaxBondedEras = MaxBondedEras; type SlashDeferDuration = SlashDeferDuration; type AdminOrigin = EitherOf, StakingAdmin>; type SessionInterface = Self; @@ -756,6 +767,10 @@ 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 MaxRewardPagesPerValidator = frame_support::traits::ConstU32<20>; + type MaxValidatorsCount = dynamic_params::staking::MaxValidatorsCount; + type MaxDisabledValidators = ConstU32<100>; } impl pallet_fast_unstake::Config for Runtime { @@ -1417,7 +1432,6 @@ impl parachains_slashing::Config for Runtime { ReportLongevity, >; type WeightInfo = weights::polkadot_runtime_parachains_disputes_slashing::WeightInfo; - type BenchmarkingConfig = parachains_slashing::BenchConfig<300>; } parameter_types! { @@ -1841,6 +1855,7 @@ 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 393fa0b37176..24d0a3e1485b 100644 --- a/polkadot/runtime/westend/src/weights/pallet_staking.rs +++ b/polkadot/runtime/westend/src/weights/pallet_staking.rs @@ -488,8 +488,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/prdoc/pr_6445.prdoc b/prdoc/pr_6445.prdoc new file mode 100644 index 000000000000..8784e4fb22bd --- /dev/null +++ b/prdoc/pr_6445.prdoc @@ -0,0 +1,51 @@ +title: Add storage bounds for pallet `staking` and clean up deprecated non paged exposure storages +doc: +- audience: Runtime Dev + description: |- + This is part of #6289 and necessary for the Asset Hub migration. + + Building on the observations and suggestions from #255 . + + **Changes** + + - Add `MaxInvulnerables` to bound `Invulnerables` Vec -> `BoundedVec`. + - Set to constant 20 in the pallet (must be >= 17 for backward compatibility of runtime `westend`). + - Add `MaxDisabledValidators` to bound `DisabledValidators` Vec -> `BoundedVec` + - Set to constant 100 in the pallet (it should be <= 1/3 * `MaxValidatorsCount` according to the current disabling strategy) + - Remove `ErasStakers` and `ErasStakersClipped` (see #433 ) + - They were deprecated in v14 and could have been removed since staking era 1504 (now it's > 1600) + - Completing the task from #5986 + - Use `MaxExposurePageSize` to bound `ErasStakersPaged` mapping to exposure pages: each `ExposurePage.others` Vec is turned into a `WeakBoundedVec` to allow easy and quick changes to this bound + - Add `MaxBondedEras` to bound `BondedEras` Vec -> `BoundedVec` + - Set to `BondingDuration::get() + 1` everywhere to include both time interval endpoints in [`current_era - BondingDuration::get()`, `current_era`]. Notice that this was done manually in every test and runtime. + - Add `MaxRewardPagesPerValidator` to bound `ClaimedRewards` Vec of pages -> `WeakBoundedVec` + - Set to constant 20 in the pallet. The vector of pages is now a `WeakBoundedVec` to allow easy and quick changes to this parameter + - Remove `MaxValidatorsCount` optional storage item to add `MaxValidatorsCount` mandatory config parameter + - Using it to to bound `EraRewardPoints.individual` BTreeMap -> `BoundedBTreeMap`; + - Set to dynamic parameter in runtime westend so that changing it should not require migrations for it + +crates: +- name: pallet-staking + bump: major +- name: westend-runtime + bump: minor +- name: sp-staking + bump: major +- name: polkadot-runtime-common + bump: patch +- name: pallet-fast-unstake + bump: patch +- name: pallet-babe + bump: patch +- name: pallet-beefy + bump: patch +- name: pallet-delegated-staking + bump: patch +- name: pallet-grandpa + bump: patch +- name: pallet-root-offences + bump: patch +- name: polkadot-runtime-parachains + bump: major +- name: rococo-runtime + bump: patch diff --git a/substrate/bin/node/cli/tests/res/default_genesis_config.json b/substrate/bin/node/cli/tests/res/default_genesis_config.json index a2e52837d882..17cc618a6f6f 100644 --- a/substrate/bin/node/cli/tests/res/default_genesis_config.json +++ b/substrate/bin/node/cli/tests/res/default_genesis_config.json @@ -30,7 +30,6 @@ "stakers": [], "minNominatorBond": 0, "minValidatorBond": 0, - "maxValidatorCount": null, "maxNominatorCount": null }, "session": { diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index faffcd23fbcf..7a2dc3388ace 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -694,6 +694,7 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const SessionsPerEra: sp_staking::SessionIndex = 6; pub const BondingDuration: sp_staking::EraIndex = 24 * 28; + pub const MaxBondedEras: u32 = (BondingDuration::get() as u32) + 1; pub const SlashDeferDuration: sp_staking::EraIndex = 24 * 7; // 1/4 the bonding duration. pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub const MaxNominators: u32 = 64; @@ -708,7 +709,6 @@ const MAX_QUOTA_NOMINATIONS: u32 = 16; pub struct StakingBenchmarkingConfig; impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig { type MaxNominators = ConstU32<1000>; - type MaxValidators = ConstU32<1000>; } impl pallet_staking::Config for Runtime { @@ -722,6 +722,7 @@ impl pallet_staking::Config for Runtime { type Reward = (); // rewards are minted from the void type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; + type MaxBondedEras = MaxBondedEras; type SlashDeferDuration = SlashDeferDuration; /// A super-majority of the council can cancel the slash. type AdminOrigin = EitherOfDiverse< @@ -745,6 +746,10 @@ 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 MaxRewardPagesPerValidator = ConstU32<20>; + type MaxValidatorsCount = MaxAuthorities; + type MaxDisabledValidators = ConstU32<100>; } impl pallet_fast_unstake::Config for Runtime { @@ -1480,7 +1485,7 @@ parameter_types! { pub const ImOnlineUnsignedPriority: TransactionPriority = TransactionPriority::max_value(); /// We prioritize im-online heartbeats over election solution submission. pub const StakingUnsignedPriority: TransactionPriority = TransactionPriority::max_value() / 2; - pub const MaxAuthorities: u32 = 100; + pub const MaxAuthorities: u32 = 300; pub const MaxKeys: u32 = 10_000; pub const MaxPeerInHeartbeats: u32 = 10_000; } diff --git a/substrate/bin/node/testing/src/genesis.rs b/substrate/bin/node/testing/src/genesis.rs index 7f5364744c66..376c2bf343e5 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 23857470adc4..99fc692a1629 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}; @@ -141,6 +141,7 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const SessionsPerEra: SessionIndex = 3; pub const BondingDuration: EraIndex = 3; + pub const MaxBondedEras: u32 = (BondingDuration::get() as u32) + 1; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build(); } @@ -160,6 +161,7 @@ impl pallet_staking::Config for Test { type Currency = Balances; type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; + type MaxBondedEras = MaxBondedEras; type AdminOrigin = frame_system::EnsureRoot; type SessionInterface = Self; type UnixTime = pallet_timestamp::Pallet; @@ -342,7 +344,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 7ae41c609180..4fa0fc83d079 100644 --- a/substrate/frame/beefy/src/mock.rs +++ b/substrate/frame/beefy/src/mock.rs @@ -17,7 +17,6 @@ use codec::{Decode, Encode}; use scale_info::TypeInfo; -use std::vec; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, @@ -36,7 +35,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; @@ -303,7 +302,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 811d5739f4e9..3db6e77179fe 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}, @@ -217,7 +217,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/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs index eaab848c1694..d9e6b7353e71 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -227,6 +227,7 @@ parameter_types! { pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; pub const SessionsPerEra: sp_staking::SessionIndex = 2; pub static BondingDuration: sp_staking::EraIndex = 28; + pub static MaxBondedEras: u32 = (BondingDuration::get() as u32) + 1; pub const SlashDeferDuration: sp_staking::EraIndex = 7; // 1/4 the bonding duration. } @@ -290,6 +291,7 @@ impl pallet_staking::Config for Runtime { type UnixTime = Timestamp; type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; + type MaxBondedEras = MaxBondedEras; type SlashDeferDuration = SlashDeferDuration; type AdminOrigin = EnsureRoot; // root can cancel slashes type SessionInterface = Self; @@ -437,6 +439,7 @@ impl StakingExtBuilder { } pub fn bonding_duration(self, eras: EraIndex) -> Self { ::set(eras); + ::set(eras + 1); self } } diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs index 757052e230a1..c1fbe2209345 100644 --- a/substrate/frame/fast-unstake/src/mock.rs +++ b/substrate/frame/fast-unstake/src/mock.rs @@ -80,6 +80,7 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; pub static BondingDuration: u32 = 3; + pub static MaxBondedEras: u32 = BondingDuration::get() + 1; pub static CurrentEra: u32 = 0; pub static Ongoing: bool = false; pub static MaxWinners: u32 = 100; @@ -109,6 +110,7 @@ impl pallet_staking::Config for Runtime { type UnixTime = pallet_timestamp::Pallet; type AdminOrigin = frame_system::EnsureRoot; type BondingDuration = BondingDuration; + type MaxBondedEras = MaxBondedEras; type EraPayout = pallet_staking::ConvertCurve; type ElectionProvider = MockElection; type GenesisElectionProvider = Self::ElectionProvider; diff --git a/substrate/frame/fast-unstake/src/weights.rs b/substrate/frame/fast-unstake/src/weights.rs index efa2a67ae35d..8b16fccef470 100644 --- a/substrate/frame/fast-unstake/src/weights.rs +++ b/substrate/frame/fast-unstake/src/weights.rs @@ -120,8 +120,6 @@ impl WeightInfo for SubstrateWeight { /// Proof: `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:1 w:0) - /// Proof: `Staking::ErasStakers` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Staking::ErasStakersPaged` (r:257 w:0) /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `v` is `[1, 256]`. @@ -277,8 +275,6 @@ impl WeightInfo for () { /// Proof: `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:1 w:0) - /// Proof: `Staking::ErasStakers` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Staking::ErasStakersPaged` (r:257 w:0) /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `v` is `[1, 256]`. diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index 87369c23948c..38832630ebb1 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}; @@ -258,7 +258,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/nomination-pools/test-delegate-stake/src/lib.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs index cc6335959ab7..3b42e982bb51 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs @@ -256,7 +256,6 @@ fn pool_chill_e2e() { pallet_staking::ConfigOp::Noop, pallet_staking::ConfigOp::Noop, pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, )); // members can unbond as long as total stake of the pool is above min nominator bond diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs index d1bc4ef8ff28..18033c6a54eb 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -329,7 +329,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_staking::ConfigOp::Noop, pallet_staking::ConfigOp::Noop, pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, )); }); diff --git a/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs index cc39cfee91c8..519b94281c1a 100644 --- a/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs +++ b/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs @@ -338,7 +338,6 @@ fn pool_chill_e2e() { pallet_staking::ConfigOp::Noop, pallet_staking::ConfigOp::Noop, pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, )); // members can unbond as long as total stake of the pool is above min nominator bond diff --git a/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs index d913c5fe6948..e564ae23c27b 100644 --- a/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs @@ -80,6 +80,7 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; pub static BondingDuration: u32 = 3; + pub static MaxBondedEras: u32 = BondingDuration::get() + 1; } #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] @@ -88,6 +89,7 @@ impl pallet_staking::Config for Runtime { type UnixTime = pallet_timestamp::Pallet; type AdminOrigin = frame_system::EnsureRoot; type BondingDuration = BondingDuration; + type MaxBondedEras = MaxBondedEras; type EraPayout = pallet_staking::ConvertCurve; type ElectionProvider = frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; @@ -195,7 +197,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_staking::ConfigOp::Noop, pallet_staking::ConfigOp::Noop, pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, )); }); diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs index a27fb36f64a6..c34e6e0bf237 100644 --- a/substrate/frame/root-offences/src/mock.rs +++ b/substrate/frame/root-offences/src/mock.rs @@ -28,7 +28,9 @@ use frame_support::{ traits::{ConstU32, ConstU64, Hooks, OneSessionHandler}, }; use pallet_staking::StakerStatus; -use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; +use sp_runtime::{ + curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BoundedVec, BuildStorage, +}; use sp_staking::{EraIndex, SessionIndex}; type Block = frame_system::mocking::MockBlock; @@ -121,6 +123,7 @@ parameter_types! { pub static SessionsPerEra: SessionIndex = 3; pub static SlashDeferDuration: EraIndex = 0; pub const BondingDuration: EraIndex = 3; + pub const MaxBondedEras: u32 = (BondingDuration::get() as u32) + 1; pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); } @@ -133,6 +136,7 @@ impl pallet_staking::Config for Test { type SlashDeferDuration = SlashDeferDuration; type AdminOrigin = frame_system::EnsureRoot; type BondingDuration = BondingDuration; + type MaxBondedEras = MaxBondedEras; type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; @@ -179,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, } @@ -188,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 79d8dd3fbc30..c9e0fd73bc28 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -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}, @@ -43,7 +44,6 @@ const SEED: u32 = 0; const MAX_SPANS: u32 = 100; const MAX_SLASHES: u32 = 1000; -type MaxValidators = <::BenchmarkingConfig as BenchmarkingConfig>::MaxValidators; type MaxNominators = <::BenchmarkingConfig as BenchmarkingConfig>::MaxNominators; // Add slashing spans to a user account. Not relevant for actual use, only to benchmark @@ -122,10 +122,14 @@ pub fn create_validator_with_nominators( assert_ne!(Validators::::count(), 0); assert_eq!(Nominators::::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:: { + let reward = EraRewardPoints:: { total: points_total, - individual: points_individual.into_iter().collect(), + individual: reward_map, }; let current_era = CurrentEra::::get().unwrap(); @@ -565,7 +569,7 @@ mod benchmarks { #[benchmark] fn set_validator_count() { - let validator_count = MaxValidators::::get(); + let validator_count = T::MaxValidatorsCount::get(); #[extrinsic_call] _(RawOrigin::Root, validator_count); @@ -598,8 +602,7 @@ mod benchmarks { } #[benchmark] - // Worst case scenario, the list of invulnerables is very long. - fn set_invulnerables(v: Linear<0, { MaxValidators::::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)); @@ -865,10 +868,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:: { + let reward = EraRewardPoints:: { total: points_total, - individual: points_individual.into_iter().collect(), + individual: reward_map, }; ErasRewardPoints::::insert(current_era, reward); @@ -938,7 +945,7 @@ mod benchmarks { #[benchmark] fn get_npos_voters( // number of validator intention. we will iterate all of them. - v: Linear<{ MaxValidators::::get() / 2 }, { MaxValidators::::get() }>, + v: Linear<{ T::MaxValidatorsCount::get() / 2 }, { T::MaxValidatorsCount::get() }>, // number of nominator intention. we will iterate all of them. n: Linear<{ MaxNominators::::get() / 2 }, { MaxNominators::::get() }>, @@ -971,7 +978,7 @@ mod benchmarks { #[benchmark] fn get_npos_targets( // number of validator intention. - v: Linear<{ MaxValidators::::get() / 2 }, { MaxValidators::::get() }>, + v: Linear<{ T::MaxValidatorsCount::get() / 2 }, { T::MaxValidatorsCount::get() }>, ) -> Result<(), BenchmarkError> { // number of nominator intention. let n = MaxNominators::::get(); @@ -1004,7 +1011,6 @@ mod benchmarks { ConfigOp::Set(BalanceOf::::max_value()), ConfigOp::Set(BalanceOf::::max_value()), ConfigOp::Set(u32::MAX), - ConfigOp::Set(u32::MAX), ConfigOp::Set(Percent::max_value()), ConfigOp::Set(Perbill::max_value()), ConfigOp::Set(Percent::max_value()), @@ -1013,7 +1019,6 @@ mod benchmarks { assert_eq!(MinNominatorBond::::get(), BalanceOf::::max_value()); assert_eq!(MinValidatorBond::::get(), BalanceOf::::max_value()); assert_eq!(MaxNominatorsCount::::get(), Some(u32::MAX)); - assert_eq!(MaxValidatorsCount::::get(), Some(u32::MAX)); assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(100))); assert_eq!(MinCommission::::get(), Perbill::from_percent(100)); assert_eq!(MaxStakedRewards::::get(), Some(Percent::from_percent(100))); @@ -1030,13 +1035,11 @@ mod benchmarks { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, - ConfigOp::Remove, ); assert!(!MinNominatorBond::::exists()); assert!(!MinValidatorBond::::exists()); assert!(!MaxNominatorsCount::::exists()); - assert!(!MaxValidatorsCount::::exists()); assert!(!ChillThreshold::::exists()); assert!(!MinCommission::::exists()); assert!(!MaxStakedRewards::::exists()); @@ -1060,7 +1063,6 @@ mod benchmarks { ConfigOp::Set(BalanceOf::::max_value()), ConfigOp::Set(BalanceOf::::max_value()), ConfigOp::Set(0), - ConfigOp::Set(0), ConfigOp::Set(Percent::from_percent(0)), ConfigOp::Set(Zero::zero()), ConfigOp::Noop, diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 6361663b2b1c..2bec5eab3fc3 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -315,13 +315,13 @@ use frame_support::{ ConstU32, Currency, Defensive, DefensiveMax, DefensiveSaturating, Get, LockIdentifier, }, weights::Weight, - BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, + BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, WeakBoundedVec, }; 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, OffenceSeverity, ReportOffence}, @@ -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 { +#[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(MaxValidatorsCount))] +pub struct EraRewardPoints> { /// 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, + pub individual: BoundedBTreeMap, } -impl Default for EraRewardPoints { +impl> Default + for EraRewardPoints +{ fn default() -> Self { - EraRewardPoints { total: Default::default(), individual: BTreeMap::new() } + EraRewardPoints { total: Default::default(), individual: BoundedBTreeMap::new() } } } @@ -740,26 +743,36 @@ pub struct Nominations { /// /// This is useful where we need to take into account the validator's own stake and total exposure /// in consideration, in addition to the individual nominators backing them. -#[derive(Encode, Decode, RuntimeDebug, TypeInfo, PartialEq, Eq)] -pub struct PagedExposure { +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, PartialEq, Eq, MaxEncodedLen)] +pub struct PagedExposure< + AccountId, + Balance: HasCompact + MaxEncodedLen, + MaxExposurePageSize: Get, +> { exposure_metadata: PagedExposureMetadata, - exposure_page: ExposurePage, + exposure_page: ExposurePage, } -impl - PagedExposure +impl< + AccountId, + Balance: HasCompact + Copy + AtLeast32BitUnsigned + MaxEncodedLen, + MaxExposurePageSize: Get, + > PagedExposure { - /// Create a new instance of `PagedExposure` from legacy clipped exposures. - pub fn from_clipped(exposure: Exposure) -> Self { - Self { + /// Create a new instance of `PagedExposure` from legacy clipped exposures (now removed). + pub fn from_clipped(exposure: Exposure) -> Result { + let old_exposures = exposure.others.len(); + let others = WeakBoundedVec::try_from(exposure.others).unwrap_or_default(); + defensive_assert!(old_exposures == others.len(), "Too many exposures for a page"); + Ok(Self { exposure_metadata: PagedExposureMetadata { total: exposure.total, own: exposure.own, - nominator_count: exposure.others.len() as u32, + nominator_count: others.len() as u32, page_count: 1, }, - exposure_page: ExposurePage { page_total: exposure.total, others: exposure.others }, - } + exposure_page: ExposurePage { page_total: exposure.total, others }, + }) } /// Returns total exposure of this validator across pages @@ -1049,44 +1062,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. @@ -1094,7 +1075,7 @@ impl EraInfo { /// This is only used for paged rewards. Once older non-paged rewards are no longer /// relevant, `is_rewards_claimed_with_legacy_fallback` can be removed and this function can /// be made public. - fn is_rewards_claimed(era: EraIndex, validator: &T::AccountId, page: Page) -> bool { + pub(crate) fn is_rewards_claimed(era: EraIndex, validator: &T::AccountId, page: Page) -> bool { ClaimedRewards::::get(era, validator).contains(&page) } @@ -1106,21 +1087,8 @@ impl EraInfo { era: EraIndex, 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"); + ) -> Option, T::MaxExposurePageSize>> { + let overview = >::get(&era, validator)?; // validator stake is added only in page zero let validator_stake = if page == 0 { overview.own } else { Zero::zero() }; @@ -1144,15 +1112,18 @@ impl EraInfo { let overview = >::get(&era, validator); if overview.is_none() { - return ErasStakers::::get(era, validator) + return Exposure::default() } let overview = overview.expect("checked above; qed"); let mut others = Vec::with_capacity(overview.nominator_count as usize); for page in 0..overview.page_count { - let nominators = >::get((era, validator, page)); - others.append(&mut nominators.map(|n| n.others).defensive_unwrap_or_default()); + let mut nominators_exposures = >::get((era, validator, page)) + .defensive_unwrap_or_default() + .others + .into_inner(); + others.append(&mut nominators_exposures); } Exposure { total: overview.total, own: overview.own, others } @@ -1178,20 +1149,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(); @@ -1200,11 +1158,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, @@ -1225,8 +1178,11 @@ impl EraInfo { return } - // add page to claimed entries - claimed_pages.push(page); + // try to add page to claimed entries + if claimed_pages.try_push(page).is_err() { + defensive!("Limit reached for maximum number of pages."); + return + } ClaimedRewards::::insert(era, validator, claimed_pages); } @@ -1244,7 +1200,7 @@ impl EraInfo { .defensive_saturating_add((page_size as usize).defensive_saturating_sub(1)) .saturating_div(page_size as usize); - let (exposure_metadata, exposure_pages) = exposure.into_pages(page_size); + let (exposure_metadata, exposure_pages) = exposure.into_pages::(); defensive_assert!(exposure_pages.len() == expected_page_count, "unexpected page count"); >::insert(era, &validator, &exposure_metadata); @@ -1262,8 +1218,6 @@ impl EraInfo { /// Configurations of the benchmarking of the pallet. pub trait BenchmarkingConfig { /// The maximum number of validators to use. - type MaxValidators: Get; - /// The maximum number of nominators to use. type MaxNominators: Get; } @@ -1275,7 +1229,6 @@ pub struct TestBenchmarkingConfig; #[cfg(feature = "std")] impl BenchmarkingConfig for TestBenchmarkingConfig { - type MaxValidators = frame_support::traits::ConstU32<100>; type MaxNominators = frame_support::traits::ConstU32<100>; } diff --git a/substrate/frame/staking/src/migrations.rs b/substrate/frame/staking/src/migrations.rs index 9dfa93c70b32..2a911a65ddcd 100644 --- a/substrate/frame/staking/src/migrations.rs +++ b/substrate/frame/staking/src/migrations.rs @@ -21,9 +21,10 @@ use super::*; use frame_election_provider_support::SortedListProvider; use frame_support::{ migrations::VersionedMigration, - pallet_prelude::ValueQuery, + pallet_prelude::{NMapKey, OptionQuery, ValueQuery}, storage_alias, traits::{GetStorageVersion, OnRuntimeUpgrade, UncheckedOnRuntimeUpgrade}, + WeakBoundedVec, }; #[cfg(feature = "try-runtime")] @@ -60,12 +61,253 @@ impl Default for ObsoleteReleases { #[storage_alias] type StorageVersion = StorageValue, ObsoleteReleases, ValueQuery>; +/// Migrating all non-slashing related unbounded storage items to bounded +pub mod v17 { + use super::*; + + pub struct VersionUncheckedMigrateV16ToV17(core::marker::PhantomData); + impl UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateV16ToV17 { + fn on_runtime_upgrade() -> Weight { + let mut migration_errors = false; + + v16::MaxValidatorsCount::::kill(); + + let mut eras_stakers_keys = + v16::ErasStakers::::iter_keys().map(|(k1, _k2)| k1).collect::>(); + eras_stakers_keys.dedup(); + for k in eras_stakers_keys { + let mut removal_result = + v16::ErasStakers::::clear_prefix(k, u32::max_value(), None); + while let Some(next_cursor) = removal_result.maybe_cursor { + removal_result = v16::ErasStakers::::clear_prefix( + k, + u32::max_value(), + Some(&next_cursor[..]), + ); + } + } + + let mut eras_stakers_clipped_keys = v16::ErasStakersClipped::::iter_keys() + .map(|(k1, _k2)| k1) + .collect::>(); + eras_stakers_clipped_keys.dedup(); + for k in eras_stakers_clipped_keys { + let mut removal_result = + v16::ErasStakersClipped::::clear_prefix(k, u32::max_value(), None); + while let Some(next_cursor) = removal_result.maybe_cursor { + removal_result = v16::ErasStakersClipped::::clear_prefix( + k, + u32::max_value(), + Some(&next_cursor[..]), + ); + } + } + + let old_disabled_validators = v16::DisabledValidators::::get(); + // BoundedVec with MaxDisabledValidators limit, this should always work + let disabled_validators_maybe = BoundedVec::try_from(old_disabled_validators); + match disabled_validators_maybe { + Ok(disabled_validators) => DisabledValidators::::set(disabled_validators), + Err(_) => { + log!(warn, "Migration failed for DisabledValidators from v16 to v17."); + migration_errors = true; + }, + } + + let old_bonded_eras = v16::BondedEras::::get(); + // BoundedVec with MaxBondedEras limit, this should always work + let bonded_eras_maybe = BoundedVec::try_from(old_bonded_eras); + match bonded_eras_maybe { + Ok(bonded_eras) => BondedEras::::set(bonded_eras), + Err(_) => { + log!(warn, "Migration failed for BondedEras from v16 to v17."); + migration_errors = true; + }, + } + + let old_invulnerables = v16::Invulnerables::::get(); + // BoundedVec with MaxInvulnerables limit, this should always work + let invulnerables_maybe = BoundedVec::try_from(old_invulnerables); + match invulnerables_maybe { + Ok(invulnerables) => Invulnerables::::set(invulnerables), + Err(_) => { + log!(warn, "Migration failed for Invulnerables from v16 to v17."); + migration_errors = true; + }, + } + + for (era_index, era_rewards) in v16::ErasRewardPoints::::iter() { + let individual_rewards_maybe = BoundedBTreeMap::try_from(era_rewards.individual); + match individual_rewards_maybe { + Ok(individual_rewards) => { + let bounded_era_rewards = EraRewardPoints::< + ::AccountId, + ::MaxValidatorsCount, + > { + individual: individual_rewards, + total: era_rewards.total, + }; + ErasRewardPoints::::insert(era_index, bounded_era_rewards); + }, + Err(_) => { + log!(warn, "Migration failed for ErasRewardPoints from v16 to v17."); + migration_errors = true; + }, + } + } + + for ((era, validator, page), old_exposure_page) in v16::ErasStakersPaged::::iter() { + let individual_exposures_maybe = WeakBoundedVec::try_from(old_exposure_page.others); + match individual_exposures_maybe { + Ok(individual_exposures) => { + let exposure_page = ExposurePage::< + ::AccountId, + BalanceOf, + ::MaxExposurePageSize, + > { + page_total: old_exposure_page.page_total, + others: individual_exposures, + }; + ErasStakersPaged::::insert((era, validator, page), exposure_page); + }, + Err(_) => { + log!(warn, "Migration failed for ErasStakersPaged from v16 to v17."); + migration_errors = true; + }, + } + } + + for (era, validator, old_reward_pages) in v16::ClaimedRewards::::iter() { + let reward_pages_maybe = WeakBoundedVec::try_from(old_reward_pages.clone()); + match reward_pages_maybe { + Ok(reward_pages) => { + ClaimedRewards::::insert(era, validator, reward_pages); + }, + Err(_) => { + let reward_pages = WeakBoundedVec::force_from(old_reward_pages, None); + log!(warn, "Forced migration of ClaimedRewards items from v16 to v17 having {} pages.", reward_pages.len()); + ClaimedRewards::::insert(era, validator, reward_pages); + }, + } + } + + if migration_errors { + log!(warn, "v17 applied with some errors: state may be not coherent."); + } else { + log!(info, "v17 applied successfully."); + } + T::DbWeight::get().reads_writes(1, 1) + } + } + + pub type MigrateV16ToV17 = VersionedMigration< + 16, + 17, + VersionUncheckedMigrateV16ToV17, + Pallet, + ::DbWeight, + >; +} + /// 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; + #[frame_support::storage_alias] + pub(crate) type Invulnerables = + StorageValue, Vec<::AccountId>, ValueQuery>; + + #[frame_support::storage_alias] + pub(crate) type BondedEras = + StorageValue, Vec<(EraIndex, SessionIndex)>, ValueQuery>; + + #[frame_support::storage_alias] + pub(crate) type ClaimedRewards = StorageDoubleMap< + Pallet, + Twox64Concat, + EraIndex, + Twox64Concat, + ::AccountId, + Vec, + ValueQuery, + >; + + #[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct EraRewardPoints { + pub total: u32, + pub individual: BTreeMap, + } + + impl Default for EraRewardPoints { + fn default() -> Self { + EraRewardPoints { total: Default::default(), individual: BTreeMap::new() } + } + } + + #[frame_support::storage_alias] + pub(crate) type ErasRewardPoints = StorageMap< + Pallet, + Twox64Concat, + u32, + EraRewardPoints<::AccountId>, + ValueQuery, + >; + + #[frame_support::storage_alias] + pub(crate) type DisabledValidators = + StorageValue, Vec<(u32, OffenceSeverity)>, ValueQuery>; + + #[derive( + PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, + )] + pub struct ExposurePage { + /// The total balance of this chunk/page. + #[codec(compact)] + pub page_total: Balance, + /// The portions of nominators stashes that are exposed. + pub others: Vec>, + } + + #[frame_support::storage_alias] + pub(crate) type ErasStakersPaged = StorageNMap< + Pallet, + ( + NMapKey, + NMapKey::AccountId>, + NMapKey, + ), + ExposurePage<::AccountId, BalanceOf>, + OptionQuery, + >; + + #[frame_support::storage_alias] + pub(crate) type MaxValidatorsCount = StorageValue, u32, OptionQuery>; + + #[frame_support::storage_alias] + pub(crate) type ErasStakers = StorageDoubleMap< + Pallet, + Twox64Concat, + EraIndex, + Twox64Concat, + ::AccountId, + Exposure<::AccountId, BalanceOf>, + ValueQuery, + >; + + #[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 { #[cfg(feature = "try-runtime")] @@ -86,7 +328,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 df8cb38e8b37..ed90d5584af0 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -116,6 +116,7 @@ parameter_types! { pub static Period: BlockNumber = 5; pub static Offset: BlockNumber = 0; pub static MaxControllersInDeprecationBatch: u32 = 5900; + pub static MaxValidatorsCount: u32 = 300; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] @@ -177,6 +178,7 @@ pallet_staking_reward_curve::build! { } parameter_types! { pub const BondingDuration: EraIndex = 3; + pub const MaxBondedEras: u32 = (BondingDuration::get() as u32) + 1; pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS; } @@ -287,6 +289,10 @@ impl crate::pallet::pallet::Config for Test { type EventListeners = EventListenerMock; type DisablingStrategy = pallet_staking::UpToLimitWithReEnablingDisablingStrategy; + type MaxInvulnerables = ConstU32<20>; + type MaxRewardPagesPerValidator = ConstU32<20>; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxDisabledValidators = ConstU32<100>; } pub struct WeightedNominationsQuota; @@ -320,7 +326,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, @@ -338,7 +344,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(), @@ -372,7 +378,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 2ae925d03643..a8ed37468981 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -233,13 +233,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)) })?; @@ -294,13 +289,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)) @@ -350,7 +345,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(); @@ -546,8 +541,6 @@ impl Pallet { let bonding_duration = T::BondingDuration::get(); BondedEras::::mutate(|bonded| { - bonded.push((active_era, start_session)); - if active_era > bonding_duration { let first_kept = active_era.defensive_saturating_sub(bonding_duration); @@ -564,6 +557,9 @@ impl Pallet { T::SessionInterface::prune_historical_up_to(first_session); } } + + debug_assert!((bonded.len() as u32) < T::MaxBondedEras::get()); + let _ = bonded.try_push((active_era, start_session)); }); Self::apply_unapplied_slashes(active_era); @@ -808,11 +804,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()); @@ -857,8 +849,24 @@ impl Pallet { if let Some(active_era) = ActiveEra::::get() { >::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; + era_rewards.total += 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: max numbers of rewarded validators reached" + ); + } + }, + }; } }); } @@ -1154,9 +1162,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 the 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, @@ -1848,12 +1857,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) @@ -1981,7 +1984,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() @@ -2067,7 +2069,7 @@ impl Pallet { /// * Number of voters in `VoterList` match that of the number of Nominators and Validators in /// the system (validator is both voter and target). /// * Number of targets in `TargetList` matches the number of validators in the system. - /// * Current validator count is bounded by the election provider's max winners. + /// * Current validator count is bounded by `MaxValidatorsCount`. fn check_count() -> Result<(), TryRuntimeError> { ensure!( ::VoterList::count() == @@ -2079,8 +2081,7 @@ impl Pallet { "wrong external count" ); ensure!( - ValidatorCount::::get() <= - ::MaxWinners::get(), + ValidatorCount::::get() <= T::MaxValidatorsCount::get(), Error::::TooManyValidators ); Ok(()) @@ -2139,27 +2140,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 b3f8c18f704c..32ca05b57135 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -50,10 +50,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 @@ -70,7 +70,7 @@ pub mod pallet { use super::*; /// The in-code storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(16); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(17); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -143,13 +143,18 @@ pub mod pallet { #[pallet::no_default_bounds] type NominationsQuota: NominationsQuota>; + /// The maximum validator count before we stop allowing new validators to join. + /// + /// This is a dynamic runtime parameters, so updates do not require storage migrations. + #[pallet::constant] + type MaxValidatorsCount: Get; + /// 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 > @@ -193,6 +198,13 @@ pub mod pallet { #[pallet::constant] type BondingDuration: Get; + /// Maximum number of eras that staked funds could remain bonded for. + /// + /// Ideally, it should be `BondingDuration::get() + 1` to include all eras in the interval + /// [current_era - BondingDuration::get(), current_era]. + #[pallet::constant] + type MaxBondedEras: Get; + /// Number of eras that slashes are deferred by, after computation. /// /// This should be less than the bonding duration. Set to 0 if slashes @@ -224,15 +236,18 @@ pub mod pallet { /// An `ExposurePage` is weakly bounded to a maximum of `MaxExposurePageSize` /// nominators. /// - /// For older non-paged exposure, a reward payout was restricted to the top - /// `MaxExposurePageSize` nominators. This is to limit the i/o cost for the - /// nominator payout. - /// - /// Note: `MaxExposurePageSize` is used to bound `ClaimedRewards` and is unsafe to reduce - /// without handling it in a migration. + /// Note: `MaxExposurePageSize` is used to (weakly) bound the size of an `ExposurePage` and + /// it may be unsafe to reduce without handling it in a migration. #[pallet::constant] type MaxExposurePageSize: Get; + /// The maximum number of nominators reward pages per nominator. + /// + /// Note: `MaxRewardPagesPerValidator` is used to (weakly) bound the number of pages in + /// `ClaimedRewards` and it may be unsafe to reduce without handling it in a migration. + #[pallet::constant] + type MaxRewardPagesPerValidator: Get; + /// Something that provides a best-effort sorted list of voters aka electing nominators, /// used for NPoS election. /// @@ -297,6 +312,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; @@ -321,6 +344,7 @@ pub mod pallet { parameter_types! { pub const SessionsPerEra: SessionIndex = 3; pub const BondingDuration: EraIndex = 3; + pub const MaxBondedEras: u32 = (BondingDuration::get() as u32) + 1; } #[frame_support::register_default_impl(TestDefaultConfig)] @@ -336,6 +360,7 @@ pub mod pallet { type Reward = (); type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; + type MaxBondedEras = MaxBondedEras; type SlashDeferDuration = (); type SessionInterface = (); type NextNewSession = (); @@ -344,6 +369,10 @@ pub mod pallet { type MaxControllersInDeprecationBatch = ConstU32<100>; type EventListeners = (); type DisablingStrategy = crate::UpToLimitDisablingStrategy; + type MaxInvulnerables = ConstU32<20>; + type MaxRewardPagesPerValidator = ConstU32<20>; + type MaxValidatorsCount = ConstU32<300>; + type MaxDisabledValidators = ConstU32<100>; #[cfg(feature = "std")] type BenchmarkingConfig = crate::TestBenchmarkingConfig; type WeightInfo = (); @@ -362,8 +391,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. /// @@ -410,12 +439,6 @@ pub mod pallet { pub type Validators = CountedStorageMap<_, Twox64Concat, T::AccountId, ValidatorPrefs, ValueQuery>; - /// The maximum validator count before we stop allowing new validators to join. - /// - /// When this value is not set, no limits are enforced. - #[pallet::storage] - pub type MaxValidatorsCount = StorageValue<_, u32, OptionQuery>; - /// The map from nominator stash key to their nomination preferences, namely the validators that /// they wish to support. /// @@ -475,26 +498,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, @@ -518,34 +521,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 @@ -553,7 +528,6 @@ pub mod pallet { /// /// This is cleared after [`Config::HistoryDepth`] eras. #[pallet::storage] - #[pallet::unbounded] pub type ErasStakersPaged = StorageNMap< _, ( @@ -561,7 +535,7 @@ pub mod pallet { NMapKey, NMapKey, ), - ExposurePage>, + ExposurePage, T::MaxExposurePageSize>, OptionQuery, >; @@ -572,18 +546,17 @@ pub mod pallet { /// /// It is removed after [`Config::HistoryDepth`] eras. #[pallet::storage] - #[pallet::unbounded] pub type ClaimedRewards = StorageDoubleMap< _, Twox64Concat, EraIndex, Twox64Concat, T::AccountId, - Vec, + WeakBoundedVec, 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. /// @@ -609,9 +582,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 = - StorageMap<_, Twox64Concat, EraIndex, EraRewardPoints, ValueQuery>; + pub type ErasRewardPoints = StorageMap< + _, + Twox64Concat, + EraIndex, + EraRewardPoints, + 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. @@ -654,11 +631,10 @@ pub mod pallet { /// A mapping from still-bonded eras to the first session index of that era. /// /// Must contains information for eras for the range: - /// `[active_era - bounding_duration; active_era]` + /// `[active_era - bonding_duration; active_era]` #[pallet::storage] - #[pallet::unbounded] pub(crate) type BondedEras = - StorageValue<_, Vec<(EraIndex, SessionIndex)>, ValueQuery>; + StorageValue<_, BoundedVec<(EraIndex, SessionIndex), T::MaxBondedEras>, ValueQuery>; /// All slashing events on validators, mapped by era to the highest slash proportion /// and slash value of the era. @@ -704,16 +680,15 @@ pub mod pallet { /// whole era. For this reason they are kept here - only staking pallet knows about eras. The /// 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 based on the u32 index so that we can find whether a given /// validator has previously offended using binary search. /// /// 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 @@ -726,7 +701,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, @@ -734,7 +709,6 @@ pub mod pallet { Vec<(T::AccountId, T::AccountId, BalanceOf, crate::StakerStatus)>, pub min_nominator_bond: BalanceOf, pub min_validator_bond: BalanceOf, - pub max_validator_count: Option, pub max_nominator_count: Option, } @@ -743,15 +717,16 @@ 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); MinNominatorBond::::put(self.min_nominator_bond); MinValidatorBond::::put(self.min_validator_bond); - if let Some(x) = self.max_validator_count { - MaxValidatorsCount::::put(x); - } if let Some(x) = self.max_nominator_count { MaxNominatorsCount::::put(x); } @@ -784,10 +759,7 @@ pub mod pallet { ), _ => Ok(()), }); - assert!( - ValidatorCount::::get() <= - ::MaxWinners::get() - ); + assert!(ValidatorCount::::get() <= T::MaxValidatorsCount::get()); } // all voters are reported to the `VoterList`. @@ -993,7 +965,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() } @@ -1036,23 +1008,11 @@ 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, account_id: EncodeLikeAccountId, - ) -> Vec + ) -> WeakBoundedVec where EncodeLikeEraIndex: codec::EncodeLike, EncodeLikeAccountId: codec::EncodeLike, @@ -1085,7 +1045,7 @@ pub mod pallet { /// Get the rewards for the last [`Config::HistoryDepth`] eras. pub fn eras_reward_points( era_index: EncodeLikeEraIndex, - ) -> EraRewardPoints + ) -> EraRewardPoints where EncodeLikeEraIndex: codec::EncodeLike, { @@ -1379,12 +1339,10 @@ pub mod pallet { // If this error is reached, we need to adjust the `MinValidatorBond` and start // calling `chill_other`. Until then, we explicitly block new validators to protect // the runtime. - if let Some(max_validators) = MaxValidatorsCount::::get() { - ensure!( - Validators::::count() < max_validators, - Error::::TooManyValidators - ); - } + ensure!( + Validators::::count() < T::MaxValidatorsCount::get(), + Error::::TooManyValidators, + ); } Self::do_remove_nominator(stash); @@ -1570,18 +1528,13 @@ pub mod pallet { #[pallet::compact] new: u32, ) -> DispatchResult { ensure_root(origin)?; - // ensure new validator count does not exceed maximum winners - // support by election provider. - ensure!( - new <= ::MaxWinners::get(), - Error::::TooManyValidators - ); + // ensure new validator count does not exceed maximum number of validators allowed. + ensure!(new <= T::MaxValidatorsCount::get(), Error::::TooManyValidators); ValidatorCount::::put(new); Ok(()) } - /// Increments the ideal number of validators up to maximum of - /// `ElectionProviderBase::MaxWinners`. + /// Increments the ideal number of validators up to maximum of `T::MaxValidatorsCount`. /// /// The dispatch origin must be Root. /// @@ -1596,17 +1549,14 @@ pub mod pallet { ensure_root(origin)?; let old = ValidatorCount::::get(); let new = old.checked_add(additional).ok_or(ArithmeticError::Overflow)?; - ensure!( - new <= ::MaxWinners::get(), - Error::::TooManyValidators - ); + ensure!(new <= T::MaxValidatorsCount::get(), Error::::TooManyValidators); ValidatorCount::::put(new); Ok(()) } /// Scale up the ideal number of validators by a factor up to maximum of - /// `ElectionProviderBase::MaxWinners`. + /// `T::MaxValidatorsCount`. /// /// The dispatch origin must be Root. /// @@ -1619,10 +1569,7 @@ pub mod pallet { let old = ValidatorCount::::get(); let new = old.checked_add(factor.mul_floor(old)).ok_or(ArithmeticError::Overflow)?; - ensure!( - new <= ::MaxWinners::get(), - Error::::TooManyValidators - ); + ensure!(new <= T::MaxValidatorsCount::get(), Error::::TooManyValidators); ValidatorCount::::put(new); Ok(()) @@ -1681,7 +1628,11 @@ pub mod pallet { invulnerables: Vec, ) -> DispatchResult { ensure_root(origin)?; - >::put(invulnerables); + ensure!( + invulnerables.len() as u32 <= T::MaxInvulnerables::get(), + Error::::BoundNotMet + ); + >::put(BoundedVec::truncate_from(invulnerables)); Ok(()) } @@ -1939,7 +1890,6 @@ pub mod pallet { min_nominator_bond: ConfigOp>, min_validator_bond: ConfigOp>, max_nominator_count: ConfigOp, - max_validator_count: ConfigOp, chill_threshold: ConfigOp, min_commission: ConfigOp, max_staked_rewards: ConfigOp, @@ -1959,7 +1909,6 @@ pub mod pallet { config_op_exp!(MinNominatorBond, min_nominator_bond); config_op_exp!(MinValidatorBond, min_validator_bond); config_op_exp!(MaxNominatorsCount, max_nominator_count); - config_op_exp!(MaxValidatorsCount, max_validator_count); config_op_exp!(ChillThreshold, chill_threshold); config_op_exp!(MinCommission, min_commission); config_op_exp!(MaxStakedRewards, max_staked_rewards); @@ -1983,8 +1932,8 @@ pub mod pallet { /// /// * A `ChillThreshold` must be set and checked which defines how close to the max /// nominators or validators we must reach before users can start chilling one-another. - /// * A `MaxNominatorCount` and `MaxValidatorCount` must be set which is used to determine - /// how close we are to the threshold. + /// * A `MaxNominatorCount` must be set which is used to determine how close we are to the + /// threshold. /// * A `MinNominatorBond` and `MinValidatorBond` must be set and checked, which determines /// if this is a person that should be chilled because they have not met the threshold /// bond required. @@ -2012,7 +1961,7 @@ pub mod pallet { // // * A `ChillThreshold` is set which defines how close to the max nominators or // validators we must reach before users can start chilling one-another. - // * A `MaxNominatorCount` and `MaxValidatorCount` which is used to determine how close + // * A `MaxNominatorCount` and `MaxValidatorsCount` which is used to determine how close // we are to the threshold. // * A `MinNominatorBond` and `MinValidatorBond` which is the final condition checked to // determine this is a person that should be chilled because they have not met the @@ -2037,8 +1986,7 @@ pub mod pallet { ); MinNominatorBond::::get() } else if Validators::::contains_key(&stash) { - let max_validator_count = - MaxValidatorsCount::::get().ok_or(Error::::CannotChillOther)?; + let max_validator_count = T::MaxValidatorsCount::get(); let current_validator_count = Validators::::count(); ensure!( threshold * max_validator_count < current_validator_count, diff --git a/substrate/frame/staking/src/slashing.rs b/substrate/frame/staking/src/slashing.rs index ae76b0707dcb..d32dc992bd47 100644 --- a/substrate/frame/staking/src/slashing.rs +++ b/substrate/frame/staking/src/slashing.rs @@ -50,7 +50,7 @@ //! Based on research at use crate::{ - asset, BalanceOf, Config, DisabledValidators, DisablingStrategy, Error, Exposure, + asset, log, BalanceOf, Config, DisabledValidators, DisablingStrategy, Error, Exposure, NegativeImbalanceOf, NominatorSlashInEra, Pallet, Perbill, SessionInterface, SpanSlash, UnappliedSlash, ValidatorSlashInEra, }; @@ -340,13 +340,20 @@ 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)).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(), + }); + } else { + log!( + warn, + "Validator with index {} could not be disabled: max disabled validators limit reached", + index + ) + } }, } } diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 6c2335e1aac8..aa35d092fbb8 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -28,6 +28,7 @@ use frame_support::{ dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, pallet_prelude::*, traits::{Currency, Get, ReservableCurrency}, + BoundedVec, }; use mock::*; @@ -52,7 +53,6 @@ fn set_staking_configs_works() { ConfigOp::Set(1_500), ConfigOp::Set(2_000), ConfigOp::Set(10), - ConfigOp::Set(20), ConfigOp::Set(Percent::from_percent(75)), ConfigOp::Set(Zero::zero()), ConfigOp::Set(Zero::zero()) @@ -60,7 +60,6 @@ fn set_staking_configs_works() { assert_eq!(MinNominatorBond::::get(), 1_500); assert_eq!(MinValidatorBond::::get(), 2_000); assert_eq!(MaxNominatorsCount::::get(), Some(10)); - assert_eq!(MaxValidatorsCount::::get(), Some(20)); assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(75))); assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); assert_eq!(MaxStakedRewards::::get(), Some(Percent::from_percent(0))); @@ -73,7 +72,6 @@ fn set_staking_configs_works() { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, - ConfigOp::Noop, ConfigOp::Noop ))); @@ -85,13 +83,11 @@ fn set_staking_configs_works() { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, - ConfigOp::Remove, ConfigOp::Remove )); assert_eq!(MinNominatorBond::::get(), 0); assert_eq!(MinValidatorBond::::get(), 0); assert_eq!(MaxNominatorsCount::::get(), None); - assert_eq!(MaxValidatorsCount::::get(), None); assert_eq!(ChillThreshold::::get(), None); assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); assert_eq!(MaxStakedRewards::::get(), None); @@ -234,6 +230,9 @@ fn basic_setup_works() { // New era is not being forced assert_eq!(ForceEra::::get(), Forcing::NotForcing); + + // MaxBondedEras must be coherent with BondingDuration + assert_eq!(MaxBondedEras::get(), (BondingDuration::get() as u32) + 1); }); } @@ -335,13 +334,14 @@ fn rewards_should_work() { assert_eq!(asset::total_balance::(&11), init_balance_11); assert_eq!(asset::total_balance::(&21), init_balance_21); assert_eq!(asset::total_balance::(&101), init_balance_101); - assert_eq!( - ErasRewardPoints::::get(active_era()), - EraRewardPoints { - total: 50 * 3, - individual: vec![(11, 100), (21, 50)].into_iter().collect(), - } - ); + + let eras_reward_points = Staking::eras_reward_points(active_era()); + + assert_eq!(eras_reward_points.total, 50 * 3); + assert_eq!(eras_reward_points.individual.get(&11), Some(&100)); + assert_eq!(eras_reward_points.individual.get(&21), Some(&50)); + assert_eq!(eras_reward_points.individual.keys().cloned().collect::>(), [11, 21]); + let part_for_11 = Perbill::from_rational::(1000, 1125); let part_for_21 = Perbill::from_rational::(1000, 1375); let part_for_101_from_11 = Perbill::from_rational::(125, 1125); @@ -1808,7 +1808,6 @@ fn max_staked_rewards_works() { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, - ConfigOp::Noop, ConfigOp::Set(Percent::from_percent(max_staked_rewards)), )); @@ -2328,9 +2327,11 @@ fn reward_validator_slashing_validator_does_not_overflow() { let _ = asset::set_stakeable_balance::(&11, stake); let exposure = Exposure:: { total: stake, own: stake, others: vec![] }; - let reward = EraRewardPoints:: { + let mut reward_map = BoundedBTreeMap::new(); + assert_ok!(reward_map.try_insert(11, 1)); + let reward = EraRewardPoints::::MaxValidatorsCount> { total: 1, - individual: vec![(11, 1)].into_iter().collect(), + individual: reward_map, }; // Check reward @@ -2386,10 +2387,11 @@ fn reward_from_authorship_event_handler_works() { // 21 is rewarded as an uncle producer // 11 is rewarded as a block producer and uncle referencer and uncle producer - assert_eq!( - ErasRewardPoints::::get(active_era()), - EraRewardPoints { individual: vec![(11, 20 * 2)].into_iter().collect(), total: 40 }, - ); + + let eras_reward_points = ErasRewardPoints::::get(active_era()); + assert_eq!(eras_reward_points.total, 40); + assert_eq!(eras_reward_points.individual.get(&11), Some(&(20 * 2))); + assert_eq!(eras_reward_points.individual.keys().cloned().collect::>(), [11]); }) } @@ -2403,10 +2405,11 @@ fn add_reward_points_fns_works() { Pallet::::reward_by_ids(vec![(21, 1), (11, 1), (11, 1)]); - assert_eq!( - ErasRewardPoints::::get(active_era()), - EraRewardPoints { individual: vec![(11, 4), (21, 2)].into_iter().collect(), total: 6 }, - ); + let eras_reward_points = ErasRewardPoints::::get(active_era()); + assert_eq!(eras_reward_points.total, 6); + assert_eq!(eras_reward_points.individual.get(&11), Some(&4)); + assert_eq!(eras_reward_points.individual.get(&21), Some(&2)); + assert_eq!(eras_reward_points.individual.keys().cloned().collect::>(), [11, 21]); }) } @@ -4060,17 +4063,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 { @@ -4092,15 +4086,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); } } @@ -4259,7 +4245,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 { @@ -4273,15 +4258,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 { @@ -4303,15 +4280,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); } } @@ -5739,7 +5708,6 @@ fn chill_other_works() { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, - ConfigOp::Remove, ConfigOp::Noop, )); @@ -5754,12 +5722,13 @@ fn chill_other_works() { ); // Add limits, but no threshold + let max = 10; + MaxValidatorsCount::set(max); assert_ok!(Staking::set_staking_configs( RuntimeOrigin::root(), ConfigOp::Noop, ConfigOp::Noop, - ConfigOp::Set(10), - ConfigOp::Set(10), + ConfigOp::Set(max), ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, @@ -5781,7 +5750,6 @@ fn chill_other_works() { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Remove, - ConfigOp::Remove, ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, @@ -5803,7 +5771,6 @@ fn chill_other_works() { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Set(10), - ConfigOp::Set(10), ConfigOp::Set(Percent::from_percent(75)), ConfigOp::Noop, ConfigOp::Noop, @@ -5844,12 +5811,12 @@ fn capped_stakers_works() { // Change the maximums let max = 10; + MaxValidatorsCount::set(max); assert_ok!(Staking::set_staking_configs( RuntimeOrigin::root(), ConfigOp::Set(10), ConfigOp::Set(10), ConfigOp::Set(max), - ConfigOp::Set(max), ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Noop, @@ -5914,13 +5881,13 @@ fn capped_stakers_works() { ValidatorPrefs::default() )); - // No problem when we set to `None` again + // No problem when we set to `None` again and increase MaxValidatorsCount + MaxValidatorsCount::set(2 * MaxValidatorsCount::get()); assert_ok!(Staking::set_staking_configs( RuntimeOrigin::root(), ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Remove, - ConfigOp::Remove, ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, @@ -5957,7 +5924,6 @@ fn min_commission_works() { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, - ConfigOp::Remove, ConfigOp::Set(Perbill::from_percent(10)), ConfigOp::Noop, )); @@ -6499,7 +6465,7 @@ fn reducing_max_unlocking_chunks_abrupt() { #[test] fn cannot_set_unsupported_validator_count() { ExtBuilder::default().build_and_execute(|| { - MaxWinners::set(50); + MaxValidatorsCount::set(50); // set validator count works assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 30)); assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 50)); @@ -6514,7 +6480,7 @@ fn cannot_set_unsupported_validator_count() { #[test] fn increase_validator_count_errors() { ExtBuilder::default().build_and_execute(|| { - MaxWinners::set(50); + MaxValidatorsCount::set(50); assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 40)); // increase works @@ -6532,7 +6498,7 @@ fn increase_validator_count_errors() { #[test] fn scale_validator_count_errors() { ExtBuilder::default().build_and_execute(|| { - MaxWinners::set(50); + MaxValidatorsCount::set(50); assert_ok!(Staking::set_validator_count(RuntimeOrigin::root(), 20)); // scale value works @@ -6608,10 +6574,11 @@ fn can_page_exposure() { Exposure { total: total_stake, own: own_stake, others }; // when + MaxExposurePageSize::set(3); let (exposure_metadata, exposure_page): ( PagedExposureMetadata, - Vec>, - ) = exposure.clone().into_pages(3); + Vec>, + ) = exposure.clone().into_pages::(); // then // 7 pages of nominators. @@ -6643,11 +6610,13 @@ fn should_retain_era_info_only_upto_history_depth() { let validator_stash = 10; for era in 0..4 { - ClaimedRewards::::insert(era, &validator_stash, vec![0, 1, 2]); + let rewards = WeakBoundedVec::try_from(vec![0, 1, 2]); + assert!(rewards.is_ok()); + ClaimedRewards::::insert(era, &validator_stash, rewards.unwrap()); for page in 0..3 { ErasStakersPaged::::insert( (era, &validator_stash, page), - ExposurePage { page_total: 100, others: vec![] }, + ExposurePage { page_total: 100, others: WeakBoundedVec::default() }, ); } } @@ -6670,218 +6639,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(|| { @@ -6902,12 +6659,14 @@ fn test_runtime_api_pending_rewards() { assert_ok!(Staking::bond(RuntimeOrigin::signed(v), stake, RewardDestination::Staked)); } + let mut reward_map = BoundedBTreeMap::new(); + assert_ok!(reward_map.try_insert(validator_one, 1)); + assert_ok!(reward_map.try_insert(validator_two, 1)); + assert_ok!(reward_map.try_insert(validator_three, 1)); // Add reward points - let reward = EraRewardPoints:: { + let reward = EraRewardPoints::::MaxValidatorsCount> { total: 1, - individual: vec![(validator_one, 1), (validator_two, 1), (validator_three, 1)] - .into_iter() - .collect(), + individual: reward_map, }; ErasRewardPoints::::insert(0, reward); @@ -6922,70 +6681,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::::set_exposure(0, &validator_three, exposure); + // add exposure for validators + EraInfo::::set_exposure(0, &validator_one, exposure.clone()); + EraInfo::::set_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)); }); } @@ -8752,14 +8477,14 @@ mod getters { mock::{self}, pallet::pallet::{Invulnerables, MinimumValidatorCount, ValidatorCount}, slashing, - tests::{Staking, Test}, - ActiveEra, ActiveEraInfo, BalanceOf, CanceledSlashPayout, ClaimedRewards, CurrentEra, - CurrentPlannedSession, EraRewardPoints, ErasRewardPoints, ErasStakersClipped, + tests::{Staking, Test, WeakBoundedVec}, + ActiveEra, ActiveEraInfo, BalanceOf, BoundedBTreeMap, BoundedVec, CanceledSlashPayout, + ClaimedRewards, CurrentEra, 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 sp_staking::{EraIndex, SessionIndex}; #[test] fn get_validator_count_returns_value_from_storage() { @@ -8796,7 +8521,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(); @@ -8895,34 +8622,13 @@ 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(|| { // given let era: EraIndex = 12; let account_id: mock::AccountId = 1; - let rewards = Vec::::new(); + let rewards = WeakBoundedVec::default(); ClaimedRewards::::insert(era, account_id, rewards.clone()); // when @@ -8973,10 +8679,9 @@ mod getters { sp_io::TestExternalities::default().execute_with(|| { // given let era: EraIndex = 12; - let reward_points = EraRewardPoints:: { - total: 1, - individual: vec![(11, 1)].into_iter().collect(), - }; + let mut reward_map = BoundedBTreeMap::new(); + frame_support::assert_ok!(reward_map.try_insert(11, 1)); + let reward_points = EraRewardPoints::<_, _> { total: 1, individual: reward_map }; ErasRewardPoints::::insert(era, reward_points); // when diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 17010a8907fc..82d9b25fecbc 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -28,8 +28,8 @@ use codec::{Decode, Encode, FullCodec, HasCompact, MaxEncodedLen}; use core::ops::Sub; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Zero}, - DispatchError, DispatchResult, Perbill, RuntimeDebug, Saturating, + traits::{AtLeast32BitUnsigned, Get, Zero}, + DispatchError, DispatchResult, Perbill, RuntimeDebug, Saturating, WeakBoundedVec, }; pub mod offence; @@ -346,8 +346,10 @@ pub trait StakingUnchecked: StakingInterface { } /// The amount of exposure for an era that an individual nominator has (susceptible to slashing). -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct IndividualExposure { +#[derive( + PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] +pub struct IndividualExposure { /// The stash account of the nominator in question. pub who: AccountId, /// Amount of funds exposed. @@ -357,7 +359,7 @@ pub struct IndividualExposure { /// A snapshot of the stake backing a single validator in the system. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct Exposure { +pub struct Exposure { /// The total balance backing this validator. #[codec(compact)] pub total: Balance, @@ -368,37 +370,44 @@ pub struct Exposure { pub others: Vec>, } -impl Default for Exposure { +impl Default + for Exposure +{ fn default() -> Self { Self { total: Default::default(), own: Default::default(), others: vec![] } } } -impl< - AccountId: Clone, - Balance: HasCompact + AtLeast32BitUnsigned + Copy + codec::MaxEncodedLen, - > Exposure +impl + Exposure { /// Splits an `Exposure` into `PagedExposureMetadata` and multiple chunks of - /// `IndividualExposure` with each chunk having maximum of `page_size` elements. - pub fn into_pages( + /// `IndividualExposure` with each chunk having at least 1 element and a maximum of + /// `MaxExposurePageSize::get()` elements. + pub fn into_pages( self, - page_size: Page, - ) -> (PagedExposureMetadata, Vec>) { - let individual_chunks = self.others.chunks(page_size as usize); - let mut exposure_pages: Vec> = + ) -> (PagedExposureMetadata, Vec>) + where + MaxExposurePageSize: Get, + { + debug_assert!(MaxExposurePageSize::get() >= 1); + let individual_chunks = self.others.chunks(MaxExposurePageSize::get() as usize); + let mut exposure_pages: Vec> = Vec::with_capacity(individual_chunks.len()); for chunk in individual_chunks { + debug_assert!(chunk.len() <= MaxExposurePageSize::get() as usize); let mut page_total: Balance = Zero::zero(); - let mut others: Vec> = - Vec::with_capacity(chunk.len()); + let mut others: WeakBoundedVec< + IndividualExposure, + MaxExposurePageSize, + > = WeakBoundedVec::default(); for individual in chunk.iter() { page_total.saturating_accrue(individual.value); - others.push(IndividualExposure { + let _ = others.try_push(IndividualExposure { who: individual.who.clone(), value: individual.value, - }) + }); } exposure_pages.push(ExposurePage { page_total, others }); @@ -417,18 +426,25 @@ impl< } /// A snapshot of the stake backing a single validator in the system. -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct ExposurePage { +#[derive( + PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] +#[scale_info(skip_type_params(MaxExposurePageSize))] +pub struct ExposurePage< + AccountId, + Balance: HasCompact + MaxEncodedLen, + MaxExposurePageSize: Get, +> { /// The total balance of this chunk/page. #[codec(compact)] pub page_total: Balance, /// The portions of nominators stashes that are exposed. - pub others: Vec>, + pub others: WeakBoundedVec, MaxExposurePageSize>, } -impl Default for ExposurePage { +impl> Default for ExposurePage { fn default() -> Self { - ExposurePage { page_total: Default::default(), others: vec![] } + ExposurePage { page_total: Default::default(), others: WeakBoundedVec::default() } } } @@ -450,7 +466,7 @@ impl Default for ExposurePage { Default, MaxEncodedLen, )] -pub struct PagedExposureMetadata { +pub struct PagedExposureMetadata { /// The total balance backing this validator. #[codec(compact)] pub total: Balance, diff --git a/substrate/primitives/staking/src/offence.rs b/substrate/primitives/staking/src/offence.rs index e73e8efe5839..705e6aa0c4ac 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, + sp_runtime::RuntimeDebug, + scale_info::TypeInfo, + MaxEncodedLen, )] pub struct OffenceSeverity(pub Perbill);