// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Polkadot is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . //! 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 should not be used on a production relay chain, //! only on a test relay chain (e.g. Rococo). pub mod benchmarking; pub mod migration; use crate::{ slots::{self, Pallet as Slots, WeightInfo as SlotsWeightInfo}, traits::{LeaseError, Leaser, Registrar}, }; use frame_support::{pallet_prelude::*, traits::Currency}; use frame_system::pallet_prelude::*; pub use pallet::*; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::Id as ParaId; use runtime_parachains::{ configuration, paras::{self}, }; use scale_info::TypeInfo; use sp_runtime::traits::{One, Saturating, Zero}; use sp_std::prelude::*; const LOG_TARGET: &str = "runtime::assigned_slots"; /// 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, MaxEncodedLen, RuntimeDebug, TypeInfo)] pub struct ParachainTemporarySlot { /// 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, } pub trait WeightInfo { fn assign_perm_parachain_slot() -> Weight; fn assign_temp_parachain_slot() -> Weight; fn unassign_parachain_slot() -> Weight; fn set_max_permanent_slots() -> Weight; fn set_max_temporary_slots() -> Weight; } pub struct TestWeightInfo; impl WeightInfo for TestWeightInfo { fn assign_perm_parachain_slot() -> Weight { Weight::zero() } fn assign_temp_parachain_slot() -> Weight { Weight::zero() } fn unassign_parachain_slot() -> Weight { Weight::zero() } fn set_max_permanent_slots() -> Weight { Weight::zero() } fn set_max_temporary_slots() -> Weight { Weight::zero() } } type BalanceOf = <<::Leaser as Leaser>>::Currency as Currency< ::AccountId, >>::Balance; type LeasePeriodOf = <::Leaser as Leaser>>::LeasePeriod; #[frame_support::pallet] pub mod pallet { use super::*; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] 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 RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Origin for assigning slots. type AssignSlotOrigin: EnsureOrigin<::RuntimeOrigin>; /// The type representing the leasing system. type Leaser: Leaser< BlockNumberFor, AccountId = Self::AccountId, LeasePeriod = BlockNumberFor, >; /// 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 temporary slots to be scheduled per lease periods. #[pallet::constant] type MaxTemporarySlotPerLeasePeriod: Get; /// Weight Information for the Extrinsics in the Pallet type WeightInfo: WeightInfo; } /// Assigned permanent slots, with their start lease period, and duration. #[pallet::storage] #[pallet::getter(fn permanent_slots)] pub type PermanentSlots = StorageMap<_, Twox64Concat, ParaId, (LeasePeriodOf, LeasePeriodOf), OptionQuery>; /// 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< _, Twox64Concat, ParaId, ParachainTemporarySlot>, OptionQuery, >; /// 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>; /// The max number of temporary slots that can be assigned. #[pallet::storage] pub type MaxTemporarySlots = StorageValue<_, u32, ValueQuery>; /// The max number of permanent slots that can be assigned. #[pallet::storage] pub type MaxPermanentSlots = StorageValue<_, u32, ValueQuery>; #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub max_temporary_slots: u32, pub max_permanent_slots: u32, pub _config: PhantomData, } #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { >::put(&self.max_permanent_slots); >::put(&self.max_temporary_slots); } } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// A parachain was assigned a permanent parachain slot PermanentSlotAssigned(ParaId), /// A parachain was assigned a temporary parachain slot TemporarySlotAssigned(ParaId), /// The maximum number of permanent slots has been changed MaxPermanentSlotsChanged { slots: u32 }, /// The maximum number of temporary slots has been changed MaxTemporarySlotsChanged { slots: u32 }, } #[pallet::error] pub enum Error { /// The specified parachain is not registered. ParaDoesntExist, /// Not a parathread (on-demand parachain). NotParathread, /// Cannot upgrade on-demand parachain to lease holding /// parachain. CannotUpgrade, /// Cannot downgrade lease holding parachain to /// on-demand. CannotDowngrade, /// Permanent or Temporary slot already assigned. SlotAlreadyAssigned, /// Permanent or Temporary slot has not been assigned. SlotNotAssigned, /// An ongoing lease already exists. OngoingLeaseExists, // The maximum number of permanent slots exceeded MaxPermanentSlotsExceeded, // The maximum number of temporary slots exceeded MaxTemporarySlotsExceeded, } #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(n: BlockNumberFor) -> Weight { 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. Weight::zero() } } #[pallet::call] impl Pallet { /// Assign a permanent parachain slot and immediately create a lease for it. #[pallet::call_index(0)] #[pallet::weight((::WeightInfo::assign_perm_parachain_slot(), 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: BlockNumberFor = Self::current_lease_period_index(); ensure!( !T::Leaser::already_leased( id, current_lease_period, // Check current lease & next one current_lease_period.saturating_add( BlockNumberFor::::from(2u32) .saturating_mul(T::PermanentSlotLeasePeriodLength::get().into()) ) ), Error::::OngoingLeaseExists ); ensure!( PermanentSlotCount::::get() < MaxPermanentSlots::::get(), Error::::MaxPermanentSlotsExceeded ); // 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, LeasePeriodOf::::from(T::PermanentSlotLeasePeriodLength::get()), ), ); >::mutate(|count| count.saturating_inc()); Self::deposit_event(Event::::PermanentSlotAssigned(id)); Ok(()) } /// Assign a temporary parachain slot. The function tries to create a lease for it /// immediately if `SlotLeasePeriodStart::Current` is specified, and if the number /// of currently active temporary slots is below `MaxTemporarySlotPerLeasePeriod`. #[pallet::call_index(1)] #[pallet::weight((::WeightInfo::assign_temp_parachain_slot(), 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: BlockNumberFor = Self::current_lease_period_index(); ensure!( !T::Leaser::already_leased( id, current_lease_period, // Check current lease & next one current_lease_period.saturating_add( BlockNumberFor::::from(2u32) .saturating_mul(T::TemporarySlotLeasePeriodLength::get().into()) ) ), Error::::OngoingLeaseExists ); ensure!( TemporarySlotCount::::get() < MaxTemporarySlots::::get(), Error::::MaxTemporarySlotsExceeded ); 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, }; 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: LOG_TARGET, "Failed to allocate a temp slot for para {:?} at period {:?}: {:?}", id, current_lease_period, err ); }, } } TemporarySlots::::insert(id, temp_slot); >::mutate(|count| count.saturating_inc()); Self::deposit_event(Event::::TemporarySlotAssigned(id)); Ok(()) } /// Unassign a permanent or temporary parachain slot #[pallet::call_index(2)] #[pallet::weight((::WeightInfo::unassign_parachain_slot(), 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 ); // 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) { PermanentSlots::::remove(id); >::mutate(|count| *count = count.saturating_sub(One::one())); } else if TemporarySlots::::contains_key(id) { 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 on-demand parachain (if needed) before end of lease period 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: LOG_TARGET, "Failed to downgrade parachain {:?} at period {:?}: {:?}", id, Self::current_lease_period_index(), err ); } } Ok(()) } /// Sets the storage value [`MaxPermanentSlots`]. #[pallet::call_index(3)] #[pallet::weight((::WeightInfo::set_max_permanent_slots(), DispatchClass::Operational))] pub fn set_max_permanent_slots(origin: OriginFor, slots: u32) -> DispatchResult { ensure_root(origin)?; >::put(slots); Self::deposit_event(Event::::MaxPermanentSlotsChanged { slots }); Ok(()) } /// Sets the storage value [`MaxTemporarySlots`]. #[pallet::call_index(4)] #[pallet::weight((::WeightInfo::set_max_temporary_slots(), DispatchClass::Operational))] pub fn set_max_temporary_slots(origin: OriginFor, slots: u32) -> DispatchResult { ensure_root(origin)?; >::put(slots); Self::deposit_event(Event::::MaxTemporarySlotsChanged { slots }); Ok(()) } } } impl Pallet { /// Allocate temporary slot leases up to `MaxTemporarySlotPerLeasePeriod` per lease period. /// 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 /// 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(); TemporarySlots::::iter().for_each(|(para, 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.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 pending_temp_slots.insert(0, (para, slot)); }, _ => { // Slot not being considered for this lease period (will be for a subsequent one) }, } }); let mut newly_created_lease = 0u32; 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 // (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().take( (T::MaxTemporarySlotPerLeasePeriod::get().saturating_sub(active_temp_slots)) as usize, ); for (id, temp_slot) in slots_to_be_upgraded { TemporarySlots::::try_mutate::<_, _, Error, _>(id, |s| { // Configure temp slot lease Self::configure_slot_lease( *id, temp_slot.manager.clone(), 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, }); 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, lease_period: LeasePeriodOf, lease_duration: LeasePeriodOf, ) -> Result<(), LeaseError> { 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 lease holding parachain. fn is_parachain(id: ParaId) -> bool { T::Registrar::is_parachain(id) } /// Returns current 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 lease period index for block fn lease_period_index(block: BlockNumberFor) -> Option<(LeasePeriodOf, bool)> { T::Leaser::lease_period_index(block) } /// 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. if let Err(err) = Self::allocate_temporary_slot_leases(lease_period_index) { log::error!( target: LOG_TARGET, "Allocating slots failed for lease period {:?}, with: {:?}", lease_period_index, err ); } ::WeightInfo::force_lease() * (T::MaxTemporarySlotPerLeasePeriod::get() as u64) } } /// tests for this pallet #[cfg(test)] mod tests { use super::*; use crate::{assigned_slots, mock::TestRegistrar, slots}; use ::test_helpers::{dummy_head_data, dummy_validation_code}; use frame_support::{assert_noop, assert_ok, derive_impl, parameter_types}; use frame_system::EnsureRoot; use pallet_balances; use primitives::BlockNumber; 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}, transaction_validity::TransactionPriority, BuildStorage, DispatchError::BadOrigin, }; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlockU32; frame_support::construct_runtime!( pub enum Test { 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, Call, Storage, Config, Event}, Slots: slots::{Pallet, Call, Storage, Event}, AssignedSlots: assigned_slots::{Pallet, Call, Storage, Event}, } ); impl frame_system::offchain::SendTransactionTypes for Test where RuntimeCall: From, { type Extrinsic = UncheckedExtrinsic; type OverarchingCall = RuntimeCall; } parameter_types! { pub const BlockHashCount: u32 = 250; } #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; type Block = Block; type RuntimeEvent = RuntimeEvent; 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 = (); type MaxConsumers = frame_support::traits::ConstU32<16>; } parameter_types! { pub const ExistentialDeposit: u64 = 1; } impl pallet_balances::Config for Test { type Balance = u64; type RuntimeEvent = RuntimeEvent; type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; type FreezeIdentifier = (); type MaxHolds = ConstU32<1>; type MaxFreezes = ConstU32<1>; } impl parachains_configuration::Config for Test { type WeightInfo = parachains_configuration::TestWeightInfo; } parameter_types! { pub const ParasUnsignedPriority: TransactionPriority = TransactionPriority::max_value(); } impl parachains_paras::Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = parachains_paras::TestWeightInfo; type UnsignedPriority = ParasUnsignedPriority; type QueueFootprinter = (); type NextSessionRotation = crate::mock::TestNextSessionRotation; type OnNewHead = (); type AssignCoretime = (); } impl parachains_shared::Config for Test {} parameter_types! { pub const LeasePeriod: BlockNumber = 3; pub static LeaseOffset: BlockNumber = 0; pub const ParaDeposit: u64 = 1; } impl slots::Config for Test { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type Registrar = TestRegistrar; type LeasePeriod = LeasePeriod; type LeaseOffset = LeaseOffset; type ForceOrigin = EnsureRoot; type WeightInfo = crate::slots::TestWeightInfo; } parameter_types! { pub const PermanentSlotLeasePeriodLength: u32 = 3; pub const TemporarySlotLeasePeriodLength: u32 = 2; pub const MaxTemporarySlotPerLeasePeriod: u32 = 2; } impl assigned_slots::Config for Test { type RuntimeEvent = RuntimeEvent; type AssignSlotOrigin = EnsureRoot; type Leaser = Slots; type PermanentSlotLeasePeriodLength = PermanentSlotLeasePeriodLength; type TemporarySlotLeasePeriodLength = TemporarySlotLeasePeriodLength; type MaxTemporarySlotPerLeasePeriod = MaxTemporarySlotPerLeasePeriod; type WeightInfo = crate::assigned_slots::TestWeightInfo; } // 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(); crate::assigned_slots::GenesisConfig:: { max_temporary_slots: 6, max_permanent_slots: 2, _config: Default::default(), } .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!(AssignedSlots::current_lease_period_index(), 0); assert_eq!(Slots::deposit_held(1.into(), &1), 0); run_to_block(3); assert_eq!(AssignedSlots::current_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( RuntimeOrigin::root(), ParaId::from(1_u32), ), 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( RuntimeOrigin::signed(1), ParaId::from(1_u32), ), 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_u32), dummy_head_data(), dummy_validation_code(), )); assert_ok!(TestRegistrar::::make_parachain(ParaId::from(1_u32))); assert_noop!( AssignedSlots::assign_perm_parachain_slot( RuntimeOrigin::root(), ParaId::from(1_u32), ), 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_u32), dummy_head_data(), dummy_validation_code(), )); // Register lease in current lease period assert_ok!(Slots::lease_out(ParaId::from(1_u32), &1, 1, 1, 1)); // Try to assign a perm slot in current period fails assert_noop!( AssignedSlots::assign_perm_parachain_slot( RuntimeOrigin::root(), ParaId::from(1_u32), ), Error::::OngoingLeaseExists ); // Cleanup assert_ok!(Slots::clear_all_leases(RuntimeOrigin::root(), 1.into())); // Register lease for next lease period assert_ok!(Slots::lease_out(ParaId::from(1_u32), &1, 1, 2, 1)); // Should be detected and also fail assert_noop!( AssignedSlots::assign_perm_parachain_slot( RuntimeOrigin::root(), ParaId::from(1_u32), ), 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_u32), dummy_head_data(), dummy_validation_code(), )); assert_ok!(TestRegistrar::::register( 2, ParaId::from(2_u32), dummy_head_data(), dummy_validation_code(), )); assert_ok!(TestRegistrar::::register( 3, ParaId::from(3_u32), dummy_head_data(), dummy_validation_code(), )); assert_ok!(AssignedSlots::assign_perm_parachain_slot( RuntimeOrigin::root(), ParaId::from(1_u32), )); assert_ok!(AssignedSlots::assign_perm_parachain_slot( RuntimeOrigin::root(), ParaId::from(2_u32), )); assert_eq!(AssignedSlots::permanent_slot_count(), 2); assert_noop!( AssignedSlots::assign_perm_parachain_slot( RuntimeOrigin::root(), ParaId::from(3_u32), ), 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_u32), dummy_head_data(), dummy_validation_code(), )); assert_eq!(AssignedSlots::permanent_slot_count(), 0); assert_eq!(AssignedSlots::permanent_slots(ParaId::from(1_u32)), None); assert_ok!(AssignedSlots::assign_perm_parachain_slot( RuntimeOrigin::root(), ParaId::from(1_u32), )); // Para is a lease holding parachain for PermanentSlotLeasePeriodLength * LeasePeriod // blocks while block < 9 { println!("block #{}", block); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), true); assert_eq!(AssignedSlots::permanent_slot_count(), 1); assert_eq!(AssignedSlots::has_permanent_slot(ParaId::from(1_u32)), true); assert_eq!(AssignedSlots::permanent_slots(ParaId::from(1_u32)), Some((0, 3))); assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 2), true); block += 1; run_to_block(block); } // Para lease ended, downgraded back to parathread (on-demand parachain) assert_eq!(TestRegistrar::::is_parathread(ParaId::from(1_u32)), true); assert_eq!(Slots::already_leased(ParaId::from(1_u32), 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( RuntimeOrigin::root(), ParaId::from(1_u32), 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( RuntimeOrigin::signed(1), ParaId::from(1_u32), 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_u32), dummy_head_data(), dummy_validation_code(), )); assert_ok!(TestRegistrar::::make_parachain(ParaId::from(1_u32))); assert_noop!( AssignedSlots::assign_temp_parachain_slot( RuntimeOrigin::root(), ParaId::from(1_u32), 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_u32), dummy_head_data(), dummy_validation_code(), )); // Register lease in current lease period assert_ok!(Slots::lease_out(ParaId::from(1_u32), &1, 1, 1, 1)); // Try to assign a perm slot in current period fails assert_noop!( AssignedSlots::assign_temp_parachain_slot( RuntimeOrigin::root(), ParaId::from(1_u32), SlotLeasePeriodStart::Current ), Error::::OngoingLeaseExists ); // Cleanup assert_ok!(Slots::clear_all_leases(RuntimeOrigin::root(), 1.into())); // Register lease for next lease period assert_ok!(Slots::lease_out(ParaId::from(1_u32), &1, 1, 2, 1)); // Should be detected and also fail assert_noop!( AssignedSlots::assign_temp_parachain_slot( RuntimeOrigin::root(), ParaId::from(1_u32), SlotLeasePeriodStart::Current ), Error::::OngoingLeaseExists ); }); } #[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), dummy_head_data(), dummy_validation_code() )); assert_ok!(AssignedSlots::assign_temp_parachain_slot( RuntimeOrigin::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_u32), dummy_head_data(), dummy_validation_code(), )); assert_noop!( AssignedSlots::assign_temp_parachain_slot( RuntimeOrigin::root(), ParaId::from(7_u32), SlotLeasePeriodStart::Current ), Error::::MaxTemporarySlotsExceeded ); }); } #[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_u32), dummy_head_data(), dummy_validation_code(), )); assert_eq!(AssignedSlots::temporary_slots(ParaId::from(1_u32)), None); assert_ok!(AssignedSlots::assign_temp_parachain_slot( RuntimeOrigin::root(), ParaId::from(1_u32), SlotLeasePeriodStart::Current )); assert_eq!(AssignedSlots::temporary_slot_count(), 1); assert_eq!(AssignedSlots::active_temporary_slot_count(), 1); // Block 1-5 // Para is a lease holding parachain for TemporarySlotLeasePeriodLength * LeasePeriod // blocks while block < 6 { println!("block #{}", block); println!("lease period #{}", AssignedSlots::current_lease_period_index()); println!("lease {:?}", Slots::lease(ParaId::from(1_u32))); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), true); assert_eq!(AssignedSlots::has_temporary_slot(ParaId::from(1_u32)), true); assert_eq!(AssignedSlots::active_temporary_slot_count(), 1); assert_eq!( AssignedSlots::temporary_slots(ParaId::from(1_u32)), 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_u32), 0, 1), true); block += 1; run_to_block(block); } // Block 6 println!("block #{}", block); println!("lease period #{}", AssignedSlots::current_lease_period_index()); println!("lease {:?}", Slots::lease(ParaId::from(1_u32))); // Para lease ended, downgraded back to on-demand parachain assert_eq!(TestRegistrar::::is_parathread(ParaId::from(1_u32)), true); assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 3), false); assert_eq!(AssignedSlots::active_temporary_slot_count(), 0); // Block 12 // Para should get a turn after TemporarySlotLeasePeriodLength * LeasePeriod blocks run_to_block(12); println!("block #{}", block); println!("lease period #{}", AssignedSlots::current_lease_period_index()); println!("lease {:?}", Slots::lease(ParaId::from(1_u32))); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), true); assert_eq!(Slots::already_leased(ParaId::from(1_u32), 4, 5), true); assert_eq!(AssignedSlots::active_temporary_slot_count(), 1); }); } #[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), dummy_head_data(), dummy_validation_code() )); assert_ok!(AssignedSlots::assign_temp_parachain_slot( RuntimeOrigin::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_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5_u32)), false); assert_eq!(AssignedSlots::active_temporary_slot_count(), 2); } // 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_u32)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3_u32)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5_u32)), false); assert_eq!(AssignedSlots::active_temporary_slot_count(), 2); } // 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_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4_u32)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5_u32)), true); assert_eq!(AssignedSlots::active_temporary_slot_count(), 2); } // 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_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5_u32)), false); assert_eq!(AssignedSlots::active_temporary_slot_count(), 2); } // 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_u32)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3_u32)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5_u32)), false); assert_eq!(AssignedSlots::active_temporary_slot_count(), 2); } // 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_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(3_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(4_u32)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(5_u32)), true); assert_eq!(AssignedSlots::active_temporary_slot_count(), 2); } }); } #[test] fn unassign_slot_fails_for_unknown_para() { new_test_ext().execute_with(|| { run_to_block(1); assert_noop!( AssignedSlots::unassign_parachain_slot(RuntimeOrigin::root(), ParaId::from(1_u32),), 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( RuntimeOrigin::signed(1), ParaId::from(1_u32), ), BadOrigin ); }); } #[test] fn unassign_perm_slot_succeeds() { new_test_ext().execute_with(|| { run_to_block(1); assert_ok!(TestRegistrar::::register( 1, ParaId::from(1_u32), dummy_head_data(), dummy_validation_code(), )); assert_ok!(AssignedSlots::assign_perm_parachain_slot( RuntimeOrigin::root(), ParaId::from(1_u32), )); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), true); assert_ok!(AssignedSlots::unassign_parachain_slot( RuntimeOrigin::root(), ParaId::from(1_u32), )); assert_eq!(AssignedSlots::permanent_slot_count(), 0); assert_eq!(AssignedSlots::has_permanent_slot(ParaId::from(1_u32)), false); assert_eq!(AssignedSlots::permanent_slots(ParaId::from(1_u32)), None); assert_eq!(Slots::already_leased(ParaId::from(1_u32), 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_u32), dummy_head_data(), dummy_validation_code(), )); assert_ok!(AssignedSlots::assign_temp_parachain_slot( RuntimeOrigin::root(), ParaId::from(1_u32), SlotLeasePeriodStart::Current )); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), true); assert_ok!(AssignedSlots::unassign_parachain_slot( RuntimeOrigin::root(), ParaId::from(1_u32), )); assert_eq!(AssignedSlots::temporary_slot_count(), 0); assert_eq!(AssignedSlots::active_temporary_slot_count(), 0); assert_eq!(AssignedSlots::has_temporary_slot(ParaId::from(1_u32)), false); assert_eq!(AssignedSlots::temporary_slots(ParaId::from(1_u32)), None); assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 1), false); }); } #[test] fn set_max_permanent_slots_fails_for_no_root_origin() { new_test_ext().execute_with(|| { run_to_block(1); assert_noop!( AssignedSlots::set_max_permanent_slots(RuntimeOrigin::signed(1), 5), BadOrigin ); }); } #[test] fn set_max_permanent_slots_succeeds() { new_test_ext().execute_with(|| { run_to_block(1); assert_eq!(MaxPermanentSlots::::get(), 2); assert_ok!(AssignedSlots::set_max_permanent_slots(RuntimeOrigin::root(), 10),); assert_eq!(MaxPermanentSlots::::get(), 10); }); } #[test] fn set_max_temporary_slots_fails_for_no_root_origin() { new_test_ext().execute_with(|| { run_to_block(1); assert_noop!( AssignedSlots::set_max_temporary_slots(RuntimeOrigin::signed(1), 5), BadOrigin ); }); } #[test] fn set_max_temporary_slots_succeeds() { new_test_ext().execute_with(|| { run_to_block(1); assert_eq!(MaxTemporarySlots::::get(), 6); assert_ok!(AssignedSlots::set_max_temporary_slots(RuntimeOrigin::root(), 12),); assert_eq!(MaxTemporarySlots::::get(), 12); }); } }