diff --git a/Cargo.lock b/Cargo.lock index c99790ba28be..dd96a1486c96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12814,6 +12814,7 @@ dependencies = [ "frame-system", "futures", "hex-literal 0.4.1", + "impl-trait-for-tuples", "log", "pallet-authority-discovery", "pallet-authorship", diff --git a/polkadot/runtime/common/src/assigned_slots/mod.rs b/polkadot/runtime/common/src/assigned_slots/mod.rs index 3683cfc210fa..cc8ec339c118 100644 --- a/polkadot/runtime/common/src/assigned_slots/mod.rs +++ b/polkadot/runtime/common/src/assigned_slots/mod.rs @@ -739,6 +739,7 @@ mod tests { type UnsignedPriority = ParasUnsignedPriority; type QueueFootprinter = (); type NextSessionRotation = crate::mock::TestNextSessionRotation; + type OnNewHead = (); } impl parachains_shared::Config for Test {} diff --git a/polkadot/runtime/common/src/crowdloan/mod.rs b/polkadot/runtime/common/src/crowdloan/mod.rs index 0303808e0747..5a2939145925 100644 --- a/polkadot/runtime/common/src/crowdloan/mod.rs +++ b/polkadot/runtime/common/src/crowdloan/mod.rs @@ -441,8 +441,6 @@ pub mod pallet { ); NextFundIndex::::put(new_fund_index); - // Add a lock to the para so that the configuration cannot be changed. - T::Registrar::apply_lock(index); Self::deposit_event(Event::::Created { para_id: index }); Ok(()) diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index f78347dedd8c..f14db68267d5 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -204,6 +204,7 @@ impl paras::Config for Test { type UnsignedPriority = ParasUnsignedPriority; type QueueFootprinter = (); type NextSessionRotation = crate::mock::TestNextSessionRotation; + type OnNewHead = (); } parameter_types! { diff --git a/polkadot/runtime/common/src/paras_registrar/migration.rs b/polkadot/runtime/common/src/paras_registrar/migration.rs new file mode 100644 index 000000000000..7d3dda54e0ed --- /dev/null +++ b/polkadot/runtime/common/src/paras_registrar/migration.rs @@ -0,0 +1,71 @@ +// 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 super::*; +use frame_support::traits::{Contains, OnRuntimeUpgrade}; + +#[derive(Encode, Decode)] +pub struct ParaInfoV1 { + manager: Account, + deposit: Balance, + locked: bool, +} + +pub struct VersionUncheckedMigrateToV1( + sp_std::marker::PhantomData<(T, UnlockParaIds)>, +); +impl> OnRuntimeUpgrade + for VersionUncheckedMigrateToV1 +{ + fn on_runtime_upgrade() -> Weight { + let mut count = 0u64; + Paras::::translate::>, _>(|key, v1| { + count.saturating_inc(); + Some(ParaInfo { + manager: v1.manager, + deposit: v1.deposit, + locked: if UnlockParaIds::contains(&key) { None } else { Some(v1.locked) }, + }) + }); + + log::info!(target: "runtime::registrar", "Upgraded {} storages to version 1", count); + T::DbWeight::get().reads_writes(count, count) + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + Ok((Paras::::iter_keys().count() as u32).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + let old_count = u32::decode(&mut &state[..]).expect("Known good"); + let new_count = Paras::::iter_values().count() as u32; + + ensure!(old_count == new_count, "Paras count should not change"); + Ok(()) + } +} + +#[cfg(feature = "experimental")] +pub type VersionCheckedMigrateToV1 = + frame_support::migrations::VersionedMigration< + 0, + 1, + VersionUncheckedMigrateToV1, + super::Pallet, + ::DbWeight, + >; diff --git a/polkadot/runtime/common/src/paras_registrar.rs b/polkadot/runtime/common/src/paras_registrar/mod.rs similarity index 97% rename from polkadot/runtime/common/src/paras_registrar.rs rename to polkadot/runtime/common/src/paras_registrar/mod.rs index 3f5a8e1a5f93..f2751803a413 100644 --- a/polkadot/runtime/common/src/paras_registrar.rs +++ b/polkadot/runtime/common/src/paras_registrar/mod.rs @@ -17,6 +17,8 @@ //! Pallet to handle parachain registration and related fund management. //! In essence this is a simple wrapper around `paras`. +pub mod migration; + use frame_support::{ dispatch::DispatchResult, ensure, @@ -35,7 +37,7 @@ use sp_std::{prelude::*, result}; use crate::traits::{OnSwap, Registrar}; pub use pallet::*; use parity_scale_codec::{Decode, Encode}; -use runtime_parachains::paras::ParaKind; +use runtime_parachains::paras::{OnNewHead, ParaKind}; use scale_info::TypeInfo; use sp_runtime::{ traits::{CheckedSub, Saturating}, @@ -49,7 +51,15 @@ pub struct ParaInfo { /// The amount reserved by the `manager` account for the registration. deposit: Balance, /// Whether the para registration should be locked from being controlled by the manager. - locked: bool, + /// None means the lock had not been explicitly set, and should be treated as false. + locked: Option, +} + +impl ParaInfo { + /// Returns if the para is locked. + pub fn is_locked(&self) -> bool { + self.locked.unwrap_or(false) + } } type BalanceOf = @@ -96,8 +106,12 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] #[pallet::without_storage_info] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] @@ -446,12 +460,12 @@ impl Registrar for Pallet { // Apply a lock to the parachain. fn apply_lock(id: ParaId) { - Paras::::mutate(id, |x| x.as_mut().map(|info| info.locked = true)); + Paras::::mutate(id, |x| x.as_mut().map(|info| info.locked = Some(true))); } // Remove a lock from the parachain. fn remove_lock(id: ParaId) { - Paras::::mutate(id, |x| x.as_mut().map(|info| info.locked = false)); + Paras::::mutate(id, |x| x.as_mut().map(|info| info.locked = Some(false))); } // Register a Para ID under control of `manager`. @@ -481,9 +495,7 @@ impl Registrar for Pallet { ); runtime_parachains::schedule_parathread_upgrade::(id) .map_err(|_| Error::::CannotUpgrade)?; - // Once a para has upgraded to a parachain, it can no longer be managed by the owner. - // Intentionally, the flag stays with the para even after downgrade. - Self::apply_lock(id); + Ok(()) } @@ -533,7 +545,7 @@ impl Pallet { .map_err(|e| e.into()) .and_then(|who| -> DispatchResult { let para_info = Paras::::get(id).ok_or(Error::::NotRegistered)?; - ensure!(!para_info.locked, Error::::ParaLocked); + ensure!(!para_info.is_locked(), Error::::ParaLocked); ensure!(para_info.manager == who, Error::::NotOwner); Ok(()) }) @@ -566,7 +578,7 @@ impl Pallet { let deposit = deposit_override.unwrap_or_else(T::ParaDeposit::get); ::Currency::reserve(&who, deposit)?; - let info = ParaInfo { manager: who.clone(), deposit, locked: false }; + let info = ParaInfo { manager: who.clone(), deposit, locked: None }; Paras::::insert(id, info); Self::deposit_event(Event::::Reserved { para_id: id, who }); @@ -585,7 +597,7 @@ impl Pallet { ) -> DispatchResult { let deposited = if let Some(para_data) = Paras::::get(id) { ensure!(para_data.manager == who, Error::::NotOwner); - ensure!(!para_data.locked, Error::::ParaLocked); + ensure!(!para_data.is_locked(), Error::::ParaLocked); para_data.deposit } else { ensure!(!ensure_reserved, Error::::NotReserved); @@ -601,7 +613,7 @@ impl Pallet { } else if let Some(rebate) = deposited.checked_sub(&deposit) { ::Currency::unreserve(&who, rebate); }; - let info = ParaInfo { manager: who.clone(), deposit, locked: false }; + let info = ParaInfo { manager: who.clone(), deposit, locked: None }; Paras::::insert(id, info); // We check above that para has no lifecycle, so this should not fail. @@ -665,6 +677,21 @@ impl Pallet { } } +impl OnNewHead for Pallet { + fn on_new_head(id: ParaId, _head: &HeadData) -> Weight { + // mark the parachain locked if the locked value is not already set + let mut writes = 0; + if let Some(mut info) = Paras::::get(id) { + if info.locked.is_none() { + info.locked = Some(true); + Paras::::insert(id, info); + writes += 1; + } + } + T::DbWeight::get().reads_writes(1, writes) + } +} + #[cfg(test)] mod tests { use super::*; @@ -784,6 +811,7 @@ mod tests { type UnsignedPriority = ParasUnsignedPriority; type QueueFootprinter = (); type NextSessionRotation = crate::mock::TestNextSessionRotation; + type OnNewHead = (); } impl configuration::Config for Test { @@ -1270,8 +1298,10 @@ mod tests { )); assert_noop!(Registrar::add_lock(RuntimeOrigin::signed(2), para_id), BadOrigin); - // Once they begin onboarding, we lock them in. - assert_ok!(Registrar::add_lock(RuntimeOrigin::signed(1), para_id)); + + // Once they produces new block, we lock them in. + Registrar::on_new_head(para_id, &Default::default()); + // Owner cannot pass origin check when checking lock assert_noop!( Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id), @@ -1283,6 +1313,11 @@ mod tests { assert_ok!(Registrar::remove_lock(para_origin(para_id), para_id)); // Owner can pass origin check again assert_ok!(Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id)); + + // Won't lock again after it is unlocked + Registrar::on_new_head(para_id, &Default::default()); + + assert_ok!(Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id)); }); } diff --git a/polkadot/runtime/kusama/Cargo.toml b/polkadot/runtime/kusama/Cargo.toml index 8b0f59516c6d..6b0370633f0b 100644 --- a/polkadot/runtime/kusama/Cargo.toml +++ b/polkadot/runtime/kusama/Cargo.toml @@ -103,7 +103,7 @@ frame-system-benchmarking = { path = "../../../substrate/frame/system/benchmarki pallet-election-provider-support-benchmarking = { path = "../../../substrate/frame/election-provider-support/benchmarking", default-features = false, optional = true } hex-literal = "0.4.1" -runtime-common = { package = "polkadot-runtime-common", path = "../common", default-features = false } +runtime-common = { package = "polkadot-runtime-common", path = "../common", default-features = false, features = ["experimental"] } runtime-parachains = { package = "polkadot-runtime-parachains", path = "../parachains", default-features = false } primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false } diff --git a/polkadot/runtime/kusama/src/lib.rs b/polkadot/runtime/kusama/src/lib.rs index 4b5f03b38c6c..8fa1c91e440b 100644 --- a/polkadot/runtime/kusama/src/lib.rs +++ b/polkadot/runtime/kusama/src/lib.rs @@ -1214,6 +1214,7 @@ impl parachains_paras::Config for Runtime { type UnsignedPriority = ParasUnsignedPriority; type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; + type OnNewHead = Registrar; } parameter_types! { @@ -1709,6 +1710,19 @@ pub mod migrations { } } + pub struct ParachainsToUnlock; + impl Contains for ParachainsToUnlock { + fn contains(id: &ParaId) -> bool { + let id: u32 = (*id).into(); + // ksuama parachains/parathreads that are locked and never produced block + match id { + 2003 | 2008 | 2018 | 2077 | 2089 | 2111 | 2112 | 2120 | 2126 | 2127 | 2130 | + 2226 | 2227 | 2231 | 2233 | 2237 | 2256 | 2257 | 2261 | 2268 | 2275 => true, + _ => false, + } + } + } + /// Unreleased migrations. Add new ones here: pub type Unreleased = ( init_state_migration::InitMigrate, @@ -1740,6 +1754,8 @@ pub mod migrations { UpgradeSessionKeys, parachains_configuration::migration::v9::MigrateToV9, + // Migrate parachain info format + paras_registrar::migration::VersionCheckedMigrateToV1, ); } diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index 9a8bd5017e07..0e2f6aa1aa24 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true license.workspace = true [dependencies] +impl-trait-for-tuples = "0.2.2" bitvec = { version = "1.0.0", default-features = false, features = ["alloc"] } parity-scale-codec = { version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } log = { version = "0.4.17", default-features = false } diff --git a/polkadot/runtime/parachains/src/mock.rs b/polkadot/runtime/parachains/src/mock.rs index f978b6c3360e..ded7de08e4fa 100644 --- a/polkadot/runtime/parachains/src/mock.rs +++ b/polkadot/runtime/parachains/src/mock.rs @@ -214,6 +214,7 @@ impl crate::paras::Config for Test { type UnsignedPriority = ParasUnsignedPriority; type QueueFootprinter = ParaInclusion; type NextSessionRotation = TestNextSessionRotation; + type OnNewHead = (); } impl crate::dmp::Config for Test {} diff --git a/polkadot/runtime/parachains/src/paras/mod.rs b/polkadot/runtime/parachains/src/paras/mod.rs index 95b89a1ca2c3..2f370b5bfe47 100644 --- a/polkadot/runtime/parachains/src/paras/mod.rs +++ b/polkadot/runtime/parachains/src/paras/mod.rs @@ -481,6 +481,22 @@ impl PvfCheckActiveVoteState { } } +/// Runtime hook for when a parachain head is updated. +pub trait OnNewHead { + /// Called when a parachain head is updated. + /// Returns the weight consumed by this function. + fn on_new_head(id: ParaId, head: &HeadData) -> Weight; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl OnNewHead for Tuple { + fn on_new_head(id: ParaId, head: &HeadData) -> Weight { + let mut weight: Weight = Default::default(); + for_tuples!( #( weight.saturating_accrue(Tuple::on_new_head(id, head)); )* ); + weight + } +} + pub trait WeightInfo { fn force_set_current_code(c: u32) -> Weight; fn force_set_current_head(s: u32) -> Weight; @@ -575,6 +591,9 @@ pub mod pallet { /// be set to the `ParaInclusion` pallet. type QueueFootprinter: QueueFootprinter; + /// Runtime hook for when a parachain head is updated. + type OnNewHead: OnNewHead; + /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } @@ -1962,10 +1981,10 @@ impl Pallet { new_head: HeadData, execution_context: BlockNumberFor, ) -> Weight { - Heads::::insert(&id, new_head); + Heads::::insert(&id, &new_head); MostRecentContext::::insert(&id, execution_context); - if let Some(expected_at) = FutureCodeUpgrades::::get(&id) { + let weight = if let Some(expected_at) = FutureCodeUpgrades::::get(&id) { if expected_at <= execution_context { FutureCodeUpgrades::::remove(&id); UpgradeGoAheadSignal::::remove(&id); @@ -2005,7 +2024,9 @@ impl Pallet { // the `Abort` signal. UpgradeGoAheadSignal::::remove(&id); T::DbWeight::get().reads_writes(1, 2) - } + }; + + weight.saturating_add(T::OnNewHead::on_new_head(id, &new_head)) } /// Returns the list of PVFs (aka validation code) that require casting a vote by a validator in diff --git a/polkadot/runtime/polkadot/Cargo.toml b/polkadot/runtime/polkadot/Cargo.toml index d185677ab8d2..0b9498347ca4 100644 --- a/polkadot/runtime/polkadot/Cargo.toml +++ b/polkadot/runtime/polkadot/Cargo.toml @@ -94,7 +94,7 @@ pallet-session-benchmarking = { path = "../../../substrate/frame/session/benchma pallet-nomination-pools-benchmarking = { path = "../../../substrate/frame/nomination-pools/benchmarking", default-features = false, optional = true } hex-literal = { version = "0.4.1", optional = true } -runtime-common = { package = "polkadot-runtime-common", path = "../common", default-features = false } +runtime-common = { package = "polkadot-runtime-common", path = "../common", default-features = false, features = ["experimental"] } runtime-parachains = { package = "polkadot-runtime-parachains", path = "../parachains", default-features = false } primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false } diff --git a/polkadot/runtime/polkadot/src/lib.rs b/polkadot/runtime/polkadot/src/lib.rs index 2efd329eea0c..b756cd0f1ff8 100644 --- a/polkadot/runtime/polkadot/src/lib.rs +++ b/polkadot/runtime/polkadot/src/lib.rs @@ -47,8 +47,8 @@ use frame_election_provider_support::{ use frame_support::{ construct_runtime, parameter_types, traits::{ - ConstU32, EitherOf, EitherOfDiverse, InstanceFilter, KeyOwnerProofSystem, PrivilegeCmp, - ProcessMessage, ProcessMessageError, WithdrawReasons, + ConstU32, Contains, EitherOf, EitherOfDiverse, InstanceFilter, KeyOwnerProofSystem, + PrivilegeCmp, ProcessMessage, ProcessMessageError, WithdrawReasons, }, weights::{ConstantMultiplier, WeightMeter}, PalletId, @@ -1090,6 +1090,7 @@ impl parachains_paras::Config for Runtime { type UnsignedPriority = ParasUnsignedPriority; type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; + type OnNewHead = Registrar; } parameter_types! { @@ -1502,6 +1503,19 @@ pub mod migrations { type PalletName = TipsPalletName; } + pub struct ParachainsToUnlock; + impl Contains for ParachainsToUnlock { + fn contains(id: &ParaId) -> bool { + let id: u32 = (*id).into(); + // polkadot parachains/parathreads that are locked and never produced block + match id { + 2003 | 2015 | 2017 | 2018 | 2025 | 2028 | 2036 | 2038 | 2053 | 2055 | 2090 | + 2097 | 2106 | 3336 | 3338 | 3342 => true, + _ => false, + } + } + } + /// Unreleased migrations. Add new ones here: pub type Unreleased = ( pallet_im_online::migration::v1::Migration, @@ -1524,6 +1538,8 @@ pub mod migrations { frame_support::migrations::RemovePallet::DbWeight>, parachains_configuration::migration::v9::MigrateToV9, + // Migrate parachain info format + paras_registrar::migration::VersionCheckedMigrateToV1, ); } diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 6af9407a5879..a181250cfa37 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -84,7 +84,7 @@ frame-try-runtime = { path = "../../../substrate/frame/try-runtime", default-fea frame-system-benchmarking = { path = "../../../substrate/frame/system/benchmarking", default-features = false, optional = true } hex-literal = { version = "0.4.1" } -runtime-common = { package = "polkadot-runtime-common", path = "../common", default-features = false, features=["experimental"] } +runtime-common = { package = "polkadot-runtime-common", path = "../common", default-features = false, features = ["experimental"] } runtime-parachains = { package = "polkadot-runtime-parachains", path = "../parachains", default-features = false } primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false } polkadot-parachain-primitives = { path = "../../parachain", default-features = false } diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index a80f45c340d9..e043852901f1 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -1031,6 +1031,7 @@ impl parachains_paras::Config for Runtime { type UnsignedPriority = ParasUnsignedPriority; type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; + type OnNewHead = Registrar; } parameter_types! { @@ -1550,6 +1551,7 @@ pub mod migrations { parachains_scheduler::migration::v1::MigrateToV1, parachains_configuration::migration::v8::MigrateToV8, parachains_configuration::migration::v9::MigrateToV9, + paras_registrar::migration::VersionCheckedMigrateToV1, ); } diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index b2397299430d..94852ad39f5a 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -540,6 +540,7 @@ impl parachains_paras::Config for Runtime { type UnsignedPriority = ParasUnsignedPriority; type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; + type OnNewHead = (); } impl parachains_dmp::Config for Runtime {} diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 5a47288297be..de561c8ac68b 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -95,7 +95,7 @@ pallet-offences-benchmarking = { path = "../../../substrate/frame/offences/bench pallet-session-benchmarking = { path = "../../../substrate/frame/session/benchmarking", default-features = false, optional = true } hex-literal = { version = "0.4.1", optional = true } -runtime-common = { package = "polkadot-runtime-common", path = "../common", default-features = false, features=["experimental"] } +runtime-common = { package = "polkadot-runtime-common", path = "../common", default-features = false, features = ["experimental"] } primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false } polkadot-parachain-primitives = { path = "../../parachain", default-features = false } runtime-parachains = { package = "polkadot-runtime-parachains", path = "../parachains", default-features = false } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 73aa4980151e..7dfc781d2467 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1044,6 +1044,7 @@ impl parachains_paras::Config for Runtime { type UnsignedPriority = ParasUnsignedPriority; type QueueFootprinter = ParaInclusion; type NextSessionRotation = Babe; + type OnNewHead = (); } parameter_types! { @@ -1425,6 +1426,7 @@ pub mod migrations { parachains_configuration::migration::v8::MigrateToV8, UpgradeSessionKeys, parachains_configuration::migration::v9::MigrateToV9, + paras_registrar::migration::VersionCheckedMigrateToV1, ); }