// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see .
//! A module that is responsible for migration of storage for Collator Selection.
use super::*;
use frame_support::traits::{OnRuntimeUpgrade, UncheckedOnRuntimeUpgrade};
use log;
/// Migrate to v2. Should have been part of .
pub mod v2 {
use super::*;
use frame_support::{
pallet_prelude::*,
storage_alias,
traits::{Currency, ReservableCurrency},
};
use sp_runtime::traits::{Saturating, Zero};
#[cfg(feature = "try-runtime")]
use sp_std::vec::Vec;
/// [`UncheckedMigrationToV2`] wrapped in a
/// [`VersionedMigration`](frame_support::migrations::VersionedMigration), ensuring the
/// migration is only performed when on-chain version is 1.
pub type MigrationToV2 = frame_support::migrations::VersionedMigration<
1,
2,
UncheckedMigrationToV2,
Pallet,
::DbWeight,
>;
#[storage_alias]
pub type Candidates = StorageValue<
Pallet,
BoundedVec::AccountId, <::Currency as Currency<::AccountId>>::Balance>, ::MaxCandidates>,
ValueQuery,
>;
/// Migrate to V2.
pub struct UncheckedMigrationToV2(sp_std::marker::PhantomData);
impl UncheckedOnRuntimeUpgrade for UncheckedMigrationToV2 {
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::::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 Polkadot/Kusama we messed this up, and people registered under
// `CandidateList` while their funds were locked in `Candidates`.
let new_candidate_list = CandidateList::::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::::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(
<::WeightInfo as pallet_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, sp_runtime::DispatchError> {
let number_of_candidates = Candidates::::get().to_vec().len();
Ok((number_of_candidates as u32).encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(_number_of_candidates: Vec) -> Result<(), sp_runtime::DispatchError> {
let new_number_of_candidates = Candidates::::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 frame_support::pallet_prelude::*;
#[cfg(feature = "try-runtime")]
use sp_std::prelude::*;
pub struct MigrateToV1(sp_std::marker::PhantomData);
impl OnRuntimeUpgrade for MigrateToV1 {
fn on_runtime_upgrade() -> Weight {
let on_chain_version = Pallet::::on_chain_storage_version();
if on_chain_version == 0 {
let invulnerables_len = Invulnerables::::get().to_vec().len();
Invulnerables::::mutate(|invulnerables| {
invulnerables.sort();
});
StorageVersion::new(1).put::>();
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, sp_runtime::DispatchError> {
let number_of_invulnerables = Invulnerables::::get().to_vec().len();
Ok((number_of_invulnerables as u32).encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(number_of_invulnerables: Vec) -> Result<(), sp_runtime::DispatchError> {
let stored_invulnerables = Invulnerables::::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::::on_chain_storage_version();
frame_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 frame_support::{
traits::{Currency, ReservableCurrency, StorageVersion},
BoundedVec,
};
use sp_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::>();
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::, ConstU32<20>>::try_from(vec![
candidate_one.clone(),
candidate_two.clone(),
])
.expect("it works");
let bounded_candidate_list =
BoundedVec::, ConstU32<20>>::try_from(vec![
candidate_two.clone(),
candidate_three.clone(),
])
.expect("it works");
// Set storage
Candidates::::put(bounded_candidates);
CandidateList::::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::::on_runtime_upgrade();
let new_storage_version = StorageVersion::get::>();
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::::get().is_empty());
// The new storage item should be preserved
assert_eq!(CandidateList::::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::>();
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::, ConstU32<20>>::try_from(vec![
candidate_one.clone(),
candidate_two.clone(),
])
.expect("it works");
// Set storage
Candidates::::put(bounded_candidates.clone());
// Sanity check
assert_eq!(Balances::free_balance(one), 90);
assert_eq!(Balances::free_balance(two), 90);
// Run migration
v2::MigrationToV2::::on_runtime_upgrade();
let new_storage_version = StorageVersion::get::>();
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::::get().is_empty());
// The new storage item should have the info now
assert_eq!(CandidateList::::get(), bounded_candidates);
});
}
}