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}