Refactoring Checkpoint: (WIP)

This commit is contained in:
2025-12-14 10:29:31 +03:00
parent 09735eb97a
commit c89d7cac55
1424 changed files with 6415 additions and 6064 deletions
@@ -0,0 +1,464 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Benchmarking setup for pezpallet-collator-selection
#![cfg(feature = "runtime-benchmarks")]
use super::*;
#[allow(unused)]
use crate::Pallet as CollatorSelection;
use alloc::vec::Vec;
use codec::Decode;
use core::cmp;
use pezframe_benchmarking::{account, v2::*, whitelisted_caller, BenchmarkError};
use pezframe_support::traits::{Currency, EnsureOrigin, Get, ReservableCurrency};
use pezframe_system::{pezpallet_prelude::BlockNumberFor, EventRecord, RawOrigin};
use pezpallet_authorship::EventHandler;
use pezpallet_session::{self as session, SessionManager};
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as pezframe_system::Config>::AccountId>>::Balance;
const SEED: u32 = 0;
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
let events = pezframe_system::Pallet::<T>::events();
let system_event: <T as pezframe_system::Config>::RuntimeEvent = generic_event.into();
// compare to the last event record
let EventRecord { event, .. } = &events[events.len() - 1];
assert_eq!(event, &system_event);
}
fn create_funded_user<T: Config>(
string: &'static str,
n: u32,
balance_factor: u32,
) -> T::AccountId {
let user = account(string, n, SEED);
let balance = <T as pallet::Config>::Currency::minimum_balance() * balance_factor.into();
let _ = <T as pallet::Config>::Currency::make_free_balance_be(&user, balance);
user
}
fn keys<T: Config + session::Config>(c: u32) -> <T as session::Config>::Keys {
use rand::{RngCore, SeedableRng};
let keys = {
let mut keys = [0u8; 128];
if c > 0 {
let mut rng = rand::rngs::StdRng::seed_from_u64(c as u64);
rng.fill_bytes(&mut keys);
}
keys
};
Decode::decode(&mut &keys[..]).unwrap()
}
fn validator<T: Config + session::Config>(c: u32) -> (T::AccountId, <T as session::Config>::Keys) {
(create_funded_user::<T>("candidate", c, 1000), keys::<T>(c))
}
fn register_validators<T: Config + session::Config>(count: u32) -> Vec<T::AccountId> {
let validators = (0..count).map(|c| validator::<T>(c)).collect::<Vec<_>>();
for (who, keys) in validators.clone() {
<session::Pallet<T>>::ensure_can_pay_key_deposit(&who).unwrap();
<session::Pallet<T>>::set_keys(RawOrigin::Signed(who).into(), keys, Vec::new()).unwrap();
}
validators.into_iter().map(|(who, _)| who).collect()
}
fn register_candidates<T: Config>(count: u32) {
let candidates = (0..count).map(|c| account("candidate", c, SEED)).collect::<Vec<_>>();
assert!(CandidacyBond::<T>::get() > 0u32.into(), "Bond cannot be zero!");
for who in candidates {
<T as pallet::Config>::Currency::make_free_balance_be(
&who,
CandidacyBond::<T>::get() * 3u32.into(),
);
<CollatorSelection<T>>::register_as_candidate(RawOrigin::Signed(who).into()).unwrap();
}
}
fn min_candidates<T: Config>() -> u32 {
let min_collators = T::MinEligibleCollators::get();
let invulnerable_length = Invulnerables::<T>::get().len();
min_collators.saturating_sub(invulnerable_length.try_into().unwrap())
}
fn min_invulnerables<T: Config>() -> u32 {
let min_collators = T::MinEligibleCollators::get();
let candidates_length = CandidateList::<T>::decode_len()
.unwrap_or_default()
.try_into()
.unwrap_or_default();
min_collators.saturating_sub(candidates_length)
}
#[benchmarks(where T: pezpallet_authorship::Config + session::Config)]
mod benchmarks {
use super::*;
#[benchmark]
fn set_invulnerables(
b: Linear<1, { T::MaxInvulnerables::get() }>,
) -> Result<(), BenchmarkError> {
let origin =
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let new_invulnerables = register_validators::<T>(b);
let mut sorted_new_invulnerables = new_invulnerables.clone();
sorted_new_invulnerables.sort();
#[extrinsic_call]
_(origin as T::RuntimeOrigin, new_invulnerables.clone());
// assert that it comes out sorted
assert_last_event::<T>(
Event::NewInvulnerables { invulnerables: sorted_new_invulnerables }.into(),
);
Ok(())
}
#[benchmark]
fn add_invulnerable(
b: Linear<1, { T::MaxInvulnerables::get() - 1 }>,
c: Linear<1, { T::MaxCandidates::get() - 1 }>,
) -> Result<(), BenchmarkError> {
let origin =
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
// need to fill up candidates
CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
DesiredCandidates::<T>::put(c);
// get accounts and keys for the `c` candidates
let mut candidates = (0..c).map(|cc| validator::<T>(cc)).collect::<Vec<_>>();
// add one more to the list. should not be in `b` (invulnerables) because it's the account
// we will _add_ to invulnerables. we want it to be in `candidates` because we need the
// weight associated with removing it.
let (new_invulnerable, new_invulnerable_keys) = validator::<T>(b.max(c) + 1);
candidates.push((new_invulnerable.clone(), new_invulnerable_keys));
// set their keys ...
for (who, keys) in candidates.clone() {
<session::Pallet<T>>::ensure_can_pay_key_deposit(&who).unwrap();
<session::Pallet<T>>::set_keys(RawOrigin::Signed(who).into(), keys, Vec::new())
.unwrap();
}
// ... and register them.
for (who, _) in candidates.iter() {
let deposit = CandidacyBond::<T>::get();
<T as pallet::Config>::Currency::make_free_balance_be(who, deposit * 1000_u32.into());
CandidateList::<T>::try_mutate(|list| {
list.try_push(CandidateInfo { who: who.clone(), deposit }).unwrap();
Ok::<(), BenchmarkError>(())
})
.unwrap();
<T as pallet::Config>::Currency::reserve(who, deposit)?;
LastAuthoredBlock::<T>::insert(
who.clone(),
pezframe_system::Pallet::<T>::block_number() + T::KickThreshold::get(),
);
}
// now we need to fill up invulnerables
let mut invulnerables = register_validators::<T>(b);
invulnerables.sort();
let invulnerables: pezframe_support::BoundedVec<_, T::MaxInvulnerables> =
pezframe_support::BoundedVec::try_from(invulnerables).unwrap();
Invulnerables::<T>::put(invulnerables);
#[extrinsic_call]
_(origin as T::RuntimeOrigin, new_invulnerable.clone());
assert_last_event::<T>(Event::InvulnerableAdded { account_id: new_invulnerable }.into());
Ok(())
}
#[benchmark]
fn remove_invulnerable(
b: Linear<{ min_invulnerables::<T>() + 1 }, { T::MaxInvulnerables::get() }>,
) -> Result<(), BenchmarkError> {
let origin =
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let mut invulnerables = register_validators::<T>(b);
invulnerables.sort();
let invulnerables: pezframe_support::BoundedVec<_, T::MaxInvulnerables> =
pezframe_support::BoundedVec::try_from(invulnerables).unwrap();
Invulnerables::<T>::put(invulnerables);
let to_remove = Invulnerables::<T>::get().first().unwrap().clone();
#[extrinsic_call]
_(origin as T::RuntimeOrigin, to_remove.clone());
assert_last_event::<T>(Event::InvulnerableRemoved { account_id: to_remove }.into());
Ok(())
}
#[benchmark]
fn set_desired_candidates() -> Result<(), BenchmarkError> {
let max: u32 = T::MaxCandidates::get();
let origin =
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
#[extrinsic_call]
_(origin as T::RuntimeOrigin, max);
assert_last_event::<T>(Event::NewDesiredCandidates { desired_candidates: max }.into());
Ok(())
}
#[benchmark]
fn set_candidacy_bond(
c: Linear<0, { T::MaxCandidates::get() }>,
k: Linear<0, { T::MaxCandidates::get() }>,
) -> Result<(), BenchmarkError> {
let initial_bond_amount: BalanceOf<T> =
<T as pallet::Config>::Currency::minimum_balance() * 2u32.into();
CandidacyBond::<T>::put(initial_bond_amount);
register_validators::<T>(c);
register_candidates::<T>(c);
let kicked = cmp::min(k, c);
let origin =
T::UpdateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let bond_amount = if k > 0 {
CandidateList::<T>::mutate(|candidates| {
for info in candidates.iter_mut().skip(kicked as usize) {
info.deposit = <T as pallet::Config>::Currency::minimum_balance() * 3u32.into();
}
});
<T as pallet::Config>::Currency::minimum_balance() * 3u32.into()
} else {
<T as pallet::Config>::Currency::minimum_balance()
};
#[extrinsic_call]
_(origin as T::RuntimeOrigin, bond_amount);
assert_last_event::<T>(Event::NewCandidacyBond { bond_amount }.into());
Ok(())
}
#[benchmark]
fn update_bond(
c: Linear<{ min_candidates::<T>() + 1 }, { T::MaxCandidates::get() }>,
) -> Result<(), BenchmarkError> {
CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
DesiredCandidates::<T>::put(c);
register_validators::<T>(c);
register_candidates::<T>(c);
let caller = CandidateList::<T>::get()[0].who.clone();
v2::whitelist!(caller);
let bond_amount: BalanceOf<T> = <T as pallet::Config>::Currency::minimum_balance() +
<T as pallet::Config>::Currency::minimum_balance();
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), bond_amount);
assert_last_event::<T>(
Event::CandidateBondUpdated { account_id: caller, deposit: bond_amount }.into(),
);
assert!(
CandidateList::<T>::get().iter().last().unwrap().deposit ==
<T as pallet::Config>::Currency::minimum_balance() * 2u32.into()
);
Ok(())
}
// worse case is when we have all the max-candidate slots filled except one, and we fill that
// one.
#[benchmark]
fn register_as_candidate(c: Linear<1, { T::MaxCandidates::get() - 1 }>) {
CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
DesiredCandidates::<T>::put(c + 1);
register_validators::<T>(c);
register_candidates::<T>(c);
let caller: T::AccountId = whitelisted_caller();
let bond: BalanceOf<T> = <T as pallet::Config>::Currency::minimum_balance() * 2u32.into();
<T as pallet::Config>::Currency::make_free_balance_be(&caller, bond);
<session::Pallet<T>>::ensure_can_pay_key_deposit(&caller).unwrap();
<session::Pallet<T>>::set_keys(
RawOrigin::Signed(caller.clone()).into(),
keys::<T>(c + 1),
Vec::new(),
)
.unwrap();
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()));
assert_last_event::<T>(
Event::CandidateAdded { account_id: caller, deposit: bond / 2u32.into() }.into(),
);
}
#[benchmark]
fn take_candidate_slot(c: Linear<{ min_candidates::<T>() + 1 }, { T::MaxCandidates::get() }>) {
CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
DesiredCandidates::<T>::put(1);
register_validators::<T>(c);
register_candidates::<T>(c);
let caller: T::AccountId = whitelisted_caller();
let bond: BalanceOf<T> = <T as pallet::Config>::Currency::minimum_balance() * 10u32.into();
<T as pallet::Config>::Currency::make_free_balance_be(&caller, bond);
<session::Pallet<T>>::ensure_can_pay_key_deposit(&caller).unwrap();
<session::Pallet<T>>::set_keys(
RawOrigin::Signed(caller.clone()).into(),
keys::<T>(c + 1),
Vec::new(),
)
.unwrap();
let target = CandidateList::<T>::get().iter().last().unwrap().who.clone();
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), bond / 2u32.into(), target.clone());
assert_last_event::<T>(
Event::CandidateReplaced { old: target, new: caller, deposit: bond / 2u32.into() }
.into(),
);
}
// worse case is the last candidate leaving.
#[benchmark]
fn leave_intent(c: Linear<{ min_candidates::<T>() + 1 }, { T::MaxCandidates::get() }>) {
CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
DesiredCandidates::<T>::put(c);
register_validators::<T>(c);
register_candidates::<T>(c);
let leaving = CandidateList::<T>::get().iter().last().unwrap().who.clone();
v2::whitelist!(leaving);
#[extrinsic_call]
_(RawOrigin::Signed(leaving.clone()));
assert_last_event::<T>(Event::CandidateRemoved { account_id: leaving }.into());
}
// worse case is paying a non-existing candidate account.
#[benchmark]
fn note_author() {
CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
<T as pallet::Config>::Currency::make_free_balance_be(
&<CollatorSelection<T>>::account_id(),
<T as pallet::Config>::Currency::minimum_balance() * 4u32.into(),
);
let author = account("author", 0, SEED);
let new_block: BlockNumberFor<T> = 10u32.into();
pezframe_system::Pallet::<T>::set_block_number(new_block);
assert!(<T as pallet::Config>::Currency::free_balance(&author) == 0u32.into());
#[block]
{
<CollatorSelection<T> as EventHandler<_, _>>::note_author(author.clone())
}
assert!(<T as pallet::Config>::Currency::free_balance(&author) > 0u32.into());
assert_eq!(pezframe_system::Pallet::<T>::block_number(), new_block);
}
// worst case for new session.
#[benchmark]
fn new_session(
r: Linear<1, { T::MaxCandidates::get() }>,
c: Linear<1, { T::MaxCandidates::get() }>,
) {
CandidacyBond::<T>::put(<T as pallet::Config>::Currency::minimum_balance());
DesiredCandidates::<T>::put(c);
pezframe_system::Pallet::<T>::set_block_number(0u32.into());
register_validators::<T>(c);
register_candidates::<T>(c);
let new_block: BlockNumberFor<T> = T::KickThreshold::get();
let zero_block: BlockNumberFor<T> = 0u32.into();
let candidates: Vec<T::AccountId> = CandidateList::<T>::get()
.iter()
.map(|candidate_info| candidate_info.who.clone())
.collect();
let non_removals = c.saturating_sub(r);
for i in 0..c {
LastAuthoredBlock::<T>::insert(candidates[i as usize].clone(), zero_block);
}
if non_removals > 0 {
for i in 0..non_removals {
LastAuthoredBlock::<T>::insert(candidates[i as usize].clone(), new_block);
}
} else {
for i in 0..c {
LastAuthoredBlock::<T>::insert(candidates[i as usize].clone(), new_block);
}
}
let min_candidates = min_candidates::<T>();
let pre_length = CandidateList::<T>::decode_len().unwrap_or_default();
pezframe_system::Pallet::<T>::set_block_number(new_block);
let current_length: u32 = CandidateList::<T>::decode_len()
.unwrap_or_default()
.try_into()
.unwrap_or_default();
assert!(c == current_length);
#[block]
{
<CollatorSelection<T> as SessionManager<_>>::new_session(0);
}
if c > r && non_removals >= min_candidates {
// candidates > removals and remaining candidates > min candidates
// => remaining candidates should be shorter than before removal, i.e. some were
// actually removed.
assert!(CandidateList::<T>::decode_len().unwrap_or_default() < pre_length);
} else if c > r && non_removals < min_candidates {
// candidates > removals and remaining candidates would be less than min candidates
// => remaining candidates should equal min candidates, i.e. some were removed up to
// the minimum, but then any more were "forced" to stay in candidates.
let current_length: u32 = CandidateList::<T>::decode_len()
.unwrap_or_default()
.try_into()
.unwrap_or_default();
assert!(min_candidates == current_length);
} else {
// removals >= candidates, non removals must == 0
// can't remove more than exist
assert!(CandidateList::<T>::decode_len().unwrap_or_default() == pre_length);
}
}
impl_benchmark_test_suite!(CollatorSelection, crate::mock::new_test_ext(), crate::mock::Test);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,321 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezcumulus.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! A module that is responsible for migration of storage for Collator Selection.
use super::*;
#[cfg(feature = "try-runtime")]
use alloc::vec::Vec;
use pezframe_support::traits::{OnRuntimeUpgrade, UncheckedOnRuntimeUpgrade};
use log;
/// Migrate to v2. Should have been part of <https://github.com/pezkuwichain/kurdistan-sdk/issues/104>.
pub mod v2 {
use super::*;
use pezframe_support::{
pezpallet_prelude::*,
storage_alias,
traits::{Currency, ReservableCurrency},
};
use pezsp_runtime::traits::{Saturating, Zero};
/// [`UncheckedMigrationToV2`] wrapped in a
/// [`VersionedMigration`](pezframe_support::migrations::VersionedMigration), ensuring the
/// migration is only performed when on-chain version is 1.
pub type MigrationToV2<T> = pezframe_support::migrations::VersionedMigration<
1,
2,
UncheckedMigrationToV2<T>,
Pallet<T>,
<T as pezframe_system::Config>::DbWeight,
>;
#[storage_alias]
pub type Candidates<T: Config> = StorageValue<
Pallet<T>,
BoundedVec<CandidateInfo<<T as pezframe_system::Config>::AccountId, <<T as Config>::Currency as Currency<<T as pezframe_system::Config>::AccountId>>::Balance>, <T as Config>::MaxCandidates>,
ValueQuery,
>;
/// Migrate to V2.
pub struct UncheckedMigrationToV2<T>(PhantomData<T>);
impl<T: Config + pezpallet_balances::Config> UncheckedOnRuntimeUpgrade for UncheckedMigrationToV2<T> {
fn on_runtime_upgrade() -> Weight {
let mut weight = Weight::zero();
let mut count: u64 = 0;
// candidates who exist under the old `Candidates` key
let candidates = Candidates::<T>::take();
// New candidates who have registered since the upgrade. Under normal circumstances,
// this should not exist because the migration should be applied when the upgrade
// happens. But in Pezkuwi/Kusama we messed this up, and people registered under
// `CandidateList` while their funds were locked in `Candidates`.
let new_candidate_list = CandidateList::<T>::get();
if new_candidate_list.len().is_zero() {
// The new list is empty, so this is essentially being applied correctly. We just
// put the candidates into the new storage item.
CandidateList::<T>::put(&candidates);
// 1 write for the new list
weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1));
} else {
// Oops, the runtime upgraded without the migration. There are new candidates in
// `CandidateList`. So, let's just refund the old ones and assume they have already
// started participating in the new system.
for candidate in candidates {
let err = T::Currency::unreserve(&candidate.who, candidate.deposit);
if err > Zero::zero() {
log::error!(
target: LOG_TARGET,
"{:?} balance was unable to be unreserved from {:?}",
err, &candidate.who,
);
}
count.saturating_inc();
}
weight.saturating_accrue(
<<T as pezpallet_balances::Config>::WeightInfo as pezpallet_balances::WeightInfo>::force_unreserve().saturating_mul(count.into()),
);
}
log::info!(
target: LOG_TARGET,
"Unreserved locked bond of {} candidates, upgraded storage to version 2",
count,
);
weight.saturating_accrue(T::DbWeight::get().reads_writes(3, 2));
weight
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, pezsp_runtime::DispatchError> {
let number_of_candidates = Candidates::<T>::get().to_vec().len();
Ok((number_of_candidates as u32).encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_number_of_candidates: Vec<u8>) -> Result<(), pezsp_runtime::DispatchError> {
let new_number_of_candidates = Candidates::<T>::get().to_vec().len();
assert_eq!(
new_number_of_candidates, 0 as usize,
"after migration, the candidates map should be empty"
);
Ok(())
}
}
}
/// Version 1 Migration
/// This migration ensures that any existing `Invulnerables` storage lists are sorted.
pub mod v1 {
use super::*;
use pezframe_support::pezpallet_prelude::*;
pub struct MigrateToV1<T>(PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
fn on_runtime_upgrade() -> Weight {
let on_chain_version = Pallet::<T>::on_chain_storage_version();
if on_chain_version == 0 {
let invulnerables_len = Invulnerables::<T>::get().to_vec().len();
Invulnerables::<T>::mutate(|invulnerables| {
invulnerables.sort();
});
StorageVersion::new(1).put::<Pallet<T>>();
log::info!(
target: LOG_TARGET,
"Sorted {} Invulnerables, upgraded storage to version 1",
invulnerables_len,
);
// Similar complexity to `set_invulnerables` (put storage value)
// Plus 1 read for length, 1 read for `on_chain_version`, 1 write to put version
T::WeightInfo::set_invulnerables(invulnerables_len as u32)
.saturating_add(T::DbWeight::get().reads_writes(2, 1))
} else {
log::info!(
target: LOG_TARGET,
"Migration did not execute. This probably should be removed"
);
T::DbWeight::get().reads(1)
}
}
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, pezsp_runtime::DispatchError> {
let number_of_invulnerables = Invulnerables::<T>::get().to_vec().len();
Ok((number_of_invulnerables as u32).encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(number_of_invulnerables: Vec<u8>) -> Result<(), pezsp_runtime::DispatchError> {
let stored_invulnerables = Invulnerables::<T>::get().to_vec();
let mut sorted_invulnerables = stored_invulnerables.clone();
sorted_invulnerables.sort();
assert_eq!(
stored_invulnerables, sorted_invulnerables,
"after migration, the stored invulnerables should be sorted"
);
let number_of_invulnerables: u32 = Decode::decode(
&mut number_of_invulnerables.as_slice(),
)
.expect("the state parameter should be something that was generated by pre_upgrade");
let stored_invulnerables_len = stored_invulnerables.len() as u32;
assert_eq!(
number_of_invulnerables, stored_invulnerables_len,
"after migration, there should be the same number of invulnerables"
);
let on_chain_version = Pallet::<T>::on_chain_storage_version();
pezframe_support::ensure!(on_chain_version >= 1, "must_upgrade");
Ok(())
}
}
}
#[cfg(all(feature = "try-runtime", test))]
mod tests {
use super::*;
use crate::{
migration::v2::Candidates,
mock::{new_test_ext, Balances, Test},
};
use pezframe_support::{
traits::{Currency, ReservableCurrency, StorageVersion},
BoundedVec,
};
use pezsp_runtime::traits::ConstU32;
#[test]
fn migrate_to_v2_with_new_candidates() {
new_test_ext().execute_with(|| {
let storage_version = StorageVersion::new(1);
storage_version.put::<Pallet<Test>>();
let one = 1u64;
let two = 2u64;
let three = 3u64;
let deposit = 10u64;
// Set balance to 100
Balances::make_free_balance_be(&one, 100u64);
Balances::make_free_balance_be(&two, 100u64);
Balances::make_free_balance_be(&three, 100u64);
// Reservations: 10 for the "old" candidacy and 10 for the "new"
Balances::reserve(&one, 10u64).unwrap(); // old
Balances::reserve(&two, 20u64).unwrap(); // old + new
Balances::reserve(&three, 10u64).unwrap(); // new
// Candidate info
let candidate_one = CandidateInfo { who: one, deposit };
let candidate_two = CandidateInfo { who: two, deposit };
let candidate_three = CandidateInfo { who: three, deposit };
// Storage lists
let bounded_candidates =
BoundedVec::<CandidateInfo<u64, u64>, ConstU32<20>>::try_from(vec![
candidate_one.clone(),
candidate_two.clone(),
])
.expect("it works");
let bounded_candidate_list =
BoundedVec::<CandidateInfo<u64, u64>, ConstU32<20>>::try_from(vec![
candidate_two.clone(),
candidate_three.clone(),
])
.expect("it works");
// Set storage
Candidates::<Test>::put(bounded_candidates);
CandidateList::<Test>::put(bounded_candidate_list.clone());
// Sanity check
assert_eq!(Balances::free_balance(one), 90);
assert_eq!(Balances::free_balance(two), 80);
assert_eq!(Balances::free_balance(three), 90);
// Run migration
v2::MigrationToV2::<Test>::on_runtime_upgrade();
let new_storage_version = StorageVersion::get::<Pallet<Test>>();
assert_eq!(new_storage_version, 2);
// 10 should have been unreserved from the old candidacy
assert_eq!(Balances::free_balance(one), 100);
assert_eq!(Balances::free_balance(two), 90);
assert_eq!(Balances::free_balance(three), 90);
// The storage item should be gone
assert!(Candidates::<Test>::get().is_empty());
// The new storage item should be preserved
assert_eq!(CandidateList::<Test>::get(), bounded_candidate_list);
});
}
#[test]
fn migrate_to_v2_without_new_candidates() {
new_test_ext().execute_with(|| {
let storage_version = StorageVersion::new(1);
storage_version.put::<Pallet<Test>>();
let one = 1u64;
let two = 2u64;
let deposit = 10u64;
// Set balance to 100
Balances::make_free_balance_be(&one, 100u64);
Balances::make_free_balance_be(&two, 100u64);
// Reservations
Balances::reserve(&one, 10u64).unwrap(); // old
Balances::reserve(&two, 10u64).unwrap(); // old
// Candidate info
let candidate_one = CandidateInfo { who: one, deposit };
let candidate_two = CandidateInfo { who: two, deposit };
// Storage lists
let bounded_candidates =
BoundedVec::<CandidateInfo<u64, u64>, ConstU32<20>>::try_from(vec![
candidate_one.clone(),
candidate_two.clone(),
])
.expect("it works");
// Set storage
Candidates::<Test>::put(bounded_candidates.clone());
// Sanity check
assert_eq!(Balances::free_balance(one), 90);
assert_eq!(Balances::free_balance(two), 90);
// Run migration
v2::MigrationToV2::<Test>::on_runtime_upgrade();
let new_storage_version = StorageVersion::get::<Pallet<Test>>();
assert_eq!(new_storage_version, 2);
// Nothing changes deposit-wise
assert_eq!(Balances::free_balance(one), 90);
assert_eq!(Balances::free_balance(two), 90);
// The storage item should be gone
assert!(Candidates::<Test>::get().is_empty());
// The new storage item should have the info now
assert_eq!(CandidateList::<Test>::get(), bounded_candidates);
});
}
}
@@ -0,0 +1,208 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::*;
use crate as collator_selection;
use pezframe_support::{
derive_impl, ord_parameter_types, parameter_types,
traits::{ConstBool, ConstU32, ConstU64, FindAuthor, ValidatorRegistration},
PalletId,
};
use pezframe_system as system;
use pezframe_system::EnsureSignedBy;
use pezsp_runtime::{testing::UintAuthorityId, traits::OpaqueKeys, BuildStorage, RuntimeAppPublic};
type Block = pezframe_system::mocking::MockBlock<Test>;
// Configure a mock runtime to test the pallet.
pezframe_support::construct_runtime!(
pub enum Test
{
System: pezframe_system,
Timestamp: pezpallet_timestamp,
Session: pezpallet_session,
Aura: pezpallet_aura,
Balances: pezpallet_balances,
CollatorSelection: collator_selection,
Authorship: pezpallet_authorship,
}
);
parameter_types! {
pub const SS58Prefix: u8 = 42;
}
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl system::Config for Test {
type Block = Block;
type AccountData = pezpallet_balances::AccountData<u64>;
type SS58Prefix = SS58Prefix;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 5;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for Test {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
}
pub struct Author4;
impl FindAuthor<u64> for Author4 {
fn find_author<'a, I>(_digests: I) -> Option<u64>
where
I: 'a + IntoIterator<Item = (pezframe_support::ConsensusEngineId, &'a [u8])>,
{
Some(4)
}
}
impl pezpallet_authorship::Config for Test {
type FindAuthor = Author4;
type EventHandler = CollatorSelection;
}
impl pezpallet_timestamp::Config for Test {
type Moment = u64;
type OnTimestampSet = Aura;
type MinimumPeriod = ConstU64<1>;
type WeightInfo = ();
}
impl pezpallet_aura::Config for Test {
type AuthorityId = pezsp_consensus_aura::sr25519::AuthorityId;
type MaxAuthorities = ConstU32<100_000>;
type DisabledValidators = ();
type AllowMultipleBlocksPerSlot = ConstBool<false>;
type SlotDuration = pezpallet_aura::MinimumPeriodTimesTwo<Self>;
}
pezsp_runtime::impl_opaque_keys! {
pub struct MockSessionKeys {
// a key for aura authoring
pub aura: UintAuthorityId,
}
}
impl From<UintAuthorityId> for MockSessionKeys {
fn from(aura: pezsp_runtime::testing::UintAuthorityId) -> Self {
Self { aura }
}
}
parameter_types! {
pub static SessionHandlerCollators: Vec<u64> = Vec::new();
pub static SessionChangeBlock: u64 = 0;
}
pub struct TestSessionHandler;
impl pezpallet_session::SessionHandler<u64> for TestSessionHandler {
const KEY_TYPE_IDS: &'static [pezsp_runtime::KeyTypeId] = &[UintAuthorityId::ID];
fn on_genesis_session<Ks: OpaqueKeys>(keys: &[(u64, Ks)]) {
SessionHandlerCollators::set(keys.iter().map(|(a, _)| *a).collect::<Vec<_>>())
}
fn on_new_session<Ks: OpaqueKeys>(_: bool, keys: &[(u64, Ks)], _: &[(u64, Ks)]) {
SessionChangeBlock::set(System::block_number());
SessionHandlerCollators::set(keys.iter().map(|(a, _)| *a).collect::<Vec<_>>())
}
fn on_before_session_ending() {}
fn on_disabled(_: u32) {}
}
parameter_types! {
pub const Offset: u64 = 0;
pub const Period: u64 = 10;
}
impl pezpallet_session::Config for Test {
type RuntimeEvent = RuntimeEvent;
type ValidatorId = <Self as pezframe_system::Config>::AccountId;
// we don't have stash and controller, thus we don't need the convert as well.
type ValidatorIdOf = IdentityCollator;
type ShouldEndSession = pezpallet_session::PeriodicSessions<Period, Offset>;
type NextSessionRotation = pezpallet_session::PeriodicSessions<Period, Offset>;
type SessionManager = CollatorSelection;
type SessionHandler = TestSessionHandler;
type Keys = MockSessionKeys;
type DisablingStrategy = ();
type WeightInfo = ();
type Currency = Balances;
type KeyDeposit = ();
}
ord_parameter_types! {
pub const RootAccount: u64 = 777;
}
parameter_types! {
pub const PotId: PalletId = PalletId(*b"PotStake");
}
pub struct IsRegistered;
impl ValidatorRegistration<u64> for IsRegistered {
fn is_registered(id: &u64) -> bool {
*id != 42u64
}
}
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type UpdateOrigin = EnsureSignedBy<RootAccount, u64>;
type PotId = PotId;
type MaxCandidates = ConstU32<20>;
type MinEligibleCollators = ConstU32<1>;
type MaxInvulnerables = ConstU32<20>;
type KickThreshold = Period;
type ValidatorId = <Self as pezframe_system::Config>::AccountId;
type ValidatorIdOf = IdentityCollator;
type ValidatorRegistration = IsRegistered;
type WeightInfo = ();
}
pub fn new_test_ext() -> pezsp_io::TestExternalities {
pezsp_tracing::try_init_simple();
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
let invulnerables = vec![2, 1]; // unsorted
let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)];
let keys = balances
.iter()
.map(|&(i, _)| (i, i, MockSessionKeys { aura: UintAuthorityId(i) }))
.collect::<Vec<_>>();
let collator_selection = collator_selection::GenesisConfig::<Test> {
desired_candidates: 2,
candidacy_bond: 10,
invulnerables,
};
let session = pezpallet_session::GenesisConfig::<Test> { keys, ..Default::default() };
pezpallet_balances::GenesisConfig::<Test> { balances, ..Default::default() }
.assimilate_storage(&mut t)
.unwrap();
// collator selection must be initialized before session.
collator_selection.assimilate_storage(&mut t).unwrap();
session.assimilate_storage(&mut t).unwrap();
t.into()
}
pub fn initialize_to_block(n: u64) {
for i in System::block_number() + 1..=n {
System::set_block_number(i);
<AllPalletsWithSystem as pezframe_support::traits::OnInitialize<u64>>::on_initialize(i);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,243 @@
// This file is part of Pezcumulus.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![allow(unused_parens)]
#![allow(unused_imports)]
use core::marker::PhantomData;
use pezframe_support::{
traits::Get,
weights::{constants::RocksDbWeight, Weight},
};
// The weight info trait for `pezpallet_collator_selection`.
pub trait WeightInfo {
fn set_invulnerables(_b: u32) -> Weight;
fn add_invulnerable(_b: u32, _c: u32) -> Weight;
fn remove_invulnerable(_b: u32) -> Weight;
fn set_desired_candidates() -> Weight;
fn set_candidacy_bond(_c: u32, _k: u32) -> Weight;
fn register_as_candidate(_c: u32) -> Weight;
fn leave_intent(_c: u32) -> Weight;
fn update_bond(_c: u32) -> Weight;
fn take_candidate_slot(_c: u32) -> Weight;
fn note_author() -> Weight;
fn new_session(_c: u32, _r: u32) -> Weight;
}
/// Weights for pezpallet_collator_selection using the Bizinikiwi node and recommended hardware.
pub struct BizinikiwiWeight<T>(PhantomData<T>);
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
fn set_invulnerables(b: u32) -> Weight {
Weight::from_parts(18_563_000_u64, 0)
// Standard Error: 0
.saturating_add(Weight::from_parts(68_000_u64, 0).saturating_mul(b as u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
fn set_desired_candidates() -> Weight {
Weight::from_parts(16_363_000_u64, 0).saturating_add(T::DbWeight::get().writes(1_u64))
}
fn set_candidacy_bond(_c: u32, _k: u32) -> Weight {
Weight::from_parts(16_840_000_u64, 0).saturating_add(T::DbWeight::get().writes(1_u64))
}
fn register_as_candidate(c: u32) -> Weight {
Weight::from_parts(71_196_000_u64, 0)
// Standard Error: 0
.saturating_add(Weight::from_parts(198_000_u64, 0).saturating_mul(c as u64))
.saturating_add(T::DbWeight::get().reads(4_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
fn leave_intent(c: u32) -> Weight {
Weight::from_parts(55_336_000_u64, 0)
// Standard Error: 0
.saturating_add(Weight::from_parts(151_000_u64, 0).saturating_mul(c as u64))
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
fn update_bond(c: u32) -> Weight {
Weight::from_parts(55_336_000_u64, 0)
// Standard Error: 0
.saturating_add(Weight::from_parts(151_000_u64, 0).saturating_mul(c as u64))
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
fn take_candidate_slot(c: u32) -> Weight {
Weight::from_parts(71_196_000_u64, 0)
// Standard Error: 0
.saturating_add(Weight::from_parts(198_000_u64, 0).saturating_mul(c as u64))
.saturating_add(T::DbWeight::get().reads(4_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
fn note_author() -> Weight {
Weight::from_parts(71_461_000_u64, 0)
.saturating_add(T::DbWeight::get().reads(3_u64))
.saturating_add(T::DbWeight::get().writes(4_u64))
}
fn new_session(r: u32, c: u32) -> Weight {
Weight::from_parts(0_u64, 0)
// Standard Error: 1_010_000
.saturating_add(Weight::from_parts(109_961_000_u64, 0).saturating_mul(r as u64))
// Standard Error: 1_010_000
.saturating_add(Weight::from_parts(151_952_000_u64, 0).saturating_mul(c as u64))
.saturating_add(T::DbWeight::get().reads(1_u64.saturating_mul(r as u64)))
.saturating_add(T::DbWeight::get().reads(2_u64.saturating_mul(c as u64)))
.saturating_add(T::DbWeight::get().writes(2_u64.saturating_mul(r as u64)))
.saturating_add(T::DbWeight::get().writes(2_u64.saturating_mul(c as u64)))
}
/// Storage: Session NextKeys (r:1 w:0)
/// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured)
/// Storage: CollatorSelection Invulnerables (r:1 w:1)
/// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(641), added:
/// 1136, mode: MaxEncodedLen) Storage: CollatorSelection Candidates (r:1 w:1)
/// Proof: CollatorSelection Candidates (max_values: Some(1), max_size: Some(4802), added: 5297,
/// mode: MaxEncodedLen) Storage: System Account (r:1 w:1)
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode:
/// MaxEncodedLen) The range of component `b` is `[1, 19]`.
/// The range of component `c` is `[1, 99]`.
fn add_invulnerable(b: u32, c: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `757 + b * (32 ±0) + c * (53 ±0)`
// Estimated: `6287 + b * (37 ±0) + c * (53 ±0)`
// Minimum execution time: 52_720_000 picoseconds.
Weight::from_parts(56_102_459, 0)
.saturating_add(Weight::from_parts(0, 6287))
// Standard Error: 12_957
.saturating_add(Weight::from_parts(26_422, 0).saturating_mul(b.into()))
// Standard Error: 2_456
.saturating_add(Weight::from_parts(128_528, 0).saturating_mul(c.into()))
.saturating_add(T::DbWeight::get().reads(4))
.saturating_add(T::DbWeight::get().writes(3))
.saturating_add(Weight::from_parts(0, 37).saturating_mul(b.into()))
.saturating_add(Weight::from_parts(0, 53).saturating_mul(c.into()))
}
/// Storage: CollatorSelection Invulnerables (r:1 w:1)
/// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(3202), added:
/// 3697, mode: MaxEncodedLen) The range of component `b` is `[1, 100]`.
fn remove_invulnerable(b: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `119 + b * (32 ±0)`
// Estimated: `4687`
// Minimum execution time: 183_054_000 picoseconds.
Weight::from_parts(197_205_427, 0)
.saturating_add(Weight::from_parts(0, 4687))
// Standard Error: 13_533
.saturating_add(Weight::from_parts(376_231, 0).saturating_mul(b.into()))
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
fn set_invulnerables(b: u32) -> Weight {
Weight::from_parts(18_563_000_u64, 0)
// Standard Error: 0
.saturating_add(Weight::from_parts(68_000_u64, 0).saturating_mul(b as u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
fn set_desired_candidates() -> Weight {
Weight::from_parts(16_363_000_u64, 0).saturating_add(RocksDbWeight::get().writes(1_u64))
}
fn set_candidacy_bond(_c: u32, _k: u32) -> Weight {
Weight::from_parts(16_840_000_u64, 0).saturating_add(RocksDbWeight::get().writes(1_u64))
}
fn register_as_candidate(c: u32) -> Weight {
Weight::from_parts(71_196_000_u64, 0)
// Standard Error: 0
.saturating_add(Weight::from_parts(198_000_u64, 0).saturating_mul(c as u64))
.saturating_add(RocksDbWeight::get().reads(4_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
fn leave_intent(c: u32) -> Weight {
Weight::from_parts(55_336_000_u64, 0)
// Standard Error: 0
.saturating_add(Weight::from_parts(151_000_u64, 0).saturating_mul(c as u64))
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
fn note_author() -> Weight {
Weight::from_parts(71_461_000_u64, 0)
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(4_u64))
}
fn update_bond(c: u32) -> Weight {
Weight::from_parts(55_336_000_u64, 0)
// Standard Error: 0
.saturating_add(Weight::from_parts(151_000_u64, 0).saturating_mul(c as u64))
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(4_u64))
}
fn take_candidate_slot(c: u32) -> Weight {
Weight::from_parts(71_196_000_u64, 0)
// Standard Error: 0
.saturating_add(Weight::from_parts(198_000_u64, 0).saturating_mul(c as u64))
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(4_u64))
}
fn new_session(r: u32, c: u32) -> Weight {
Weight::from_parts(0_u64, 0)
// Standard Error: 1_010_000
.saturating_add(Weight::from_parts(109_961_000_u64, 0).saturating_mul(r as u64))
// Standard Error: 1_010_000
.saturating_add(Weight::from_parts(151_952_000_u64, 0).saturating_mul(c as u64))
.saturating_add(RocksDbWeight::get().reads(1_u64.saturating_mul(r as u64)))
.saturating_add(RocksDbWeight::get().reads(2_u64.saturating_mul(c as u64)))
.saturating_add(RocksDbWeight::get().writes(2_u64.saturating_mul(r as u64)))
.saturating_add(RocksDbWeight::get().writes(2_u64.saturating_mul(c as u64)))
}
/// Storage: Session NextKeys (r:1 w:0)
/// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured)
/// Storage: CollatorSelection Invulnerables (r:1 w:1)
/// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(641), added:
/// 1136, mode: MaxEncodedLen) Storage: CollatorSelection Candidates (r:1 w:1)
/// Proof: CollatorSelection Candidates (max_values: Some(1), max_size: Some(4802), added: 5297,
/// mode: MaxEncodedLen) Storage: System Account (r:1 w:1)
/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode:
/// MaxEncodedLen) The range of component `b` is `[1, 19]`.
/// The range of component `c` is `[1, 99]`.
fn add_invulnerable(b: u32, c: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `757 + b * (32 ±0) + c * (53 ±0)`
// Estimated: `6287 + b * (37 ±0) + c * (53 ±0)`
// Minimum execution time: 52_720_000 picoseconds.
Weight::from_parts(56_102_459, 0)
.saturating_add(Weight::from_parts(0, 6287))
// Standard Error: 12_957
.saturating_add(Weight::from_parts(26_422, 0).saturating_mul(b.into()))
// Standard Error: 2_456
.saturating_add(Weight::from_parts(128_528, 0).saturating_mul(c.into()))
.saturating_add(RocksDbWeight::get().reads(4))
.saturating_add(RocksDbWeight::get().writes(3))
.saturating_add(Weight::from_parts(0, 37).saturating_mul(b.into()))
.saturating_add(Weight::from_parts(0, 53).saturating_mul(c.into()))
}
/// Storage: CollatorSelection Invulnerables (r:1 w:1)
/// Proof: CollatorSelection Invulnerables (max_values: Some(1), max_size: Some(3202), added:
/// 3697, mode: MaxEncodedLen) The range of component `b` is `[1, 100]`.
fn remove_invulnerable(b: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `119 + b * (32 ±0)`
// Estimated: `4687`
// Minimum execution time: 183_054_000 picoseconds.
Weight::from_parts(197_205_427, 0)
.saturating_add(Weight::from_parts(0, 4687))
// Standard Error: 13_533
.saturating_add(Weight::from_parts(376_231, 0).saturating_mul(b.into()))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
}