// 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); }); } }