Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC 14: Improve locking mechanism for parachains #1290

Merged
merged 15 commits into from
Sep 6, 2023
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions polkadot/runtime/common/src/assigned_slots/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,7 @@ mod tests {
type UnsignedPriority = ParasUnsignedPriority;
type QueueFootprinter = ();
type NextSessionRotation = crate::mock::TestNextSessionRotation;
type OnNewHead = ();
}

impl parachains_shared::Config for Test {}
Expand Down
2 changes: 0 additions & 2 deletions polkadot/runtime/common/src/crowdloan/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,8 +441,6 @@ pub mod pallet {
);

NextFundIndex::<T>::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::<T>::Created { para_id: index });
Ok(())
Expand Down
1 change: 1 addition & 0 deletions polkadot/runtime/common/src/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ impl paras::Config for Test {
type UnsignedPriority = ParasUnsignedPriority;
type QueueFootprinter = ();
type NextSessionRotation = crate::mock::TestNextSessionRotation;
type OnNewHead = ();
}

parameter_types! {
Expand Down
71 changes: 71 additions & 0 deletions polkadot/runtime/common/src/paras_registrar/migration.rs
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

use super::*;
use frame_support::traits::{Contains, OnRuntimeUpgrade};

#[derive(Encode, Decode)]
pub struct ParaInfoV1<Account, Balance> {
manager: Account,
deposit: Balance,
locked: bool,
}

pub struct VersionUncheckedMigrateToV1<T, UnlockParaIds>(
sp_std::marker::PhantomData<(T, UnlockParaIds)>,
);
impl<T: Config, UnlockParaIds: Contains<ParaId>> OnRuntimeUpgrade
for VersionUncheckedMigrateToV1<T, UnlockParaIds>
{
fn on_runtime_upgrade() -> Weight {
let mut count = 0u64;
Paras::<T>::translate::<ParaInfoV1<T::AccountId, BalanceOf<T>>, _>(|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<Vec<u8>, sp_runtime::TryRuntimeError> {
Ok((Paras::<T>::iter_keys().count() as u32).encode())
}

#[cfg(feature = "try-runtime")]
fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
let old_count = u32::decode(&mut &state[..]).expect("Known good");
let new_count = Paras::<T>::iter_values().count() as u32;

ensure!(old_count == new_count, "Paras count should not change");
Ok(())
}
}

#[cfg(feature = "experimental")]
pub type VersionCheckedMigrateToV1<T, UnlockParaIds> =
frame_support::migrations::VersionedMigration<
0,
1,
VersionUncheckedMigrateToV1<T, UnlockParaIds>,
super::Pallet<T>,
<T as frame_system::Config>::DbWeight,
>;
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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},
Expand All @@ -49,7 +51,15 @@ pub struct ParaInfo<Account, Balance> {
/// 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<bool>,
}

impl<Account, Balance> ParaInfo<Account, Balance> {
/// Returns if the para is locked.
pub fn is_locked(&self) -> bool {
xlc marked this conversation as resolved.
Show resolved Hide resolved
self.locked.unwrap_or(false)
}
}

type BalanceOf<T> =
Expand Down Expand Up @@ -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<T>(_);

#[pallet::config]
Expand Down Expand Up @@ -446,12 +460,12 @@ impl<T: Config> Registrar for Pallet<T> {

// Apply a lock to the parachain.
fn apply_lock(id: ParaId) {
Paras::<T>::mutate(id, |x| x.as_mut().map(|info| info.locked = true));
Paras::<T>::mutate(id, |x| x.as_mut().map(|info| info.locked = Some(true)));
}

// Remove a lock from the parachain.
fn remove_lock(id: ParaId) {
Paras::<T>::mutate(id, |x| x.as_mut().map(|info| info.locked = false));
Paras::<T>::mutate(id, |x| x.as_mut().map(|info| info.locked = Some(false)));
}

// Register a Para ID under control of `manager`.
Expand Down Expand Up @@ -481,9 +495,7 @@ impl<T: Config> Registrar for Pallet<T> {
);
runtime_parachains::schedule_parathread_upgrade::<T>(id)
.map_err(|_| Error::<T>::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(())
}

Expand Down Expand Up @@ -533,7 +545,7 @@ impl<T: Config> Pallet<T> {
.map_err(|e| e.into())
.and_then(|who| -> DispatchResult {
let para_info = Paras::<T>::get(id).ok_or(Error::<T>::NotRegistered)?;
ensure!(!para_info.locked, Error::<T>::ParaLocked);
ensure!(!para_info.is_locked(), Error::<T>::ParaLocked);
ensure!(para_info.manager == who, Error::<T>::NotOwner);
Ok(())
})
Expand Down Expand Up @@ -566,7 +578,7 @@ impl<T: Config> Pallet<T> {

let deposit = deposit_override.unwrap_or_else(T::ParaDeposit::get);
<T as Config>::Currency::reserve(&who, deposit)?;
let info = ParaInfo { manager: who.clone(), deposit, locked: false };
let info = ParaInfo { manager: who.clone(), deposit, locked: None };

Paras::<T>::insert(id, info);
Self::deposit_event(Event::<T>::Reserved { para_id: id, who });
Expand All @@ -585,7 +597,7 @@ impl<T: Config> Pallet<T> {
) -> DispatchResult {
let deposited = if let Some(para_data) = Paras::<T>::get(id) {
ensure!(para_data.manager == who, Error::<T>::NotOwner);
ensure!(!para_data.locked, Error::<T>::ParaLocked);
ensure!(!para_data.is_locked(), Error::<T>::ParaLocked);
para_data.deposit
} else {
ensure!(!ensure_reserved, Error::<T>::NotReserved);
Expand All @@ -601,7 +613,7 @@ impl<T: Config> Pallet<T> {
} else if let Some(rebate) = deposited.checked_sub(&deposit) {
<T as Config>::Currency::unreserve(&who, rebate);
};
let info = ParaInfo { manager: who.clone(), deposit, locked: false };
let info = ParaInfo { manager: who.clone(), deposit, locked: None };

Paras::<T>::insert(id, info);
// We check above that para has no lifecycle, so this should not fail.
Expand Down Expand Up @@ -665,6 +677,21 @@ impl<T: Config> Pallet<T> {
}
}

impl<T: Config> OnNewHead for Pallet<T> {
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::<T>::get(id) {
if info.locked.is_none() {
info.locked = Some(true);
Paras::<T>::insert(id, info);
writes += 1;
}
}
T::DbWeight::get().reads_writes(1, writes)
eskimor marked this conversation as resolved.
Show resolved Hide resolved
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -784,6 +811,7 @@ mod tests {
type UnsignedPriority = ParasUnsignedPriority;
type QueueFootprinter = ();
type NextSessionRotation = crate::mock::TestNextSessionRotation;
type OnNewHead = ();
}

impl configuration::Config for Test {
Expand Down Expand Up @@ -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),
Expand All @@ -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));
});
}

Expand Down
2 changes: 1 addition & 1 deletion polkadot/runtime/kusama/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
16 changes: 16 additions & 0 deletions polkadot/runtime/kusama/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,7 @@ impl parachains_paras::Config for Runtime {
type UnsignedPriority = ParasUnsignedPriority;
type QueueFootprinter = ParaInclusion;
type NextSessionRotation = Babe;
type OnNewHead = Registrar;
}

parameter_types! {
Expand Down Expand Up @@ -1709,6 +1710,19 @@ pub mod migrations {
}
}

pub struct ParachainsToUnlock;
impl Contains<ParaId> 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,
Expand Down Expand Up @@ -1740,6 +1754,8 @@ pub mod migrations {
UpgradeSessionKeys,

parachains_configuration::migration::v9::MigrateToV9<Runtime>,
// Migrate parachain info format
paras_registrar::migration::VersionCheckedMigrateToV1<Runtime, ParachainsToUnlock>,
);
}

Expand Down
1 change: 1 addition & 0 deletions polkadot/runtime/parachains/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
1 change: 1 addition & 0 deletions polkadot/runtime/parachains/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand Down
27 changes: 24 additions & 3 deletions polkadot/runtime/parachains/src/paras/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,22 @@ impl<BlockNumber> PvfCheckActiveVoteState<BlockNumber> {
}
}

/// 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;
Expand Down Expand Up @@ -575,6 +591,9 @@ pub mod pallet {
/// be set to the `ParaInclusion` pallet.
type QueueFootprinter: QueueFootprinter<Origin = UmpQueueId>;

/// Runtime hook for when a parachain head is updated.
type OnNewHead: OnNewHead;

/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}
Expand Down Expand Up @@ -1962,10 +1981,10 @@ impl<T: Config> Pallet<T> {
new_head: HeadData,
execution_context: BlockNumberFor<T>,
) -> Weight {
Heads::<T>::insert(&id, new_head);
Heads::<T>::insert(&id, &new_head);
MostRecentContext::<T>::insert(&id, execution_context);

if let Some(expected_at) = FutureCodeUpgrades::<T>::get(&id) {
let weight = if let Some(expected_at) = FutureCodeUpgrades::<T>::get(&id) {
if expected_at <= execution_context {
FutureCodeUpgrades::<T>::remove(&id);
UpgradeGoAheadSignal::<T>::remove(&id);
Expand Down Expand Up @@ -2005,7 +2024,9 @@ impl<T: Config> Pallet<T> {
// the `Abort` signal.
UpgradeGoAheadSignal::<T>::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
Expand Down
2 changes: 1 addition & 1 deletion polkadot/runtime/polkadot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand Down
Loading