Skip to content

Commit 4bbcf42

Browse files
Doordashconbkchrggwpez
authored
Clamp benchmark ranks to respect MaxRank (#7720)
resolves #7517 ### Issues #### 1. Compile-Time vs Runtime Rank Mismatch - **`promote_fast` Benchmark** uses mock runtime's `MaxRank` during benchmark generation while allowing runtime overrides. Creating potential parameter mismatch between benchmark metadata(i.e. generated `r` values) and runtime configuration(i.e. `T::MaxRank`). #### 2. Static Rank Assumptions - **`bump_demote` Benchmark** initialized members at rank 2, making it incompatible with `MaxRank=1` configurations - **`promote` Benchmark** contained hardcoded rank values (1 → 2) which fails for `MaxRank=1` ## Changes #### Dynamic Rank Clamping ```rust // promote_fast // Get target rank for promotion. let max_rank = T::MaxRank::get(); let target_rank = (r as u16).min(max_rank); // promote // Set `to_rank` dynamically based on `max_rank`. let to_rank = (current_rank + 1).min(max_rank); --------- Co-authored-by: Bastian Köcher <git@kchr.de> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
1 parent c36d306 commit 4bbcf42

File tree

12 files changed

+94
-40
lines changed

12 files changed

+94
-40
lines changed

cumulus/parachains/runtimes/collectives/collectives-westend/src/ambassador/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ impl pallet_core_fellowship::Config<AmbassadorCoreInstance> for Runtime {
223223
type PromoteOrigin = PromoteOrigin;
224224
type FastPromoteOrigin = Self::PromoteOrigin;
225225
type EvidenceSize = ConstU32<65536>;
226-
type MaxRank = ConstU32<9>;
226+
type MaxRank = ConstU16<9>;
227227
}
228228

229229
pub type AmbassadorSalaryInstance = pallet_salary::Instance2;

cumulus/parachains/runtimes/collectives/collectives-westend/src/fellowship/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ impl pallet_core_fellowship::Config<FellowshipCoreInstance> for Runtime {
211211
>;
212212
type FastPromoteOrigin = Self::PromoteOrigin;
213213
type EvidenceSize = ConstU32<65536>;
214-
type MaxRank = ConstU32<9>;
214+
type MaxRank = ConstU16<9>;
215215
}
216216

217217
pub type FellowshipSalaryInstance = pallet_salary::Instance1;

cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_ambassador_core.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo<
193193
/// Proof: `AmbassadorCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`)
194194
/// The range of component `r` is `[1, 9]`.
195195
/// The range of component `r` is `[1, 9]`.
196-
fn promote_fast(r: u32, ) -> Weight {
196+
fn promote_fast(r: u16, ) -> Weight {
197197
// Proof Size summary in bytes:
198198
// Measured: `65968`
199199
// Estimated: `69046 + r * (2489 ±0)`

cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_fellowship_core.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ impl<T: frame_system::Config> pallet_core_fellowship::WeightInfo for WeightInfo<
193193
/// Proof: `FellowshipCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`)
194194
/// The range of component `r` is `[1, 9]`.
195195
/// The range of component `r` is `[1, 9]`.
196-
fn promote_fast(r: u32, ) -> Weight {
196+
fn promote_fast(r: u16, ) -> Weight {
197197
// Proof Size summary in bytes:
198198
// Measured: `65996`
199199
// Estimated: `69046 + r * (2489 ±0)`

prdoc/pr_7720.prdoc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
2+
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
3+
4+
title: Clamp Core Fellowship Benchmarks to Runtime MaxRank Configuration
5+
6+
doc:
7+
- audience: Runtime Dev
8+
description: |
9+
10+
This change modifies the `MaxRank` type in the Core Fellowship pallet configuration from
11+
`u32` to `u16`. Runtime developers updating to this version must:
12+
- Change any `ConstU32<N>` usages for `MaxRank` to `ConstU16<N>`
13+
14+
Enabling reliable benchmarking for constrained configurations like `MaxRank=1` while
15+
maintaining compatibility with larger rank ranges.
16+
17+
18+
19+
crates:
20+
- name: pallet-core-fellowship
21+
bump: major
22+
- name: collectives-westend-runtime
23+
bump: minor
24+
- name: kitchensink-runtime
25+
bump: minor

substrate/bin/node/runtime/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2060,7 +2060,7 @@ impl pallet_core_fellowship::Config for Runtime {
20602060
type PromoteOrigin = EnsureRootWithSuccess<AccountId, ConstU16<9>>;
20612061
type FastPromoteOrigin = Self::PromoteOrigin;
20622062
type EvidenceSize = ConstU32<16_384>;
2063-
type MaxRank = ConstU32<9>;
2063+
type MaxRank = ConstU16<9>;
20642064
}
20652065

20662066
parameter_types! {

substrate/frame/core-fellowship/src/benchmarking.rs

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ mod benchmarks {
5656
}
5757

5858
fn set_benchmark_params<T: Config<I>, I: 'static>() -> Result<(), BenchmarkError> {
59-
let max_rank = T::MaxRank::get().try_into().unwrap();
59+
let max_rank = T::MaxRank::get() as usize;
6060
let params = ParamsType {
6161
active_salary: BoundedVec::try_from(vec![100u32.into(); max_rank]).unwrap(),
6262
passive_salary: BoundedVec::try_from(vec![10u32.into(); max_rank]).unwrap(),
@@ -71,7 +71,7 @@ mod benchmarks {
7171

7272
#[benchmark]
7373
fn set_params() -> Result<(), BenchmarkError> {
74-
let max_rank = T::MaxRank::get().try_into().unwrap();
74+
let max_rank = T::MaxRank::get() as usize;
7575
let params = ParamsType {
7676
active_salary: BoundedVec::try_from(vec![100u32.into(); max_rank]).unwrap(),
7777
passive_salary: BoundedVec::try_from(vec![10u32.into(); max_rank]).unwrap(),
@@ -89,7 +89,7 @@ mod benchmarks {
8989

9090
#[benchmark]
9191
fn set_partial_params() -> Result<(), BenchmarkError> {
92-
let max_rank = T::MaxRank::get().try_into().unwrap();
92+
let max_rank = T::MaxRank::get() as usize;
9393

9494
// Set up the initial default state for the Params storage
9595
let params = ParamsType {
@@ -149,19 +149,22 @@ mod benchmarks {
149149
fn bump_demote() -> Result<(), BenchmarkError> {
150150
set_benchmark_params::<T, I>()?;
151151

152-
let member = make_member::<T, I>(2)?;
152+
let initial_rank = T::MaxRank::get();
153+
154+
let member = make_member::<T, I>(initial_rank)?;
153155

154156
// Set it to the max value to ensure that any possible auto-demotion period has passed.
155157
frame_system::Pallet::<T>::set_block_number(BlockNumberFor::<T>::max_value());
156158
ensure_evidence::<T, I>(&member)?;
159+
157160
assert!(Member::<T, I>::contains_key(&member));
158-
assert_eq!(T::Members::rank_of(&member), Some(2));
161+
assert_eq!(T::Members::rank_of(&member), Some(initial_rank));
159162

160163
#[extrinsic_call]
161164
CoreFellowship::<T, I>::bump(RawOrigin::Signed(member.clone()), member.clone());
162165

163166
assert!(Member::<T, I>::contains_key(&member));
164-
assert_eq!(T::Members::rank_of(&member), Some(1));
167+
assert_eq!(T::Members::rank_of(&member), Some(initial_rank.saturating_sub(1)));
165168
assert!(!MemberEvidence::<T, I>::contains_key(&member));
166169
Ok(())
167170
}
@@ -194,36 +197,51 @@ mod benchmarks {
194197
fn promote() -> Result<(), BenchmarkError> {
195198
// Ensure that the `min_promotion_period` wont get in our way.
196199
let mut params = Params::<T, I>::get();
197-
let max_rank = T::MaxRank::get().try_into().unwrap();
198-
params.min_promotion_period = BoundedVec::try_from(vec![Zero::zero(); max_rank]).unwrap();
200+
let max_rank = T::MaxRank::get();
201+
202+
// Get minimum promotion period.
203+
params.min_promotion_period =
204+
BoundedVec::try_from(vec![Zero::zero(); max_rank as usize]).unwrap();
199205
Params::<T, I>::put(&params);
200206

201-
let member = make_member::<T, I>(1)?;
207+
// Start at rank 0 to allow at least one promotion.
208+
let current_rank = 0;
209+
let member = make_member::<T, I>(current_rank)?;
202210

203-
// Set it to the max value to ensure that any possible auto-demotion period has passed.
211+
// Set `to_rank` dynamically based on `max_rank`.
212+
let to_rank = (current_rank + 1).min(max_rank); // Ensure `to_rank` <= `max_rank`.
213+
214+
// Set block number to avoid auto-demotion.
204215
frame_system::Pallet::<T>::set_block_number(BlockNumberFor::<T>::max_value());
205216
ensure_evidence::<T, I>(&member)?;
206217

207218
#[extrinsic_call]
208-
_(RawOrigin::Root, member.clone(), 2u8.into());
219+
_(RawOrigin::Root, member.clone(), to_rank);
209220

210-
assert_eq!(T::Members::rank_of(&member), Some(2));
221+
// Assert the new rank matches `to_rank` (not a hardcoded value).
222+
assert_eq!(T::Members::rank_of(&member), Some(to_rank));
211223
assert!(!MemberEvidence::<T, I>::contains_key(&member));
212224
Ok(())
213225
}
214226

215227
/// Benchmark the `promote_fast` extrinsic to promote someone up to `r`.
216228
#[benchmark]
217-
fn promote_fast(r: Linear<1, { T::MaxRank::get() as u32 }>) -> Result<(), BenchmarkError> {
218-
let r = r.try_into().expect("r is too large");
229+
fn promote_fast(
230+
r: Linear<1, { ConvertU16ToU32::<T::MaxRank>::get() }>,
231+
) -> Result<(), BenchmarkError> {
232+
// Get target rank for promotion.
233+
let max_rank = T::MaxRank::get();
234+
let target_rank = (r as u16).min(max_rank);
235+
236+
// Begin with Candidate.
219237
let member = make_member::<T, I>(0)?;
220238

221239
ensure_evidence::<T, I>(&member)?;
222240

223241
#[extrinsic_call]
224-
_(RawOrigin::Root, member.clone(), r);
242+
_(RawOrigin::Root, member.clone(), target_rank);
225243

226-
assert_eq!(T::Members::rank_of(&member), Some(r));
244+
assert_eq!(T::Members::rank_of(&member), Some(target_rank));
227245
assert!(!MemberEvidence::<T, I>::contains_key(&member));
228246
Ok(())
229247
}

substrate/frame/core-fellowship/src/lib.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,13 @@ impl<
163163
}
164164
}
165165

166+
pub struct ConvertU16ToU32<Inner>(PhantomData<Inner>);
167+
impl<Inner: Get<u16>> Get<u32> for ConvertU16ToU32<Inner> {
168+
fn get() -> u32 {
169+
Inner::get() as u32
170+
}
171+
}
172+
166173
/// The status of a single member.
167174
#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)]
168175
pub struct MemberStatus<BlockNumber> {
@@ -238,15 +245,18 @@ pub mod pallet {
238245
///
239246
/// Increasing this value is supported, but decreasing it may lead to a broken state.
240247
#[pallet::constant]
241-
type MaxRank: Get<u32>;
248+
type MaxRank: Get<u16>;
242249
}
243250

244-
pub type ParamsOf<T, I> =
245-
ParamsType<<T as Config<I>>::Balance, BlockNumberFor<T>, <T as Config<I>>::MaxRank>;
251+
pub type ParamsOf<T, I> = ParamsType<
252+
<T as Config<I>>::Balance,
253+
BlockNumberFor<T>,
254+
ConvertU16ToU32<<T as Config<I>>::MaxRank>,
255+
>;
246256
pub type PartialParamsOf<T, I> = ParamsType<
247257
Option<<T as Config<I>>::Balance>,
248258
Option<BlockNumberFor<T>>,
249-
<T as Config<I>>::MaxRank,
259+
ConvertU16ToU32<<T as Config<I>>::MaxRank>,
250260
>;
251261
pub type MemberStatusOf<T> = MemberStatus<BlockNumberFor<T>>;
252262
pub type RankOf<T, I> = <<T as Config<I>>::Members as RankedMembers>::Rank;
@@ -523,7 +533,7 @@ pub mod pallet {
523533
/// This is useful for out-of-band promotions, hence it has its own `FastPromoteOrigin` to
524534
/// be (possibly) more restrictive than `PromoteOrigin`. Note that the member must already
525535
/// be inducted.
526-
#[pallet::weight(T::WeightInfo::promote_fast(*to_rank as u32))]
536+
#[pallet::weight(T::WeightInfo::promote_fast(*to_rank))]
527537
#[pallet::call_index(10)]
528538
pub fn promote_fast(
529539
origin: OriginFor<T>,
@@ -534,7 +544,7 @@ pub mod pallet {
534544
Ok(allow_rank) => ensure!(allow_rank >= to_rank, Error::<T, I>::NoPermission),
535545
Err(origin) => ensure_root(origin)?,
536546
}
537-
ensure!(to_rank as u32 <= T::MaxRank::get(), Error::<T, I>::InvalidRank);
547+
ensure!(to_rank <= T::MaxRank::get(), Error::<T, I>::InvalidRank);
538548
let curr_rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
539549
ensure!(to_rank > curr_rank, Error::<T, I>::UnexpectedRank);
540550

@@ -681,8 +691,8 @@ pub mod pallet {
681691
///
682692
/// Only elements in the base slice which has a new value in the new slice will be updated.
683693
pub(crate) fn set_partial_params_slice<S>(
684-
base_slice: &mut BoundedVec<S, <T as Config<I>>::MaxRank>,
685-
new_slice: BoundedVec<Option<S>, <T as Config<I>>::MaxRank>,
694+
base_slice: &mut BoundedVec<S, ConvertU16ToU32<T::MaxRank>>,
695+
new_slice: BoundedVec<Option<S>, ConvertU16ToU32<T::MaxRank>>,
686696
) {
687697
for (base_element, new_element) in base_slice.iter_mut().zip(new_slice) {
688698
if let Some(element) = new_element {
@@ -713,9 +723,10 @@ pub mod pallet {
713723
/// Rank 1 becomes index 0, rank `RANK_COUNT` becomes index `RANK_COUNT - 1`. Any rank not
714724
/// in the range `1..=RANK_COUNT` is `None`.
715725
pub(crate) fn rank_to_index(rank: RankOf<T, I>) -> Option<usize> {
716-
match TryInto::<usize>::try_into(rank) {
717-
Ok(r) if r as u32 <= <T as Config<I>>::MaxRank::get() && r > 0 => Some(r - 1),
718-
_ => return None,
726+
if rank == 0 || rank > T::MaxRank::get() {
727+
None
728+
} else {
729+
Some((rank - 1) as usize)
719730
}
720731
}
721732

substrate/frame/core-fellowship/src/migration.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ impl<T: Config<I>, I: 'static> UncheckedOnRuntimeUpgrade for MigrateToV1<T, I> {
7373
#[cfg(feature = "try-runtime")]
7474
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
7575
ensure!(
76-
T::MaxRank::get() >= v0::RANK_COUNT as u32,
76+
T::MaxRank::get() as usize >= v0::RANK_COUNT,
7777
"pallet-core-fellowship: new bound should not truncate"
7878
);
7979
Ok(Default::default())

substrate/frame/core-fellowship/src/tests/integration.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use frame_support::{
2727
};
2828
use frame_system::EnsureSignedBy;
2929
use pallet_ranked_collective::{EnsureRanked, Geometric, Rank};
30-
use sp_core::{ConstU32, Get};
30+
use sp_core::Get;
3131
use sp_runtime::{
3232
bounded_vec,
3333
traits::{Convert, ReduceBy, ReplaceWithDefault, TryMorphInto},
@@ -82,7 +82,7 @@ impl Config for Test {
8282
type PromoteOrigin = TryMapSuccess<EnsureSignedBy<IsInVec<ZeroToNine>, u64>, TryMorphInto<u16>>;
8383
type FastPromoteOrigin = Self::PromoteOrigin;
8484
type EvidenceSize = EvidenceSize;
85-
type MaxRank = ConstU32<9>;
85+
type MaxRank = ConstU16<9>;
8686
}
8787

8888
/// Convert the tally class into the minimum rank required to vote on the poll.

0 commit comments

Comments
 (0)