diff --git a/Cargo.lock b/Cargo.lock index e3ebd0a536..1062ef061c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7652,6 +7652,7 @@ dependencies = [ "log", "pallet-bags-list", "pallet-balances", + "pallet-conviction-voting", "pallet-fast-unstake", "pallet-multisig", "pallet-nomination-pools", @@ -8885,6 +8886,7 @@ dependencies = [ "log", "pallet-bags-list", "pallet-balances", + "pallet-conviction-voting", "pallet-fast-unstake", "pallet-multisig", "pallet-nomination-pools", diff --git a/pallets/ah-migrator/Cargo.toml b/pallets/ah-migrator/Cargo.toml index c7260084ba..69823af59a 100644 --- a/pallets/ah-migrator/Cargo.toml +++ b/pallets/ah-migrator/Cargo.toml @@ -15,6 +15,7 @@ frame-system = { workspace = true } log = { workspace = true } pallet-balances = { workspace = true } pallet-bags-list = { workspace = true } +pallet-conviction-voting = { workspace = true } pallet-fast-unstake = { workspace = true } pallet-multisig = { workspace = true } pallet-nomination-pools = { workspace = true } @@ -49,6 +50,7 @@ std = [ "log/std", "pallet-bags-list/std", "pallet-balances/std", + "pallet-conviction-voting/std", "pallet-fast-unstake/std", "pallet-multisig/std", "pallet-nomination-pools/std", @@ -76,6 +78,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-bags-list/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-conviction-voting/runtime-benchmarks", "pallet-fast-unstake/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-nomination-pools/runtime-benchmarks", @@ -96,6 +99,7 @@ try-runtime = [ "frame-system/try-runtime", "pallet-bags-list/try-runtime", "pallet-balances/try-runtime", + "pallet-conviction-voting/try-runtime", "pallet-fast-unstake/try-runtime", "pallet-multisig/try-runtime", "pallet-nomination-pools/try-runtime", diff --git a/pallets/ah-migrator/src/conviction_voting.rs b/pallets/ah-migrator/src/conviction_voting.rs new file mode 100644 index 0000000000..e76af65467 --- /dev/null +++ b/pallets/ah-migrator/src/conviction_voting.rs @@ -0,0 +1,72 @@ +// 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 . + +use crate::*; +use frame_support::traits::{ClassCountOf, DefensiveTruncateFrom}; +use pallet_conviction_voting::TallyOf; +use pallet_rc_migrator::conviction_voting::{ + alias, RcConvictionVotingMessage, RcConvictionVotingMessageOf, +}; + +impl Pallet { + pub fn do_receive_conviction_voting_messages( + messages: Vec>, + ) -> Result<(), Error> { + log::info!(target: LOG_TARGET, "Processing {} conviction voting messages", messages.len()); + let count = messages.len() as u32; + Self::deposit_event(Event::ConvictionVotingMessagesReceived { count }); + + for message in messages { + Self::do_receive_conviction_voting_message(message); + } + + Self::deposit_event(Event::ConvictionVotingMessagesProcessed { count_good: count }); + + Ok(()) + } + + pub fn do_receive_conviction_voting_message(message: RcConvictionVotingMessageOf) { + match message { + RcConvictionVotingMessage::VotingFor(account_id, class, voting) => { + Self::do_process_voting_for(account_id, class, voting); + }, + RcConvictionVotingMessage::ClassLocksFor(account_id, balance_per_class) => { + Self::do_process_class_locks_for(account_id, balance_per_class); + }, + }; + } + + pub fn do_process_voting_for( + account_id: T::AccountId, + class: alias::ClassOf, + voting: alias::VotingOf, + ) { + log::debug!(target: LOG_TARGET, "Processing VotingFor record for: {:?}", &account_id); + alias::VotingFor::::insert(account_id, class, voting); + } + + pub fn do_process_class_locks_for( + account_id: T::AccountId, + balance_per_class: Vec<(alias::ClassOf, alias::BalanceOf)>, + ) { + log::debug!(target: LOG_TARGET, "Processing ClassLocksFor record for: {:?}", &account_id); + let balance_per_class = + BoundedVec::<_, ClassCountOf>>::defensive_truncate_from( + balance_per_class, + ); + pallet_conviction_voting::ClassLocksFor::::insert(account_id, balance_per_class); + } +} diff --git a/pallets/ah-migrator/src/lib.rs b/pallets/ah-migrator/src/lib.rs index 304d51ad80..35880d4406 100644 --- a/pallets/ah-migrator/src/lib.rs +++ b/pallets/ah-migrator/src/lib.rs @@ -34,6 +34,7 @@ pub mod account; pub mod call; pub mod claims; +pub mod conviction_voting; pub mod multisig; pub mod preimage; pub mod proxy; @@ -58,6 +59,7 @@ use pallet_balances::{AccountData, Reasons as LockReasons}; use pallet_rc_migrator::{ accounts::Account as RcAccount, claims::RcClaimsMessageOf, + conviction_voting::RcConvictionVotingMessageOf, multisig::*, preimage::*, proxy::*, @@ -113,6 +115,7 @@ pub mod pallet { + pallet_fast_unstake::Config + pallet_bags_list::Config + pallet_scheduler::Config + + pallet_conviction_voting::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -329,6 +332,14 @@ pub mod pallet { /// How many scheduler messages failed to integrate. count_bad: u32, }, + ConvictionVotingMessagesReceived { + /// How many conviction voting messages are in the batch. + count: u32, + }, + ConvictionVotingMessagesProcessed { + /// How many conviction voting messages were successfully integrated. + count_good: u32, + }, } #[pallet::pallet] @@ -497,6 +508,16 @@ pub mod pallet { Self::do_receive_scheduler_messages(messages).map_err(Into::into) } + + #[pallet::call_index(14)] + pub fn receive_conviction_voting_messages( + origin: OriginFor, + messages: Vec>, + ) -> DispatchResult { + ensure_root(origin)?; + + Self::do_receive_conviction_voting_messages(messages).map_err(Into::into) + } } #[pallet::hooks] diff --git a/pallets/rc-migrator/Cargo.toml b/pallets/rc-migrator/Cargo.toml index 1a3804449f..dcd910e1b0 100644 --- a/pallets/rc-migrator/Cargo.toml +++ b/pallets/rc-migrator/Cargo.toml @@ -21,6 +21,7 @@ sp-std = { workspace = true } sp-io = { workspace = true } pallet-balances = { workspace = true } pallet-bags-list = { workspace = true } +pallet-conviction-voting = { workspace = true } pallet-scheduler = { workspace = true } pallet-staking = { workspace = true } pallet-proxy = { workspace = true } @@ -46,6 +47,7 @@ std = [ "log/std", "pallet-bags-list/std", "pallet-balances/std", + "pallet-conviction-voting/std", "pallet-fast-unstake/std", "pallet-multisig/std", "pallet-nomination-pools/std", @@ -72,6 +74,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-bags-list/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-conviction-voting/runtime-benchmarks", "pallet-fast-unstake/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-nomination-pools/runtime-benchmarks", @@ -91,6 +94,7 @@ try-runtime = [ "frame-system/try-runtime", "pallet-bags-list/try-runtime", "pallet-balances/try-runtime", + "pallet-conviction-voting/try-runtime", "pallet-fast-unstake/try-runtime", "pallet-multisig/try-runtime", "pallet-nomination-pools/try-runtime", diff --git a/pallets/rc-migrator/src/conviction_voting.rs b/pallets/rc-migrator/src/conviction_voting.rs new file mode 100644 index 0000000000..5d490c11e8 --- /dev/null +++ b/pallets/rc-migrator/src/conviction_voting.rs @@ -0,0 +1,248 @@ +// 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 . + +use crate::*; +use frame_support::traits::{Currency, Polling}; +use pallet_conviction_voting::{ClassLocksFor, TallyOf, Voting}; +use sp_runtime::traits::Zero; + +/// Stage of the scheduler pallet migration. +#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo, MaxEncodedLen, PartialEq, Eq)] +pub enum ConvictionVotingStage { + VotingFor(Option<(AccountId, Class)>), + ClassLocksFor(Option), + Finished, +} + +#[derive(Encode, Decode, RuntimeDebug, Clone, TypeInfo, MaxEncodedLen, PartialEq, Eq)] +pub enum RcConvictionVotingMessage { + VotingFor(AccountId, Class, Voting), + ClassLocksFor(AccountId, Vec<(Class, Balance)>), +} + +pub type RcConvictionVotingMessageOf = RcConvictionVotingMessage< + ::AccountId, + alias::ClassOf, + alias::VotingOf, + alias::BalanceOf, +>; + +pub struct ConvictionVotingMigrator { + _phantom: sp_std::marker::PhantomData, +} + +impl PalletMigration for ConvictionVotingMigrator { + type Key = ConvictionVotingStage>; + type Error = Error; + + fn migrate_many( + last_key: Option, + weight_counter: &mut WeightMeter, + ) -> Result, Self::Error> { + let mut last_key = last_key.unwrap_or(ConvictionVotingStage::VotingFor(None)); + let mut messages = Vec::new(); + + loop { + if weight_counter + .try_consume(::DbWeight::get().reads_writes(1, 1)) + .is_err() + { + if messages.is_empty() { + return Err(Error::OutOfWeight); + } else { + break; + } + } + if messages.len() > 10_000 { + log::warn!(target: LOG_TARGET, "Weight allowed very big batch, stopping"); + break; + } + + last_key = match last_key { + ConvictionVotingStage::VotingFor(last_voting_key) => { + let mut iter = match last_voting_key { + None => alias::VotingFor::::iter(), + Some((account_id, class)) => alias::VotingFor::::iter_from( + alias::VotingFor::::hashed_key_for(account_id, class), + ), + }; + match iter.next() { + Some((account_id, class, voting)) => { + alias::VotingFor::::remove(&account_id, &class); + if Pallet::::is_empty_conviction_vote(&voting) { + // from the Polkadot 17.01.2025 snapshot 20575 records + // issue: https://github.com/paritytech/polkadot-sdk/issues/7458 + log::info!( + "VotingFor {:?} is ignored since it has zero voting capital", + (&account_id, &class) + ); + } else { + messages.push(RcConvictionVotingMessage::VotingFor( + account_id.clone(), + class.clone(), + voting, + )); + } + ConvictionVotingStage::VotingFor(Some((account_id, class))) + }, + None => ConvictionVotingStage::ClassLocksFor(None), + } + }, + ConvictionVotingStage::ClassLocksFor(last_key) => { + let mut iter = if let Some(last_key) = last_key { + ClassLocksFor::::iter_from_key(last_key) + } else { + ClassLocksFor::::iter() + }; + match iter.next() { + Some((account_id, balance_per_class)) => { + ClassLocksFor::::remove(&account_id); + let mut balance_per_class = balance_per_class.into_inner(); + balance_per_class.retain(|(class, balance)| { + if balance.is_zero() { + // from the Polkadot 17.01.2025 snapshot 8522 records + // issue: https://github.com/paritytech/polkadot-sdk/issues/7458 + log::info!( + "ClassLocksFor {:?} is ignored since it has a zero balance", + (&account_id, &class) + ); + false + } else { + true + } + }); + if balance_per_class.len() > 0 { + messages.push(RcConvictionVotingMessage::ClassLocksFor( + account_id.clone(), + balance_per_class, + )); + } + ConvictionVotingStage::ClassLocksFor(Some(account_id)) + }, + None => ConvictionVotingStage::Finished, + } + }, + ConvictionVotingStage::Finished => { + break; + }, + }; + } + + Pallet::::send_chunked_xcm(messages, |messages| { + types::AhMigratorCall::::ReceiveConvictionVotingMessages { messages } + })?; + + if last_key == ConvictionVotingStage::Finished { + Ok(None) + } else { + Ok(Some(last_key)) + } + } +} + +impl Pallet { + fn is_empty_conviction_vote(voting: &alias::VotingOf) -> bool { + if !voting.locked_balance().is_zero() { + return false; + } + match voting { + Voting::Casting(casting) if casting.delegations.capital.is_zero() => true, + Voting::Delegating(delegating) if delegating.delegations.capital.is_zero() => true, + _ => false, + } + } +} + +pub mod alias { + use super::*; + use core::fmt; + + /// Copy of [`pallet_conviction_voting::BalanceOf`]. + /// + /// Required since original type is private. + pub type BalanceOf = + <>::Currency as Currency< + ::AccountId, + >>::Balance; + + /// Copy of [`pallet_conviction_voting::ClassOf`]. + /// + /// Required since original type is private. + pub type ClassOf = + <>::Polls as Polling>>::Class; + + /// Copy of [`pallet_conviction_voting::PollIndexOf`]. + /// + /// Required since original type is private. + pub type PollIndexOf = + <>::Polls as Polling>>::Index; + + /// Wrapper around the `MaxVotes` since the SDK does not derive Clone correctly. + pub struct MaxVotes { + _phantom: sp_std::marker::PhantomData, + } + + impl> Get for MaxVotes { + fn get() -> u32 { + Inner::get() + } + } + + impl Clone for MaxVotes { + fn clone(&self) -> Self { + Self { _phantom: sp_std::marker::PhantomData } + } + } + + impl> fmt::Debug for MaxVotes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "MaxVotes({})", Inner::get()) + } + } + + impl> PartialEq for MaxVotes { + fn eq(&self, _other: &Self) -> bool { + true // other has same type as us + } + } + + impl> Eq for MaxVotes {} + + /// Copy of [`pallet_conviction_voting::VotingOf`]. + /// + /// Required since original type is private. + pub type VotingOf = Voting< + BalanceOf, + ::AccountId, + BlockNumberFor, + PollIndexOf, + MaxVotes<>::MaxVotes>, + >; + + /// Storage alias of [`pallet_conviction_voting::VotingFor`]. + /// + /// Required to replace the stored private type with the public alias. + #[frame_support::storage_alias(pallet_name)] + pub type VotingFor> = StorageDoubleMap< + pallet_conviction_voting::Pallet, + Twox64Concat, + ::AccountId, + Twox64Concat, + ClassOf, + VotingOf, + ValueQuery, + >; +} diff --git a/pallets/rc-migrator/src/lib.rs b/pallets/rc-migrator/src/lib.rs index 4dc8e7b660..31984a37fe 100644 --- a/pallets/rc-migrator/src/lib.rs +++ b/pallets/rc-migrator/src/lib.rs @@ -41,6 +41,7 @@ pub mod staking; pub mod types; mod weights; pub use pallet::*; +pub mod conviction_voting; pub mod scheduler; use accounts::AccountsMigrator; @@ -103,6 +104,7 @@ pub type MigrationStageOf = MigrationStage< ::AccountId, BlockNumberFor, >::Score, + conviction_voting::alias::ClassOf, >; #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] @@ -112,7 +114,7 @@ pub enum PalletEventName { } #[derive(Encode, Decode, Clone, Default, RuntimeDebug, TypeInfo, MaxEncodedLen, PartialEq, Eq)] -pub enum MigrationStage { +pub enum MigrationStage { /// The migration has not yet started but will start in the next block. #[default] Pending, @@ -192,16 +194,22 @@ pub enum MigrationStage { next_key: Option>, }, BagsListMigrationDone, - SchedulerMigrationInit, SchedulerMigrationOngoing { last_key: Option>, }, SchedulerMigrationDone, + ConvictionVotingMigrationInit, + ConvictionVotingMigrationOngoing { + last_key: Option>, + }, + ConvictionVotingMigrationDone, MigrationDone, } -impl MigrationStage { +impl + MigrationStage +{ /// Whether the migration is finished. /// /// This is **not** the same as `!self.is_ongoing()`. @@ -218,8 +226,8 @@ impl MigrationStage std::str::FromStr - for MigrationStage +impl std::str::FromStr + for MigrationStage { type Err = std::string::String; @@ -228,6 +236,7 @@ impl std::str::FromStr "preimage" => MigrationStage::PreimageMigrationInit, "referenda" => MigrationStage::ReferendaMigrationInit, "multisig" => MigrationStage::MultisigMigrationInit, + "voting" => MigrationStage::ConvictionVotingMigrationInit, other => return Err(format!("Unknown migration stage: {}", other)), }) } @@ -260,6 +269,7 @@ pub mod pallet { + pallet_fast_unstake::Config + pallet_bags_list::Config + pallet_scheduler::Config + + pallet_conviction_voting::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -783,6 +793,40 @@ pub mod pallet { } }, MigrationStage::SchedulerMigrationDone => { + Self::transition(MigrationStage::ConvictionVotingMigrationInit); + }, + MigrationStage::ConvictionVotingMigrationInit => { + Self::transition(MigrationStage::ConvictionVotingMigrationOngoing { + last_key: None, + }); + }, + MigrationStage::ConvictionVotingMigrationOngoing { last_key } => { + let res = with_transaction_opaque_err::, Error, _>(|| { + match conviction_voting::ConvictionVotingMigrator::::migrate_many( + last_key, + &mut weight_counter, + ) { + Ok(last_key) => TransactionOutcome::Commit(Ok(last_key)), + Err(e) => TransactionOutcome::Rollback(Err(e)), + } + }) + .expect("Always returning Ok; qed"); + + match res { + Ok(None) => { + Self::transition(MigrationStage::ConvictionVotingMigrationDone); + }, + Ok(Some(last_key)) => { + Self::transition(MigrationStage::ConvictionVotingMigrationOngoing { + last_key: Some(last_key), + }); + }, + Err(err) => { + defensive!("Error while migrating conviction voting: {:?}", err); + }, + } + }, + MigrationStage::ConvictionVotingMigrationDone => { Self::transition(MigrationStage::MigrationDone); }, MigrationStage::MigrationDone => (), diff --git a/pallets/rc-migrator/src/types.rs b/pallets/rc-migrator/src/types.rs index d2b4f78a58..999b631dcf 100644 --- a/pallets/rc-migrator/src/types.rs +++ b/pallets/rc-migrator/src/types.rs @@ -63,6 +63,10 @@ pub enum AhMigratorCall { ReceiveBagsListMessages { messages: Vec> }, #[codec(index = 13)] ReceiveSchedulerMessages { messages: Vec> }, + #[codec(index = 14)] + ReceiveConvictionVotingMessages { + messages: Vec>, + }, } /// Copy of `ParaInfo` type from `paras_registrar` pallet.