referrerpolicy=no-referrer-when-downgrade

pallet_broker/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#![cfg_attr(not(feature = "std"), no_std)]
19#![doc = include_str!("../README.md")]
20
21pub use pallet::*;
22
23mod adapt_price;
24mod benchmarking;
25mod coretime_interface;
26mod dispatchable_impls;
27
28#[cfg(test)]
29mod mock;
30mod nonfungible_impl;
31#[cfg(test)]
32mod test_fungibles;
33#[cfg(test)]
34mod tests;
35mod tick_impls;
36mod types;
37mod utility_impls;
38
39pub mod migration;
40pub mod runtime_api;
41
42pub mod weights;
43pub use weights::WeightInfo;
44
45pub use adapt_price::*;
46pub use coretime_interface::*;
47pub use fp_coretime::{
48	market, CoreIndex, CoreMask, PartsOf57600, PotentialRenewalId, RegionId, TaskId, Timeslice,
49	CORE_MASK_BITS,
50};
51pub use types::*;
52
53extern crate alloc;
54
55/// The log target for this pallet.
56const LOG_TARGET: &str = "runtime::broker";
57
58#[frame_support::pallet]
59pub mod pallet {
60	use super::*;
61	use alloc::vec::Vec;
62	use frame_support::{
63		pallet_prelude::{DispatchResult, DispatchResultWithPostInfo, *},
64		traits::{
65			fungible::{Balanced, Credit, Mutate},
66			BuildGenesisConfig, EnsureOrigin, OnUnbalanced,
67		},
68		PalletId,
69	};
70	use frame_system::pallet_prelude::*;
71	use sp_runtime::traits::{Convert, ConvertBack, MaybeConvert};
72
73	const STORAGE_VERSION: StorageVersion = StorageVersion::new(5);
74
75	#[pallet::pallet]
76	#[pallet::storage_version(STORAGE_VERSION)]
77	pub struct Pallet<T>(_);
78
79	#[pallet::config]
80	pub trait Config: frame_system::Config {
81		#[allow(deprecated)]
82		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
83
84		/// Weight information for all calls of this pallet.
85		type WeightInfo: WeightInfo;
86
87		/// Currency used to pay for Coretime.
88		type Currency: Mutate<Self::AccountId> + Balanced<Self::AccountId>;
89
90		/// The origin test needed for administrating this pallet.
91		type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
92
93		/// What to do with any revenues collected from the sale of Coretime.
94		type OnRevenue: OnUnbalanced<Credit<Self::AccountId, Self::Currency>>;
95
96		/// Relay chain's Coretime API used to interact with and instruct the low-level scheduling
97		/// system.
98		type Coretime: CoretimeInterface;
99
100		/// The algorithm to determine the next price on the basis of market performance.
101		type PriceAdapter: AdaptPrice<BalanceOf<Self>>;
102
103		/// Reversible conversion from local balance to Relay-chain balance. This will typically be
104		/// the `Identity`, but provided just in case the chains use different representations.
105		type ConvertBalance: Convert<BalanceOf<Self>, RelayBalanceOf<Self>>
106			+ ConvertBack<BalanceOf<Self>, RelayBalanceOf<Self>>;
107
108		/// Type used for getting the associated account of a task. This account is controlled by
109		/// the task itself.
110		type SovereignAccountOf: MaybeConvert<TaskId, Self::AccountId>;
111
112		/// Identifier from which the internal Pot is generated.
113		#[pallet::constant]
114		type PalletId: Get<PalletId>;
115
116		/// Number of Relay-chain blocks per timeslice.
117		#[pallet::constant]
118		type TimeslicePeriod: Get<RelayBlockNumberOf<Self>>;
119
120		/// Maximum number of legacy leases.
121		#[pallet::constant]
122		type MaxLeasedCores: Get<u32>;
123
124		/// Maximum number of system cores.
125		#[pallet::constant]
126		type MaxReservedCores: Get<u32>;
127
128		/// Given that we are performing all auto-renewals in a single block, it has to be limited.
129		#[pallet::constant]
130		type MaxAutoRenewals: Get<u32>;
131
132		/// The smallest amount of credits a user can purchase.
133		///
134		/// Needed to prevent spam attacks.
135		#[pallet::constant]
136		type MinimumCreditPurchase: Get<BalanceOf<Self>>;
137	}
138
139	/// The current configuration of this pallet.
140	#[pallet::storage]
141	pub type Configuration<T> = StorageValue<_, ConfigRecordOf<T>, OptionQuery>;
142
143	/// The Polkadot Core reservations (generally tasked with the maintenance of System Chains).
144	#[pallet::storage]
145	pub type Reservations<T> = StorageValue<_, ReservationsRecordOf<T>, ValueQuery>;
146
147	/// Force reservations that need to be inserted into the workplan at the next sale rotation.
148	///
149	/// They are automatically freed at the next sale rotation.
150	#[pallet::storage]
151	pub type ForceReservations<T> = StorageValue<_, ReservationsRecordOf<T>, ValueQuery>;
152
153	/// The Polkadot Core legacy leases.
154	#[pallet::storage]
155	pub type Leases<T> = StorageValue<_, LeasesRecordOf<T>, ValueQuery>;
156
157	/// The current status of miscellaneous subsystems of this pallet.
158	#[pallet::storage]
159	pub type Status<T> = StorageValue<_, StatusRecord, OptionQuery>;
160
161	/// The details of the current sale, including its properties and status.
162	#[pallet::storage]
163	pub type SaleInfo<T> = StorageValue<_, SaleInfoRecordOf<T>, OptionQuery>;
164
165	/// Records of potential renewals.
166	///
167	/// Renewals will only actually be allowed if `CompletionStatus` is actually `Complete`.
168	#[pallet::storage]
169	pub type PotentialRenewals<T> =
170		StorageMap<_, Twox64Concat, PotentialRenewalId, PotentialRenewalRecordOf<T>, OptionQuery>;
171
172	/// The current (unassigned or provisionally assigend) Regions.
173	#[pallet::storage]
174	pub type Regions<T> = StorageMap<_, Blake2_128Concat, RegionId, RegionRecordOf<T>, OptionQuery>;
175
176	/// The work we plan on having each core do at a particular time in the future.
177	#[pallet::storage]
178	pub type Workplan<T> =
179		StorageMap<_, Twox64Concat, (Timeslice, CoreIndex), Schedule, OptionQuery>;
180
181	/// The current workload of each core. This gets updated with workplan as timeslices pass.
182	#[pallet::storage]
183	pub type Workload<T> = StorageMap<_, Twox64Concat, CoreIndex, Schedule, ValueQuery>;
184
185	/// Record of a single contribution to the Instantaneous Coretime Pool.
186	#[pallet::storage]
187	pub type InstaPoolContribution<T> =
188		StorageMap<_, Blake2_128Concat, RegionId, ContributionRecordOf<T>, OptionQuery>;
189
190	/// Record of Coretime entering or leaving the Instantaneous Coretime Pool.
191	#[pallet::storage]
192	pub type InstaPoolIo<T> = StorageMap<_, Blake2_128Concat, Timeslice, PoolIoRecord, ValueQuery>;
193
194	/// Total InstaPool rewards for each Timeslice and the number of core parts which contributed.
195	#[pallet::storage]
196	pub type InstaPoolHistory<T> =
197		StorageMap<_, Blake2_128Concat, Timeslice, InstaPoolHistoryRecordOf<T>>;
198
199	/// Received core count change from the relay chain.
200	#[pallet::storage]
201	pub type CoreCountInbox<T> = StorageValue<_, CoreIndex, OptionQuery>;
202
203	/// Keeping track of cores which have auto-renewal enabled.
204	///
205	/// Sorted by `CoreIndex` to make the removal of cores from auto-renewal more efficient.
206	#[pallet::storage]
207	pub type AutoRenewals<T: Config> =
208		StorageValue<_, BoundedVec<AutoRenewalRecord, T::MaxAutoRenewals>, ValueQuery>;
209
210	/// Received revenue info from the relay chain.
211	#[pallet::storage]
212	pub type RevenueInbox<T> = StorageValue<_, OnDemandRevenueRecordOf<T>, OptionQuery>;
213
214	#[pallet::event]
215	#[pallet::generate_deposit(pub(super) fn deposit_event)]
216	pub enum Event<T: Config> {
217		/// A Region of Bulk Coretime has been purchased.
218		Purchased {
219			/// The identity of the purchaser.
220			who: T::AccountId,
221			/// The identity of the Region.
222			region_id: RegionId,
223			/// The price paid for this Region.
224			price: BalanceOf<T>,
225			/// The duration of the Region.
226			duration: Timeslice,
227		},
228		/// The workload of a core has become renewable.
229		Renewable {
230			/// The core whose workload can be renewed.
231			core: CoreIndex,
232			/// The price at which the workload can be renewed.
233			price: BalanceOf<T>,
234			/// The time at which the workload would recommence of this renewal. The call to renew
235			/// cannot happen before the beginning of the interlude prior to the sale for regions
236			/// which begin at this time.
237			begin: Timeslice,
238			/// The actual workload which can be renewed.
239			workload: Schedule,
240		},
241		/// A workload has been renewed.
242		Renewed {
243			/// The identity of the renewer.
244			who: T::AccountId,
245			/// The price paid for this renewal.
246			price: BalanceOf<T>,
247			/// The index of the core on which the `workload` was previously scheduled.
248			old_core: CoreIndex,
249			/// The index of the core on which the renewed `workload` has been scheduled.
250			core: CoreIndex,
251			/// The time at which the `workload` will begin on the `core`.
252			begin: Timeslice,
253			/// The number of timeslices for which this `workload` is newly scheduled.
254			duration: Timeslice,
255			/// The workload which was renewed.
256			workload: Schedule,
257		},
258		/// Ownership of a Region has been transferred.
259		Transferred {
260			/// The Region which has been transferred.
261			region_id: RegionId,
262			/// The duration of the Region.
263			duration: Timeslice,
264			/// The old owner of the Region.
265			old_owner: Option<T::AccountId>,
266			/// The new owner of the Region.
267			owner: Option<T::AccountId>,
268		},
269		/// A Region has been split into two non-overlapping Regions.
270		Partitioned {
271			/// The Region which was split.
272			old_region_id: RegionId,
273			/// The new Regions into which it became.
274			new_region_ids: (RegionId, RegionId),
275		},
276		/// A Region has been converted into two overlapping Regions each of lesser regularity.
277		Interlaced {
278			/// The Region which was interlaced.
279			old_region_id: RegionId,
280			/// The new Regions into which it became.
281			new_region_ids: (RegionId, RegionId),
282		},
283		/// A Region has been assigned to a particular task.
284		Assigned {
285			/// The Region which was assigned.
286			region_id: RegionId,
287			/// The duration of the assignment.
288			duration: Timeslice,
289			/// The task to which the Region was assigned.
290			task: TaskId,
291		},
292		/// An assignment has been removed from the workplan.
293		AssignmentRemoved {
294			/// The Region which was removed from the workplan.
295			region_id: RegionId,
296		},
297		/// A Region has been added to the Instantaneous Coretime Pool.
298		Pooled {
299			/// The Region which was added to the Instantaneous Coretime Pool.
300			region_id: RegionId,
301			/// The duration of the Region.
302			duration: Timeslice,
303		},
304		/// A new number of cores has been requested.
305		CoreCountRequested {
306			/// The number of cores requested.
307			core_count: CoreIndex,
308		},
309		/// The number of cores available for scheduling has changed.
310		CoreCountChanged {
311			/// The new number of cores available for scheduling.
312			core_count: CoreIndex,
313		},
314		/// There is a new reservation for a workload.
315		ReservationMade {
316			/// The index of the reservation.
317			index: u32,
318			/// The workload of the reservation.
319			workload: Schedule,
320		},
321		/// A reservation for a workload has been cancelled.
322		ReservationCancelled {
323			/// The index of the reservation which was cancelled.
324			index: u32,
325			/// The workload of the now cancelled reservation.
326			workload: Schedule,
327		},
328		/// A new sale has been initialized.
329		SaleInitialized {
330			/// The relay block number at which the sale will/did start.
331			sale_start: RelayBlockNumberOf<T>,
332			/// The length in relay chain blocks of the Leadin Period (where the price is
333			/// decreasing).
334			leadin_length: RelayBlockNumberOf<T>,
335			/// The price of Bulk Coretime at the beginning of the Leadin Period.
336			start_price: BalanceOf<T>,
337			/// The price of Bulk Coretime after the Leadin Period.
338			end_price: BalanceOf<T>,
339			/// The first timeslice of the Regions which are being sold in this sale.
340			region_begin: Timeslice,
341			/// The timeslice on which the Regions which are being sold in the sale terminate.
342			/// (i.e. One after the last timeslice which the Regions control.)
343			region_end: Timeslice,
344			/// The number of cores we want to sell, ideally.
345			ideal_cores_sold: CoreIndex,
346			/// Number of cores which are/have been offered for sale.
347			cores_offered: CoreIndex,
348			/// Sequential identifier for the current sale period.
349			sale_index: SaleIndex,
350		},
351		/// A new lease has been created.
352		Leased {
353			/// The task to which a core will be assigned.
354			task: TaskId,
355			/// The timeslice contained in the sale period after which this lease will
356			/// self-terminate (and therefore the earliest timeslice at which the lease may no
357			/// longer apply).
358			until: Timeslice,
359		},
360		/// A lease has been removed.
361		LeaseRemoved {
362			/// The task to which a core was assigned.
363			task: TaskId,
364		},
365		/// A lease is about to end.
366		LeaseEnding {
367			/// The task to which a core was assigned.
368			task: TaskId,
369			/// The timeslice at which the task will no longer be scheduled.
370			when: Timeslice,
371		},
372		/// The sale rotation has been started and a new sale is imminent.
373		SalesStarted {
374			/// The nominal price of an Region of Bulk Coretime.
375			price: BalanceOf<T>,
376			/// The maximum number of cores which this pallet will attempt to assign.
377			core_count: CoreIndex,
378		},
379		/// The act of claiming revenue has begun.
380		RevenueClaimBegun {
381			/// The region to be claimed for.
382			region: RegionId,
383			/// The maximum number of timeslices which should be searched for claimed.
384			max_timeslices: Timeslice,
385		},
386		/// A particular timeslice has a non-zero claim.
387		RevenueClaimItem {
388			/// The timeslice whose claim is being processed.
389			when: Timeslice,
390			/// The amount which was claimed at this timeslice.
391			amount: BalanceOf<T>,
392		},
393		/// A revenue claim has (possibly only in part) been paid.
394		RevenueClaimPaid {
395			/// The account to whom revenue has been paid.
396			who: T::AccountId,
397			/// The total amount of revenue claimed and paid.
398			amount: BalanceOf<T>,
399			/// The next region which should be claimed for the continuation of this contribution.
400			next: Option<RegionId>,
401		},
402		/// Some Instantaneous Coretime Pool credit has been purchased.
403		CreditPurchased {
404			/// The account which purchased the credit.
405			who: T::AccountId,
406			/// The Relay-chain account to which the credit will be made.
407			beneficiary: RelayAccountIdOf<T>,
408			/// The amount of credit purchased.
409			amount: BalanceOf<T>,
410		},
411		/// A Region has been dropped due to being out of date.
412		RegionDropped {
413			/// The Region which no longer exists.
414			region_id: RegionId,
415			/// The duration of the Region.
416			duration: Timeslice,
417		},
418		/// Some historical Instantaneous Core Pool contribution record has been dropped.
419		ContributionDropped {
420			/// The Region whose contribution is no longer exists.
421			region_id: RegionId,
422		},
423		/// A region has been force-removed from the pool. This is usually due to a provisionally
424		/// pooled region being redeployed.
425		RegionUnpooled {
426			/// The Region which has been force-removed from the pool.
427			region_id: RegionId,
428			/// The timeslice at which the region was force-removed.
429			when: Timeslice,
430		},
431		/// Some historical Instantaneous Core Pool payment record has been initialized.
432		HistoryInitialized {
433			/// The timeslice whose history has been initialized.
434			when: Timeslice,
435			/// The amount of privately contributed Coretime to the Instantaneous Coretime Pool.
436			private_pool_size: CoreMaskBitCount,
437			/// The amount of Coretime contributed to the Instantaneous Coretime Pool by the
438			/// Polkadot System.
439			system_pool_size: CoreMaskBitCount,
440		},
441		/// Some historical Instantaneous Core Pool payment record has been dropped.
442		HistoryDropped {
443			/// The timeslice whose history is no longer available.
444			when: Timeslice,
445			/// The amount of revenue the system has taken.
446			revenue: BalanceOf<T>,
447		},
448		/// Some historical Instantaneous Core Pool payment record has been ignored because the
449		/// timeslice was already known. Governance may need to intervene.
450		HistoryIgnored {
451			/// The timeslice whose history is was ignored.
452			when: Timeslice,
453			/// The amount of revenue which was ignored.
454			revenue: BalanceOf<T>,
455		},
456		/// Some historical Instantaneous Core Pool Revenue is ready for payout claims.
457		ClaimsReady {
458			/// The timeslice whose history is available.
459			when: Timeslice,
460			/// The amount of revenue the Polkadot System has already taken.
461			system_payout: BalanceOf<T>,
462			/// The total amount of revenue remaining to be claimed.
463			private_payout: BalanceOf<T>,
464		},
465		/// A Core has been assigned to one or more tasks and/or the Pool on the Relay-chain.
466		CoreAssigned {
467			/// The index of the Core which has been assigned.
468			core: CoreIndex,
469			/// The Relay-chain block at which this assignment should take effect.
470			when: RelayBlockNumberOf<T>,
471			/// The workload to be done on the Core.
472			assignment: Vec<(CoreAssignment, PartsOf57600)>,
473		},
474		/// Some historical Instantaneous Core Pool payment record has been dropped.
475		PotentialRenewalDropped {
476			/// The timeslice whose renewal is no longer available.
477			when: Timeslice,
478			/// The core whose workload is no longer available to be renewed for `when`.
479			core: CoreIndex,
480		},
481		AutoRenewalEnabled {
482			/// The core for which the renewal was enabled.
483			core: CoreIndex,
484			/// The task for which the renewal was enabled.
485			task: TaskId,
486		},
487		AutoRenewalDisabled {
488			/// The core for which the renewal was disabled.
489			core: CoreIndex,
490			/// The task for which the renewal was disabled.
491			task: TaskId,
492		},
493		/// Failed to auto-renew a core, likely due to the payer account not being sufficiently
494		/// funded.
495		AutoRenewalFailed {
496			/// The core for which the renewal failed.
497			core: CoreIndex,
498			/// The account which was supposed to pay for renewal.
499			///
500			/// If `None` it indicates that we failed to get the sovereign account of a task.
501			payer: Option<T::AccountId>,
502		},
503		/// The auto-renewal limit has been reached upon renewing cores.
504		///
505		/// This should never happen, given that enable_auto_renew checks for this before enabling
506		/// auto-renewal.
507		AutoRenewalLimitReached,
508		/// Failed to assign a force reservation due to no free cores available.
509		ForceReservationFailed {
510			/// The schedule that could not be assigned.
511			schedule: Schedule,
512		},
513		/// Potential renewal was forcefully removed.
514		PotentialRenewalRemoved {
515			/// The core associated with the potential renewal that was removed.
516			core: CoreIndex,
517			/// The timeslice associated with the potential renewal that was removed.
518			timeslice: Timeslice,
519		},
520	}
521
522	#[pallet::error]
523	#[derive(PartialEq)]
524	pub enum Error<T> {
525		/// The given region identity is not known.
526		UnknownRegion,
527		/// The owner of the region is not the origin.
528		NotOwner,
529		/// The pivot point of the partition at or after the end of the region.
530		PivotTooLate,
531		/// The pivot point of the partition at the beginning of the region.
532		PivotTooEarly,
533		/// The pivot mask for the interlacing is not contained within the region's interlace mask.
534		ExteriorPivot,
535		/// The pivot mask for the interlacing is void (and therefore unschedulable).
536		VoidPivot,
537		/// The pivot mask for the interlacing is complete (and therefore not a strict subset).
538		CompletePivot,
539		/// The workplan of the pallet's state is invalid. This indicates a state corruption.
540		CorruptWorkplan,
541		/// There is no sale happening currently.
542		NoSales,
543		/// The price limit is exceeded.
544		Overpriced,
545		/// There are no cores available.
546		Unavailable,
547		/// The sale limit has been reached.
548		SoldOut,
549		/// The renewal operation is not valid at the current time (it may become valid in the next
550		/// sale).
551		WrongTime,
552		/// Invalid attempt to renew.
553		NotAllowed,
554		/// This pallet has not yet been initialized.
555		Uninitialized,
556		/// The purchase cannot happen yet as the sale period is yet to begin.
557		TooEarly,
558		/// There is no work to be done.
559		NothingToDo,
560		/// The maximum amount of reservations has already been reached.
561		TooManyReservations,
562		/// The maximum amount of leases has already been reached.
563		TooManyLeases,
564		/// The lease does not exist.
565		LeaseNotFound,
566		/// The revenue for the Instantaneous Core Sales of this period is not (yet) known and thus
567		/// this operation cannot proceed.
568		UnknownRevenue,
569		/// The identified contribution to the Instantaneous Core Pool is unknown.
570		UnknownContribution,
571		/// The workload assigned for renewal is incomplete. This is unexpected and indicates a
572		/// logic error.
573		IncompleteAssignment,
574		/// An item cannot be dropped because it is still valid.
575		StillValid,
576		/// The history item does not exist.
577		NoHistory,
578		/// No reservation of the given index exists.
579		UnknownReservation,
580		/// The renewal record cannot be found.
581		UnknownRenewal,
582		/// The lease expiry time has already passed.
583		AlreadyExpired,
584		/// The configuration could not be applied because it is invalid.
585		InvalidConfig,
586		/// The revenue must be claimed for 1 or more timeslices.
587		NoClaimTimeslices,
588		/// The caller doesn't have the permission to enable or disable auto-renewal.
589		NoPermission,
590		/// We reached the limit for auto-renewals.
591		TooManyAutoRenewals,
592		/// Only cores which are assigned to a task can be auto-renewed.
593		NonTaskAutoRenewal,
594		/// Failed to get the sovereign account of a task.
595		SovereignAccountNotFound,
596		/// Attempted to disable auto-renewal for a core that didn't have it enabled.
597		AutoRenewalNotEnabled,
598		/// Attempted to force remove an assignment that doesn't exist.
599		AssignmentNotFound,
600		/// Needed to prevent spam attacks.The amount of credits the user attempted to purchase is
601		/// below `T::MinimumCreditPurchase`.
602		CreditPurchaseTooSmall,
603	}
604
605	#[derive(frame_support::DefaultNoBound)]
606	#[pallet::genesis_config]
607	pub struct GenesisConfig<T: Config> {
608		#[serde(skip)]
609		pub _config: core::marker::PhantomData<T>,
610	}
611
612	#[pallet::genesis_build]
613	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
614		fn build(&self) {
615			frame_system::Pallet::<T>::inc_providers(&Pallet::<T>::account_id());
616		}
617	}
618
619	#[pallet::hooks]
620	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
621		fn on_initialize(_now: BlockNumberFor<T>) -> Weight {
622			Self::do_tick()
623		}
624	}
625
626	#[pallet::call(weight(<T as Config>::WeightInfo))]
627	impl<T: Config> Pallet<T> {
628		/// Configure the pallet.
629		///
630		/// - `origin`: Must be Root or pass `AdminOrigin`.
631		/// - `config`: The configuration for this pallet.
632		#[pallet::call_index(0)]
633		pub fn configure(
634			origin: OriginFor<T>,
635			config: ConfigRecordOf<T>,
636		) -> DispatchResultWithPostInfo {
637			T::AdminOrigin::ensure_origin_or_root(origin)?;
638			Self::do_configure(config)?;
639			Ok(Pays::No.into())
640		}
641
642		/// Reserve a core for a workload.
643		///
644		/// The workload will be given a reservation, but two sale period boundaries must pass
645		/// before the core is actually assigned.
646		///
647		/// - `origin`: Must be Root or pass `AdminOrigin`.
648		/// - `workload`: The workload which should be permanently placed on a core.
649		#[pallet::call_index(1)]
650		pub fn reserve(origin: OriginFor<T>, workload: Schedule) -> DispatchResultWithPostInfo {
651			T::AdminOrigin::ensure_origin_or_root(origin)?;
652			Self::do_reserve(workload)?;
653			Ok(Pays::No.into())
654		}
655
656		/// Cancel a reservation for a workload.
657		///
658		/// - `origin`: Must be Root or pass `AdminOrigin`.
659		/// - `item_index`: The index of the reservation. Usually this will also be the index of the
660		///   core on which the reservation has been scheduled. However, it is possible that if
661		///   other cores are reserved or unreserved in the same sale rotation that they won't
662		///   correspond, so it's better to look up the core properly in the `Reservations` storage.
663		#[pallet::call_index(2)]
664		pub fn unreserve(origin: OriginFor<T>, item_index: u32) -> DispatchResultWithPostInfo {
665			T::AdminOrigin::ensure_origin_or_root(origin)?;
666			Self::do_unreserve(item_index)?;
667			Ok(Pays::No.into())
668		}
669
670		/// Reserve a core for a single task workload for a limited period.
671		///
672		/// In the interlude and sale period where Bulk Coretime is sold for the period immediately
673		/// after `until`, then the same workload may be renewed.
674		///
675		/// - `origin`: Must be Root or pass `AdminOrigin`.
676		/// - `task`: The workload which should be placed on a core.
677		/// - `until`: The timeslice now earlier than which `task` should be placed as a workload on
678		///   a core.
679		#[pallet::call_index(3)]
680		pub fn set_lease(
681			origin: OriginFor<T>,
682			task: TaskId,
683			until: Timeslice,
684		) -> DispatchResultWithPostInfo {
685			T::AdminOrigin::ensure_origin_or_root(origin)?;
686			Self::do_set_lease(task, until)?;
687			Ok(Pays::No.into())
688		}
689
690		/// Begin the Bulk Coretime sales rotation.
691		///
692		/// - `origin`: Must be Root or pass `AdminOrigin`.
693		/// - `end_price`: The price after the leadin period of Bulk Coretime in the first sale.
694		/// - `extra_cores`: Number of extra cores that should be requested on top of the cores
695		///   required for `Reservations` and `Leases`.
696		///
697		/// This will call [`Self::request_core_count`] internally to set the correct core count on
698		/// the relay chain.
699		#[pallet::call_index(4)]
700		#[pallet::weight(T::WeightInfo::start_sales(
701			T::MaxLeasedCores::get() + T::MaxReservedCores::get() + *extra_cores as u32
702		))]
703		pub fn start_sales(
704			origin: OriginFor<T>,
705			end_price: BalanceOf<T>,
706			extra_cores: CoreIndex,
707		) -> DispatchResultWithPostInfo {
708			T::AdminOrigin::ensure_origin_or_root(origin)?;
709			Self::do_start_sales(end_price, extra_cores)?;
710			Ok(Pays::No.into())
711		}
712
713		/// Purchase Bulk Coretime in the ongoing Sale.
714		///
715		/// - `origin`: Must be a Signed origin with at least enough funds to pay the current price
716		///   of Bulk Coretime.
717		/// - `price_limit`: An amount no more than which should be paid.
718		#[pallet::call_index(5)]
719		pub fn purchase(
720			origin: OriginFor<T>,
721			price_limit: BalanceOf<T>,
722		) -> DispatchResultWithPostInfo {
723			let who = ensure_signed(origin)?;
724			Self::do_purchase(who, price_limit)?;
725			Ok(Pays::No.into())
726		}
727
728		/// Renew Bulk Coretime in the ongoing Sale or its prior Interlude Period.
729		///
730		/// - `origin`: Must be a Signed origin with at least enough funds to pay the renewal price
731		///   of the core.
732		/// - `core`: The core which should be renewed.
733		#[pallet::call_index(6)]
734		pub fn renew(origin: OriginFor<T>, core: CoreIndex) -> DispatchResultWithPostInfo {
735			let who = ensure_signed(origin)?;
736			Self::do_renew(who, core)?;
737			Ok(Pays::No.into())
738		}
739
740		/// Transfer a Bulk Coretime Region to a new owner.
741		///
742		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
743		/// - `region_id`: The Region whose ownership should change.
744		/// - `new_owner`: The new owner for the Region.
745		#[pallet::call_index(7)]
746		pub fn transfer(
747			origin: OriginFor<T>,
748			region_id: RegionId,
749			new_owner: T::AccountId,
750		) -> DispatchResult {
751			let who = ensure_signed(origin)?;
752			Self::do_transfer(region_id, Some(who), new_owner)?;
753			Ok(())
754		}
755
756		/// Split a Bulk Coretime Region into two non-overlapping Regions at a particular time into
757		/// the region.
758		///
759		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
760		/// - `region_id`: The Region which should be partitioned into two non-overlapping Regions.
761		/// - `pivot`: The offset in time into the Region at which to make the split.
762		#[pallet::call_index(8)]
763		pub fn partition(
764			origin: OriginFor<T>,
765			region_id: RegionId,
766			pivot: Timeslice,
767		) -> DispatchResult {
768			let who = ensure_signed(origin)?;
769			Self::do_partition(region_id, Some(who), pivot)?;
770			Ok(())
771		}
772
773		/// Split a Bulk Coretime Region into two wholly-overlapping Regions with complementary
774		/// interlace masks which together make up the original Region's interlace mask.
775		///
776		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
777		/// - `region_id`: The Region which should become two interlaced Regions of incomplete
778		///   regularity.
779		/// - `pivot`: The interlace mask of one of the two new regions (the other is its partial
780		///   complement).
781		#[pallet::call_index(9)]
782		pub fn interlace(
783			origin: OriginFor<T>,
784			region_id: RegionId,
785			pivot: CoreMask,
786		) -> DispatchResult {
787			let who = ensure_signed(origin)?;
788			Self::do_interlace(region_id, Some(who), pivot)?;
789			Ok(())
790		}
791
792		/// Assign a Bulk Coretime Region to a task.
793		///
794		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
795		/// - `region_id`: The Region which should be assigned to the task.
796		/// - `task`: The task to assign.
797		/// - `finality`: Indication of whether this assignment is final (in which case it may be
798		///   eligible for renewal) or provisional (in which case it may be manipulated and/or
799		/// reassigned at a later stage).
800		#[pallet::call_index(10)]
801		pub fn assign(
802			origin: OriginFor<T>,
803			region_id: RegionId,
804			task: TaskId,
805			finality: Finality,
806		) -> DispatchResultWithPostInfo {
807			let who = ensure_signed(origin)?;
808			Self::do_assign(region_id, Some(who), task, finality)?;
809			Ok(if finality == Finality::Final { Pays::No } else { Pays::Yes }.into())
810		}
811
812		/// Place a Bulk Coretime Region into the Instantaneous Coretime Pool.
813		///
814		/// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
815		/// - `region_id`: The Region which should be assigned to the Pool.
816		/// - `payee`: The account which is able to collect any revenue due for the usage of this
817		///   Coretime.
818		#[pallet::call_index(11)]
819		pub fn pool(
820			origin: OriginFor<T>,
821			region_id: RegionId,
822			payee: T::AccountId,
823			finality: Finality,
824		) -> DispatchResultWithPostInfo {
825			let who = ensure_signed(origin)?;
826			Self::do_pool(region_id, Some(who), payee, finality)?;
827			Ok(if finality == Finality::Final { Pays::No } else { Pays::Yes }.into())
828		}
829
830		/// Claim the revenue owed from inclusion in the Instantaneous Coretime Pool.
831		///
832		/// - `origin`: Must be a Signed origin.
833		/// - `region_id`: The Region which was assigned to the Pool.
834		/// - `max_timeslices`: The maximum number of timeslices which should be processed. This
835		///   must be greater than 0. This may affect the weight of the call but should be ideally
836		///   made equivalent to the length of the Region `region_id`. If less, further dispatches
837		///   will be required with the same `region_id` to claim revenue for the remainder.
838		#[pallet::call_index(12)]
839		#[pallet::weight(T::WeightInfo::claim_revenue(*max_timeslices))]
840		pub fn claim_revenue(
841			origin: OriginFor<T>,
842			region_id: RegionId,
843			max_timeslices: Timeslice,
844		) -> DispatchResultWithPostInfo {
845			ensure_signed(origin)?;
846			Self::do_claim_revenue(region_id, max_timeslices)?;
847			Ok(Pays::No.into())
848		}
849
850		/// Purchase credit for use in the Instantaneous Coretime Pool.
851		///
852		/// - `origin`: Must be a Signed origin able to pay at least `amount`.
853		/// - `amount`: The amount of credit to purchase.
854		/// - `beneficiary`: The account on the Relay-chain which controls the credit (generally
855		///   this will be the collator's hot wallet).
856		#[pallet::call_index(13)]
857		pub fn purchase_credit(
858			origin: OriginFor<T>,
859			amount: BalanceOf<T>,
860			beneficiary: RelayAccountIdOf<T>,
861		) -> DispatchResult {
862			let who = ensure_signed(origin)?;
863			Self::do_purchase_credit(who, amount, beneficiary)?;
864			Ok(())
865		}
866
867		/// Drop an expired Region from the chain.
868		///
869		/// - `origin`: Can be any kind of origin.
870		/// - `region_id`: The Region which has expired.
871		#[pallet::call_index(14)]
872		pub fn drop_region(
873			_origin: OriginFor<T>,
874			region_id: RegionId,
875		) -> DispatchResultWithPostInfo {
876			Self::do_drop_region(region_id)?;
877			Ok(Pays::No.into())
878		}
879
880		/// Drop an expired Instantaneous Pool Contribution record from the chain.
881		///
882		/// - `origin`: Can be any kind of origin.
883		/// - `region_id`: The Region identifying the Pool Contribution which has expired.
884		#[pallet::call_index(15)]
885		pub fn drop_contribution(
886			_origin: OriginFor<T>,
887			region_id: RegionId,
888		) -> DispatchResultWithPostInfo {
889			Self::do_drop_contribution(region_id)?;
890			Ok(Pays::No.into())
891		}
892
893		/// Drop an expired Instantaneous Pool History record from the chain.
894		///
895		/// - `origin`: Can be any kind of origin.
896		/// - `region_id`: The time of the Pool History record which has expired.
897		#[pallet::call_index(16)]
898		pub fn drop_history(_origin: OriginFor<T>, when: Timeslice) -> DispatchResultWithPostInfo {
899			Self::do_drop_history(when)?;
900			Ok(Pays::No.into())
901		}
902
903		/// Drop an expired Allowed Renewal record from the chain.
904		///
905		/// - `origin`: Can be any kind of origin.
906		/// - `core`: The core to which the expired renewal refers.
907		/// - `when`: The timeslice to which the expired renewal refers. This must have passed.
908		#[pallet::call_index(17)]
909		pub fn drop_renewal(
910			_origin: OriginFor<T>,
911			core: CoreIndex,
912			when: Timeslice,
913		) -> DispatchResultWithPostInfo {
914			Self::do_drop_renewal(core, when)?;
915			Ok(Pays::No.into())
916		}
917
918		/// Request a change to the number of cores available for scheduling work.
919		///
920		/// - `origin`: Must be Root or pass `AdminOrigin`.
921		/// - `core_count`: The desired number of cores to be made available.
922		#[pallet::call_index(18)]
923		#[pallet::weight(T::WeightInfo::request_core_count((*core_count).into()))]
924		pub fn request_core_count(origin: OriginFor<T>, core_count: CoreIndex) -> DispatchResult {
925			T::AdminOrigin::ensure_origin_or_root(origin)?;
926			Self::do_request_core_count(core_count)?;
927			Ok(())
928		}
929
930		#[pallet::call_index(19)]
931		#[pallet::weight(T::WeightInfo::notify_core_count())]
932		pub fn notify_core_count(origin: OriginFor<T>, core_count: CoreIndex) -> DispatchResult {
933			T::AdminOrigin::ensure_origin_or_root(origin)?;
934			Self::do_notify_core_count(core_count)?;
935			Ok(())
936		}
937
938		#[pallet::call_index(20)]
939		#[pallet::weight(T::WeightInfo::notify_revenue())]
940		pub fn notify_revenue(
941			origin: OriginFor<T>,
942			revenue: OnDemandRevenueRecordOf<T>,
943		) -> DispatchResult {
944			T::AdminOrigin::ensure_origin_or_root(origin)?;
945			Self::do_notify_revenue(revenue)?;
946			Ok(())
947		}
948
949		/// Extrinsic for enabling auto renewal.
950		///
951		/// Callable by the sovereign account of the task on the specified core. This account
952		/// will be charged at the start of every bulk period for renewing core time.
953		///
954		/// - `origin`: Must be the sovereign account of the task
955		/// - `core`: The core to which the task to be renewed is currently assigned.
956		/// - `task`: The task for which we want to enable auto renewal.
957		/// - `workload_end_hint`: should be used when enabling auto-renewal for a core that is not
958		///   expiring in the upcoming bulk period (e.g., due to holding a lease) since it would be
959		///   inefficient to look up when the core expires to schedule the next renewal.
960		#[pallet::call_index(21)]
961		#[pallet::weight(T::WeightInfo::enable_auto_renew())]
962		pub fn enable_auto_renew(
963			origin: OriginFor<T>,
964			core: CoreIndex,
965			task: TaskId,
966			workload_end_hint: Option<Timeslice>,
967		) -> DispatchResult {
968			let who = ensure_signed(origin)?;
969
970			let sovereign_account = T::SovereignAccountOf::maybe_convert(task)
971				.ok_or(Error::<T>::SovereignAccountNotFound)?;
972			// Only the sovereign account of a task can enable auto renewal for its own core.
973			ensure!(who == sovereign_account, Error::<T>::NoPermission);
974
975			Self::do_enable_auto_renew(sovereign_account, core, task, workload_end_hint)?;
976			Ok(())
977		}
978
979		/// Extrinsic for disabling auto renewal.
980		///
981		/// Callable by the sovereign account of the task on the specified core.
982		///
983		/// - `origin`: Must be the sovereign account of the task.
984		/// - `core`: The core for which we want to disable auto renewal.
985		/// - `task`: The task for which we want to disable auto renewal.
986		#[pallet::call_index(22)]
987		#[pallet::weight(T::WeightInfo::disable_auto_renew())]
988		pub fn disable_auto_renew(
989			origin: OriginFor<T>,
990			core: CoreIndex,
991			task: TaskId,
992		) -> DispatchResult {
993			let who = ensure_signed(origin)?;
994
995			let sovereign_account = T::SovereignAccountOf::maybe_convert(task)
996				.ok_or(Error::<T>::SovereignAccountNotFound)?;
997			// Only the sovereign account of the task can disable auto-renewal.
998			ensure!(who == sovereign_account, Error::<T>::NoPermission);
999
1000			Self::do_disable_auto_renew(core, task)?;
1001
1002			Ok(())
1003		}
1004
1005		/// Reserve a core for a workload immediately.
1006		///
1007		/// - `origin`: Must be Root or pass `AdminOrigin`.
1008		/// - `workload`: The workload which should be permanently placed on a core starting
1009		///   immediately.
1010		/// - `core`: The core to which the assignment should be made until the reservation takes
1011		///   effect. It is left to the caller to either add this new core or reassign any other
1012		///   tasks to this existing core.
1013		///
1014		/// This reserves the workload and then injects the workload into the Workplan for the next
1015		/// two sale periods. This overwrites any existing assignments for this core at the start of
1016		/// the next sale period.
1017		#[pallet::call_index(23)]
1018		pub fn force_reserve(
1019			origin: OriginFor<T>,
1020			workload: Schedule,
1021			core: CoreIndex,
1022		) -> DispatchResultWithPostInfo {
1023			T::AdminOrigin::ensure_origin_or_root(origin)?;
1024			Self::do_force_reserve(workload, core)?;
1025			Ok(Pays::No.into())
1026		}
1027
1028		/// Remove a lease.
1029		///
1030		/// - `origin`: Must be Root or pass `AdminOrigin`.
1031		/// - `task`: The task id of the lease which should be removed.
1032		#[pallet::call_index(24)]
1033		pub fn remove_lease(origin: OriginFor<T>, task: TaskId) -> DispatchResult {
1034			T::AdminOrigin::ensure_origin_or_root(origin)?;
1035			Self::do_remove_lease(task)
1036		}
1037
1038		/// Remove an assignment from the Workplan.
1039		///
1040		/// - `origin`: Must be Root or pass `AdminOrigin`.
1041		/// - `region_id`: The Region to be removed from the workplan.
1042		#[pallet::call_index(26)]
1043		pub fn remove_assignment(origin: OriginFor<T>, region_id: RegionId) -> DispatchResult {
1044			T::AdminOrigin::ensure_origin_or_root(origin)?;
1045			Self::do_remove_assignment(region_id)
1046		}
1047
1048		/// Forcefully remove a potential renewal record from chain.
1049		///
1050		/// Note that only the specified potential renewal will be removed while any related auto
1051		/// renewals will stay intact and will fail.
1052		///
1053		/// - `origin`: Must be Root or pass `AdminOrigin`.
1054		/// - `core`: Core which the target potential renewal record refers to.
1055		/// - `when`: Timeslice which the target potential renewal record refers to.
1056		#[pallet::call_index(27)]
1057		pub fn remove_potential_renewal(
1058			origin: OriginFor<T>,
1059			core: CoreIndex,
1060			when: Timeslice,
1061		) -> DispatchResult {
1062			T::AdminOrigin::ensure_origin_or_root(origin)?;
1063			Self::do_remove_potential_renewal(core, when)
1064		}
1065
1066		/// Transfer a Bulk Coretime Region to a new owner, ignoring the previous owner.
1067		///
1068		/// This can also be used to recover regions that have been "burned" (e.g., from an
1069		/// XCM reserve transfer).
1070		///
1071		/// - `origin`: Must be Root or pass `AdminOrigin`.
1072		/// - `region_id`: The Region whose ownership should change.
1073		/// - `new_owner`: The new owner for the Region.
1074		#[pallet::call_index(28)]
1075		pub fn force_transfer(
1076			origin: OriginFor<T>,
1077			region_id: RegionId,
1078			new_owner: T::AccountId,
1079		) -> DispatchResult {
1080			T::AdminOrigin::ensure_origin_or_root(origin)?;
1081			Self::do_transfer(region_id, None, new_owner)?;
1082			Ok(())
1083		}
1084
1085		#[pallet::call_index(99)]
1086		#[pallet::weight(T::WeightInfo::swap_leases())]
1087		pub fn swap_leases(origin: OriginFor<T>, id: TaskId, other: TaskId) -> DispatchResult {
1088			T::AdminOrigin::ensure_origin_or_root(origin)?;
1089			Self::do_swap_leases(id, other)?;
1090			Ok(())
1091		}
1092	}
1093}