Unverified Commit 7ace3b77 authored by Shawn Tabrizi's avatar Shawn Tabrizi Committed by GitHub
Browse files

Relax Origin Checks in Registrar, Add Lock to Registration (#2808)

* Relax Origin Checks in Registrar

* fix tests

* Update runtime/common/src/paras_registrar.rs

* introduce para locks

* apply a lock after upgrade

* add test

* add lock when creating crowdloan
parent 170481b6
Pipeline #133139 canceled with stages
in 1 minute and 41 seconds
...@@ -294,6 +294,9 @@ decl_module! { ...@@ -294,6 +294,9 @@ decl_module! {
fn deposit_event() = default; fn deposit_event() = default;
/// Create a new crowdloaning campaign for a parachain slot with the given lease period range. /// Create a new crowdloaning campaign for a parachain slot with the given lease period range.
///
/// This applies a lock to your parachain configuration, ensuring that it cannot be changed
/// by the parachain manager.
#[weight = T::WeightInfo::create()] #[weight = T::WeightInfo::create()]
pub fn create(origin, pub fn create(origin,
#[compact] index: ParaId, #[compact] index: ParaId,
...@@ -342,6 +345,8 @@ decl_module! { ...@@ -342,6 +345,8 @@ decl_module! {
}); });
NextTrieIndex::put(new_trie_index); NextTrieIndex::put(new_trie_index);
// Add a lock to the para so that the configuration cannot be changed.
T::Registrar::apply_lock(index);
Self::deposit_event(RawEvent::Created(index)); Self::deposit_event(RawEvent::Created(index));
} }
......
...@@ -728,8 +728,8 @@ fn basic_swap_works() { ...@@ -728,8 +728,8 @@ fn basic_swap_works() {
assert_eq!(Paras::lifecycle(ParaId::from(2)), Some(ParaLifecycle::Parathread)); assert_eq!(Paras::lifecycle(ParaId::from(2)), Some(ParaLifecycle::Parathread));
// Initiate a swap // Initiate a swap
assert_ok!(Registrar::swap(para_origin(1).into(), ParaId::from(2))); assert_ok!(Registrar::swap(para_origin(1).into(), ParaId::from(1), ParaId::from(2)));
assert_ok!(Registrar::swap(para_origin(2).into(), ParaId::from(1))); assert_ok!(Registrar::swap(para_origin(2).into(), ParaId::from(2), ParaId::from(1)));
assert_eq!(Paras::lifecycle(ParaId::from(1)), Some(ParaLifecycle::DowngradingParachain)); assert_eq!(Paras::lifecycle(ParaId::from(1)), Some(ParaLifecycle::DowngradingParachain));
assert_eq!(Paras::lifecycle(ParaId::from(2)), Some(ParaLifecycle::UpgradingParathread)); assert_eq!(Paras::lifecycle(ParaId::from(2)), Some(ParaLifecycle::UpgradingParathread));
......
...@@ -27,6 +27,7 @@ thread_local! { ...@@ -27,6 +27,7 @@ thread_local! {
static OPERATIONS: RefCell<Vec<(ParaId, u32, bool)>> = RefCell::new(Vec::new()); static OPERATIONS: RefCell<Vec<(ParaId, u32, bool)>> = RefCell::new(Vec::new());
static PARACHAINS: RefCell<Vec<ParaId>> = RefCell::new(Vec::new()); static PARACHAINS: RefCell<Vec<ParaId>> = RefCell::new(Vec::new());
static PARATHREADS: RefCell<Vec<ParaId>> = RefCell::new(Vec::new()); static PARATHREADS: RefCell<Vec<ParaId>> = RefCell::new(Vec::new());
static LOCKS: RefCell<HashMap<ParaId, bool>> = RefCell::new(HashMap::new());
static MANAGERS: RefCell<HashMap<ParaId, Vec<u8>>> = RefCell::new(HashMap::new()); static MANAGERS: RefCell<HashMap<ParaId, Vec<u8>>> = RefCell::new(HashMap::new());
} }
...@@ -47,6 +48,14 @@ impl<T: frame_system::Config> Registrar for TestRegistrar<T> { ...@@ -47,6 +48,14 @@ impl<T: frame_system::Config> Registrar for TestRegistrar<T> {
PARATHREADS.with(|x| x.borrow().binary_search(&id).is_ok()) PARATHREADS.with(|x| x.borrow().binary_search(&id).is_ok())
} }
fn apply_lock(id: ParaId) {
LOCKS.with(|x| x.borrow_mut().insert(id, true));
}
fn remove_lock(id: ParaId) {
LOCKS.with(|x| x.borrow_mut().insert(id, false));
}
fn register( fn register(
manager: Self::AccountId, manager: Self::AccountId,
id: ParaId, id: ParaId,
......
...@@ -43,8 +43,12 @@ use sp_runtime::{RuntimeDebug, traits::Saturating}; ...@@ -43,8 +43,12 @@ use sp_runtime::{RuntimeDebug, traits::Saturating};
#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug)] #[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug)]
pub struct ParaInfo<Account, Balance> { pub struct ParaInfo<Account, Balance> {
/// The account that has placed a deposit for registering this para.
pub(crate) manager: Account, pub(crate) manager: Account,
/// The amount reserved by the `manager` account for the registration.
deposit: Balance, deposit: Balance,
/// Whether the para registration should be locked from being controlled by the manager.
locked: bool,
} }
type BalanceOf<T> = type BalanceOf<T> =
...@@ -142,6 +146,8 @@ decl_error! { ...@@ -142,6 +146,8 @@ decl_error! {
CannotDowngrade, CannotDowngrade,
/// Cannot schedule upgrade of parathread to parachain /// Cannot schedule upgrade of parathread to parachain
CannotUpgrade, CannotUpgrade,
/// Para is locked from manipulation by the manager. Must use parachain or relay chain governance.
ParaLocked,
} }
} }
...@@ -179,21 +185,17 @@ decl_module! { ...@@ -179,21 +185,17 @@ decl_module! {
/// Deregister a Para Id, freeing all data and returning any deposit. /// Deregister a Para Id, freeing all data and returning any deposit.
/// ///
/// The caller must be the para itself or Root and the para must be a parathread. /// The caller must be Root, the `para` owner, or the `para` itself. The para must be a parathread.
#[weight = T::WeightInfo::deregister()] #[weight = T::WeightInfo::deregister()]
pub fn deregister(origin, id: ParaId) -> DispatchResult { pub fn deregister(origin, id: ParaId) -> DispatchResult {
match ensure_root(origin.clone()) { Self::ensure_root_para_or_owner(origin, id)?;
Ok(_) => {},
Err(_) => {
let caller_id = ensure_parachain(<T as Config>::Origin::from(origin))?;
ensure!(caller_id == id, Error::<T>::NotOwner);
},
};
Self::do_deregister(id) Self::do_deregister(id)
} }
/// Swap a parachain with another parachain or parathread. The origin must be a `Parachain`. /// Swap a parachain with another parachain or parathread.
///
/// The origin must be Root, the `para` owner, or the `para` itself.
///
/// The swap will happen only if there is already an opposite swap pending. If there is not, /// The swap will happen only if there is already an opposite swap pending. If there is not,
/// the swap will be stored in the pending swaps map, ready for a later confirmatory swap. /// the swap will be stored in the pending swaps map, ready for a later confirmatory swap.
/// ///
...@@ -202,8 +204,9 @@ decl_module! { ...@@ -202,8 +204,9 @@ decl_module! {
/// scheduling info (i.e. whether they're a parathread or parachain), auction information /// scheduling info (i.e. whether they're a parathread or parachain), auction information
/// and the auction deposit are switched. /// and the auction deposit are switched.
#[weight = T::WeightInfo::swap()] #[weight = T::WeightInfo::swap()]
pub fn swap(origin, other: ParaId) { pub fn swap(origin, id: ParaId, other: ParaId) {
let id = ensure_parachain(<T as Config>::Origin::from(origin))?; Self::ensure_root_para_or_owner(origin, id)?;
if PendingSwap::get(other) == Some(id) { if PendingSwap::get(other) == Some(id) {
if let Some(other_lifecycle) = paras::Module::<T>::lifecycle(other) { if let Some(other_lifecycle) = paras::Module::<T>::lifecycle(other) {
if let Some(id_lifecycle) = paras::Module::<T>::lifecycle(id) { if let Some(id_lifecycle) = paras::Module::<T>::lifecycle(id) {
...@@ -259,6 +262,16 @@ impl<T: Config> Registrar for Module<T> { ...@@ -259,6 +262,16 @@ impl<T: Config> Registrar for Module<T> {
paras::Module::<T>::is_parachain(id) paras::Module::<T>::is_parachain(id)
} }
// Apply a lock to the parachain.
fn apply_lock(id: ParaId) {
Paras::<T>::mutate(id, |x| x.as_mut().map(|mut info| info.locked = true));
}
// Apply a lock to the parachain.
fn remove_lock(id: ParaId) {
Paras::<T>::mutate(id, |x| x.as_mut().map(|mut info| info.locked = false));
}
// Register a Para ID under control of `manager`. // Register a Para ID under control of `manager`.
fn register( fn register(
manager: T::AccountId, manager: T::AccountId,
...@@ -279,6 +292,9 @@ impl<T: Config> Registrar for Module<T> { ...@@ -279,6 +292,9 @@ impl<T: Config> Registrar for Module<T> {
// Para backend should think this is a parathread... // Para backend should think this is a parathread...
ensure!(paras::Module::<T>::lifecycle(id) == Some(ParaLifecycle::Parathread), Error::<T>::NotParathread); ensure!(paras::Module::<T>::lifecycle(id) == Some(ParaLifecycle::Parathread), Error::<T>::NotParathread);
runtime_parachains::schedule_parathread_upgrade::<T>(id).map_err(|_| Error::<T>::CannotUpgrade)?; 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(()) Ok(())
} }
...@@ -316,6 +332,27 @@ impl<T: Config> Registrar for Module<T> { ...@@ -316,6 +332,27 @@ impl<T: Config> Registrar for Module<T> {
} }
impl<T: Config> Module<T> { impl<T: Config> Module<T> {
/// Ensure the origin is one of Root, the `para` owner, or the `para` itself.
/// If the origin is the `para` owner, the `para` must be unlocked.
fn ensure_root_para_or_owner(origin: <T as frame_system::Config>::Origin, id: ParaId) -> DispatchResult {
ensure_signed(origin.clone()).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.manager == who, Error::<T>::NotOwner);
Ok(())
})
.or_else(|_| -> DispatchResult {
// Else check if para origin...
let caller_id = ensure_parachain(<T as Config>::Origin::from(origin.clone()))?;
ensure!(caller_id == id, Error::<T>::NotOwner);
Ok(())
}).or_else(|_| -> DispatchResult {
// Check if root...
ensure_root(origin.clone()).map_err(|e| e.into())
})
}
/// Attempt to register a new Para Id under management of `who` in the /// Attempt to register a new Para Id under management of `who` in the
/// system with the given information. /// system with the given information.
fn do_register( fn do_register(
...@@ -336,6 +373,7 @@ impl<T: Config> Module<T> { ...@@ -336,6 +373,7 @@ impl<T: Config> Module<T> {
let info = ParaInfo { let info = ParaInfo {
manager: who.clone(), manager: who.clone(),
deposit: deposit, deposit: deposit,
locked: false,
}; };
Paras::<T>::insert(id, info); Paras::<T>::insert(id, info);
...@@ -348,7 +386,11 @@ impl<T: Config> Module<T> { ...@@ -348,7 +386,11 @@ impl<T: Config> Module<T> {
/// Deregister a Para Id, freeing all data returning any deposit. /// Deregister a Para Id, freeing all data returning any deposit.
fn do_deregister(id: ParaId) -> DispatchResult { fn do_deregister(id: ParaId) -> DispatchResult {
ensure!(paras::Module::<T>::lifecycle(id) == Some(ParaLifecycle::Parathread), Error::<T>::NotParathread); match paras::Module::<T>::lifecycle(id) {
// Para must be a parathread, or not exist at all.
Some(ParaLifecycle::Parathread) | None => {},
_ => return Err(Error::<T>::NotParathread.into())
}
runtime_parachains::schedule_para_cleanup::<T>(id).map_err(|_| Error::<T>::CannotDeregister)?; runtime_parachains::schedule_para_cleanup::<T>(id).map_err(|_| Error::<T>::CannotDeregister)?;
if let Some(info) = Paras::<T>::take(&id) { if let Some(info) = Paras::<T>::take(&id) {
...@@ -401,8 +443,8 @@ mod tests { ...@@ -401,8 +443,8 @@ mod tests {
use frame_system::limits; use frame_system::limits;
use frame_support::{ use frame_support::{
traits::{OnInitialize, OnFinalize}, traits::{OnInitialize, OnFinalize},
error::BadOrigin,
assert_ok, assert_noop, parameter_types, assert_ok, assert_noop, parameter_types,
error::BadOrigin,
}; };
use runtime_parachains::{configuration, shared}; use runtime_parachains::{configuration, shared};
use pallet_balances::Error as BalancesError; use pallet_balances::Error as BalancesError;
...@@ -713,16 +755,11 @@ mod tests { ...@@ -713,16 +755,11 @@ mod tests {
)); ));
run_to_session(2); run_to_session(2);
assert!(Parachains::is_parathread(32.into())); assert!(Parachains::is_parathread(32.into()));
// Origin check // Owner check
assert_noop!(Registrar::deregister( assert_noop!(Registrar::deregister(
Origin::signed(1), Origin::signed(2),
32.into(), 32.into(),
), BadOrigin); ), BadOrigin);
// not registered
assert_noop!(Registrar::deregister(
Origin::root(),
33.into(),
), Error::<Test>::NotParathread);
assert_ok!(Registrar::make_parachain(32.into())); assert_ok!(Registrar::make_parachain(32.into()));
run_to_session(4); run_to_session(4);
// Cant directly deregister parachain // Cant directly deregister parachain
...@@ -765,11 +802,13 @@ mod tests { ...@@ -765,11 +802,13 @@ mod tests {
// Both paras initiate a swap // Both paras initiate a swap
assert_ok!(Registrar::swap( assert_ok!(Registrar::swap(
para_origin(23.into()), para_origin(23.into()),
32.into() 23.into(),
32.into(),
)); ));
assert_ok!(Registrar::swap( assert_ok!(Registrar::swap(
para_origin(32.into()), para_origin(32.into()),
23.into() 32.into(),
23.into(),
)); ));
run_to_session(6); run_to_session(6);
...@@ -827,6 +866,33 @@ mod tests { ...@@ -827,6 +866,33 @@ mod tests {
)); ));
}); });
} }
#[test]
fn para_lock_works() {
new_test_ext().execute_with(|| {
run_to_block(1);
assert_ok!(Registrar::register(
Origin::signed(1),
1u32.into(),
vec![1; 3].into(),
WASM_MAGIC.to_vec().into(),
));
// Owner can call swap
assert_ok!(Registrar::swap(Origin::signed(1), 1u32.into(), 2u32.into()));
// 2 session changes to fully onboard.
run_to_session(2);
assert_eq!(Parachains::lifecycle(1u32.into()), Some(ParaLifecycle::Parathread));
// Once they begin onboarding, we lock them in.
assert_ok!(Registrar::make_parachain(1u32.into()));
// Owner cannot call swap anymore
assert_noop!(Registrar::swap(Origin::signed(1), 1u32.into(), 3u32.into()), BadOrigin);
});
}
} }
#[cfg(feature = "runtime-benchmarks")] #[cfg(feature = "runtime-benchmarks")]
...@@ -838,7 +904,7 @@ mod benchmarking { ...@@ -838,7 +904,7 @@ mod benchmarking {
use crate::traits::{Registrar as RegistrarT}; use crate::traits::{Registrar as RegistrarT};
use runtime_parachains::{paras, shared, Origin as ParaOrigin}; use runtime_parachains::{paras, shared, Origin as ParaOrigin};
use frame_benchmarking::{benchmarks, whitelisted_caller}; use frame_benchmarking::{benchmarks, whitelisted_caller, impl_benchmark_test_suite};
fn assert_last_event<T: Config>(generic_event: <T as Config>::Event) { fn assert_last_event<T: Config>(generic_event: <T as Config>::Event) {
let events = frame_system::Pallet::<T>::events(); let events = frame_system::Pallet::<T>::events();
...@@ -890,7 +956,8 @@ mod benchmarking { ...@@ -890,7 +956,8 @@ mod benchmarking {
deregister { deregister {
let para = register_para::<T>(1337); let para = register_para::<T>(1337);
next_scheduled_session::<T>(); next_scheduled_session::<T>();
}: _(RawOrigin::Root, para) let caller: T::AccountId = whitelisted_caller();
}: _(RawOrigin::Signed(caller), para)
verify { verify {
assert_last_event::<T>(RawEvent::Deregistered(para).into()); assert_last_event::<T>(RawEvent::Deregistered(para).into());
} }
...@@ -899,8 +966,7 @@ mod benchmarking { ...@@ -899,8 +966,7 @@ mod benchmarking {
let parathread = register_para::<T>(1337); let parathread = register_para::<T>(1337);
let parachain = register_para::<T>(1338); let parachain = register_para::<T>(1338);
let parathread_origin = para_origin(1337); let parachain_origin = para_origin(parachain.into());
let parachain_origin = para_origin(1338);
// Actually finish registration process // Actually finish registration process
next_scheduled_session::<T>(); next_scheduled_session::<T>();
...@@ -912,10 +978,10 @@ mod benchmarking { ...@@ -912,10 +978,10 @@ mod benchmarking {
assert_eq!(paras::Module::<T>::lifecycle(parachain), Some(ParaLifecycle::Parachain)); assert_eq!(paras::Module::<T>::lifecycle(parachain), Some(ParaLifecycle::Parachain));
assert_eq!(paras::Module::<T>::lifecycle(parathread), Some(ParaLifecycle::Parathread)); assert_eq!(paras::Module::<T>::lifecycle(parathread), Some(ParaLifecycle::Parathread));
Registrar::<T>::swap(parathread_origin.into(), parachain)?; let caller: T::AccountId = whitelisted_caller();
}: { Registrar::<T>::swap(parachain_origin.into(), parachain, parathread)?;
Registrar::<T>::swap(parachain_origin.into(), parathread)?; }: _(RawOrigin::Signed(caller.clone()), parathread, parachain)
} verify { verify {
next_scheduled_session::<T>(); next_scheduled_session::<T>();
// Swapped! // Swapped!
assert_eq!(paras::Module::<T>::lifecycle(parachain), Some(ParaLifecycle::Parathread)); assert_eq!(paras::Module::<T>::lifecycle(parachain), Some(ParaLifecycle::Parathread));
...@@ -923,19 +989,9 @@ mod benchmarking { ...@@ -923,19 +989,9 @@ mod benchmarking {
} }
} }
#[cfg(test)] impl_benchmark_test_suite!(
mod tests { Registrar,
use super::*; crate::integration_tests::new_test_ext(),
use crate::integration_tests::{new_test_ext, Test}; crate::integration_tests::Test,
use frame_support::assert_ok; );
#[test]
fn test_benchmarks() {
new_test_ext().execute_with(|| {
assert_ok!(test_benchmark_register::<Test>());
assert_ok!(test_benchmark_deregister::<Test>());
assert_ok!(test_benchmark_swap::<Test>());
});
}
}
} }
...@@ -47,6 +47,14 @@ pub trait Registrar { ...@@ -47,6 +47,14 @@ pub trait Registrar {
Self::is_parathread(id) || Self::is_parachain(id) Self::is_parathread(id) || Self::is_parachain(id)
} }
/// Apply a lock to the para registration so that it cannot be modified by
/// the manager directly. Instead the para must use its sovereign governance
/// or the governance of the relay chain.
fn apply_lock(id: ParaId);
/// Remove any lock on the para registration.
fn remove_lock(id: ParaId);
/// Register a Para ID under control of `who`. Registration may be be /// Register a Para ID under control of `who`. Registration may be be
/// delayed by session rotation. /// delayed by session rotation.
fn register( fn register(
......
...@@ -660,20 +660,25 @@ impl<T: Config> Module<T> { ...@@ -660,20 +660,25 @@ impl<T: Config> Module<T> {
/// Schedule a para to be cleaned up at the start of the next session. /// Schedule a para to be cleaned up at the start of the next session.
/// ///
/// Will return error if para is not a stable parachain or parathread. /// Will return error if para is not a stable parachain or parathread.
///
/// No-op if para is not registered at all.
pub(crate) fn schedule_para_cleanup(id: ParaId) -> DispatchResult { pub(crate) fn schedule_para_cleanup(id: ParaId) -> DispatchResult {
let scheduled_session = Self::scheduled_session(); let lifecycle = ParaLifecycles::get(&id);
let lifecycle = ParaLifecycles::get(&id).ok_or(Error::<T>::NotRegistered)?;
match lifecycle { match lifecycle {
ParaLifecycle::Parathread => { // If para is not registered, nothing to do!
None => {
return Ok(())
},
Some(ParaLifecycle::Parathread) => {
ParaLifecycles::insert(&id, ParaLifecycle::OffboardingParathread); ParaLifecycles::insert(&id, ParaLifecycle::OffboardingParathread);
}, },
ParaLifecycle::Parachain => { Some(ParaLifecycle::Parachain) => {
ParaLifecycles::insert(&id, ParaLifecycle::OffboardingParachain); ParaLifecycles::insert(&id, ParaLifecycle::OffboardingParachain);
}, },
_ => return Err(Error::<T>::CannotOffboard)?, _ => return Err(Error::<T>::CannotOffboard)?,
} }
let scheduled_session = Self::scheduled_session();
ActionsQueue::mutate(scheduled_session, |v| { ActionsQueue::mutate(scheduled_session, |v| {
if let Err(i) = v.binary_search(&id) { if let Err(i) = v.binary_search(&id) {
v.insert(i, id); v.insert(i, id);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment