From 9fdde166b2a7005d5585111e02e802309a443ab1 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Fri, 24 Sep 2021 12:13:07 +0200 Subject: [PATCH 01/23] Permanent & Temp parachain slots on Rococo - WIP --- runtime/common/src/paras_sudo_wrapper.rs | 360 ++++++++++++++++++++++- runtime/rococo/src/lib.rs | 21 +- runtime/westend/src/lib.rs | 18 +- 3 files changed, 388 insertions(+), 11 deletions(-) diff --git a/runtime/common/src/paras_sudo_wrapper.rs b/runtime/common/src/paras_sudo_wrapper.rs index 91430155b34d..9f5882c906ba 100644 --- a/runtime/common/src/paras_sudo_wrapper.rs +++ b/runtime/common/src/paras_sudo_wrapper.rs @@ -16,7 +16,9 @@ //! A simple wrapper allowing `Sudo` to call into `paras` routines. -use frame_support::pallet_prelude::*; +use crate::slots::{self, Pallet as Slots}; +use crate::traits::{Leaser, Registrar}; +use frame_support::{pallet_prelude::*, traits::Currency}; use frame_system::pallet_prelude::*; pub use pallet::*; use parity_scale_codec::Encode; @@ -26,7 +28,32 @@ use runtime_parachains::{ paras::{self, ParaGenesisArgs}, ump, ParaLifecycle, }; -use sp_std::boxed::Box; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{One, Saturating, Zero}, + ArithmeticError, +}; +use sp_std::prelude::*; + +#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub enum SlotLeasePeriodStart { + Current, + Next, +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, TypeInfo)] +pub struct ParachainTemporarySlot { + manager: AccountId, + period_begin: LeasePeriod, + period_count: LeasePeriod, + last_lease: Option, + lease_count: u32, +} + +type BalanceOf = <<::Leaser as Leaser>::Currency as Currency< + ::AccountId, +>>::Balance; +type LeasePeriodOf = <::Leaser as Leaser>::LeasePeriod; #[frame_support::pallet] pub mod pallet { @@ -39,8 +66,62 @@ pub mod pallet { #[pallet::config] #[pallet::disable_frame_system_supertrait_check] pub trait Config: - configuration::Config + paras::Config + dmp::Config + ump::Config + hrmp::Config + configuration::Config + + paras::Config + + dmp::Config + + ump::Config + + hrmp::Config + + slots::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// The type representing the leasing system. + type Leaser: Leaser; + + /// The number of lease periods a permanent parachain slot lasts + #[pallet::constant] + type PermanentSlotLeasePeriodLength: Get; + + /// The number of lease periods a temporary parachain slot lasts + #[pallet::constant] + type TemporarySlotLeasePeriodLength: Get; + + /// The max number of permanent slots that can be assigned + #[pallet::constant] + type MaxPermanentSlots: Get; + + /// The max number of temporary slots to be scheduled per lease periods + #[pallet::constant] + type MaxTemporarySlotPerLeasePeriod: Get; + } + + #[pallet::storage] + #[pallet::getter(fn permanent_slots)] + pub type PermanentSlots = + StorageMap<_, Twox64Concat, ParaId, Option>, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn permanent_slot_count)] + pub type PermanentSlotCount = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn temporary_slots)] + pub type TemporarySlots = StorageMap< + _, + Twox64Concat, + ParaId, + Option>>, + ValueQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A para was assigned a permanent parachain slot + PermanentSlotAssigned(ParaId), + /// A para was assigned a temporary parachain slot + TemporarySlotAssigned(ParaId), } #[pallet::error] @@ -62,13 +143,166 @@ pub mod pallet { CannotUpgrade, /// Cannot downgrade parachain. CannotDowngrade, + /// Permanent or Temporary slot already assigned. + SlotAlreadyAssigned, + /// Permanent or Temporary slot has not been assigned. + SlotNotAssigned, + /// An ongoing lease already exists. + OngoingLeaseExists, + // Maximum number of permanent slots exceeded + MaxPermanentSlotsExceeded, } #[pallet::hooks] - impl Hooks> for Pallet {} + impl Hooks> for Pallet { + fn on_initialize(n: T::BlockNumber) -> Weight { + let lease_period = Self::lease_period(); + let lease_period_index = Self::lease_period_index(); + if (n % lease_period).is_zero() { + Self::manage_lease_period_start(lease_period_index) + } else { + 0 + } + } + } #[pallet::call] impl Pallet { + /// Assign a permanent parachain slot + #[pallet::weight((1_000, DispatchClass::Operational))] + pub fn sudo_assign_perm_parachain_slot(origin: OriginFor, id: ParaId) -> DispatchResult { + ensure_root(origin.clone())?; + + let manager = T::Registrar::manager_of(id).ok_or(Error::::ParaDoesntExist)?; + + ensure!(T::Registrar::is_parathread(id), Error::::NotParathread,); + + ensure!( + !Self::has_permanent_slot(id) && !Self::has_temporary_slot(id), + Error::::SlotAlreadyAssigned + ); + + let current_lease_period: T::BlockNumber = Self::lease_period_index(); + ensure!( + !T::Leaser::already_leased( + id, + current_lease_period, + // Check current lease & next one + current_lease_period.saturating_add( + T::BlockNumber::from(2u32) + .saturating_mul(T::PermanentSlotLeasePeriodLength::get().into()) + ) + ), + Error::::OngoingLeaseExists + ); + + ensure!( + PermanentSlotCount::::get() + 1 < T::MaxPermanentSlots::get(), + Error::::MaxPermanentSlotsExceeded + ); + + >::try_mutate(|count| -> DispatchResult { + Self::configure_slot_lease( + id, + manager, + current_lease_period, + T::PermanentSlotLeasePeriodLength::get().into(), + )?; + PermanentSlots::::insert(id, Some(current_lease_period)); + *count = count.checked_add(One::one()).ok_or(ArithmeticError::Overflow)?; + Ok(()) + })?; + + Self::deposit_event(Event::::PermanentSlotAssigned(id)); + Ok(()) + } + + /// Assign a temporary parachain slot + #[pallet::weight((1_000, DispatchClass::Operational))] + pub fn sudo_assign_temp_parachain_slot( + origin: OriginFor, + id: ParaId, + lease_period_start: SlotLeasePeriodStart, + ) -> DispatchResult { + ensure_root(origin.clone())?; + + let manager = T::Registrar::manager_of(id).ok_or(Error::::ParaDoesntExist)?; + + ensure!(T::Registrar::is_parathread(id), Error::::NotParathread,); + + ensure!( + !Self::has_permanent_slot(id) && !Self::has_temporary_slot(id), + Error::::SlotAlreadyAssigned + ); + + let current_lease_period: T::BlockNumber = Self::lease_period_index(); + ensure!( + !T::Leaser::already_leased( + id, + current_lease_period, + // Check current lease & next one + current_lease_period.saturating_add( + T::BlockNumber::from(2u32) + .saturating_mul(T::TemporarySlotLeasePeriodLength::get().into()) + ) + ), + Error::::OngoingLeaseExists + ); + + TemporarySlots::::insert( + id, + Some(ParachainTemporarySlot { + manager, + period_begin: match lease_period_start { + SlotLeasePeriodStart::Current => current_lease_period, + SlotLeasePeriodStart::Next => current_lease_period + One::one(), + }, + period_count: T::TemporarySlotLeasePeriodLength::get().into(), + last_lease: None, + lease_count: 0, + }), + ); + + if lease_period_start == SlotLeasePeriodStart::Current { + Self::allocate_temporary_slot_leases(current_lease_period)?; + } + + Self::deposit_event(Event::::TemporarySlotAssigned(id)); + + Ok(()) + } + + /// Unassign a permanent or temporary parachain slot + #[pallet::weight((1_000, DispatchClass::Operational))] + pub fn sudo_unassign_parachain_slot(origin: OriginFor, id: ParaId) -> DispatchResult { + ensure_root(origin.clone())?; + + ensure!( + Self::has_permanent_slot(id) || Self::has_temporary_slot(id), + Error::::SlotNotAssigned + ); + + if PermanentSlots::::contains_key(id) { + >::try_mutate(|count| -> DispatchResult { + *count = count.checked_sub(1).ok_or(ArithmeticError::Underflow)?; + Ok(()) + })?; + PermanentSlots::::remove(id); + } else if TemporarySlots::::contains_key(id) { + TemporarySlots::::remove(id); + } + + // Clean any para lease + Self::clear_slot_leases(origin.clone(), id)?; + + // Force downgrade to parathread (if needed) before end of lease period + if paras::Pallet::::lifecycle(id) == Some(ParaLifecycle::Parachain) { + Self::sudo_schedule_parachain_downgrade(origin, id)?; + } + + Ok(()) + } + /// Schedule a para to be initialized at the start of the next session. #[pallet::weight((1_000, DispatchClass::Operational))] pub fn sudo_schedule_para_initialize( @@ -140,8 +374,9 @@ pub mod pallet { let config = >::config(); >::queue_downward_message(&config, id, xcm.encode()).map_err(|e| match e { - dmp::QueueDownwardMessageError::ExceedsMaxMessageSize => - Error::::ExceedsMaxMessageSize.into(), + dmp::QueueDownwardMessageError::ExceedsMaxMessageSize => { + Error::::ExceedsMaxMessageSize.into() + } }) } @@ -170,3 +405,116 @@ pub mod pallet { } } } + +impl Pallet { + fn allocate_temporary_slot_leases(lease_period_index: LeasePeriodOf) -> DispatchResult { + let mut active_temp_slots = 0u32; + let mut pending_temp_slots = Vec::new(); + TemporarySlots::::iter().for_each(|(para, maybe_slot)| { + if let Some(slot) = maybe_slot { + match slot.last_lease { + Some(last_lease) + if last_lease <= lease_period_index + && lease_period_index + < (last_lease.saturating_add(slot.period_count)) => + { + // Active slot lease + active_temp_slots += 1; + } + Some(last_lease) => { + // Slot w/ past lease, only consider it every other slot lease period (times period_count) + if last_lease + <= lease_period_index - (slot.period_count.saturating_mul(2u32.into())) + { + pending_temp_slots.push((para, slot)); + } + } + None if slot.period_begin <= lease_period_index => { + // Slot hasn't had a lease yet + pending_temp_slots.insert(0, (para, slot)); + } + _ => { + // Slot not being considered for this lease period (will be for a subsequent one) + } + } + } + }); + + if active_temp_slots < T::MaxTemporarySlotPerLeasePeriod::get() + && !pending_temp_slots.is_empty() + { + // Sort by lease_count, favoring slots that had no or less turns first + pending_temp_slots.sort_unstable_by_key(|(_id, s)| s.lease_count); + + let slots_to_be_upgraded = pending_temp_slots + .iter() + .take((T::MaxTemporarySlotPerLeasePeriod::get() - active_temp_slots) as usize) + .collect::>(); + + for (id, temp_slot) in slots_to_be_upgraded.iter() { + TemporarySlots::::try_mutate::<_, _, Error, _>(id, |s| { + // Configure temp slot lease + T::Leaser::lease_out( + *id, + &temp_slot.manager, + BalanceOf::::zero(), + lease_period_index, + temp_slot.period_count, + ) + .map_err(|_| Error::::CannotUpgrade)?; + + // Update temp slot lease info in storage + *s = Some(ParachainTemporarySlot { + manager: temp_slot.manager.clone(), + period_begin: temp_slot.period_begin, + period_count: temp_slot.period_count, + last_lease: Some(lease_period_index), + lease_count: temp_slot.lease_count + 1, + }); + + Ok(()) + })?; + } + } + Ok(()) + } + + fn clear_slot_leases(origin: OriginFor, id: ParaId) -> DispatchResult { + Slots::::clear_all_leases(origin, id) + } + + fn configure_slot_lease( + para: ParaId, + manager: T::AccountId, + lease_period: LeasePeriodOf, + lease_duration: LeasePeriodOf, + ) -> DispatchResult { + T::Leaser::lease_out(para, &manager, BalanceOf::::zero(), lease_period, lease_duration) + .map_err(|_| Error::::CannotUpgrade)?; + + Ok(()) + } + + fn has_permanent_slot(id: ParaId) -> bool { + PermanentSlots::::contains_key(id) + } + + fn has_temporary_slot(id: ParaId) -> bool { + TemporarySlots::::contains_key(id) + } + + fn lease_period_index() -> LeasePeriodOf { + T::Leaser::lease_period_index() + } + + fn lease_period() -> LeasePeriodOf { + T::Leaser::lease_period() + } + + fn manage_lease_period_start(lease_period_index: LeasePeriodOf) -> Weight { + // Note: leases that have ended in previous lease period, + // should have been cleaned in slots pallet. + let _ = Self::allocate_temporary_slot_leases(lease_period_index); + 0 + } +} diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index 3cb61efd3c4a..9e29f508bc29 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -219,7 +219,7 @@ construct_runtime! { Auctions: auctions::{Pallet, Call, Storage, Event}, Crowdloan: crowdloan::{Pallet, Call, Storage, Event}, Slots: slots::{Pallet, Call, Storage, Event}, - ParasSudoWrapper: paras_sudo_wrapper::{Pallet, Call}, + ParasSudoWrapper: paras_sudo_wrapper::{Pallet, Call, Storage, Event}, // Sudo Sudo: pallet_sudo::{Pallet, Call, Storage, Event, Config}, @@ -753,7 +753,21 @@ impl parachains_initializer::Config for Runtime { type ForceOrigin = EnsureRoot; } -impl paras_sudo_wrapper::Config for Runtime {} +parameter_types! { + pub const PermanentSlotLeasePeriodLength: u32 = 26; + pub const TemporarySlotLeasePeriodLength: u32 = 1; + pub const MaxPermanentSlots: u32 = 5; + pub const MaxTemporarySlotPerLeasePeriod: u32 = 5; +} + +impl paras_sudo_wrapper::Config for Runtime { + type Event = Event; + type Leaser = Slots; + type PermanentSlotLeasePeriodLength = PermanentSlotLeasePeriodLength; + type TemporarySlotLeasePeriodLength = TemporarySlotLeasePeriodLength; + type MaxPermanentSlots = MaxPermanentSlots; + type MaxTemporarySlotPerLeasePeriod = MaxTemporarySlotPerLeasePeriod; +} parameter_types! { pub const ParaDeposit: Balance = 5 * DOLLARS; @@ -988,7 +1002,8 @@ impl auctions::Config for Runtime { } parameter_types! { - pub const LeasePeriod: BlockNumber = 1 * DAYS; + // pub const LeasePeriod: BlockNumber = 1 * WEEKS; + pub const LeasePeriod: BlockNumber = 5 * MINUTES; } impl slots::Config for Runtime { diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index 196a28d57819..0496fbff44f6 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -852,7 +852,21 @@ impl parachains_initializer::Config for Runtime { type ForceOrigin = EnsureRoot; } -impl paras_sudo_wrapper::Config for Runtime {} +parameter_types! { + pub const PermanentSlotLeasePeriodLength: u32 = 13; + pub const TemporarySlotLeasePeriodLength: u32 = 1; + pub const MaxPermanentSlots: u32 = 5; + pub const MaxTemporarySlotPerLeasePeriod: u32 = 5; +} + +impl paras_sudo_wrapper::Config for Runtime { + type Event = Event; + type Leaser = Slots; + type PermanentSlotLeasePeriodLength = PermanentSlotLeasePeriodLength; + type TemporarySlotLeasePeriodLength = TemporarySlotLeasePeriodLength; + type MaxPermanentSlots = MaxPermanentSlots; + type MaxTemporarySlotPerLeasePeriod = MaxTemporarySlotPerLeasePeriod; +} parameter_types! { pub const ParaDeposit: Balance = 2000 * CENTS; @@ -1097,7 +1111,7 @@ construct_runtime! { // Parachain Onboarding Pallets. Start indices at 60 to leave room. Registrar: paras_registrar::{Pallet, Call, Storage, Event, Config} = 60, Slots: slots::{Pallet, Call, Storage, Event} = 61, - ParasSudoWrapper: paras_sudo_wrapper::{Pallet, Call} = 62, + ParasSudoWrapper: paras_sudo_wrapper::{Pallet, Call, Event} = 62, Auctions: auctions::{Pallet, Call, Storage, Event} = 63, Crowdloan: crowdloan::{Pallet, Call, Storage, Event} = 64, From b52ad660a8f36f98d811a8ba8c15fb21dcf8147e Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Fri, 24 Sep 2021 12:17:54 +0200 Subject: [PATCH 02/23] Revert test change --- runtime/rococo/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index 9e29f508bc29..2edc57f49511 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -1002,8 +1002,7 @@ impl auctions::Config for Runtime { } parameter_types! { - // pub const LeasePeriod: BlockNumber = 1 * WEEKS; - pub const LeasePeriod: BlockNumber = 5 * MINUTES; + pub const LeasePeriod: BlockNumber = 1 * WEEKS; } impl slots::Config for Runtime { From dbd850e1eb5007aa1bf3f409eccac16c5d5d7b7a Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Fri, 24 Sep 2021 14:01:30 +0200 Subject: [PATCH 03/23] Revert test change --- runtime/rococo/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index 2edc57f49511..4d7f3202e216 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -1002,7 +1002,7 @@ impl auctions::Config for Runtime { } parameter_types! { - pub const LeasePeriod: BlockNumber = 1 * WEEKS; + pub const LeasePeriod: BlockNumber = 7 * DAYS; } impl slots::Config for Runtime { From dd11565417b30ba5a73bc58d37d0daa7f77c88f3 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Mon, 27 Sep 2021 13:56:00 +0200 Subject: [PATCH 04/23] Fix formatting --- runtime/common/src/paras_sudo_wrapper.rs | 31 ++++++++++++------------ 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/runtime/common/src/paras_sudo_wrapper.rs b/runtime/common/src/paras_sudo_wrapper.rs index 9f5882c906ba..8555d3b0bb06 100644 --- a/runtime/common/src/paras_sudo_wrapper.rs +++ b/runtime/common/src/paras_sudo_wrapper.rs @@ -16,8 +16,10 @@ //! A simple wrapper allowing `Sudo` to call into `paras` routines. -use crate::slots::{self, Pallet as Slots}; -use crate::traits::{Leaser, Registrar}; +use crate::{ + slots::{self, Pallet as Slots}, + traits::{Leaser, Registrar}, +}; use frame_support::{pallet_prelude::*, traits::Currency}; use frame_system::pallet_prelude::*; pub use pallet::*; @@ -374,9 +376,8 @@ pub mod pallet { let config = >::config(); >::queue_downward_message(&config, id, xcm.encode()).map_err(|e| match e { - dmp::QueueDownwardMessageError::ExceedsMaxMessageSize => { - Error::::ExceedsMaxMessageSize.into() - } + dmp::QueueDownwardMessageError::ExceedsMaxMessageSize => + Error::::ExceedsMaxMessageSize.into(), }) } @@ -414,34 +415,34 @@ impl Pallet { if let Some(slot) = maybe_slot { match slot.last_lease { Some(last_lease) - if last_lease <= lease_period_index - && lease_period_index - < (last_lease.saturating_add(slot.period_count)) => + if last_lease <= lease_period_index && + lease_period_index < + (last_lease.saturating_add(slot.period_count)) => { // Active slot lease active_temp_slots += 1; } Some(last_lease) => { // Slot w/ past lease, only consider it every other slot lease period (times period_count) - if last_lease - <= lease_period_index - (slot.period_count.saturating_mul(2u32.into())) + if last_lease <= + lease_period_index - (slot.period_count.saturating_mul(2u32.into())) { pending_temp_slots.push((para, slot)); } - } + }, None if slot.period_begin <= lease_period_index => { // Slot hasn't had a lease yet pending_temp_slots.insert(0, (para, slot)); - } + }, _ => { // Slot not being considered for this lease period (will be for a subsequent one) - } + }, } } }); - if active_temp_slots < T::MaxTemporarySlotPerLeasePeriod::get() - && !pending_temp_slots.is_empty() + if active_temp_slots < T::MaxTemporarySlotPerLeasePeriod::get() && + !pending_temp_slots.is_empty() { // Sort by lease_count, favoring slots that had no or less turns first pending_temp_slots.sort_unstable_by_key(|(_id, s)| s.lease_count); From afcaf75cb477dc07929c4f5e9f3a638692a8cd10 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Mon, 27 Sep 2021 19:31:35 +0200 Subject: [PATCH 05/23] Extract logic to separate assigned_slots pallet --- runtime/common/src/assigned_slots.rs | 406 +++++++++++++++++++++++ runtime/common/src/lib.rs | 1 + runtime/common/src/paras_sudo_wrapper.rs | 338 +------------------ runtime/rococo/src/lib.rs | 18 +- runtime/westend/src/lib.rs | 17 +- 5 files changed, 439 insertions(+), 341 deletions(-) create mode 100644 runtime/common/src/assigned_slots.rs diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs new file mode 100644 index 000000000000..7294e5517a77 --- /dev/null +++ b/runtime/common/src/assigned_slots.rs @@ -0,0 +1,406 @@ +// Copyright 2020 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 . + +//! A simple wrapper allowing `Sudo` to call into `paras` routines. + +use crate::{ + slots::{self, Pallet as Slots}, + traits::{Leaser, Registrar}, +}; +use frame_support::{pallet_prelude::*, traits::Currency}; +use frame_system::pallet_prelude::*; +pub use pallet::*; +use parity_scale_codec::Encode; +use primitives::v1::Id as ParaId; +use runtime_parachains::{ + configuration, + paras::{self}, + ParaLifecycle, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{One, Saturating, Zero}, + ArithmeticError, +}; +use sp_std::prelude::*; + +#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub enum SlotLeasePeriodStart { + Current, + Next, +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, TypeInfo)] +pub struct ParachainTemporarySlot { + manager: AccountId, + period_begin: LeasePeriod, + period_count: LeasePeriod, + last_lease: Option, + lease_count: u32, +} + +type BalanceOf = <<::Leaser as Leaser>::Currency as Currency< + ::AccountId, +>>::Balance; +type LeasePeriodOf = <::Leaser as Leaser>::LeasePeriod; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: configuration::Config + paras::Config + slots::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// Origin for assigning slots + type AssignSlotOrigin: EnsureOrigin<::Origin>; + + /// The type representing the leasing system. + type Leaser: Leaser; + + /// The number of lease periods a permanent parachain slot lasts + #[pallet::constant] + type PermanentSlotLeasePeriodLength: Get; + + /// The number of lease periods a temporary parachain slot lasts + #[pallet::constant] + type TemporarySlotLeasePeriodLength: Get; + + /// The max number of permanent slots that can be assigned + #[pallet::constant] + type MaxPermanentSlots: Get; + + /// The max number of temporary slots to be scheduled per lease periods + #[pallet::constant] + type MaxTemporarySlotPerLeasePeriod: Get; + } + + #[pallet::storage] + #[pallet::getter(fn permanent_slots)] + pub type PermanentSlots = + StorageMap<_, Twox64Concat, ParaId, Option>, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn permanent_slot_count)] + pub type PermanentSlotCount = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn temporary_slots)] + pub type TemporarySlots = StorageMap< + _, + Twox64Concat, + ParaId, + Option>>, + ValueQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A para was assigned a permanent parachain slot + PermanentSlotAssigned(ParaId), + /// A para was assigned a temporary parachain slot + TemporarySlotAssigned(ParaId), + } + + #[pallet::error] + pub enum Error { + /// The specified parachain or parathread is not registered. + ParaDoesntExist, + /// Not a parathread. + NotParathread, + /// Cannot upgrade parathread. + CannotUpgrade, + /// Permanent or Temporary slot already assigned. + SlotAlreadyAssigned, + /// Permanent or Temporary slot has not been assigned. + SlotNotAssigned, + /// An ongoing lease already exists. + OngoingLeaseExists, + // Maximum number of permanent slots exceeded + MaxPermanentSlotsExceeded, + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(n: T::BlockNumber) -> Weight { + let lease_period = Self::lease_period(); + let lease_period_index = Self::lease_period_index(); + if (n % lease_period).is_zero() { + Self::manage_lease_period_start(lease_period_index) + } else { + 0 + } + } + } + + #[pallet::call] + impl Pallet { + /// Assign a permanent parachain slot + #[pallet::weight((1_000, DispatchClass::Operational))] + pub fn assign_perm_parachain_slot(origin: OriginFor, id: ParaId) -> DispatchResult { + T::AssignSlotOrigin::ensure_origin(origin)?; + + let manager = T::Registrar::manager_of(id).ok_or(Error::::ParaDoesntExist)?; + + ensure!(T::Registrar::is_parathread(id), Error::::NotParathread,); + + ensure!( + !Self::has_permanent_slot(id) && !Self::has_temporary_slot(id), + Error::::SlotAlreadyAssigned + ); + + let current_lease_period: T::BlockNumber = Self::lease_period_index(); + ensure!( + !T::Leaser::already_leased( + id, + current_lease_period, + // Check current lease & next one + current_lease_period.saturating_add( + T::BlockNumber::from(2u32) + .saturating_mul(T::PermanentSlotLeasePeriodLength::get().into()) + ) + ), + Error::::OngoingLeaseExists + ); + + ensure!( + PermanentSlotCount::::get() + 1 < T::MaxPermanentSlots::get(), + Error::::MaxPermanentSlotsExceeded + ); + + >::try_mutate(|count| -> DispatchResult { + Self::configure_slot_lease( + id, + manager, + current_lease_period, + T::PermanentSlotLeasePeriodLength::get().into(), + )?; + PermanentSlots::::insert(id, Some(current_lease_period)); + *count = count.checked_add(One::one()).ok_or(ArithmeticError::Overflow)?; + Ok(()) + })?; + + Self::deposit_event(Event::::PermanentSlotAssigned(id)); + Ok(()) + } + + /// Assign a temporary parachain slot + #[pallet::weight((1_000, DispatchClass::Operational))] + pub fn assign_temp_parachain_slot( + origin: OriginFor, + id: ParaId, + lease_period_start: SlotLeasePeriodStart, + ) -> DispatchResult { + T::AssignSlotOrigin::ensure_origin(origin)?; + + let manager = T::Registrar::manager_of(id).ok_or(Error::::ParaDoesntExist)?; + + ensure!(T::Registrar::is_parathread(id), Error::::NotParathread,); + + ensure!( + !Self::has_permanent_slot(id) && !Self::has_temporary_slot(id), + Error::::SlotAlreadyAssigned + ); + + let current_lease_period: T::BlockNumber = Self::lease_period_index(); + ensure!( + !T::Leaser::already_leased( + id, + current_lease_period, + // Check current lease & next one + current_lease_period.saturating_add( + T::BlockNumber::from(2u32) + .saturating_mul(T::TemporarySlotLeasePeriodLength::get().into()) + ) + ), + Error::::OngoingLeaseExists + ); + + TemporarySlots::::insert( + id, + Some(ParachainTemporarySlot { + manager, + period_begin: match lease_period_start { + SlotLeasePeriodStart::Current => current_lease_period, + SlotLeasePeriodStart::Next => current_lease_period + One::one(), + }, + period_count: T::TemporarySlotLeasePeriodLength::get().into(), + last_lease: None, + lease_count: 0, + }), + ); + + if lease_period_start == SlotLeasePeriodStart::Current { + Self::allocate_temporary_slot_leases(current_lease_period)?; + } + + Self::deposit_event(Event::::TemporarySlotAssigned(id)); + + Ok(()) + } + + /// Unassign a permanent or temporary parachain slot + #[pallet::weight((1_000, DispatchClass::Operational))] + pub fn unassign_parachain_slot(origin: OriginFor, id: ParaId) -> DispatchResult { + T::AssignSlotOrigin::ensure_origin(origin.clone())?; + + ensure!( + Self::has_permanent_slot(id) || Self::has_temporary_slot(id), + Error::::SlotNotAssigned + ); + + if PermanentSlots::::contains_key(id) { + >::try_mutate(|count| -> DispatchResult { + *count = count.checked_sub(1).ok_or(ArithmeticError::Underflow)?; + Ok(()) + })?; + PermanentSlots::::remove(id); + } else if TemporarySlots::::contains_key(id) { + TemporarySlots::::remove(id); + } + + // Clean any para lease + Self::clear_slot_leases(origin.clone(), id)?; + + // Force downgrade to parathread (if needed) before end of lease period + if paras::Pallet::::lifecycle(id) == Some(ParaLifecycle::Parachain) { + // Self::sudo_schedule_parachain_downgrade(origin, id)?; + } + + Ok(()) + } + } +} + +impl Pallet { + fn allocate_temporary_slot_leases(lease_period_index: LeasePeriodOf) -> DispatchResult { + let mut active_temp_slots = 0u32; + let mut pending_temp_slots = Vec::new(); + TemporarySlots::::iter().for_each(|(para, maybe_slot)| { + if let Some(slot) = maybe_slot { + match slot.last_lease { + Some(last_lease) + if last_lease <= lease_period_index && + lease_period_index < + (last_lease.saturating_add(slot.period_count)) => + { + // Active slot lease + active_temp_slots += 1; + } + Some(last_lease) => { + // Slot w/ past lease, only consider it every other slot lease period (times period_count) + if last_lease <= + lease_period_index - (slot.period_count.saturating_mul(2u32.into())) + { + pending_temp_slots.push((para, slot)); + } + }, + None if slot.period_begin <= lease_period_index => { + // Slot hasn't had a lease yet + pending_temp_slots.insert(0, (para, slot)); + }, + _ => { + // Slot not being considered for this lease period (will be for a subsequent one) + }, + } + } + }); + + if active_temp_slots < T::MaxTemporarySlotPerLeasePeriod::get() && + !pending_temp_slots.is_empty() + { + // Sort by lease_count, favoring slots that had no or less turns first + pending_temp_slots.sort_unstable_by_key(|(_id, s)| s.lease_count); + + let slots_to_be_upgraded = pending_temp_slots + .iter() + .take((T::MaxTemporarySlotPerLeasePeriod::get() - active_temp_slots) as usize) + .collect::>(); + + for (id, temp_slot) in slots_to_be_upgraded.iter() { + TemporarySlots::::try_mutate::<_, _, Error, _>(id, |s| { + // Configure temp slot lease + T::Leaser::lease_out( + *id, + &temp_slot.manager, + BalanceOf::::zero(), + lease_period_index, + temp_slot.period_count, + ) + .map_err(|_| Error::::CannotUpgrade)?; + + // Update temp slot lease info in storage + *s = Some(ParachainTemporarySlot { + manager: temp_slot.manager.clone(), + period_begin: temp_slot.period_begin, + period_count: temp_slot.period_count, + last_lease: Some(lease_period_index), + lease_count: temp_slot.lease_count + 1, + }); + + Ok(()) + })?; + } + } + Ok(()) + } + + fn clear_slot_leases(origin: OriginFor, id: ParaId) -> DispatchResult { + Slots::::clear_all_leases(origin, id) + } + + fn configure_slot_lease( + para: ParaId, + manager: T::AccountId, + lease_period: LeasePeriodOf, + lease_duration: LeasePeriodOf, + ) -> DispatchResult { + T::Leaser::lease_out(para, &manager, BalanceOf::::zero(), lease_period, lease_duration) + .map_err(|_| Error::::CannotUpgrade)?; + + Ok(()) + } + + fn has_permanent_slot(id: ParaId) -> bool { + PermanentSlots::::contains_key(id) + } + + fn has_temporary_slot(id: ParaId) -> bool { + TemporarySlots::::contains_key(id) + } + + fn lease_period_index() -> LeasePeriodOf { + T::Leaser::lease_period_index() + } + + fn lease_period() -> LeasePeriodOf { + T::Leaser::lease_period() + } + + fn manage_lease_period_start(lease_period_index: LeasePeriodOf) -> Weight { + // Note: leases that have ended in previous lease period, + // should have been cleaned in slots pallet. + let _ = Self::allocate_temporary_slot_leases(lease_period_index); + 0 + } +} diff --git a/runtime/common/src/lib.rs b/runtime/common/src/lib.rs index 704924a411db..6ccea8f2ab1a 100644 --- a/runtime/common/src/lib.rs +++ b/runtime/common/src/lib.rs @@ -18,6 +18,7 @@ #![cfg_attr(not(feature = "std"), no_std)] +pub mod assigned_slots; pub mod auctions; pub mod claims; pub mod crowdloan; diff --git a/runtime/common/src/paras_sudo_wrapper.rs b/runtime/common/src/paras_sudo_wrapper.rs index 8555d3b0bb06..3f308b81b4c9 100644 --- a/runtime/common/src/paras_sudo_wrapper.rs +++ b/runtime/common/src/paras_sudo_wrapper.rs @@ -16,11 +16,8 @@ //! A simple wrapper allowing `Sudo` to call into `paras` routines. -use crate::{ - slots::{self, Pallet as Slots}, - traits::{Leaser, Registrar}, -}; -use frame_support::{pallet_prelude::*, traits::Currency}; +use crate::assigned_slots::{self, Pallet as AssignedSlots, SlotLeasePeriodStart}; +use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; pub use pallet::*; use parity_scale_codec::Encode; @@ -30,33 +27,8 @@ use runtime_parachains::{ paras::{self, ParaGenesisArgs}, ump, ParaLifecycle, }; -use scale_info::TypeInfo; -use sp_runtime::{ - traits::{One, Saturating, Zero}, - ArithmeticError, -}; use sp_std::prelude::*; -#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub enum SlotLeasePeriodStart { - Current, - Next, -} - -#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, TypeInfo)] -pub struct ParachainTemporarySlot { - manager: AccountId, - period_begin: LeasePeriod, - period_count: LeasePeriod, - last_lease: Option, - lease_count: u32, -} - -type BalanceOf = <<::Leaser as Leaser>::Currency as Currency< - ::AccountId, ->>::Balance; -type LeasePeriodOf = <::Leaser as Leaser>::LeasePeriod; - #[frame_support::pallet] pub mod pallet { use super::*; @@ -73,57 +45,8 @@ pub mod pallet { + dmp::Config + ump::Config + hrmp::Config - + slots::Config + + assigned_slots::Config { - /// The overarching event type. - type Event: From> + IsType<::Event>; - - /// The type representing the leasing system. - type Leaser: Leaser; - - /// The number of lease periods a permanent parachain slot lasts - #[pallet::constant] - type PermanentSlotLeasePeriodLength: Get; - - /// The number of lease periods a temporary parachain slot lasts - #[pallet::constant] - type TemporarySlotLeasePeriodLength: Get; - - /// The max number of permanent slots that can be assigned - #[pallet::constant] - type MaxPermanentSlots: Get; - - /// The max number of temporary slots to be scheduled per lease periods - #[pallet::constant] - type MaxTemporarySlotPerLeasePeriod: Get; - } - - #[pallet::storage] - #[pallet::getter(fn permanent_slots)] - pub type PermanentSlots = - StorageMap<_, Twox64Concat, ParaId, Option>, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn permanent_slot_count)] - pub type PermanentSlotCount = StorageValue<_, u32, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn temporary_slots)] - pub type TemporarySlots = StorageMap< - _, - Twox64Concat, - ParaId, - Option>>, - ValueQuery, - >; - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// A para was assigned a permanent parachain slot - PermanentSlotAssigned(ParaId), - /// A para was assigned a temporary parachain slot - TemporarySlotAssigned(ParaId), } #[pallet::error] @@ -145,27 +68,6 @@ pub mod pallet { CannotUpgrade, /// Cannot downgrade parachain. CannotDowngrade, - /// Permanent or Temporary slot already assigned. - SlotAlreadyAssigned, - /// Permanent or Temporary slot has not been assigned. - SlotNotAssigned, - /// An ongoing lease already exists. - OngoingLeaseExists, - // Maximum number of permanent slots exceeded - MaxPermanentSlotsExceeded, - } - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(n: T::BlockNumber) -> Weight { - let lease_period = Self::lease_period(); - let lease_period_index = Self::lease_period_index(); - if (n % lease_period).is_zero() { - Self::manage_lease_period_start(lease_period_index) - } else { - 0 - } - } } #[pallet::call] @@ -174,49 +76,7 @@ pub mod pallet { #[pallet::weight((1_000, DispatchClass::Operational))] pub fn sudo_assign_perm_parachain_slot(origin: OriginFor, id: ParaId) -> DispatchResult { ensure_root(origin.clone())?; - - let manager = T::Registrar::manager_of(id).ok_or(Error::::ParaDoesntExist)?; - - ensure!(T::Registrar::is_parathread(id), Error::::NotParathread,); - - ensure!( - !Self::has_permanent_slot(id) && !Self::has_temporary_slot(id), - Error::::SlotAlreadyAssigned - ); - - let current_lease_period: T::BlockNumber = Self::lease_period_index(); - ensure!( - !T::Leaser::already_leased( - id, - current_lease_period, - // Check current lease & next one - current_lease_period.saturating_add( - T::BlockNumber::from(2u32) - .saturating_mul(T::PermanentSlotLeasePeriodLength::get().into()) - ) - ), - Error::::OngoingLeaseExists - ); - - ensure!( - PermanentSlotCount::::get() + 1 < T::MaxPermanentSlots::get(), - Error::::MaxPermanentSlotsExceeded - ); - - >::try_mutate(|count| -> DispatchResult { - Self::configure_slot_lease( - id, - manager, - current_lease_period, - T::PermanentSlotLeasePeriodLength::get().into(), - )?; - PermanentSlots::::insert(id, Some(current_lease_period)); - *count = count.checked_add(One::one()).ok_or(ArithmeticError::Overflow)?; - Ok(()) - })?; - - Self::deposit_event(Event::::PermanentSlotAssigned(id)); - Ok(()) + AssignedSlots::::assign_perm_parachain_slot(origin.clone(), id) } /// Assign a temporary parachain slot @@ -227,82 +87,14 @@ pub mod pallet { lease_period_start: SlotLeasePeriodStart, ) -> DispatchResult { ensure_root(origin.clone())?; - - let manager = T::Registrar::manager_of(id).ok_or(Error::::ParaDoesntExist)?; - - ensure!(T::Registrar::is_parathread(id), Error::::NotParathread,); - - ensure!( - !Self::has_permanent_slot(id) && !Self::has_temporary_slot(id), - Error::::SlotAlreadyAssigned - ); - - let current_lease_period: T::BlockNumber = Self::lease_period_index(); - ensure!( - !T::Leaser::already_leased( - id, - current_lease_period, - // Check current lease & next one - current_lease_period.saturating_add( - T::BlockNumber::from(2u32) - .saturating_mul(T::TemporarySlotLeasePeriodLength::get().into()) - ) - ), - Error::::OngoingLeaseExists - ); - - TemporarySlots::::insert( - id, - Some(ParachainTemporarySlot { - manager, - period_begin: match lease_period_start { - SlotLeasePeriodStart::Current => current_lease_period, - SlotLeasePeriodStart::Next => current_lease_period + One::one(), - }, - period_count: T::TemporarySlotLeasePeriodLength::get().into(), - last_lease: None, - lease_count: 0, - }), - ); - - if lease_period_start == SlotLeasePeriodStart::Current { - Self::allocate_temporary_slot_leases(current_lease_period)?; - } - - Self::deposit_event(Event::::TemporarySlotAssigned(id)); - - Ok(()) + AssignedSlots::::assign_temp_parachain_slot(origin.clone(), id, lease_period_start) } /// Unassign a permanent or temporary parachain slot #[pallet::weight((1_000, DispatchClass::Operational))] pub fn sudo_unassign_parachain_slot(origin: OriginFor, id: ParaId) -> DispatchResult { ensure_root(origin.clone())?; - - ensure!( - Self::has_permanent_slot(id) || Self::has_temporary_slot(id), - Error::::SlotNotAssigned - ); - - if PermanentSlots::::contains_key(id) { - >::try_mutate(|count| -> DispatchResult { - *count = count.checked_sub(1).ok_or(ArithmeticError::Underflow)?; - Ok(()) - })?; - PermanentSlots::::remove(id); - } else if TemporarySlots::::contains_key(id) { - TemporarySlots::::remove(id); - } - - // Clean any para lease - Self::clear_slot_leases(origin.clone(), id)?; - - // Force downgrade to parathread (if needed) before end of lease period - if paras::Pallet::::lifecycle(id) == Some(ParaLifecycle::Parachain) { - Self::sudo_schedule_parachain_downgrade(origin, id)?; - } - - Ok(()) + AssignedSlots::::unassign_parachain_slot(origin.clone(), id) } /// Schedule a para to be initialized at the start of the next session. @@ -376,8 +168,9 @@ pub mod pallet { let config = >::config(); >::queue_downward_message(&config, id, xcm.encode()).map_err(|e| match e { - dmp::QueueDownwardMessageError::ExceedsMaxMessageSize => - Error::::ExceedsMaxMessageSize.into(), + dmp::QueueDownwardMessageError::ExceedsMaxMessageSize => { + Error::::ExceedsMaxMessageSize.into() + } }) } @@ -406,116 +199,3 @@ pub mod pallet { } } } - -impl Pallet { - fn allocate_temporary_slot_leases(lease_period_index: LeasePeriodOf) -> DispatchResult { - let mut active_temp_slots = 0u32; - let mut pending_temp_slots = Vec::new(); - TemporarySlots::::iter().for_each(|(para, maybe_slot)| { - if let Some(slot) = maybe_slot { - match slot.last_lease { - Some(last_lease) - if last_lease <= lease_period_index && - lease_period_index < - (last_lease.saturating_add(slot.period_count)) => - { - // Active slot lease - active_temp_slots += 1; - } - Some(last_lease) => { - // Slot w/ past lease, only consider it every other slot lease period (times period_count) - if last_lease <= - lease_period_index - (slot.period_count.saturating_mul(2u32.into())) - { - pending_temp_slots.push((para, slot)); - } - }, - None if slot.period_begin <= lease_period_index => { - // Slot hasn't had a lease yet - pending_temp_slots.insert(0, (para, slot)); - }, - _ => { - // Slot not being considered for this lease period (will be for a subsequent one) - }, - } - } - }); - - if active_temp_slots < T::MaxTemporarySlotPerLeasePeriod::get() && - !pending_temp_slots.is_empty() - { - // Sort by lease_count, favoring slots that had no or less turns first - pending_temp_slots.sort_unstable_by_key(|(_id, s)| s.lease_count); - - let slots_to_be_upgraded = pending_temp_slots - .iter() - .take((T::MaxTemporarySlotPerLeasePeriod::get() - active_temp_slots) as usize) - .collect::>(); - - for (id, temp_slot) in slots_to_be_upgraded.iter() { - TemporarySlots::::try_mutate::<_, _, Error, _>(id, |s| { - // Configure temp slot lease - T::Leaser::lease_out( - *id, - &temp_slot.manager, - BalanceOf::::zero(), - lease_period_index, - temp_slot.period_count, - ) - .map_err(|_| Error::::CannotUpgrade)?; - - // Update temp slot lease info in storage - *s = Some(ParachainTemporarySlot { - manager: temp_slot.manager.clone(), - period_begin: temp_slot.period_begin, - period_count: temp_slot.period_count, - last_lease: Some(lease_period_index), - lease_count: temp_slot.lease_count + 1, - }); - - Ok(()) - })?; - } - } - Ok(()) - } - - fn clear_slot_leases(origin: OriginFor, id: ParaId) -> DispatchResult { - Slots::::clear_all_leases(origin, id) - } - - fn configure_slot_lease( - para: ParaId, - manager: T::AccountId, - lease_period: LeasePeriodOf, - lease_duration: LeasePeriodOf, - ) -> DispatchResult { - T::Leaser::lease_out(para, &manager, BalanceOf::::zero(), lease_period, lease_duration) - .map_err(|_| Error::::CannotUpgrade)?; - - Ok(()) - } - - fn has_permanent_slot(id: ParaId) -> bool { - PermanentSlots::::contains_key(id) - } - - fn has_temporary_slot(id: ParaId) -> bool { - TemporarySlots::::contains_key(id) - } - - fn lease_period_index() -> LeasePeriodOf { - T::Leaser::lease_period_index() - } - - fn lease_period() -> LeasePeriodOf { - T::Leaser::lease_period() - } - - fn manage_lease_period_start(lease_period_index: LeasePeriodOf) -> Weight { - // Note: leases that have ended in previous lease period, - // should have been cleaned in slots pallet. - let _ = Self::allocate_temporary_slot_leases(lease_period_index); - 0 - } -} diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index 4d7f3202e216..0aa8f5539979 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -42,8 +42,9 @@ use primitives::v1::{ Signature, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, }; use runtime_common::{ - auctions, crowdloan, impls::ToAuthor, paras_registrar, paras_sudo_wrapper, slots, xcm_sender, - BlockHashCount, BlockLength, BlockWeights, RocksDbWeight, SlowAdjustingFeeUpdate, + assigned_slots, auctions, crowdloan, impls::ToAuthor, paras_registrar, paras_sudo_wrapper, + slots, xcm_sender, BlockHashCount, BlockLength, BlockWeights, RocksDbWeight, + SlowAdjustingFeeUpdate, }; use runtime_parachains::{self, runtime_api_impl::v1 as runtime_api_impl}; use scale_info::TypeInfo; @@ -219,7 +220,8 @@ construct_runtime! { Auctions: auctions::{Pallet, Call, Storage, Event}, Crowdloan: crowdloan::{Pallet, Call, Storage, Event}, Slots: slots::{Pallet, Call, Storage, Event}, - ParasSudoWrapper: paras_sudo_wrapper::{Pallet, Call, Storage, Event}, + ParasSudoWrapper: paras_sudo_wrapper::{Pallet, Call}, + AssignedSlots: assigned_slots::{Pallet, Call, Storage, Event}, // Sudo Sudo: pallet_sudo::{Pallet, Call, Storage, Event, Config}, @@ -753,6 +755,8 @@ impl parachains_initializer::Config for Runtime { type ForceOrigin = EnsureRoot; } +impl paras_sudo_wrapper::Config for Runtime {} + parameter_types! { pub const PermanentSlotLeasePeriodLength: u32 = 26; pub const TemporarySlotLeasePeriodLength: u32 = 1; @@ -760,8 +764,9 @@ parameter_types! { pub const MaxTemporarySlotPerLeasePeriod: u32 = 5; } -impl paras_sudo_wrapper::Config for Runtime { +impl assigned_slots::Config for Runtime { type Event = Event; + type AssignSlotOrigin = EnsureRoot; type Leaser = Slots; type PermanentSlotLeasePeriodLength = PermanentSlotLeasePeriodLength; type TemporarySlotLeasePeriodLength = TemporarySlotLeasePeriodLength; @@ -1089,8 +1094,9 @@ impl InstanceFilter for ProxyType { fn filter(&self, c: &Call) -> bool { match self { ProxyType::Any => true, - ProxyType::CancelProxy => - matches!(c, Call::Proxy(pallet_proxy::Call::reject_announcement { .. })), + ProxyType::CancelProxy => { + matches!(c, Call::Proxy(pallet_proxy::Call::reject_announcement { .. })) + }, ProxyType::Auction => matches!( c, Call::Auctions { .. } | diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index 0496fbff44f6..30067c87b96d 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -30,9 +30,10 @@ use primitives::v1::{ ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, }; use runtime_common::{ - auctions, crowdloan, impls::ToAuthor, paras_registrar, paras_sudo_wrapper, slots, xcm_sender, - BlockHashCount, BlockLength, BlockWeights, CurrencyToVote, OffchainSolutionLengthLimit, - OffchainSolutionWeightLimit, RocksDbWeight, SlowAdjustingFeeUpdate, + assigned_slots, auctions, crowdloan, impls::ToAuthor, paras_registrar, paras_sudo_wrapper, + slots, xcm_sender, BlockHashCount, BlockLength, BlockWeights, CurrencyToVote, + OffchainSolutionLengthLimit, OffchainSolutionWeightLimit, RocksDbWeight, + SlowAdjustingFeeUpdate, }; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; @@ -852,15 +853,18 @@ impl parachains_initializer::Config for Runtime { type ForceOrigin = EnsureRoot; } +impl paras_sudo_wrapper::Config for Runtime {} + parameter_types! { - pub const PermanentSlotLeasePeriodLength: u32 = 13; + pub const PermanentSlotLeasePeriodLength: u32 = 26; pub const TemporarySlotLeasePeriodLength: u32 = 1; pub const MaxPermanentSlots: u32 = 5; pub const MaxTemporarySlotPerLeasePeriod: u32 = 5; } -impl paras_sudo_wrapper::Config for Runtime { +impl assigned_slots::Config for Runtime { type Event = Event; + type AssignSlotOrigin = EnsureRoot; type Leaser = Slots; type PermanentSlotLeasePeriodLength = PermanentSlotLeasePeriodLength; type TemporarySlotLeasePeriodLength = TemporarySlotLeasePeriodLength; @@ -1111,9 +1115,10 @@ construct_runtime! { // Parachain Onboarding Pallets. Start indices at 60 to leave room. Registrar: paras_registrar::{Pallet, Call, Storage, Event, Config} = 60, Slots: slots::{Pallet, Call, Storage, Event} = 61, - ParasSudoWrapper: paras_sudo_wrapper::{Pallet, Call, Event} = 62, + ParasSudoWrapper: paras_sudo_wrapper::{Pallet, Call} = 62, Auctions: auctions::{Pallet, Call, Storage, Event} = 63, Crowdloan: crowdloan::{Pallet, Call, Storage, Event} = 64, + AssignedSlots: assigned_slots::{Pallet, Call, Storage, Event} = 65, // Pallet for sending XCM. XcmPallet: pallet_xcm::{Pallet, Call, Storage, Event, Origin} = 99, From 2b4d20f2aefb2f2d4ca40b781a569d8bd93ee8a4 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Mon, 27 Sep 2021 19:41:42 +0200 Subject: [PATCH 06/23] Formatting --- runtime/common/src/assigned_slots.rs | 2 +- runtime/common/src/paras_sudo_wrapper.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index 7294e5517a77..b5c6397b1ac0 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -72,7 +72,7 @@ pub mod pallet { type Event: From> + IsType<::Event>; /// Origin for assigning slots - type AssignSlotOrigin: EnsureOrigin<::Origin>; + type AssignSlotOrigin: EnsureOrigin<::Origin>; /// The type representing the leasing system. type Leaser: Leaser; diff --git a/runtime/common/src/paras_sudo_wrapper.rs b/runtime/common/src/paras_sudo_wrapper.rs index 3f308b81b4c9..a3a0af459043 100644 --- a/runtime/common/src/paras_sudo_wrapper.rs +++ b/runtime/common/src/paras_sudo_wrapper.rs @@ -168,9 +168,8 @@ pub mod pallet { let config = >::config(); >::queue_downward_message(&config, id, xcm.encode()).map_err(|e| match e { - dmp::QueueDownwardMessageError::ExceedsMaxMessageSize => { - Error::::ExceedsMaxMessageSize.into() - } + dmp::QueueDownwardMessageError::ExceedsMaxMessageSize => + Error::::ExceedsMaxMessageSize.into(), }) } From 5115e25fa83ce291deb2a7aa488b1a1aac29e380 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Mon, 27 Sep 2021 19:55:40 +0200 Subject: [PATCH 07/23] Parachain downgrade logic --- runtime/common/src/assigned_slots.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index b5c6397b1ac0..761d956d3a3e 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -130,6 +130,8 @@ pub mod pallet { NotParathread, /// Cannot upgrade parathread. CannotUpgrade, + /// Cannot downgrade parachain. + CannotDowngrade, /// Permanent or Temporary slot already assigned. SlotAlreadyAssigned, /// Permanent or Temporary slot has not been assigned. @@ -284,7 +286,8 @@ pub mod pallet { // Force downgrade to parathread (if needed) before end of lease period if paras::Pallet::::lifecycle(id) == Some(ParaLifecycle::Parachain) { - // Self::sudo_schedule_parachain_downgrade(origin, id)?; + runtime_parachains::schedule_parachain_downgrade::(id) + .map_err(|_| Error::::CannotDowngrade)?; } Ok(()) From 18d2cc8f5746697e793a20fd409381658839d1d5 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Mon, 27 Sep 2021 20:09:57 +0200 Subject: [PATCH 08/23] Pallet doc comment --- runtime/common/src/assigned_slots.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index 761d956d3a3e..d64b740e9a5e 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -14,7 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! A simple wrapper allowing `Sudo` to call into `paras` routines. +//! This pallet allows to assign permanent (long-lived) or temporary +//! (short-lived) parachain slots to paras, leveraging the existing +//! parachain slot lease mechanism. Temporary slots are given turns +//! in a fair (though best-effort) manner. +//! The dispatchables must be called from the configured origin +//! (typically `Sudo` or a governance origin). +//! This pallet is mostly to be used on test relay chain (e.g. Rococo). use crate::{ slots::{self, Pallet as Slots}, From 98f90ca2dc52c009064ab60abe47d97fc78c1670 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Mon, 27 Sep 2021 20:14:23 +0200 Subject: [PATCH 09/23] Revert unnecessary changes --- runtime/common/src/paras_sudo_wrapper.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime/common/src/paras_sudo_wrapper.rs b/runtime/common/src/paras_sudo_wrapper.rs index a3a0af459043..3604f6453af3 100644 --- a/runtime/common/src/paras_sudo_wrapper.rs +++ b/runtime/common/src/paras_sudo_wrapper.rs @@ -27,7 +27,7 @@ use runtime_parachains::{ paras::{self, ParaGenesisArgs}, ump, ParaLifecycle, }; -use sp_std::prelude::*; +use sp_std::boxed::Box; #[frame_support::pallet] pub mod pallet { @@ -70,6 +70,9 @@ pub mod pallet { CannotDowngrade, } + #[pallet::hooks] + impl Hooks> for Pallet {} + #[pallet::call] impl Pallet { /// Assign a permanent parachain slot From e9de5ac42b55d14ae7683ae6319c278f0c2f143c Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Fri, 1 Oct 2021 15:11:33 +0200 Subject: [PATCH 10/23] Fix few issues, tweak temp slots allocation logic; add a bunch of tests --- runtime/common/src/assigned_slots.rs | 703 ++++++++++++++++++++++++++- 1 file changed, 687 insertions(+), 16 deletions(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index d64b740e9a5e..63497cd2c836 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -24,7 +24,7 @@ use crate::{ slots::{self, Pallet as Slots}, - traits::{Leaser, Registrar}, + traits::{LeaseError, Leaser, Registrar}, }; use frame_support::{pallet_prelude::*, traits::Currency}; use frame_system::pallet_prelude::*; @@ -192,7 +192,7 @@ pub mod pallet { ); ensure!( - PermanentSlotCount::::get() + 1 < T::MaxPermanentSlots::get(), + PermanentSlotCount::::get() + 1 <= T::MaxPermanentSlots::get(), Error::::MaxPermanentSlotsExceeded ); @@ -202,7 +202,8 @@ pub mod pallet { manager, current_lease_period, T::PermanentSlotLeasePeriodLength::get().into(), - )?; + ) + .map_err(|_| Error::::CannotUpgrade)?; PermanentSlots::::insert(id, Some(current_lease_period)); *count = count.checked_add(One::one()).ok_or(ArithmeticError::Overflow)?; Ok(()) @@ -316,13 +317,10 @@ impl Pallet { // Active slot lease active_temp_slots += 1; } - Some(last_lease) => { + Some(last_lease) // Slot w/ past lease, only consider it every other slot lease period (times period_count) - if last_lease <= - lease_period_index - (slot.period_count.saturating_mul(2u32.into())) - { + if last_lease.saturating_add(slot.period_count.saturating_mul(2u32.into())) <= lease_period_index => { pending_temp_slots.push((para, slot)); - } }, None if slot.period_begin <= lease_period_index => { // Slot hasn't had a lease yet @@ -339,7 +337,13 @@ impl Pallet { !pending_temp_slots.is_empty() { // Sort by lease_count, favoring slots that had no or less turns first - pending_temp_slots.sort_unstable_by_key(|(_id, s)| s.lease_count); + // (then by last_lease index, and then Para ID) + pending_temp_slots.sort_by(|a, b| { + a.1.lease_count + .cmp(&b.1.lease_count) + .then_with(|| a.1.last_lease.cmp(&b.1.last_lease)) + .then_with(|| a.0.cmp(&b.0)) + }); let slots_to_be_upgraded = pending_temp_slots .iter() @@ -349,10 +353,9 @@ impl Pallet { for (id, temp_slot) in slots_to_be_upgraded.iter() { TemporarySlots::::try_mutate::<_, _, Error, _>(id, |s| { // Configure temp slot lease - T::Leaser::lease_out( + Self::configure_slot_lease( *id, - &temp_slot.manager, - BalanceOf::::zero(), + temp_slot.manager.clone(), lease_period_index, temp_slot.period_count, ) @@ -383,11 +386,8 @@ impl Pallet { manager: T::AccountId, lease_period: LeasePeriodOf, lease_duration: LeasePeriodOf, - ) -> DispatchResult { + ) -> Result<(), LeaseError> { T::Leaser::lease_out(para, &manager, BalanceOf::::zero(), lease_period, lease_duration) - .map_err(|_| Error::::CannotUpgrade)?; - - Ok(()) } fn has_permanent_slot(id: ParaId) -> bool { @@ -413,3 +413,674 @@ impl Pallet { 0 } } + +/// tests for this pallet +#[cfg(test)] +mod tests { + use super::*; + + use crate::{assigned_slots, mock::TestRegistrar, slots}; + use frame_support::{assert_noop, assert_ok, parameter_types}; + use frame_system::EnsureRoot; + use pallet_balances; + use primitives::v1::{BlockNumber, Header}; + use runtime_parachains::{ + configuration as parachains_configuration, paras as parachains_paras, + shared as parachains_shared, + }; + use sp_core::H256; + use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + DispatchError::BadOrigin, + }; + + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + type Block = frame_system::mocking::MockBlock; + + frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Configuration: parachains_configuration::{Pallet, Call, Storage, Config}, + ParasShared: parachains_shared::{Pallet, Call, Storage}, + Parachains: parachains_paras::{Pallet, Origin, Call, Storage, Config, Event}, + Slots: slots::{Pallet, Call, Storage, Event}, + AssignedSlots: assigned_slots::{Pallet, Call, Storage, Event}, + } + ); + + parameter_types! { + pub const BlockHashCount: u32 = 250; + } + impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + } + + parameter_types! { + pub const ExistentialDeposit: u64 = 1; + } + + impl pallet_balances::Config for Test { + type Balance = u64; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + } + + impl parachains_configuration::Config for Test { + type WeightInfo = parachains_configuration::weights::WeightInfo; + } + + impl parachains_paras::Config for Test { + type Origin = Origin; + type Event = Event; + type WeightInfo = parachains_paras::weights::WeightInfo; + } + + impl parachains_shared::Config for Test {} + + parameter_types! { + pub const LeasePeriod: BlockNumber = 3; + pub const ParaDeposit: u64 = 1; + } + + impl slots::Config for Test { + type Event = Event; + type Currency = Balances; + type Registrar = TestRegistrar; + type LeasePeriod = LeasePeriod; + type WeightInfo = crate::slots::TestWeightInfo; + } + + parameter_types! { + pub const PermanentSlotLeasePeriodLength: u32 = 3; + pub const TemporarySlotLeasePeriodLength: u32 = 2; + pub const MaxPermanentSlots: u32 = 2; + pub const MaxTemporarySlotPerLeasePeriod: u32 = 2; + } + + impl assigned_slots::Config for Test { + type Event = Event; + type AssignSlotOrigin = EnsureRoot; + type Leaser = Slots; + type PermanentSlotLeasePeriodLength = PermanentSlotLeasePeriodLength; + type TemporarySlotLeasePeriodLength = TemporarySlotLeasePeriodLength; + type MaxPermanentSlots = MaxPermanentSlots; + type MaxTemporarySlotPerLeasePeriod = MaxTemporarySlotPerLeasePeriod; + } + + // This function basically just builds a genesis storage key/value store according to + // our desired mock up. + pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + } + .assimilate_storage(&mut t) + .unwrap(); + t.into() + } + + fn run_to_block(n: BlockNumber) { + while System::block_number() < n { + let mut block = System::block_number(); + // on_finalize hooks + AssignedSlots::on_finalize(block); + Slots::on_finalize(block); + Parachains::on_finalize(block); + ParasShared::on_finalize(block); + Configuration::on_finalize(block); + Balances::on_finalize(block); + System::on_finalize(block); + // Set next block + System::set_block_number(block + 1); + block = System::block_number(); + // on_initialize hooks + System::on_initialize(block); + Balances::on_initialize(block); + Configuration::on_initialize(block); + ParasShared::on_initialize(block); + Parachains::on_initialize(block); + Slots::on_initialize(block); + AssignedSlots::on_initialize(block); + } + } + + #[test] + fn basic_setup_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_eq!(Slots::lease_period(), 3); + assert_eq!(Slots::lease_period_index(), 0); + assert_eq!(Slots::deposit_held(1.into(), &1), 0); + + run_to_block(3); + assert_eq!(Slots::lease_period_index(), 1); + }); + } + + #[test] + fn assign_perm_slot_fails_for_unknown_para() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_noop!( + AssignedSlots::assign_perm_parachain_slot(Origin::root(), ParaId::from(1),), + Error::::ParaDoesntExist + ); + }); + } + + #[test] + fn assign_perm_slot_fails_for_invalid_origin() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_noop!( + AssignedSlots::assign_perm_parachain_slot(Origin::signed(1), ParaId::from(1),), + BadOrigin + ); + }); + } + + #[test] + fn assign_perm_slot_fails_when_not_parathread() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(TestRegistrar::::register( + 1, + ParaId::from(1), + Default::default(), + Default::default() + )); + assert_ok!(TestRegistrar::::make_parachain(ParaId::from(1))); + + assert_noop!( + AssignedSlots::assign_perm_parachain_slot(Origin::root(), ParaId::from(1),), + Error::::NotParathread + ); + }); + } + + #[test] + fn assign_perm_slot_fails_when_existing_lease() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(TestRegistrar::::register( + 1, + ParaId::from(1), + Default::default(), + Default::default() + )); + + // Register lease in current lease period + assert_ok!(Slots::lease_out(ParaId::from(1), &1, 1, 1, 1)); + // Try to assign a perm slot in current period fails + assert_noop!( + AssignedSlots::assign_perm_parachain_slot(Origin::root(), ParaId::from(1),), + Error::::OngoingLeaseExists + ); + + // Cleanup + assert_ok!(Slots::clear_all_leases(Origin::root(), 1.into())); + + // Register lease for next lease period + assert_ok!(Slots::lease_out(ParaId::from(1), &1, 1, 2, 1)); + // Should be detected and also fail + assert_noop!( + AssignedSlots::assign_perm_parachain_slot(Origin::root(), ParaId::from(1),), + Error::::OngoingLeaseExists + ); + }); + } + + #[test] + fn assign_perm_slot_fails_when_max_perm_slots_exceeded() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(TestRegistrar::::register( + 1, + ParaId::from(1), + Default::default(), + Default::default() + )); + + assert_ok!(TestRegistrar::::register( + 2, + ParaId::from(2), + Default::default(), + Default::default() + )); + + assert_ok!(TestRegistrar::::register( + 3, + ParaId::from(3), + Default::default(), + Default::default() + )); + + assert_ok!(AssignedSlots::assign_perm_parachain_slot(Origin::root(), ParaId::from(1),)); + assert_ok!(AssignedSlots::assign_perm_parachain_slot(Origin::root(), ParaId::from(2),)); + assert_eq!(AssignedSlots::permanent_slot_count(), 2); + + assert_noop!( + AssignedSlots::assign_perm_parachain_slot(Origin::root(), ParaId::from(3),), + Error::::MaxPermanentSlotsExceeded + ); + }); + } + + #[test] + fn assign_perm_slot_succeeds_for_parathread() { + new_test_ext().execute_with(|| { + let mut block = 1; + run_to_block(block); + assert_ok!(TestRegistrar::::register( + 1, + ParaId::from(1), + Default::default(), + Default::default() + )); + + assert_eq!(AssignedSlots::permanent_slot_count(), 0); + assert_eq!(AssignedSlots::permanent_slots(ParaId::from(1)), None); + + assert_ok!(AssignedSlots::assign_perm_parachain_slot(Origin::root(), ParaId::from(1),)); + + // Para is a parachain for PermanentSlotLeasePeriodLength * LeasePeriod blocks + while block < 9 { + println!("block #{}", block); + + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), true); + + assert_eq!(AssignedSlots::permanent_slot_count(), 1); + assert_eq!(AssignedSlots::has_permanent_slot(ParaId::from(1)), true); + assert_eq!(AssignedSlots::permanent_slots(ParaId::from(1)), Some(0)); + + assert_eq!(Slots::already_leased(ParaId::from(1), 0, 2), true); + + block += 1; + run_to_block(block); + } + + // Para lease ended, downgraded back to parathread + assert_eq!(TestRegistrar::::is_parathread(ParaId::from(1)), true); + assert_eq!(Slots::already_leased(ParaId::from(1), 0, 5), false); + }); + } + + #[test] + fn assign_temp_slot_fails_for_unknown_para() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_noop!( + AssignedSlots::assign_temp_parachain_slot( + Origin::root(), + ParaId::from(1), + SlotLeasePeriodStart::Current + ), + Error::::ParaDoesntExist + ); + }); + } + + #[test] + fn assign_temp_slot_fails_for_invalid_origin() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_noop!( + AssignedSlots::assign_temp_parachain_slot( + Origin::signed(1), + ParaId::from(1), + SlotLeasePeriodStart::Current + ), + BadOrigin + ); + }); + } + + #[test] + fn assign_temp_slot_fails_when_not_parathread() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(TestRegistrar::::register( + 1, + ParaId::from(1), + Default::default(), + Default::default() + )); + assert_ok!(TestRegistrar::::make_parachain(ParaId::from(1))); + + assert_noop!( + AssignedSlots::assign_temp_parachain_slot( + Origin::root(), + ParaId::from(1), + SlotLeasePeriodStart::Current + ), + Error::::NotParathread + ); + }); + } + + #[test] + fn assign_temp_slot_fails_when_existing_lease() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(TestRegistrar::::register( + 1, + ParaId::from(1), + Default::default(), + Default::default() + )); + + // Register lease in current lease period + assert_ok!(Slots::lease_out(ParaId::from(1), &1, 1, 1, 1)); + // Try to assign a perm slot in current period fails + assert_noop!( + AssignedSlots::assign_temp_parachain_slot( + Origin::root(), + ParaId::from(1), + SlotLeasePeriodStart::Current + ), + Error::::OngoingLeaseExists + ); + + // Cleanup + assert_ok!(Slots::clear_all_leases(Origin::root(), 1.into())); + + // Register lease for next lease period + assert_ok!(Slots::lease_out(ParaId::from(1), &1, 1, 2, 1)); + // Should be detected and also fail + assert_noop!( + AssignedSlots::assign_temp_parachain_slot( + Origin::root(), + ParaId::from(1), + SlotLeasePeriodStart::Current + ), + Error::::OngoingLeaseExists + ); + }); + } + + #[test] + fn assign_temp_slot_succeeds_for_single_parathread() { + new_test_ext().execute_with(|| { + let mut block = 1; + run_to_block(block); + assert_ok!(TestRegistrar::::register( + 1, + ParaId::from(1), + Default::default(), + Default::default() + )); + + assert_eq!(AssignedSlots::temporary_slots(ParaId::from(1)), None); + + assert_ok!(AssignedSlots::assign_temp_parachain_slot( + Origin::root(), + ParaId::from(1), + SlotLeasePeriodStart::Current + )); + + // Block 1-5 + // Para is a parachain for TemporarySlotLeasePeriodLength * LeasePeriod blocks + while block < 6 { + println!("block #{}", block); + println!("lease period #{}", Slots::lease_period_index()); + println!("lease {:?}", Slots::lease(ParaId::from(1))); + + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), true); + + assert_eq!(AssignedSlots::has_temporary_slot(ParaId::from(1)), true); + assert_eq!( + AssignedSlots::temporary_slots(ParaId::from(1)), + Some(ParachainTemporarySlot { + manager: 1, + period_begin: 0, + period_count: 2, // TemporarySlotLeasePeriodLength + last_lease: Some(0), + lease_count: 1 + }) + ); + + assert_eq!(Slots::already_leased(ParaId::from(1), 0, 1), true); + + block += 1; + run_to_block(block); + } + + // Block 6 + println!("block #{}", block); + println!("lease period #{}", Slots::lease_period_index()); + println!("lease {:?}", Slots::lease(ParaId::from(1))); + + // Para lease ended, downgraded back to parathread + assert_eq!(TestRegistrar::::is_parathread(ParaId::from(1)), true); + assert_eq!(Slots::already_leased(ParaId::from(1), 0, 3), false); + + // Block 12 + // Para should get a turn after TemporarySlotLeasePeriodLength * LeasePeriod blocks + run_to_block(12); + println!("block #{}", block); + println!("lease period #{}", Slots::lease_period_index()); + println!("lease {:?}", Slots::lease(ParaId::from(1))); + + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), true); + assert_eq!(Slots::already_leased(ParaId::from(1), 4, 5), true); + }); + } + + #[test] + fn assign_temp_slot_succeeds_for_multiple_parathreads() { + new_test_ext().execute_with(|| { + // Block 1, Period 0 + run_to_block(1); + + // Register 6 paras & a temp slot for each + // (3 slots in current lease period, 3 in the next one) + for n in 0..=5 { + assert_ok!(TestRegistrar::::register( + n, + ParaId::from(n as u32), + Default::default(), + Default::default() + )); + + assert_ok!(AssignedSlots::assign_temp_parachain_slot( + Origin::root(), + ParaId::from(n as u32), + if (n % 2).is_zero() { + SlotLeasePeriodStart::Current + } else { + SlotLeasePeriodStart::Next + } + )); + } + + // Block 1-5, Period 0-1 + for n in 1..=5 { + if n > 1 { + run_to_block(n); + } + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), true); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2)), true); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5)), false); + } + + // Block 6-11, Period 2-3 + for n in 6..=11 { + run_to_block(n); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), true); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3)), true); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5)), false); + } + + // Block 12-17, Period 4-5 + for n in 12..=17 { + run_to_block(n); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4)), true); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5)), true); + } + + // Block 18-23, Period 6-7 + for n in 18..=23 { + run_to_block(n); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), true); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2)), true); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5)), false); + } + + // Block 24-29, Period 8-9 + for n in 24..=29 { + run_to_block(n); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), true); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3)), true); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5)), false); + } + + // Block 30-35, Period 10-11 + for n in 30..=35 { + run_to_block(n); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3)), false); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4)), true); + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5)), true); + } + }); + } + + #[test] + fn unassign_slot_fails_for_unknown_para() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_noop!( + AssignedSlots::unassign_parachain_slot(Origin::root(), ParaId::from(1),), + Error::::SlotNotAssigned + ); + }); + } + + #[test] + fn unassign_slot_fails_for_invalid_origin() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_noop!( + AssignedSlots::assign_perm_parachain_slot(Origin::signed(1), ParaId::from(1),), + BadOrigin + ); + }); + } + + #[test] + fn unassign_perm_slot_succeeds() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(TestRegistrar::::register( + 1, + ParaId::from(1), + Default::default(), + Default::default() + )); + + assert_ok!(AssignedSlots::assign_perm_parachain_slot(Origin::root(), ParaId::from(1),)); + + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), true); + + assert_ok!(AssignedSlots::unassign_parachain_slot(Origin::root(), ParaId::from(1),)); + + assert_eq!(AssignedSlots::permanent_slot_count(), 0); + assert_eq!(AssignedSlots::has_permanent_slot(ParaId::from(1)), false); + assert_eq!(AssignedSlots::permanent_slots(ParaId::from(1)), None); + + assert_eq!(Slots::already_leased(ParaId::from(1), 0, 2), false); + }); + } + + #[test] + fn unassign_temp_slot_succeeds() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(TestRegistrar::::register( + 1, + ParaId::from(1), + Default::default(), + Default::default() + )); + + assert_ok!(AssignedSlots::assign_temp_parachain_slot( + Origin::root(), + ParaId::from(1), + SlotLeasePeriodStart::Current + )); + + assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), true); + + assert_ok!(AssignedSlots::unassign_parachain_slot(Origin::root(), ParaId::from(1),)); + + assert_eq!(AssignedSlots::has_temporary_slot(ParaId::from(1)), false); + assert_eq!(AssignedSlots::temporary_slots(ParaId::from(1)), None); + + assert_eq!(Slots::already_leased(ParaId::from(1), 0, 1), false); + }); + } +} From 27e2d4ed3d29f710341115a1c36943662bf168c7 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Tue, 5 Oct 2021 11:13:33 +0200 Subject: [PATCH 11/23] Address review comments; track active temp slots --- runtime/common/src/assigned_slots.rs | 287 ++++++++++++++++++++++----- runtime/rococo/src/lib.rs | 2 + runtime/westend/src/lib.rs | 2 + 3 files changed, 241 insertions(+), 50 deletions(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index 63497cd2c836..2bc7ab5038b4 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -1,4 +1,4 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. +// Copyright 2021 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -20,10 +20,11 @@ //! in a fair (though best-effort) manner. //! The dispatchables must be called from the configured origin //! (typically `Sudo` or a governance origin). -//! This pallet is mostly to be used on test relay chain (e.g. Rococo). +//! This pallet should not be used on a production relay chain, +//! only on a test relay chain (e.g. Rococo). use crate::{ - slots::{self, Pallet as Slots}, + slots::{self, Pallet as Slots, WeightInfo}, traits::{LeaseError, Leaser, Registrar}, }; use frame_support::{pallet_prelude::*, traits::Currency}; @@ -34,7 +35,6 @@ use primitives::v1::Id as ParaId; use runtime_parachains::{ configuration, paras::{self}, - ParaLifecycle, }; use scale_info::TypeInfo; use sp_runtime::{ @@ -43,19 +43,30 @@ use sp_runtime::{ }; use sp_std::prelude::*; +/// Lease period an assigned slot should start from (current, or next one). #[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo)] pub enum SlotLeasePeriodStart { Current, Next, } +/// Information about a temporary parachain slot. #[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, TypeInfo)] pub struct ParachainTemporarySlot { - manager: AccountId, - period_begin: LeasePeriod, - period_count: LeasePeriod, - last_lease: Option, - lease_count: u32, + /// Manager account of the para. + pub manager: AccountId, + /// Lease period the parachain slot should ideally start from, + /// As slot are allocated in a best-effort manner, this could be later, + /// but not earlier than the specified period. + pub period_begin: LeasePeriod, + /// Number of lease period the slot lease will last. + /// This is set to the value configured in `TemporarySlotLeasePeriodLength`. + pub period_count: LeasePeriod, + /// Last lease period this slot had a turn in (incl. current). + /// This is set to the beginning period of a slot. + pub last_lease: Option, + /// Number of leases this temporary slot had (incl. current). + pub lease_count: u32, } type BalanceOf = <<::Leaser as Leaser>::Currency as Currency< @@ -77,38 +88,45 @@ pub mod pallet { /// The overarching event type. type Event: From> + IsType<::Event>; - /// Origin for assigning slots + /// Origin for assigning slots. type AssignSlotOrigin: EnsureOrigin<::Origin>; /// The type representing the leasing system. type Leaser: Leaser; - /// The number of lease periods a permanent parachain slot lasts + /// The number of lease periods a permanent parachain slot lasts. #[pallet::constant] type PermanentSlotLeasePeriodLength: Get; - /// The number of lease periods a temporary parachain slot lasts + /// The number of lease periods a temporary parachain slot lasts. #[pallet::constant] type TemporarySlotLeasePeriodLength: Get; - /// The max number of permanent slots that can be assigned + /// The max number of permanent slots that can be assigned. #[pallet::constant] type MaxPermanentSlots: Get; - /// The max number of temporary slots to be scheduled per lease periods + /// The max number of temporary slots that can be assigned. + #[pallet::constant] + type MaxTemporarySlots: Get; + + /// The max number of temporary slots to be scheduled per lease periods. #[pallet::constant] type MaxTemporarySlotPerLeasePeriod: Get; } + /// Assigned permanent slots, with their start lease period. #[pallet::storage] #[pallet::getter(fn permanent_slots)] pub type PermanentSlots = StorageMap<_, Twox64Concat, ParaId, Option>, ValueQuery>; + /// Number of assigned (and active) permanent slots. #[pallet::storage] #[pallet::getter(fn permanent_slot_count)] pub type PermanentSlotCount = StorageValue<_, u32, ValueQuery>; + /// Assigned temporary slots. #[pallet::storage] #[pallet::getter(fn temporary_slots)] pub type TemporarySlots = StorageMap< @@ -119,6 +137,16 @@ pub mod pallet { ValueQuery, >; + /// Number of assigned temporary slots. + #[pallet::storage] + #[pallet::getter(fn temporary_slot_count)] + pub type TemporarySlotCount = StorageValue<_, u32, ValueQuery>; + + /// Number of active temporary slots in current slot lease period. + #[pallet::storage] + #[pallet::getter(fn active_temporary_slot_count)] + pub type ActiveTemporarySlotCount = StorageValue<_, u32, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -146,6 +174,8 @@ pub mod pallet { OngoingLeaseExists, // Maximum number of permanent slots exceeded MaxPermanentSlotsExceeded, + // Maximum number of temporary slots exceeded + MaxTemporarySlotsExceeded, } #[pallet::hooks] @@ -163,8 +193,8 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Assign a permanent parachain slot - #[pallet::weight((1_000, DispatchClass::Operational))] + /// Assign a permanent parachain slot and immediately create a lease for it. + #[pallet::weight((1_000 + ::WeightInfo::force_lease(), DispatchClass::Operational))] pub fn assign_perm_parachain_slot(origin: OriginFor, id: ParaId) -> DispatchResult { T::AssignSlotOrigin::ensure_origin(origin)?; @@ -192,11 +222,12 @@ pub mod pallet { ); ensure!( - PermanentSlotCount::::get() + 1 <= T::MaxPermanentSlots::get(), + PermanentSlotCount::::get() < T::MaxPermanentSlots::get(), Error::::MaxPermanentSlotsExceeded ); >::try_mutate(|count| -> DispatchResult { + // Permanent slot assignment fails if a lease cannot be created Self::configure_slot_lease( id, manager, @@ -204,8 +235,8 @@ pub mod pallet { T::PermanentSlotLeasePeriodLength::get().into(), ) .map_err(|_| Error::::CannotUpgrade)?; - PermanentSlots::::insert(id, Some(current_lease_period)); *count = count.checked_add(One::one()).ok_or(ArithmeticError::Overflow)?; + PermanentSlots::::insert(id, Some(current_lease_period)); Ok(()) })?; @@ -213,8 +244,9 @@ pub mod pallet { Ok(()) } - /// Assign a temporary parachain slot - #[pallet::weight((1_000, DispatchClass::Operational))] + /// Assign a temporary parachain slot. The function tries to create a lease for it + /// immediately if `SlotLeasePeriodStart::Current` is specified, and ... + #[pallet::weight((1_000 + ::WeightInfo::force_lease(), DispatchClass::Operational))] pub fn assign_temp_parachain_slot( origin: OriginFor, id: ParaId, @@ -245,23 +277,62 @@ pub mod pallet { Error::::OngoingLeaseExists ); - TemporarySlots::::insert( - id, - Some(ParachainTemporarySlot { - manager, - period_begin: match lease_period_start { - SlotLeasePeriodStart::Current => current_lease_period, - SlotLeasePeriodStart::Next => current_lease_period + One::one(), - }, - period_count: T::TemporarySlotLeasePeriodLength::get().into(), - last_lease: None, - lease_count: 0, - }), + ensure!( + TemporarySlotCount::::get() < T::MaxTemporarySlots::get(), + Error::::MaxTemporarySlotsExceeded ); - if lease_period_start == SlotLeasePeriodStart::Current { - Self::allocate_temporary_slot_leases(current_lease_period)?; - } + let mut temp_slot = ParachainTemporarySlot { + manager: manager.clone(), + period_begin: match lease_period_start { + SlotLeasePeriodStart::Current => current_lease_period, + SlotLeasePeriodStart::Next => current_lease_period + One::one(), + }, + period_count: T::TemporarySlotLeasePeriodLength::get().into(), + last_lease: None, + lease_count: 0, + }; + + >::try_mutate(|count| -> DispatchResult { + *count = count.checked_add(One::one()).ok_or(ArithmeticError::Overflow)?; + + if lease_period_start == SlotLeasePeriodStart::Current && + Self::active_temporary_slot_count() < + T::MaxTemporarySlotPerLeasePeriod::get() + { + // Try to allocate slot directly + match Self::configure_slot_lease( + id, + manager, + temp_slot.period_begin, + temp_slot.period_count, + ) { + Ok(_) => { + temp_slot.last_lease = Some(temp_slot.period_begin); + temp_slot.lease_count += 1; + ActiveTemporarySlotCount::::try_mutate::<_, ArithmeticError, _>( + |count| { + *count = count + .checked_add(One::one()) + .ok_or(ArithmeticError::Overflow)?; + Ok(()) + }, + )?; + }, + Err(err) => { + // Treat failed lease creationg as warning .. slot will be allocated a lease + // in a subsequent lease period by the `allocate_temporary_slot_leases` function. + log::warn!(target: "assigned_slots", + "Failed to allocate a temp slot for para {:?} at period {:?}: {:?}", + id, current_lease_period, err + ); + }, + } + } + + TemporarySlots::::insert(id, Some(temp_slot)); + Ok(()) + })?; Self::deposit_event(Event::::TemporarySlotAssigned(id)); @@ -269,7 +340,7 @@ pub mod pallet { } /// Unassign a permanent or temporary parachain slot - #[pallet::weight((1_000, DispatchClass::Operational))] + #[pallet::weight((1_000 + ::WeightInfo::clear_all_leases(), DispatchClass::Operational))] pub fn unassign_parachain_slot(origin: OriginFor, id: ParaId) -> DispatchResult { T::AssignSlotOrigin::ensure_origin(origin.clone())?; @@ -278,23 +349,43 @@ pub mod pallet { Error::::SlotNotAssigned ); + // Check & cache para status before we clear the lease + let is_parachain = Self::is_parachain(id); + if PermanentSlots::::contains_key(id) { + // Remove perm slot >::try_mutate(|count| -> DispatchResult { - *count = count.checked_sub(1).ok_or(ArithmeticError::Underflow)?; + Self::clear_slot_leases(origin.clone(), id)?; + *count = count.saturating_sub(One::one()); + PermanentSlots::::remove(id); Ok(()) })?; - PermanentSlots::::remove(id); } else if TemporarySlots::::contains_key(id) { - TemporarySlots::::remove(id); + // Remove temp slot + >::try_mutate(|count| -> DispatchResult { + Self::clear_slot_leases(origin.clone(), id)?; + *count = count.saturating_sub(One::one()); + TemporarySlots::::remove(id); + if is_parachain { + >::mutate(|active_count| { + *active_count = active_count.saturating_sub(One::one()) + }); + } + Ok(()) + })?; } - // Clean any para lease - Self::clear_slot_leases(origin.clone(), id)?; - // Force downgrade to parathread (if needed) before end of lease period - if paras::Pallet::::lifecycle(id) == Some(ParaLifecycle::Parachain) { - runtime_parachains::schedule_parachain_downgrade::(id) - .map_err(|_| Error::::CannotDowngrade)?; + if is_parachain { + if let Err(err) = runtime_parachains::schedule_parachain_downgrade::(id) { + // Treat failed downgrade as warning .. slot lease has been cleared, + // so the parachain will be downgraded anyway by the slots pallet + // at the end of the lease period . + log::warn!(target: "assigned_slots", + "Failed to downgrade parachain {:?} at period {:?}: {:?}", + id, Self::lease_period_index(), err + ); + } } Ok(()) @@ -303,6 +394,18 @@ pub mod pallet { } impl Pallet { + /// Allocate temporary slot leases up to `MaxTemporarySlotPerLeasePeriod` per lease period. + /// Beyong the already active temporary slot leases, this function will activate more leases + /// in the following order of preference: + /// - Assigned slots that didn't have a turn yet, though their `period_begin` has passed. + /// - Assigned slots that already had one (or more) turn(s): they will be considered for the + /// current slot lease if they weren't active in the preceding one, and will be ranked by + /// total number of lease (lower first), and then when they last a turn (older ones first). + /// If any remaining ex-aequo, we just take the para ID in ascending order as discriminator. + /// + /// Assigned slots with a `period_begin` bigger than current lease period are not considered (yet). + /// + /// The function will call out to `Leaser::lease_out` to create the appropriate slot leases. fn allocate_temporary_slot_leases(lease_period_index: LeasePeriodOf) -> DispatchResult { let mut active_temp_slots = 0u32; let mut pending_temp_slots = Vec::new(); @@ -333,6 +436,7 @@ impl Pallet { } }); + let mut newly_created_lease = 0u32; if active_temp_slots < T::MaxTemporarySlotPerLeasePeriod::get() && !pending_temp_slots.is_empty() { @@ -347,7 +451,10 @@ impl Pallet { let slots_to_be_upgraded = pending_temp_slots .iter() - .take((T::MaxTemporarySlotPerLeasePeriod::get() - active_temp_slots) as usize) + .take( + (T::MaxTemporarySlotPerLeasePeriod::get().saturating_sub(active_temp_slots)) + as usize, + ) .collect::>(); for (id, temp_slot) in slots_to_be_upgraded.iter() { @@ -370,17 +477,26 @@ impl Pallet { lease_count: temp_slot.lease_count + 1, }); + newly_created_lease += 1; + Ok(()) })?; } } + + ActiveTemporarySlotCount::::set(active_temp_slots + newly_created_lease); + Ok(()) } + /// Clear out all slot leases for both permanent & temporary slots. + /// The function merely calls out to `Slots::clear_all_leases`. fn clear_slot_leases(origin: OriginFor, id: ParaId) -> DispatchResult { Slots::::clear_all_leases(origin, id) } + /// Create a parachain slot lease based on given params. + /// The function merely calls out to `Leaser::lease_out`. fn configure_slot_lease( para: ParaId, manager: T::AccountId, @@ -390,27 +506,42 @@ impl Pallet { T::Leaser::lease_out(para, &manager, BalanceOf::::zero(), lease_period, lease_duration) } + /// Returns whether a para has been assigned a permanent slot. fn has_permanent_slot(id: ParaId) -> bool { PermanentSlots::::contains_key(id) } + /// Returns whether a para has been assigned temporary slot. fn has_temporary_slot(id: ParaId) -> bool { TemporarySlots::::contains_key(id) } + /// Returns whether a para is currently a parachain. + fn is_parachain(id: ParaId) -> bool { + T::Registrar::is_parachain(id) + } + + /// Returns current lease period index. fn lease_period_index() -> LeasePeriodOf { T::Leaser::lease_period_index() } + /// Returns current lease period. fn lease_period() -> LeasePeriodOf { T::Leaser::lease_period() } + /// Handles start of a lease period. fn manage_lease_period_start(lease_period_index: LeasePeriodOf) -> Weight { - // Note: leases that have ended in previous lease period, - // should have been cleaned in slots pallet. - let _ = Self::allocate_temporary_slot_leases(lease_period_index); - 0 + // Note: leases that have ended in previous lease period, should have been cleaned in slots pallet. + if let Err(err) = Self::allocate_temporary_slot_leases(lease_period_index) { + log::error!(target: "assigned_slots", + "Allocating slots failed for lease period {:?}, with: {:?}", + lease_period_index, err + ); + } + ::WeightInfo::force_lease() * + (T::MaxTemporarySlotPerLeasePeriod::get() as u64) } } @@ -527,6 +658,7 @@ mod tests { pub const PermanentSlotLeasePeriodLength: u32 = 3; pub const TemporarySlotLeasePeriodLength: u32 = 2; pub const MaxPermanentSlots: u32 = 2; + pub const MaxTemporarySlots: u32 = 6; pub const MaxTemporarySlotPerLeasePeriod: u32 = 2; } @@ -537,6 +669,7 @@ mod tests { type PermanentSlotLeasePeriodLength = PermanentSlotLeasePeriodLength; type TemporarySlotLeasePeriodLength = TemporarySlotLeasePeriodLength; type MaxPermanentSlots = MaxPermanentSlots; + type MaxTemporarySlots = MaxTemporarySlots; type MaxTemporarySlotPerLeasePeriod = MaxTemporarySlotPerLeasePeriod; } @@ -840,6 +973,47 @@ mod tests { }); } + #[test] + fn assign_temp_slot_fails_when_max_temp_slots_exceeded() { + new_test_ext().execute_with(|| { + run_to_block(1); + + // Register 6 paras & a temp slot for each + for n in 0..=5 { + assert_ok!(TestRegistrar::::register( + n, + ParaId::from(n as u32), + Default::default(), + Default::default() + )); + + assert_ok!(AssignedSlots::assign_temp_parachain_slot( + Origin::root(), + ParaId::from(n as u32), + SlotLeasePeriodStart::Current + )); + } + + assert_eq!(AssignedSlots::temporary_slot_count(), 6); + + // Attempt to assign one more temp slot + assert_ok!(TestRegistrar::::register( + 7, + ParaId::from(7), + Default::default(), + Default::default() + )); + assert_noop!( + AssignedSlots::assign_temp_parachain_slot( + Origin::root(), + ParaId::from(7), + SlotLeasePeriodStart::Current + ), + Error::::MaxTemporarySlotsExceeded + ); + }); + } + #[test] fn assign_temp_slot_succeeds_for_single_parathread() { new_test_ext().execute_with(|| { @@ -859,6 +1033,8 @@ mod tests { ParaId::from(1), SlotLeasePeriodStart::Current )); + assert_eq!(AssignedSlots::temporary_slot_count(), 1); + assert_eq!(AssignedSlots::active_temporary_slot_count(), 1); // Block 1-5 // Para is a parachain for TemporarySlotLeasePeriodLength * LeasePeriod blocks @@ -870,6 +1046,7 @@ mod tests { assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), true); assert_eq!(AssignedSlots::has_temporary_slot(ParaId::from(1)), true); + assert_eq!(AssignedSlots::active_temporary_slot_count(), 1); assert_eq!( AssignedSlots::temporary_slots(ParaId::from(1)), Some(ParachainTemporarySlot { @@ -895,6 +1072,7 @@ mod tests { // Para lease ended, downgraded back to parathread assert_eq!(TestRegistrar::::is_parathread(ParaId::from(1)), true); assert_eq!(Slots::already_leased(ParaId::from(1), 0, 3), false); + assert_eq!(AssignedSlots::active_temporary_slot_count(), 0); // Block 12 // Para should get a turn after TemporarySlotLeasePeriodLength * LeasePeriod blocks @@ -905,6 +1083,7 @@ mod tests { assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), true); assert_eq!(Slots::already_leased(ParaId::from(1), 4, 5), true); + assert_eq!(AssignedSlots::active_temporary_slot_count(), 1); }); } @@ -946,6 +1125,7 @@ mod tests { assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5)), false); + assert_eq!(AssignedSlots::active_temporary_slot_count(), 2); } // Block 6-11, Period 2-3 @@ -957,6 +1137,7 @@ mod tests { assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5)), false); + assert_eq!(AssignedSlots::active_temporary_slot_count(), 2); } // Block 12-17, Period 4-5 @@ -968,6 +1149,7 @@ mod tests { assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5)), true); + assert_eq!(AssignedSlots::active_temporary_slot_count(), 2); } // Block 18-23, Period 6-7 @@ -979,6 +1161,7 @@ mod tests { assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5)), false); + assert_eq!(AssignedSlots::active_temporary_slot_count(), 2); } // Block 24-29, Period 8-9 @@ -990,6 +1173,7 @@ mod tests { assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5)), false); + assert_eq!(AssignedSlots::active_temporary_slot_count(), 2); } // Block 30-35, Period 10-11 @@ -1001,6 +1185,7 @@ mod tests { assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5)), true); + assert_eq!(AssignedSlots::active_temporary_slot_count(), 2); } }); } @@ -1077,6 +1262,8 @@ mod tests { assert_ok!(AssignedSlots::unassign_parachain_slot(Origin::root(), ParaId::from(1),)); + assert_eq!(AssignedSlots::temporary_slot_count(), 0); + assert_eq!(AssignedSlots::active_temporary_slot_count(), 0); assert_eq!(AssignedSlots::has_temporary_slot(ParaId::from(1)), false); assert_eq!(AssignedSlots::temporary_slots(ParaId::from(1)), None); diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index b059b0c87267..a469a797904f 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -764,6 +764,7 @@ parameter_types! { pub const PermanentSlotLeasePeriodLength: u32 = 26; pub const TemporarySlotLeasePeriodLength: u32 = 1; pub const MaxPermanentSlots: u32 = 5; + pub const MaxTemporarySlots: u32 = 20; pub const MaxTemporarySlotPerLeasePeriod: u32 = 5; } @@ -774,6 +775,7 @@ impl assigned_slots::Config for Runtime { type PermanentSlotLeasePeriodLength = PermanentSlotLeasePeriodLength; type TemporarySlotLeasePeriodLength = TemporarySlotLeasePeriodLength; type MaxPermanentSlots = MaxPermanentSlots; + type MaxTemporarySlots = MaxTemporarySlots; type MaxTemporarySlotPerLeasePeriod = MaxTemporarySlotPerLeasePeriod; } diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index 74475bf70b7b..36f32fdfdb1a 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -860,6 +860,7 @@ parameter_types! { pub const PermanentSlotLeasePeriodLength: u32 = 26; pub const TemporarySlotLeasePeriodLength: u32 = 1; pub const MaxPermanentSlots: u32 = 5; + pub const MaxTemporarySlots: u32 = 20; pub const MaxTemporarySlotPerLeasePeriod: u32 = 5; } @@ -870,6 +871,7 @@ impl assigned_slots::Config for Runtime { type PermanentSlotLeasePeriodLength = PermanentSlotLeasePeriodLength; type TemporarySlotLeasePeriodLength = TemporarySlotLeasePeriodLength; type MaxPermanentSlots = MaxPermanentSlots; + type MaxTemporarySlots = MaxTemporarySlots; type MaxTemporarySlotPerLeasePeriod = MaxTemporarySlotPerLeasePeriod; } From 6c052477e9e31c8ab91b05efcdc48100e5159275 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 5 Oct 2021 12:06:06 +0200 Subject: [PATCH 12/23] Update runtime/common/src/assigned_slots.rs --- runtime/common/src/assigned_slots.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index 2bc7ab5038b4..fbf8b26a588c 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -320,7 +320,7 @@ pub mod pallet { )?; }, Err(err) => { - // Treat failed lease creationg as warning .. slot will be allocated a lease + // Treat failed lease creation as warning .. slot will be allocated a lease // in a subsequent lease period by the `allocate_temporary_slot_leases` function. log::warn!(target: "assigned_slots", "Failed to allocate a temp slot for para {:?} at period {:?}: {:?}", From 5adc0f4feebf26797de83c68537bc23f8cf51fd8 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Tue, 5 Oct 2021 12:06:19 +0200 Subject: [PATCH 13/23] Update runtime/common/src/assigned_slots.rs --- runtime/common/src/assigned_slots.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index fbf8b26a588c..60d98d343f3d 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -395,7 +395,7 @@ pub mod pallet { impl Pallet { /// Allocate temporary slot leases up to `MaxTemporarySlotPerLeasePeriod` per lease period. - /// Beyong the already active temporary slot leases, this function will activate more leases + /// Beyond the already active temporary slot leases, this function will activate more leases /// in the following order of preference: /// - Assigned slots that didn't have a turn yet, though their `period_begin` has passed. /// - Assigned slots that already had one (or more) turn(s): they will be considered for the From 451c5b502b3fc8e092c431d405f5bbc9075df7f8 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Tue, 5 Oct 2021 14:47:10 +0200 Subject: [PATCH 14/23] Remove assigned_slots calls from paras_sudo_wrapper --- runtime/common/src/paras_sudo_wrapper.rs | 33 +----------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/runtime/common/src/paras_sudo_wrapper.rs b/runtime/common/src/paras_sudo_wrapper.rs index 3604f6453af3..91430155b34d 100644 --- a/runtime/common/src/paras_sudo_wrapper.rs +++ b/runtime/common/src/paras_sudo_wrapper.rs @@ -16,7 +16,6 @@ //! A simple wrapper allowing `Sudo` to call into `paras` routines. -use crate::assigned_slots::{self, Pallet as AssignedSlots, SlotLeasePeriodStart}; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; pub use pallet::*; @@ -40,12 +39,7 @@ pub mod pallet { #[pallet::config] #[pallet::disable_frame_system_supertrait_check] pub trait Config: - configuration::Config - + paras::Config - + dmp::Config - + ump::Config - + hrmp::Config - + assigned_slots::Config + configuration::Config + paras::Config + dmp::Config + ump::Config + hrmp::Config { } @@ -75,31 +69,6 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Assign a permanent parachain slot - #[pallet::weight((1_000, DispatchClass::Operational))] - pub fn sudo_assign_perm_parachain_slot(origin: OriginFor, id: ParaId) -> DispatchResult { - ensure_root(origin.clone())?; - AssignedSlots::::assign_perm_parachain_slot(origin.clone(), id) - } - - /// Assign a temporary parachain slot - #[pallet::weight((1_000, DispatchClass::Operational))] - pub fn sudo_assign_temp_parachain_slot( - origin: OriginFor, - id: ParaId, - lease_period_start: SlotLeasePeriodStart, - ) -> DispatchResult { - ensure_root(origin.clone())?; - AssignedSlots::::assign_temp_parachain_slot(origin.clone(), id, lease_period_start) - } - - /// Unassign a permanent or temporary parachain slot - #[pallet::weight((1_000, DispatchClass::Operational))] - pub fn sudo_unassign_parachain_slot(origin: OriginFor, id: ParaId) -> DispatchResult { - ensure_root(origin.clone())?; - AssignedSlots::::unassign_parachain_slot(origin.clone(), id) - } - /// Schedule a para to be initialized at the start of the next session. #[pallet::weight((1_000, DispatchClass::Operational))] pub fn sudo_schedule_para_initialize( From ff0ec37d57b682262ed06183977f7de51006a153 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Tue, 5 Oct 2021 14:49:24 +0200 Subject: [PATCH 15/23] Unassign is a perfectly valid verb --- scripts/gitlab/lingua.dic | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/gitlab/lingua.dic b/scripts/gitlab/lingua.dic index 68001b64a4e2..ecaaa7610014 100644 --- a/scripts/gitlab/lingua.dic +++ b/scripts/gitlab/lingua.dic @@ -259,6 +259,7 @@ typesystem ubuntu/M UDP UI +unassign unfinalize/B unfinalized union/MSG From 7b6605530e43578154670e45c7f80189b06f9411 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Tue, 5 Oct 2021 20:44:59 +0200 Subject: [PATCH 16/23] Remove unneeded collect --- runtime/common/src/assigned_slots.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index 60d98d343f3d..f94c6394627d 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -449,15 +449,12 @@ impl Pallet { .then_with(|| a.0.cmp(&b.0)) }); - let slots_to_be_upgraded = pending_temp_slots - .iter() - .take( - (T::MaxTemporarySlotPerLeasePeriod::get().saturating_sub(active_temp_slots)) - as usize, - ) - .collect::>(); + let slots_to_be_upgraded = pending_temp_slots.iter().take( + (T::MaxTemporarySlotPerLeasePeriod::get().saturating_sub(active_temp_slots)) + as usize, + ); - for (id, temp_slot) in slots_to_be_upgraded.iter() { + for (id, temp_slot) in slots_to_be_upgraded { TemporarySlots::::try_mutate::<_, _, Error, _>(id, |s| { // Configure temp slot lease Self::configure_slot_lease( From 2dd00e059f13226a3fd0e0a56009fed7d7993cd5 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Mon, 11 Oct 2021 19:08:20 +0200 Subject: [PATCH 17/23] Update code following #3980 --- runtime/common/src/assigned_slots.rs | 66 +++++++++++++++++----------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index f94c6394627d..4cb370faa164 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -69,10 +69,11 @@ pub struct ParachainTemporarySlot { pub lease_count: u32, } -type BalanceOf = <<::Leaser as Leaser>::Currency as Currency< +type BalanceOf = <<::Leaser as Leaser<::BlockNumber>>::Currency as Currency< ::AccountId, >>::Balance; -type LeasePeriodOf = <::Leaser as Leaser>::LeasePeriod; +type LeasePeriodOf = + <::Leaser as Leaser<::BlockNumber>>::LeasePeriod; #[frame_support::pallet] pub mod pallet { @@ -92,7 +93,11 @@ pub mod pallet { type AssignSlotOrigin: EnsureOrigin<::Origin>; /// The type representing the leasing system. - type Leaser: Leaser; + type Leaser: Leaser< + Self::BlockNumber, + AccountId = Self::AccountId, + LeasePeriod = Self::BlockNumber, + >; /// The number of lease periods a permanent parachain slot lasts. #[pallet::constant] @@ -181,13 +186,15 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(n: T::BlockNumber) -> Weight { - let lease_period = Self::lease_period(); - let lease_period_index = Self::lease_period_index(); - if (n % lease_period).is_zero() { - Self::manage_lease_period_start(lease_period_index) - } else { - 0 + if let Some((lease_period, first_block)) = Self::lease_period_index(n) { + // If we're beginning a new lease period then handle that. + if first_block { + return Self::manage_lease_period_start(lease_period) + } } + + // We didn't return early above, so we didn't do anything. + 0 } } @@ -207,7 +214,7 @@ pub mod pallet { Error::::SlotAlreadyAssigned ); - let current_lease_period: T::BlockNumber = Self::lease_period_index(); + let current_lease_period: T::BlockNumber = Self::current_lease_period_index(); ensure!( !T::Leaser::already_leased( id, @@ -263,7 +270,7 @@ pub mod pallet { Error::::SlotAlreadyAssigned ); - let current_lease_period: T::BlockNumber = Self::lease_period_index(); + let current_lease_period: T::BlockNumber = Self::current_lease_period_index(); ensure!( !T::Leaser::already_leased( id, @@ -383,7 +390,7 @@ pub mod pallet { // at the end of the lease period . log::warn!(target: "assigned_slots", "Failed to downgrade parachain {:?} at period {:?}: {:?}", - id, Self::lease_period_index(), err + id, Self::current_lease_period_index(), err ); } } @@ -519,15 +526,22 @@ impl Pallet { } /// Returns current lease period index. - fn lease_period_index() -> LeasePeriodOf { - T::Leaser::lease_period_index() + fn current_lease_period_index() -> LeasePeriodOf { + T::Leaser::lease_period_index(frame_system::Pallet::::block_number()) + .and_then(|x| Some(x.0)) + .unwrap() } - /// Returns current lease period. - fn lease_period() -> LeasePeriodOf { - T::Leaser::lease_period() + /// Returns lease period index for block + fn lease_period_index(block: BlockNumberFor) -> Option<(LeasePeriodOf, bool)> { + T::Leaser::lease_period_index(block) } + /// Returns current lease period. + // fn lease_period() -> LeasePeriodOf { + // T::Leaser::lease_period() + // } + /// Handles start of a lease period. fn manage_lease_period_start(lease_period_index: LeasePeriodOf) -> Weight { // Note: leases that have ended in previous lease period, should have been cleaned in slots pallet. @@ -627,19 +641,20 @@ mod tests { } impl parachains_configuration::Config for Test { - type WeightInfo = parachains_configuration::weights::WeightInfo; + type WeightInfo = parachains_configuration::TestWeightInfo; } impl parachains_paras::Config for Test { type Origin = Origin; type Event = Event; - type WeightInfo = parachains_paras::weights::WeightInfo; + type WeightInfo = parachains_paras::TestWeightInfo; } impl parachains_shared::Config for Test {} parameter_types! { pub const LeasePeriod: BlockNumber = 3; + pub static LeaseOffset: BlockNumber = 0; pub const ParaDeposit: u64 = 1; } @@ -648,6 +663,7 @@ mod tests { type Currency = Balances; type Registrar = TestRegistrar; type LeasePeriod = LeasePeriod; + type LeaseOffset = LeaseOffset; type WeightInfo = crate::slots::TestWeightInfo; } @@ -711,12 +727,12 @@ mod tests { fn basic_setup_works() { new_test_ext().execute_with(|| { run_to_block(1); - assert_eq!(Slots::lease_period(), 3); - assert_eq!(Slots::lease_period_index(), 0); + // assert_eq!(Slots::lease_period(), 3); + assert_eq!(AssignedSlots::current_lease_period_index(), 0); assert_eq!(Slots::deposit_held(1.into(), &1), 0); run_to_block(3); - assert_eq!(Slots::lease_period_index(), 1); + assert_eq!(AssignedSlots::current_lease_period_index(), 1); }); } @@ -1037,7 +1053,7 @@ mod tests { // Para is a parachain for TemporarySlotLeasePeriodLength * LeasePeriod blocks while block < 6 { println!("block #{}", block); - println!("lease period #{}", Slots::lease_period_index()); + println!("lease period #{}", AssignedSlots::current_lease_period_index()); println!("lease {:?}", Slots::lease(ParaId::from(1))); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), true); @@ -1063,7 +1079,7 @@ mod tests { // Block 6 println!("block #{}", block); - println!("lease period #{}", Slots::lease_period_index()); + println!("lease period #{}", AssignedSlots::current_lease_period_index()); println!("lease {:?}", Slots::lease(ParaId::from(1))); // Para lease ended, downgraded back to parathread @@ -1075,7 +1091,7 @@ mod tests { // Para should get a turn after TemporarySlotLeasePeriodLength * LeasePeriod blocks run_to_block(12); println!("block #{}", block); - println!("lease period #{}", Slots::lease_period_index()); + println!("lease period #{}", AssignedSlots::current_lease_period_index()); println!("lease {:?}", Slots::lease(ParaId::from(1))); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1)), true); From 039e4b965749f53d77e83c037a5f33be94a9a8c5 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Tue, 12 Oct 2021 11:47:06 +0200 Subject: [PATCH 18/23] Cleanup --- runtime/common/src/assigned_slots.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index 4cb370faa164..169b3b268616 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -537,11 +537,6 @@ impl Pallet { T::Leaser::lease_period_index(block) } - /// Returns current lease period. - // fn lease_period() -> LeasePeriodOf { - // T::Leaser::lease_period() - // } - /// Handles start of a lease period. fn manage_lease_period_start(lease_period_index: LeasePeriodOf) -> Weight { // Note: leases that have ended in previous lease period, should have been cleaned in slots pallet. @@ -727,7 +722,6 @@ mod tests { fn basic_setup_works() { new_test_ext().execute_with(|| { run_to_block(1); - // assert_eq!(Slots::lease_period(), 3); assert_eq!(AssignedSlots::current_lease_period_index(), 0); assert_eq!(Slots::deposit_held(1.into(), &1), 0); From 42e44f2264ea3e87b26a2924ccec223f56e45f82 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Wed, 3 Nov 2021 14:04:14 +0100 Subject: [PATCH 19/23] Generate storage info for pallet --- parachain/src/primitives.rs | 3 ++- runtime/common/src/assigned_slots.rs | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/parachain/src/primitives.rs b/parachain/src/primitives.rs index bda56bf59e8c..66abe0deb914 100644 --- a/parachain/src/primitives.rs +++ b/parachain/src/primitives.rs @@ -20,7 +20,7 @@ use sp_std::vec::Vec; use frame_support::weights::Weight; -use parity_scale_codec::{CompactAs, Decode, Encode}; +use parity_scale_codec::{CompactAs, Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_core::{RuntimeDebug, TypeId}; use sp_runtime::traits::Hash as _; @@ -129,6 +129,7 @@ pub struct BlockData(#[cfg_attr(feature = "std", serde(with = "bytes"))] pub Vec Encode, Eq, Hash, + MaxEncodedLen, Ord, PartialEq, PartialOrd, diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index 169b3b268616..08c96b893034 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -30,7 +30,7 @@ use crate::{ use frame_support::{pallet_prelude::*, traits::Currency}; use frame_system::pallet_prelude::*; pub use pallet::*; -use parity_scale_codec::Encode; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::v1::Id as ParaId; use runtime_parachains::{ configuration, @@ -51,7 +51,7 @@ pub enum SlotLeasePeriodStart { } /// Information about a temporary parachain slot. -#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, MaxEncodedLen, RuntimeDebug, TypeInfo)] pub struct ParachainTemporarySlot { /// Manager account of the para. pub manager: AccountId, @@ -81,6 +81,7 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] + #[pallet::generate_storage_info] pub struct Pallet(_); #[pallet::config] From 3696163967268902f4fbd9fa2b1521591a04fb51 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Wed, 3 Nov 2021 23:03:07 +0100 Subject: [PATCH 20/23] Address review comments --- runtime/common/src/assigned_slots.rs | 149 ++++++++++++--------------- 1 file changed, 64 insertions(+), 85 deletions(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index 08c96b893034..a325cb346d69 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -26,6 +26,7 @@ use crate::{ slots::{self, Pallet as Slots, WeightInfo}, traits::{LeaseError, Leaser, Registrar}, + MAXIMUM_BLOCK_WEIGHT, }; use frame_support::{pallet_prelude::*, traits::Currency}; use frame_system::pallet_prelude::*; @@ -37,10 +38,7 @@ use runtime_parachains::{ paras::{self}, }; use scale_info::TypeInfo; -use sp_runtime::{ - traits::{One, Saturating, Zero}, - ArithmeticError, -}; +use sp_runtime::traits::{One, Saturating, Zero}; use sp_std::prelude::*; /// Lease period an assigned slot should start from (current, or next one). @@ -125,7 +123,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn permanent_slots)] pub type PermanentSlots = - StorageMap<_, Twox64Concat, ParaId, Option>, ValueQuery>; + StorageMap<_, Twox64Concat, ParaId, LeasePeriodOf, OptionQuery>; /// Number of assigned (and active) permanent slots. #[pallet::storage] @@ -139,8 +137,8 @@ pub mod pallet { _, Twox64Concat, ParaId, - Option>>, - ValueQuery, + ParachainTemporarySlot>, + OptionQuery, >; /// Number of assigned temporary slots. @@ -201,8 +199,9 @@ pub mod pallet { #[pallet::call] impl Pallet { + // TODO: Benchmark this /// Assign a permanent parachain slot and immediately create a lease for it. - #[pallet::weight((1_000 + ::WeightInfo::force_lease(), DispatchClass::Operational))] + #[pallet::weight(((MAXIMUM_BLOCK_WEIGHT / 10) as Weight, DispatchClass::Operational))] pub fn assign_perm_parachain_slot(origin: OriginFor, id: ParaId) -> DispatchResult { T::AssignSlotOrigin::ensure_origin(origin)?; @@ -234,27 +233,27 @@ pub mod pallet { Error::::MaxPermanentSlotsExceeded ); - >::try_mutate(|count| -> DispatchResult { - // Permanent slot assignment fails if a lease cannot be created - Self::configure_slot_lease( - id, - manager, - current_lease_period, - T::PermanentSlotLeasePeriodLength::get().into(), - ) - .map_err(|_| Error::::CannotUpgrade)?; - *count = count.checked_add(One::one()).ok_or(ArithmeticError::Overflow)?; - PermanentSlots::::insert(id, Some(current_lease_period)); - Ok(()) - })?; + // Permanent slot assignment fails if a lease cannot be created + Self::configure_slot_lease( + id, + manager, + current_lease_period, + T::PermanentSlotLeasePeriodLength::get().into(), + ) + .map_err(|_| Error::::CannotUpgrade)?; + + PermanentSlots::::insert(id, current_lease_period); + >::mutate(|count| count.saturating_inc()); Self::deposit_event(Event::::PermanentSlotAssigned(id)); Ok(()) } + // TODO: Benchmark this /// Assign a temporary parachain slot. The function tries to create a lease for it - /// immediately if `SlotLeasePeriodStart::Current` is specified, and ... - #[pallet::weight((1_000 + ::WeightInfo::force_lease(), DispatchClass::Operational))] + /// immediately if `SlotLeasePeriodStart::Current` is specified, and if the number + /// of currently active temporary slots is below `MaxTemporarySlotPerLeasePeriod`. + #[pallet::weight(((MAXIMUM_BLOCK_WEIGHT / 10) as Weight, DispatchClass::Operational))] pub fn assign_temp_parachain_slot( origin: OriginFor, id: ParaId, @@ -264,7 +263,7 @@ pub mod pallet { let manager = T::Registrar::manager_of(id).ok_or(Error::::ParaDoesntExist)?; - ensure!(T::Registrar::is_parathread(id), Error::::NotParathread,); + ensure!(T::Registrar::is_parathread(id), Error::::NotParathread); ensure!( !Self::has_permanent_slot(id) && !Self::has_temporary_slot(id), @@ -301,54 +300,43 @@ pub mod pallet { lease_count: 0, }; - >::try_mutate(|count| -> DispatchResult { - *count = count.checked_add(One::one()).ok_or(ArithmeticError::Overflow)?; - - if lease_period_start == SlotLeasePeriodStart::Current && - Self::active_temporary_slot_count() < - T::MaxTemporarySlotPerLeasePeriod::get() - { - // Try to allocate slot directly - match Self::configure_slot_lease( - id, - manager, - temp_slot.period_begin, - temp_slot.period_count, - ) { - Ok(_) => { - temp_slot.last_lease = Some(temp_slot.period_begin); - temp_slot.lease_count += 1; - ActiveTemporarySlotCount::::try_mutate::<_, ArithmeticError, _>( - |count| { - *count = count - .checked_add(One::one()) - .ok_or(ArithmeticError::Overflow)?; - Ok(()) - }, - )?; - }, - Err(err) => { - // Treat failed lease creation as warning .. slot will be allocated a lease - // in a subsequent lease period by the `allocate_temporary_slot_leases` function. - log::warn!(target: "assigned_slots", - "Failed to allocate a temp slot for para {:?} at period {:?}: {:?}", - id, current_lease_period, err - ); - }, - } + if lease_period_start == SlotLeasePeriodStart::Current && + Self::active_temporary_slot_count() < T::MaxTemporarySlotPerLeasePeriod::get() + { + // Try to allocate slot directly + match Self::configure_slot_lease( + id, + manager, + temp_slot.period_begin, + temp_slot.period_count, + ) { + Ok(_) => { + ActiveTemporarySlotCount::::mutate(|count| count.saturating_inc()); + temp_slot.last_lease = Some(temp_slot.period_begin); + temp_slot.lease_count += 1; + }, + Err(err) => { + // Treat failed lease creation as warning .. slot will be allocated a lease + // in a subsequent lease period by the `allocate_temporary_slot_leases` function. + log::warn!(target: "assigned_slots", + "Failed to allocate a temp slot for para {:?} at period {:?}: {:?}", + id, current_lease_period, err + ); + }, } + } - TemporarySlots::::insert(id, Some(temp_slot)); - Ok(()) - })?; + TemporarySlots::::insert(id, temp_slot); + >::mutate(|count| count.saturating_inc()); Self::deposit_event(Event::::TemporarySlotAssigned(id)); Ok(()) } + // TODO: Benchmark this /// Unassign a permanent or temporary parachain slot - #[pallet::weight((1_000 + ::WeightInfo::clear_all_leases(), DispatchClass::Operational))] + #[pallet::weight(((MAXIMUM_BLOCK_WEIGHT / 10) as Weight, DispatchClass::Operational))] pub fn unassign_parachain_slot(origin: OriginFor, id: ParaId) -> DispatchResult { T::AssignSlotOrigin::ensure_origin(origin.clone())?; @@ -360,27 +348,20 @@ pub mod pallet { // Check & cache para status before we clear the lease let is_parachain = Self::is_parachain(id); + // Remove perm or temp slot + Self::clear_slot_leases(origin.clone(), id)?; + if PermanentSlots::::contains_key(id) { - // Remove perm slot - >::try_mutate(|count| -> DispatchResult { - Self::clear_slot_leases(origin.clone(), id)?; - *count = count.saturating_sub(One::one()); - PermanentSlots::::remove(id); - Ok(()) - })?; + PermanentSlots::::remove(id); + >::mutate(|count| *count = count.saturating_sub(One::one())); } else if TemporarySlots::::contains_key(id) { - // Remove temp slot - >::try_mutate(|count| -> DispatchResult { - Self::clear_slot_leases(origin.clone(), id)?; - *count = count.saturating_sub(One::one()); - TemporarySlots::::remove(id); - if is_parachain { - >::mutate(|active_count| { - *active_count = active_count.saturating_sub(One::one()) - }); - } - Ok(()) - })?; + TemporarySlots::::remove(id); + >::mutate(|count| *count = count.saturating_sub(One::one())); + if is_parachain { + >::mutate(|active_count| { + *active_count = active_count.saturating_sub(One::one()) + }); + } } // Force downgrade to parathread (if needed) before end of lease period @@ -417,8 +398,7 @@ impl Pallet { fn allocate_temporary_slot_leases(lease_period_index: LeasePeriodOf) -> DispatchResult { let mut active_temp_slots = 0u32; let mut pending_temp_slots = Vec::new(); - TemporarySlots::::iter().for_each(|(para, maybe_slot)| { - if let Some(slot) = maybe_slot { + TemporarySlots::::iter().for_each(|(para, slot)| { match slot.last_lease { Some(last_lease) if last_lease <= lease_period_index && @@ -441,7 +421,6 @@ impl Pallet { // Slot not being considered for this lease period (will be for a subsequent one) }, } - } }); let mut newly_created_lease = 0u32; From 4ddb20f705977372d22e104c800a74739f708f9e Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Thu, 4 Nov 2021 00:14:47 +0100 Subject: [PATCH 21/23] Add ForceOrigin to slots pallet --- runtime/common/src/assigned_slots.rs | 1 + runtime/common/src/integration_tests.rs | 1 + runtime/common/src/slots.rs | 13 +++++++++---- runtime/kusama/src/lib.rs | 1 + runtime/polkadot/src/lib.rs | 1 + runtime/rococo/src/lib.rs | 1 + runtime/westend/src/lib.rs | 1 + 7 files changed, 15 insertions(+), 4 deletions(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index a325cb346d69..19fe61f62bf4 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -639,6 +639,7 @@ mod tests { type Registrar = TestRegistrar; type LeasePeriod = LeasePeriod; type LeaseOffset = LeaseOffset; + type ForceOrigin = EnsureRoot; type WeightInfo = crate::slots::TestWeightInfo; } diff --git a/runtime/common/src/integration_tests.rs b/runtime/common/src/integration_tests.rs index ad750602b0db..50fc651f88b5 100644 --- a/runtime/common/src/integration_tests.rs +++ b/runtime/common/src/integration_tests.rs @@ -212,6 +212,7 @@ impl slots::Config for Test { type Registrar = Registrar; type LeasePeriod = LeasePeriod; type LeaseOffset = LeaseOffset; + type ForceOrigin = EnsureRoot; type WeightInfo = crate::slots::TestWeightInfo; } diff --git a/runtime/common/src/slots.rs b/runtime/common/src/slots.rs index 26ada547c7fe..687b8011f51e 100644 --- a/runtime/common/src/slots.rs +++ b/runtime/common/src/slots.rs @@ -87,6 +87,9 @@ pub mod pallet { #[pallet::constant] type LeaseOffset: Get; + /// The origin which may forcibly create or clear leases. Root can always do this. + type ForceOrigin: EnsureOrigin<::Origin>; + /// Weight Information for the Extrinsics in the Pallet type WeightInfo: WeightInfo; } @@ -159,7 +162,7 @@ pub mod pallet { /// Just a connect into the `lease_out` call, in case Root wants to force some lease to happen /// independently of any other on-chain mechanism to use it. /// - /// Can only be called by the Root origin. + /// The dispatch origin for this call must match `T::ForceOrigin`. #[pallet::weight(T::WeightInfo::force_lease())] pub fn force_lease( origin: OriginFor, @@ -169,7 +172,7 @@ pub mod pallet { period_begin: LeasePeriodOf, period_count: LeasePeriodOf, ) -> DispatchResult { - ensure_root(origin)?; + T::ForceOrigin::ensure_origin(origin)?; Self::lease_out(para, &leaser, amount, period_begin, period_count) .map_err(|_| Error::::LeaseError)?; Ok(()) @@ -177,10 +180,10 @@ pub mod pallet { /// Clear all leases for a Para Id, refunding any deposits back to the original owners. /// - /// Can only be called by the Root origin. + /// The dispatch origin for this call must match `T::ForceOrigin`. #[pallet::weight(T::WeightInfo::clear_all_leases())] pub fn clear_all_leases(origin: OriginFor, para: ParaId) -> DispatchResult { - ensure_root(origin)?; + T::ForceOrigin::ensure_origin(origin)?; let deposits = Self::all_deposits_held(para); // Refund any deposits for these leases @@ -495,6 +498,7 @@ mod tests { use crate::{mock::TestRegistrar, slots}; use frame_support::{assert_noop, assert_ok, parameter_types}; + use frame_system::EnsureRoot; use pallet_balances; use primitives::v1::{BlockNumber, Header}; use sp_core::H256; @@ -572,6 +576,7 @@ mod tests { type Registrar = TestRegistrar; type LeasePeriod = LeasePeriod; type LeaseOffset = LeaseOffset; + type ForceOrigin = EnsureRoot; type WeightInfo = crate::slots::TestWeightInfo; } diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index 4afe8488f93d..b186b11e0281 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -1235,6 +1235,7 @@ impl slots::Config for Runtime { type Registrar = Registrar; type LeasePeriod = LeasePeriod; type LeaseOffset = (); + type ForceOrigin = MoreThanHalfCouncil; type WeightInfo = weights::runtime_common_slots::WeightInfo; } diff --git a/runtime/polkadot/src/lib.rs b/runtime/polkadot/src/lib.rs index 88e8820c5803..47525288d0af 100644 --- a/runtime/polkadot/src/lib.rs +++ b/runtime/polkadot/src/lib.rs @@ -1225,6 +1225,7 @@ impl slots::Config for Runtime { type Registrar = Registrar; type LeasePeriod = LeasePeriod; type LeaseOffset = LeaseOffset; + type ForceOrigin = MoreThanHalfCouncil; type WeightInfo = weights::runtime_common_slots::WeightInfo; } diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index f83cafecd052..0716592e576e 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -1025,6 +1025,7 @@ impl slots::Config for Runtime { type Registrar = Registrar; type LeasePeriod = LeasePeriod; type LeaseOffset = (); + type ForceOrigin = EnsureRoot; type WeightInfo = slots::TestWeightInfo; } diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index 9a1cd0497b59..9deba59d732e 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -902,6 +902,7 @@ impl slots::Config for Runtime { type Registrar = Registrar; type LeasePeriod = LeasePeriod; type LeaseOffset = (); + type ForceOrigin = EnsureRoot; type WeightInfo = weights::runtime_common_slots::WeightInfo; } From 91015bccf377eb50026ce6cfeb5c9fd31fce5223 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Thu, 4 Nov 2021 21:27:17 +0100 Subject: [PATCH 22/23] Track permanent slot duration in storage --- runtime/common/src/assigned_slots.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index 19fe61f62bf4..0f66fce74c1a 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -119,11 +119,11 @@ pub mod pallet { type MaxTemporarySlotPerLeasePeriod: Get; } - /// Assigned permanent slots, with their start lease period. + /// Assigned permanent slots, with their start lease period, and duration. #[pallet::storage] #[pallet::getter(fn permanent_slots)] pub type PermanentSlots = - StorageMap<_, Twox64Concat, ParaId, LeasePeriodOf, OptionQuery>; + StorageMap<_, Twox64Concat, ParaId, (LeasePeriodOf, LeasePeriodOf), OptionQuery>; /// Number of assigned (and active) permanent slots. #[pallet::storage] @@ -242,7 +242,13 @@ pub mod pallet { ) .map_err(|_| Error::::CannotUpgrade)?; - PermanentSlots::::insert(id, current_lease_period); + PermanentSlots::::insert( + id, + ( + current_lease_period, + LeasePeriodOf::::from(T::PermanentSlotLeasePeriodLength::get()), + ), + ); >::mutate(|count| count.saturating_inc()); Self::deposit_event(Event::::PermanentSlotAssigned(id)); @@ -850,7 +856,7 @@ mod tests { assert_eq!(AssignedSlots::permanent_slot_count(), 1); assert_eq!(AssignedSlots::has_permanent_slot(ParaId::from(1)), true); - assert_eq!(AssignedSlots::permanent_slots(ParaId::from(1)), Some(0)); + assert_eq!(AssignedSlots::permanent_slots(ParaId::from(1)), Some((0, 3))); assert_eq!(Slots::already_leased(ParaId::from(1), 0, 2), true); From dea3de06f23a56508fc33750b9fc9d3cc12f3674 Mon Sep 17 00:00:00 2001 From: Steve Degosserie Date: Wed, 1 Dec 2021 22:42:08 +0100 Subject: [PATCH 23/23] Fix tests build --- runtime/common/src/assigned_slots.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/runtime/common/src/assigned_slots.rs b/runtime/common/src/assigned_slots.rs index 0f66fce74c1a..e0981a63d8c0 100644 --- a/runtime/common/src/assigned_slots.rs +++ b/runtime/common/src/assigned_slots.rs @@ -570,7 +570,7 @@ mod tests { Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Configuration: parachains_configuration::{Pallet, Call, Storage, Config}, ParasShared: parachains_shared::{Pallet, Call, Storage}, - Parachains: parachains_paras::{Pallet, Origin, Call, Storage, Config, Event}, + Parachains: parachains_paras::{Pallet, Call, Storage, Config, Event}, Slots: slots::{Pallet, Call, Storage, Event}, AssignedSlots: assigned_slots::{Pallet, Call, Storage, Event}, } @@ -626,7 +626,6 @@ mod tests { } impl parachains_paras::Config for Test { - type Origin = Origin; type Event = Event; type WeightInfo = parachains_paras::TestWeightInfo; }