diff --git a/Cargo.lock b/Cargo.lock index cab5c2e1aa..005d519b4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7636,6 +7636,7 @@ dependencies = [ "pallet-multisig", "pallet-nomination-pools", "pallet-preimage", + "pallet-proxy", "pallet-rc-migrator", "pallet-staking", "pallet-state-trie-migration", @@ -8859,6 +8860,7 @@ dependencies = [ "log", "pallet-balances", "pallet-multisig", + "pallet-proxy", "pallet-staking", "parity-scale-codec", "polkadot-parachain-primitives", diff --git a/integration-tests/ahm/src/mock.rs b/integration-tests/ahm/src/mock.rs index e551aec215..0b1e316db2 100644 --- a/integration-tests/ahm/src/mock.rs +++ b/integration-tests/ahm/src/mock.rs @@ -56,7 +56,7 @@ pub async fn remote_ext_test_setup( pub fn next_block_rc() { let now = frame_system::Pallet::::block_number(); - log::info!(target: LOG_RC, "Next block: {:?}", now + 1); + log::debug!(target: LOG_RC, "Next block: {:?}", now + 1); >::on_finalize(now); frame_system::Pallet::::set_block_number(now + 1); frame_system::Pallet::::reset_events(); @@ -67,7 +67,7 @@ pub fn next_block_rc() { pub fn next_block_ah() { let now = frame_system::Pallet::::block_number(); - log::info!(target: LOG_AH, "Next block: {:?}", now + 1); + log::debug!(target: LOG_AH, "Next block: {:?}", now + 1); >::on_finalize( now, ); diff --git a/integration-tests/ahm/src/tests.rs b/integration-tests/ahm/src/tests.rs index 511fe5e433..bf1faab098 100644 --- a/integration-tests/ahm/src/tests.rs +++ b/integration-tests/ahm/src/tests.rs @@ -36,10 +36,8 @@ use frame_support::{pallet_prelude::*, traits::*, weights::WeightMeter}; use pallet_rc_migrator::{MigrationStage, RcMigrationStage}; use polkadot_primitives::InboundDownwardMessage; use remote_externalities::RemoteExternalities; -use tokio::sync::mpsc::channel; -use asset_hub_polkadot_runtime::{Block as AssetHubBlock, Runtime as AssetHub}; -use polkadot_runtime::{Block as PolkadotBlock, Runtime as Polkadot}; +use polkadot_runtime::Runtime as Polkadot; use super::mock::*; @@ -61,9 +59,9 @@ async fn account_migration_works() { dmps.extend(new_dmps); if RcMigrationStage::::get() == - pallet_rc_migrator::MigrationStage::MultisigMigrationDone + pallet_rc_migrator::MigrationStage::ProxyMigrationDone { - log::info!("Multisig migration done"); + log::info!("Migration done"); break dmps; } } diff --git a/pallets/ah-migrator/Cargo.toml b/pallets/ah-migrator/Cargo.toml index 4cfe6176dd..0629a6cd4e 100644 --- a/pallets/ah-migrator/Cargo.toml +++ b/pallets/ah-migrator/Cargo.toml @@ -17,6 +17,7 @@ pallet-balances = { workspace = true } pallet-multisig = { workspace = true } pallet-nomination-pools = { workspace = true } pallet-preimage = { workspace = true } +pallet-proxy = { workspace = true } pallet-rc-migrator = { workspace = true } pallet-staking = { workspace = true } pallet-state-trie-migration = { workspace = true } @@ -44,6 +45,7 @@ std = [ "pallet-multisig/std", "pallet-nomination-pools/std", "pallet-preimage/std", + "pallet-proxy/std", "pallet-rc-migrator/std", "pallet-staking/std", "pallet-state-trie-migration/std", @@ -66,6 +68,7 @@ runtime-benchmarks = [ "pallet-multisig/runtime-benchmarks", "pallet-nomination-pools/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", + "pallet-proxy/runtime-benchmarks", "pallet-rc-migrator/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-state-trie-migration/runtime-benchmarks", @@ -81,6 +84,7 @@ try-runtime = [ "pallet-multisig/try-runtime", "pallet-nomination-pools/try-runtime", "pallet-preimage/try-runtime", + "pallet-proxy/try-runtime", "pallet-rc-migrator/try-runtime", "pallet-staking/try-runtime", "pallet-state-trie-migration/try-runtime", diff --git a/pallets/ah-migrator/src/lib.rs b/pallets/ah-migrator/src/lib.rs index 57bda347f1..395eec341a 100644 --- a/pallets/ah-migrator/src/lib.rs +++ b/pallets/ah-migrator/src/lib.rs @@ -33,7 +33,9 @@ pub mod account; pub mod multisig; +pub mod proxy; pub mod types; + pub use pallet::*; use frame_support::{ @@ -41,14 +43,17 @@ use frame_support::{ storage::{transactional::with_transaction_opaque_err, TransactionOutcome}, traits::{ fungible::{InspectFreeze, Mutate, MutateFreeze, MutateHold}, - LockableCurrency, ReservableCurrency, WithdrawReasons as LockWithdrawReasons, + Defensive, LockableCurrency, ReservableCurrency, WithdrawReasons as LockWithdrawReasons, }, }; use frame_system::pallet_prelude::*; use pallet_balances::{AccountData, Reasons as LockReasons}; -use pallet_rc_migrator::{accounts::Account as RcAccount, multisig::*}; +use pallet_rc_migrator::{accounts::Account as RcAccount, multisig::*, proxy::*}; use sp_application_crypto::Ss58Codec; -use sp_runtime::{traits::Convert, AccountId32}; +use sp_runtime::{ + traits::{Convert, TryConvert}, + AccountId32, +}; use sp_std::prelude::*; /// The log target of this pallet. @@ -65,6 +70,7 @@ pub mod pallet { frame_system::Config, AccountId = AccountId32> + pallet_balances::Config + pallet_multisig::Config + + pallet_proxy::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -85,6 +91,15 @@ pub mod pallet { type RcToAhHoldReason: Convert; /// Relay Chain to Asset Hub Freeze Reasons mapping; type RcToAhFreezeReason: Convert; + /// The abridged Relay Chain Proxy Type. + type RcProxyType: Parameter; + /// Convert a Relay Chain Proxy Type to a local AH one. + type RcToProxyType: TryConvert::ProxyType>; + /// Convert a Relay Chain Proxy Delay to a local AH one. + /// + /// Note that we make a simplification here by assuming that both chains have the same block + // number type. + type RcToProxyDelay: TryConvert, BlockNumberFor>; } #[pallet::error] @@ -109,6 +124,30 @@ pub mod pallet { /// How many multisigs failed to integrate. count_bad: u32, }, + /// We received a batch of proxies that we are going to integrate. + ProxyProxiesBatchReceived { + /// How many proxies are in the batch. + count: u32, + }, + /// We processed a batch of proxies that we received. + ProxyProxiesBatchProcessed { + /// How many proxies were successfully integrated. + count_good: u32, + /// How many proxies failed to integrate. + count_bad: u32, + }, + /// We received a batch of proxy announcements that we are going to integrate. + ProxyAnnouncementsBatchReceived { + /// How many proxy announcements are in the batch. + count: u32, + }, + /// We processed a batch of proxy announcements that we received. + ProxyAnnouncementsBatchProcessed { + /// How many proxy announcements were successfully integrated. + count_good: u32, + /// How many proxy announcements failed to integrate. + count_bad: u32, + }, } #[pallet::pallet] @@ -149,6 +188,27 @@ pub mod pallet { Self::do_receive_multisigs(accounts).map_err(Into::into) } + + /// Receive proxies from the Relay Chain. + #[pallet::call_index(2)] + pub fn receive_proxy_proxies( + origin: OriginFor, + proxies: Vec>, + ) -> DispatchResult { + ensure_root(origin)?; + + Self::do_receive_proxies(proxies).map_err(Into::into) + } + + /// Receive proxy announcements from the Relay Chain. + #[pallet::call_index(3)] + pub fn receive_proxy_announcements( + origin: OriginFor, + announcements: Vec>, + ) -> DispatchResult { + ensure_root(origin)?; + Self::do_receive_proxy_announcements(announcements).map_err(Into::into) + } } #[pallet::hooks] diff --git a/pallets/ah-migrator/src/multisig.rs b/pallets/ah-migrator/src/multisig.rs index ebac37cf03..afcd916e15 100644 --- a/pallets/ah-migrator/src/multisig.rs +++ b/pallets/ah-migrator/src/multisig.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// 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 crate::{types::*, *}; use hex_literal::hex; diff --git a/pallets/ah-migrator/src/proxy.rs b/pallets/ah-migrator/src/proxy.rs new file mode 100644 index 0000000000..4579e5a178 --- /dev/null +++ b/pallets/ah-migrator/src/proxy.rs @@ -0,0 +1,123 @@ +// This file is part of Substrate. + +// 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 crate::{types::*, *}; +use sp_runtime::{traits::Zero, BoundedSlice}; + +impl Pallet { + pub fn do_receive_proxies(proxies: Vec>) -> Result<(), Error> { + Self::deposit_event(Event::ProxyProxiesBatchReceived { count: proxies.len() as u32 }); + let (mut count_good, mut count_bad) = (0, 0); + log::info!(target: LOG_TARGET, "Integrating {} proxies", proxies.len()); + + for proxy in proxies { + match Self::do_receive_proxy(proxy) { + Ok(()) => count_good += 1, + Err(e) => { + count_bad += 1; + log::error!(target: LOG_TARGET, "Error while integrating proxy: {:?}", e); + }, + } + } + Self::deposit_event(Event::ProxyProxiesBatchProcessed { count_good, count_bad }); + + Ok(()) + } + + /// Receive a single proxy and write it to storage. + pub fn do_receive_proxy(proxy: RcProxyOf) -> Result<(), Error> { + log::debug!(target: LOG_TARGET, "Integrating proxy {}, deposit {:?}", proxy.delegator.to_ss58check(), proxy.deposit); + + let max_proxies = ::MaxProxies::get() as usize; + if proxy.proxies.len() > max_proxies { + log::error!(target: LOG_TARGET, "Truncating proxy list of {}", proxy.delegator.to_ss58check()); + } + + let proxies = proxy.proxies.into_iter().enumerate().filter_map(|(i, p)| { + let Ok(proxy_type) = T::RcToProxyType::try_convert(p.proxy_type) else { + // This is fine, eg. `Auction` proxy is not supported on AH + log::warn!(target: LOG_TARGET, "Dropping unsupported proxy at index {} for {}", i, proxy.delegator.to_ss58check()); + return None; + }; + + let Ok(delay) = T::RcToProxyDelay::try_convert(p.delay).defensive() else { + return None; + }; + + Some(pallet_proxy::ProxyDefinition { + delegate: p.delegate, + delay: delay, + proxy_type, + }) + }) + .take(max_proxies) + .collect::>(); + + let Ok(bounded_proxies) = + BoundedSlice::try_from(proxies.as_slice()).defensive_proof("unreachable") + else { + return Err(Error::TODO); + }; + + // The deposit was already taken by the account migration + + // Add the proxies + pallet_proxy::Proxies::::insert(proxy.delegator, (bounded_proxies, proxy.deposit)); + + Ok(()) + } + + pub fn do_receive_proxy_announcements( + announcements: Vec>, + ) -> Result<(), Error> { + Self::deposit_event(Event::ProxyAnnouncementsBatchReceived { + count: announcements.len() as u32, + }); + + let (mut count_good, mut count_bad) = (0, 0); + log::info!(target: LOG_TARGET, "Integrating {} proxy announcements", announcements.len()); + + for announcement in announcements { + match Self::do_receive_proxy_announcement(announcement) { + Ok(()) => count_good += 1, + Err(e) => { + count_bad += 1; + log::error!(target: LOG_TARGET, "Error while integrating proxy announcement: {:?}", e); + }, + } + } + + Self::deposit_event(Event::ProxyAnnouncementsBatchProcessed { count_good, count_bad }); + + Ok(()) + } + + pub fn do_receive_proxy_announcement( + announcement: RcProxyAnnouncementOf, + ) -> Result<(), Error> { + let missing = ::Currency::unreserve( + &announcement.depositor, + announcement.deposit, + ); + + if !missing.is_zero() { + log::warn!(target: LOG_TARGET, "Could not unreserve full proxy announcement deposit for {}, missing {:?}", announcement.depositor.to_ss58check(), missing); + } + + Ok(()) + } +} diff --git a/pallets/rc-migrator/Cargo.toml b/pallets/rc-migrator/Cargo.toml index d5419850f5..3229abdc35 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-staking = { workspace = true } +pallet-proxy = { workspace = true } pallet-multisig = { workspace = true } polkadot-runtime-common = { workspace = true } runtime-parachains = { workspace = true } @@ -38,6 +39,7 @@ std = [ "log/std", "pallet-balances/std", "pallet-multisig/std", + "pallet-proxy/std", "pallet-staking/std", "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", @@ -56,6 +58,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", + "pallet-proxy/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-common/runtime-benchmarks", @@ -67,6 +70,7 @@ try-runtime = [ "frame-system/try-runtime", "pallet-balances/try-runtime", "pallet-multisig/try-runtime", + "pallet-proxy/try-runtime", "pallet-staking/try-runtime", "polkadot-runtime-common/try-runtime", "runtime-parachains/try-runtime", diff --git a/pallets/rc-migrator/src/lib.rs b/pallets/rc-migrator/src/lib.rs index 9f79c58eeb..e2783a608a 100644 --- a/pallets/rc-migrator/src/lib.rs +++ b/pallets/rc-migrator/src/lib.rs @@ -33,6 +33,7 @@ pub mod accounts; pub mod multisig; +pub mod proxy; pub mod types; mod weights; pub use pallet::*; @@ -54,7 +55,7 @@ use polkadot_parachain_primitives::primitives::Id as ParaId; use polkadot_runtime_common::paras_registrar; use runtime_parachains::hrmp; use sp_core::crypto::Ss58Codec; -use sp_runtime::AccountId32; +use sp_runtime::{traits::TryConvert, AccountId32}; use sp_std::prelude::*; use storage::TransactionOutcome; use types::AhWeightInfo; @@ -62,6 +63,7 @@ use weights::WeightInfo; use xcm::prelude::*; use multisig::MultisigMigrator; +use proxy::*; use types::PalletMigration; /// The log target of this pallet. @@ -88,14 +90,19 @@ pub enum MigrationStage { MultisigMigrationInit, MultiSigMigrationOngoing { /// Last migrated key of the `Multisigs` double map. - /// - /// The MEL bound must be able to hold an `AccountId32` and a 32 byte hash. TODO 1024 is - /// probably overkill, but we can optimize later. - last_key: BoundedVec>, + last_key: Option<(AccountId, [u8; 32])>, }, MultisigMigrationDone, - /// Some next stage - TODO, + ProxyMigrationInit, + /// Currently migrating the proxies of the proxy pallet. + ProxyMigrationProxies { + last_key: Option, + }, + /// Currently migrating the announcements of the proxy pallet. + ProxyMigrationAnnouncements { + last_key: Option, + }, + ProxyMigrationDone, } type AccountInfoFor = @@ -117,6 +124,7 @@ pub mod pallet { + hrmp::Config + paras_registrar::Config + pallet_multisig::Config + + pallet_proxy::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -208,7 +216,7 @@ pub mod pallet { let _ = Self::obtain_rc_accounts(); Self::transition(MigrationStage::AccountsMigrationInit); - //Self::transition(MigrationStage::MultisigMigrationInit); + //Self::transition(MigrationStage::ProxyMigrationInit); }, MigrationStage::AccountsMigrationInit => { let first_acc = match Self::first_account(&mut weight_counter).defensive() { @@ -228,7 +236,6 @@ pub mod pallet { Self::transition(MigrationStage::AccountsMigrationDone); } }, - MigrationStage::AccountsMigrationOngoing { last_key } => { let res = with_transaction_opaque_err::, Error, _>(|| { @@ -256,29 +263,11 @@ pub mod pallet { } }, MigrationStage::AccountsMigrationDone => { - // Note: swap this out for faster testing + // Note: swap this out for faster testing to skip some migrations Self::transition(MigrationStage::MultisigMigrationInit); }, MigrationStage::MultisigMigrationInit => { - let first_key = match MultisigMigrator::::first_key(&mut weight_counter) - .defensive() - { - Err(e) => { - log::error!(target: LOG_TARGET, "Error while obtaining first multisig: {:?}. Retrying with higher weight.", e); - MultisigMigrator::::first_key(&mut WeightMeter::new()) - .unwrap_or_default() - }, - Ok(key) => key, - }; - - if let Some(first_key) = first_key { - Self::transition(MigrationStage::MultiSigMigrationOngoing { - last_key: first_key, - }); - } else { - // No keys to migrate at all. - Self::transition(MigrationStage::MultisigMigrationDone); - } + Self::transition(MigrationStage::MultiSigMigrationOngoing { last_key: None }); }, MigrationStage::MultiSigMigrationOngoing { last_key } => { let res = with_transaction_opaque_err::, Error, _>(|| { @@ -298,16 +287,74 @@ pub mod pallet { Ok(Some(last_key)) => { // multisig migration continues with the next block // TODO publish event - Self::transition(MigrationStage::MultiSigMigrationOngoing { last_key }); + Self::transition(MigrationStage::MultiSigMigrationOngoing { + last_key: Some(last_key), + }); }, - _ => { - // TODO handle error + e => { + log::error!(target: LOG_TARGET, "Error while migrating multisigs: {:?}", e); + defensive!("Error while migrating multisigs"); + }, + } + }, + MigrationStage::MultisigMigrationDone => { + Self::transition(MigrationStage::ProxyMigrationInit); + }, + MigrationStage::ProxyMigrationInit => { + Self::transition(MigrationStage::ProxyMigrationProxies { last_key: None }); + }, + MigrationStage::ProxyMigrationProxies { last_key } => { + let res = with_transaction_opaque_err::, Error, _>(|| { + TransactionOutcome::Commit(ProxyProxiesMigrator::::migrate_many( + last_key, + &mut weight_counter, + )) + }) + .expect("Always returning Ok; qed"); + + match res { + Ok(None) => { + Self::transition(MigrationStage::ProxyMigrationAnnouncements { + last_key: None, + }); + }, + Ok(Some(last_key)) => { + Self::transition(MigrationStage::ProxyMigrationProxies { + last_key: Some(last_key), + }); + }, + e => { + log::error!(target: LOG_TARGET, "Error while migrating proxies: {:?}", e); + defensive!("Error while migrating proxies"); + }, + } + }, + MigrationStage::ProxyMigrationAnnouncements { last_key } => { + let res = with_transaction_opaque_err::, Error, _>(|| { + TransactionOutcome::Commit(ProxyAnnouncementMigrator::::migrate_many( + last_key, + &mut weight_counter, + )) + }) + .expect("Always returning Ok; qed"); + + match res { + Ok(None) => { + Self::transition(MigrationStage::ProxyMigrationDone); + }, + Ok(Some(last_key)) => { + Self::transition(MigrationStage::ProxyMigrationAnnouncements { + last_key: Some(last_key), + }); + }, + e => { + log::error!(target: LOG_TARGET, "Error while migrating proxy announcements: {:?}", e); + defensive!("Error while migrating proxy announcements"); }, } }, - MigrationStage::MultisigMigrationDone => {}, - MigrationStage::TODO => { - todo!() + MigrationStage::ProxyMigrationDone => { + todo!(); }, }; diff --git a/pallets/rc-migrator/src/multisig.rs b/pallets/rc-migrator/src/multisig.rs index ae262a5926..cbadb12067 100644 --- a/pallets/rc-migrator/src/multisig.rs +++ b/pallets/rc-migrator/src/multisig.rs @@ -15,6 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![doc = include_str!("multisig.md")] + use frame_support::traits::Currency; extern crate alloc; @@ -99,42 +101,38 @@ pub struct MultisigMigrator { } impl PalletMigration for MultisigMigrator { - type Key = BoundedVec>; + type Key = (T::AccountId, [u8; 32]); type Error = Error; - /// The first storage key to migrate. - fn first_key(_weight: &mut WeightMeter) -> Result, Error> { - (); - let Some((k1, k2)) = aliases::Multisigs::::iter_keys().next() else { - return Ok(None); - }; - let encoded_key = aliases::Multisigs::::hashed_key_for(k1, k2); - let bounded_key = BoundedVec::try_from(encoded_key).defensive().map_err(|_| Error::TODO)?; - Ok(Some(bounded_key)) - } - /// Migrate until the weight is exhausted. Start at the given key. + /// + /// Storage changes must be rolled back on error. fn migrate_many( - last_key: Self::Key, + mut last_key: Option, weight_counter: &mut WeightMeter, ) -> Result, Error> { let mut batch = Vec::new(); - let mut iter = aliases::Multisigs::::iter_from(last_key.to_vec().clone()); - - let maybe_last_key = loop { - log::debug!("Migrating multisigs from {:?}", last_key); + let last_raw_key = last_key + .clone() + .map(|(k1, k2)| aliases::Multisigs::::hashed_key_for(k1, k2)) + .unwrap_or_default(); + let mut iter = aliases::Multisigs::::iter_from(last_raw_key); + loop { let kv = iter.next(); let Some((k1, k2, multisig)) = kv else { - break None; + last_key = None; + log::info!(target: LOG_TARGET, "No more multisigs to migrate"); + break; }; + log::debug!("Migrating multisigs of acc {:?}", k1); + match Self::migrate_single(k1.clone(), multisig, weight_counter) { Ok(ms) => batch.push(ms), // TODO continue here // Account does not need to be migrated // Not enough weight, lets try again in the next block since we made some progress. - Err(Error::OutOfWeight) if batch.len() > 0 => - break Some(aliases::Multisigs::::hashed_key_for(k1, k2)), + Err(Error::OutOfWeight) if batch.len() > 0 => break, // Not enough weight and was unable to make progress, bad. Err(Error::OutOfWeight) if batch.len() == 0 => { defensive!("Not enough weight to migrate a single account"); @@ -147,22 +145,19 @@ impl PalletMigration for MultisigMigrator { }, } - // TODO construct XCM - }; + // TODO delete old + last_key = Some((k1, k2)); + } - if batch.len() > 0 { + if !batch.is_empty() { Self::send_batch_xcm(batch)?; } - let bounded_key = maybe_last_key - .map(|k| BoundedVec::try_from(k).defensive().map_err(|_| Error::TODO)) - .transpose()?; - Ok(bounded_key) + Ok(last_key) } } impl MultisigMigrator { - /// WILL NOT MODIFY STORAGE IN THE ERROR CASE fn migrate_single( k1: AccountIdOf, ms: aliases::MultisigOf, @@ -173,11 +168,10 @@ impl MultisigMigrator { return Err(Error::::OutOfWeight); } - // TODO construct XCM - Ok(RcMultisig { creator: ms.depositor, deposit: ms.deposit, details: Some(k1) }) } + /// Storage changes must be rolled back on error. fn send_batch_xcm(multisigs: Vec>) -> Result<(), Error> { let call = types::AssetHubPalletConfig::::AhmController( types::AhMigratorCall::::ReceiveMultisigs { multisigs }, diff --git a/pallets/rc-migrator/src/proxy.md b/pallets/rc-migrator/src/proxy.md new file mode 100644 index 0000000000..72b3e57cc0 --- /dev/null +++ b/pallets/rc-migrator/src/proxy.md @@ -0,0 +1,32 @@ +## Pallet Proxy + +The proxy pallet consists of two storage variables. +## Storage: Proxies + +The [Proxies](https://github.com/paritytech/polkadot-sdk/blob/7c5224cb01710d0c14c87bf3463cc79e49b3e7b5/substrate/frame/proxy/src/lib.rs#L564-L579) storage map maps a delegator to its delegates. It can be translated one-to-one by mapping the `ProxyType` an `Delay` fields. +### Proxy Type Translation +The different kinds that are possible for a proxy are a [runtime injected type](https://github.com/paritytech/polkadot-sdk/blob/7c5224cb01710d0c14c87bf3463cc79e49b3e7b5/substrate/frame/proxy/src/lib.rs#L119-L125). Since these are different for each runtime, we need a converter that maps the Relay to AH `ProxyType` as close as possible to keep the original intention. The Relay kind is defined [here](https://github.com/polkadot-fellows/runtimes/blob/dde99603d7dbd6b8bf541d57eb30d9c07a4fce32/relay/polkadot/src/lib.rs#L1000-L1010) and the AH version [here](https://github.com/polkadot-fellows/runtimes/blob/fd8d0c23d83a7b512e721b1fde2ba3737a3478d5/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs#L453-L468). + +Mapping from Relay to AH looks as follows: +- Any: same +- NonTransfer: same +- Governance: needs to be added +- Staking: needs to be added +- Variant 4: ignore as it is a historic remnant +- Variant 5: ignore ditto +- CancelProxy: same +- Auction: ? +- NominationPools: needs to be added + +All variants that serve no purpose anymore on the Relay Chain are deleted from there. For example `Stakin`. The ones that are still usable on the relay like `NonTransfer` are **also deleted** since there is no storage deposit taken anymore. (TODO think about what is best here) +### Delay Translation + +The [delay of a ProxyDefinition](https://github.com/paritytech/polkadot-sdk/blob/7c5224cb01710d0c14c87bf3463cc79e49b3e7b5/substrate/frame/proxy/src/lib.rs#L77) is measured in blocks. These are currently 6 seconds Relay blocks. To translate them to 12s AH blocks, we can divide the number by two. +## Storage: Announcements + +The [Announcements](https://github.com/paritytech/polkadot-sdk/blob/7c5224cb01710d0c14c87bf3463cc79e49b3e7b5/substrate/frame/proxy/src/lib.rs#L581-L592) storage maps proxy account IDs to [Accouncement](https://github.com/paritytech/polkadot-sdk/blob/7c5224cb01710d0c14c87bf3463cc79e49b3e7b5/substrate/frame/proxy/src/lib.rs#L80-L89). Since an announcement contains a call hash, we cannot translate them for the same reason as with the Multisigs; the preimage of the hash would be either undecodable, decode to something else (security issue) or accidentally decode to the same thing. + +We therefore do not migrate the announcements. +## User Impact +- Announcements need to be re-created +- Proxies of type `Auction` are not migrated and need to be re-created on the Relay diff --git a/pallets/rc-migrator/src/proxy.rs b/pallets/rc-migrator/src/proxy.rs new file mode 100644 index 0000000000..8ad988ddc4 --- /dev/null +++ b/pallets/rc-migrator/src/proxy.rs @@ -0,0 +1,290 @@ +// This file is part of Substrate. + +// 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. + +#![doc = include_str!("proxy.md")] + +use frame_support::traits::Currency; + +extern crate alloc; +use crate::{types::*, *}; +use alloc::vec::Vec; +use sp_core::ByteArray; + +pub struct ProxyProxiesMigrator { + _marker: sp_std::marker::PhantomData, +} + +pub struct ProxyAnnouncementMigrator { + _marker: sp_std::marker::PhantomData, +} + +type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct RcProxy { + /// The account that is delegating to their proxy. + pub delegator: AccountId, + /// The deposit that was `Currency::reserved` from the delegator. + pub deposit: Balance, + /// The proxies that were delegated to and that can act on behalf of the delegator. + pub proxies: Vec>, +} + +pub type RcProxyOf = + RcProxy, BalanceOf, ProxyType, BlockNumberFor>; + +/// A RcProxy in Relay chain format, can only be understood by the RC and must be translated first. +pub(crate) type RcProxyLocalOf = RcProxyOf::ProxyType>; + +/// A deposit that was taken for a proxy announcement. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct RcProxyAnnouncement { + pub depositor: AccountId, + pub deposit: Balance, +} + +pub type RcProxyAnnouncementOf = RcProxyAnnouncement, BalanceOf>; + +impl PalletMigration for ProxyProxiesMigrator { + type Key = T::AccountId; + type Error = Error; + + fn migrate_many( + mut last_key: Option>, + weight_counter: &mut WeightMeter, + ) -> Result>, Error> { + let mut batch = Vec::new(); + + let mut key_iter = if let Some(last_key) = last_key.clone() { + pallet_proxy::Proxies::::iter_keys_from(pallet_proxy::Proxies::::hashed_key_for( + &last_key, + )) + } else { + pallet_proxy::Proxies::::iter_keys() + }; + + loop { + let Some(acc) = key_iter.next() else { + last_key = None; + log::info!(target: LOG_TARGET, "No more proxies to migrate, last key: {:?}", &last_key); + break; + }; + log::debug!("Migrating proxies of acc {:?}", acc); + + let (proxies, deposit) = pallet_proxy::Proxies::::get(&acc); + + if proxies.is_empty() { + last_key = None; + defensive!("No more proxies to migrate"); + break; + }; + + match Self::migrate_single(acc.clone(), (proxies.into_inner(), deposit), weight_counter) + { + Ok(proxy) => batch.push(proxy), + Err(Error::OutOfWeight) if batch.len() > 0 => { + log::info!(target: LOG_TARGET, "Out of weight, continuing with next batch"); + break; + }, + Err(Error::OutOfWeight) if batch.len() == 0 => { + defensive!("Not enough weight to migrate a single account"); + return Err(Error::OutOfWeight); + }, + Err(e) => { + defensive!("Error while migrating account"); + log::error!(target: LOG_TARGET, "Error while migrating account: {:?}", e); + return Err(e); + }, + } + + last_key = Some(acc); // Mark as successfully migrated + } + + // TODO send xcm + if !batch.is_empty() { + Self::send_batch_xcm(batch)?; + } + log::info!(target: LOG_TARGET, "Last key: {:?}", &last_key); + + Ok(last_key) + } +} + +impl ProxyProxiesMigrator { + fn migrate_single( + acc: AccountIdOf, + (proxies, deposit): ( + Vec>>, + BalanceOf, + ), + weight_counter: &mut WeightMeter, + ) -> Result, Error> { + if weight_counter.try_consume(Weight::from_all(1_000)).is_err() { + return Err(Error::::OutOfWeight); + } + + let translated_proxies = proxies + .into_iter() + .map(|proxy| pallet_proxy::ProxyDefinition { + delegate: proxy.delegate, + proxy_type: proxy.proxy_type, + delay: proxy.delay, + }) + .collect(); + + let mapped = RcProxy { delegator: acc, deposit, proxies: translated_proxies }; + + Ok(mapped) + } + + /// Storage changes must be rolled back on error. + fn send_batch_xcm(mut proxies: Vec>) -> Result<(), Error> { + const MAX_MSG_SIZE: u32 = 50_000; // Soft message size limit. Hard limit is about 64KiB + + while !proxies.is_empty() { + let mut remaining_size: u32 = MAX_MSG_SIZE; + let mut batch = Vec::new(); + + while !proxies.is_empty() { + // Order does not matter, so we take from the back as optimization + let proxy = proxies.last().unwrap(); // FAIL-CI no unwrap + let msg_size = proxy.encoded_size() as u32; + if msg_size > remaining_size { + break; + } + remaining_size -= msg_size; + + batch.push(proxies.pop().unwrap()); // FAIL-CI no unwrap + } + + log::info!(target: LOG_TARGET, "Sending batch of {} proxies", batch.len()); + let call = types::AssetHubPalletConfig::::AhmController( + types::AhMigratorCall::::ReceiveProxyProxies { proxies: batch }, + ); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Superuser, + require_weight_at_most: Weight::from_all(1), // TODO + call: call.encode().into(), + }, + ]); + + if let Err(err) = send_xcm::( + Location::new(0, [Junction::Parachain(1000)]), + message.clone(), + ) { + log::error!(target: LOG_TARGET, "Error while sending XCM message: {:?}", err); + return Err(Error::TODO); + }; + } + + Ok(()) + } +} + +impl PalletMigration for ProxyAnnouncementMigrator { + type Key = T::AccountId; + type Error = Error; + + fn migrate_many( + last_key: Option, + weight_counter: &mut WeightMeter, + ) -> Result, Self::Error> { + if weight_counter.try_consume(Weight::from_all(1_000)).is_err() { + return Err(Error::::OutOfWeight); + } + + let mut batch = Vec::new(); + let mut iter = if let Some(last_key) = last_key { + pallet_proxy::Proxies::::iter_from(pallet_proxy::Proxies::::hashed_key_for( + &last_key, + )) + } else { + pallet_proxy::Proxies::::iter() + }; + + while let Some((acc, (_announcements, deposit))) = iter.next() { + if weight_counter.try_consume(Weight::from_all(1_000)).is_err() { + break; + } + + batch.push(RcProxyAnnouncement { depositor: acc, deposit }); + } + + if !batch.is_empty() { + Self::send_batch_xcm(batch)?; + } + + Ok(None) + } +} + +impl ProxyAnnouncementMigrator { + fn send_batch_xcm(mut announcements: Vec>) -> Result<(), Error> { + const MAX_MSG_SIZE: u32 = 50_000; // Soft message size limit. Hard limit is about 64KiB + + while !announcements.is_empty() { + let mut remaining_size: u32 = MAX_MSG_SIZE; + let mut batch = Vec::new(); + + while !announcements.is_empty() { + let announcement = announcements.last().unwrap(); // FAIL-CI no unwrap + let msg_size = announcement.encoded_size() as u32; + if msg_size > remaining_size { + break; + } + remaining_size -= msg_size; + + batch.push(announcements.pop().unwrap()); // FAIL-CI no unwrap + } + + log::info!(target: LOG_TARGET, "Sending batch of {} proxy announcements", batch.len()); + let call = types::AssetHubPalletConfig::::AhmController( + types::AhMigratorCall::::ReceiveProxyAnnouncements { announcements: batch }, + ); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + Instruction::Transact { + origin_kind: OriginKind::Superuser, + require_weight_at_most: Weight::from_all(1), // TODO + call: call.encode().into(), + }, + ]); + + if let Err(err) = send_xcm::( + Location::new(0, [Junction::Parachain(1000)]), + message.clone(), + ) { + log::error!(target: LOG_TARGET, "Error while sending XCM message: {:?}", err); + return Err(Error::TODO); + }; + } + + Ok(()) + } +} diff --git a/pallets/rc-migrator/src/types.rs b/pallets/rc-migrator/src/types.rs index 8835fc19b8..e8e88f4b2b 100644 --- a/pallets/rc-migrator/src/types.rs +++ b/pallets/rc-migrator/src/types.rs @@ -34,6 +34,10 @@ pub enum AhMigratorCall { ReceiveAccounts { accounts: Vec> }, #[codec(index = 1)] ReceiveMultisigs { multisigs: Vec> }, + #[codec(index = 2)] + ReceiveProxyProxies { proxies: Vec> }, + #[codec(index = 3)] + ReceiveProxyAnnouncements { announcements: Vec> }, } /// Copy of `ParaInfo` type from `paras_registrar` pallet. @@ -63,12 +67,15 @@ impl AhWeightInfo for () { } pub trait PalletMigration { - type Key; + type Key: codec::MaxEncodedLen; type Error; - fn first_key(weight: &mut WeightMeter) -> Result, Self::Error>; + /// Migrate until the weight is exhausted. The give key is the last one that was migrated. + /// + /// Should return the last key that was migrated. This will then be passed back into the next + /// call. fn migrate_many( - maybe_last_key: Self::Key, + last_key: Option, weight_counter: &mut WeightMeter, ) -> Result, Self::Error>; } diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs index ac65630747..8a8fb350a5 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs @@ -465,6 +465,22 @@ pub enum ProxyType { AssetManager, /// Collator selection proxy. Can execute calls related to collator selection mechanism. Collator, + + // New variants introduced by the Asset Hub Migration from the Relay Chain. + /// Allow to do governance. + /// + /// Contains pallets `Treasury`, `Bounties`, `Utility`, `ChildBounties`, `ConvictionVoting`, + /// `Referenda` and `Whitelist`. + Governance, + /// Allows access to staking related calls. + /// + /// Contains the `Staking`, `Session`, `Utility`, `FastUnstake`, `VoterList`, `NominationPools` + /// pallets. + Staking, + /// Allows access to nomination pools related calls. + /// + /// Contains the `NominationPools` and `Utility` pallets. + NominationPools, } impl Default for ProxyType { fn default() -> Self { @@ -572,6 +588,30 @@ impl InstanceFilter for ProxyType { RuntimeCall::Utility { .. } | RuntimeCall::Multisig { .. } ), + + // New variants introduced by the Asset Hub Migration from the Relay Chain. + // TODO: Uncomment once all these pallets are deployed. + ProxyType::Governance => matches!( + c, + //RuntimeCall::Treasury(..) | + //RuntimeCall::Bounties(..) | + RuntimeCall::Utility(..) /*RuntimeCall::ChildBounties(..) | + *RuntimeCall::ConvictionVoting(..) | + *RuntimeCall::Referenda(..) | + *RuntimeCall::Whitelist(..) */ + ), + ProxyType::Staking => { + matches!( + c, + //RuntimeCall::Staking(..) | + RuntimeCall::Session(..) | RuntimeCall::Utility(..) /*RuntimeCall::FastUnstake(..) | + *RuntimeCall::VoterList(..) + *RuntimeCall::NominationPools(..) */ + ) + }, + ProxyType::NominationPools => { + matches!(c, /* RuntimeCall::NominationPools(..) | */ RuntimeCall::Utility(..)) + }, } } @@ -582,7 +622,13 @@ impl InstanceFilter for ProxyType { (_, ProxyType::Any) => false, (ProxyType::Assets, ProxyType::AssetOwner) => true, (ProxyType::Assets, ProxyType::AssetManager) => true, - (ProxyType::NonTransfer, ProxyType::Collator) => true, + ( + ProxyType::NonTransfer, + ProxyType::Collator | + ProxyType::Governance | + ProxyType::Staking | + ProxyType::NominationPools, + ) => true, _ => false, } } @@ -988,6 +1034,9 @@ impl pallet_ah_migrator::Config for Runtime { type RcFreezeReason = migration::RcFreezeReason; type RcToAhHoldReason = RcToAhHoldReason; type RcToAhFreezeReason = RcToAhFreezeReason; + type RcProxyType = migration::RcProxyType; + type RcToProxyType = migration::RcToProxyType; + type RcToProxyDelay = migration::RcToProxyDelay; } // Create the runtime by composing the FRAME pallets that were previously configured. diff --git a/system-parachains/asset-hubs/asset-hub-polkadot/src/migration.rs b/system-parachains/asset-hubs/asset-hub-polkadot/src/migration.rs index d3ed0a6e4c..2d06b34e6d 100644 --- a/system-parachains/asset-hubs/asset-hub-polkadot/src/migration.rs +++ b/system-parachains/asset-hubs/asset-hub-polkadot/src/migration.rs @@ -15,7 +15,8 @@ use super::*; use frame_support::pallet_prelude::TypeInfo; -use sp_runtime::traits::Convert; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_runtime::traits::{Convert, TryConvert}; /// Relay Chain Hold Reason #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] @@ -53,3 +54,46 @@ impl Convert for RcToAhFreezeReason { } } } +/// Relay Chain Proxy Type +/// +/// Coped from https://github.com/polkadot-fellows/runtimes/blob/dde99603d7dbd6b8bf541d57eb30d9c07a4fce32/relay/polkadot/src/lib.rs#L986-L1010 +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub enum RcProxyType { + Any = 0, + NonTransfer = 1, + Governance = 2, + Staking = 3, + // Skip 4 as it is now removed (was SudoBalances) + // Skip 5 as it was IdentityJudgement + CancelProxy = 6, + Auction = 7, + NominationPools = 8, +} + +pub struct RcToProxyType; +impl TryConvert for RcToProxyType { + fn try_convert(p: RcProxyType) -> Result { + match p { + RcProxyType::Any => Ok(ProxyType::Any), + RcProxyType::NonTransfer => Ok(ProxyType::NonTransfer), + RcProxyType::Governance => Ok(ProxyType::Governance), + RcProxyType::Staking => Ok(ProxyType::Staking), + RcProxyType::CancelProxy => Ok(ProxyType::CancelProxy), + RcProxyType::Auction => Err(p), // Does not exist on AH + RcProxyType::NominationPools => Ok(ProxyType::NominationPools), + } + } +} + +/// Convert a Relay Chain Proxy Delay to a local AH one. +// NOTE we assume Relay Chain and AH to have the same block type +pub struct RcToProxyDelay; +impl TryConvert, BlockNumberFor> for RcToProxyDelay { + fn try_convert( + rc: BlockNumberFor, + ) -> Result, BlockNumberFor> { + // Polkadot Relay chain: 6 seconds per block + // Asset Hub: 12 seconds per block + Ok(rc / 2) + } +}