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 or the workload on the core no longer belonging to the paying task.
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 /// The renewable workload of the core does not include the given task.
604 TaskNotInWorkload,
605 }
606
607 #[derive(frame_support::DefaultNoBound)]
608 #[pallet::genesis_config]
609 pub struct GenesisConfig<T: Config> {
610 #[serde(skip)]
611 pub _config: core::marker::PhantomData<T>,
612 }
613
614 #[pallet::genesis_build]
615 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
616 fn build(&self) {
617 frame_system::Pallet::<T>::inc_providers(&Pallet::<T>::account_id());
618 }
619 }
620
621 #[pallet::hooks]
622 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
623 fn on_initialize(_now: BlockNumberFor<T>) -> Weight {
624 Self::do_tick()
625 }
626 }
627
628 #[pallet::call(weight(<T as Config>::WeightInfo))]
629 impl<T: Config> Pallet<T> {
630 /// Configure the pallet.
631 ///
632 /// - `origin`: Must be Root or pass `AdminOrigin`.
633 /// - `config`: The configuration for this pallet.
634 #[pallet::call_index(0)]
635 pub fn configure(
636 origin: OriginFor<T>,
637 config: ConfigRecordOf<T>,
638 ) -> DispatchResultWithPostInfo {
639 T::AdminOrigin::ensure_origin_or_root(origin)?;
640 Self::do_configure(config)?;
641 Ok(Pays::No.into())
642 }
643
644 /// Reserve a core for a workload.
645 ///
646 /// The workload will be given a reservation, but two sale period boundaries must pass
647 /// before the core is actually assigned.
648 ///
649 /// - `origin`: Must be Root or pass `AdminOrigin`.
650 /// - `workload`: The workload which should be permanently placed on a core.
651 #[pallet::call_index(1)]
652 pub fn reserve(origin: OriginFor<T>, workload: Schedule) -> DispatchResultWithPostInfo {
653 T::AdminOrigin::ensure_origin_or_root(origin)?;
654 Self::do_reserve(workload)?;
655 Ok(Pays::No.into())
656 }
657
658 /// Cancel a reservation for a workload.
659 ///
660 /// - `origin`: Must be Root or pass `AdminOrigin`.
661 /// - `item_index`: The index of the reservation. Usually this will also be the index of the
662 /// core on which the reservation has been scheduled. However, it is possible that if
663 /// other cores are reserved or unreserved in the same sale rotation that they won't
664 /// correspond, so it's better to look up the core properly in the `Reservations` storage.
665 #[pallet::call_index(2)]
666 pub fn unreserve(origin: OriginFor<T>, item_index: u32) -> DispatchResultWithPostInfo {
667 T::AdminOrigin::ensure_origin_or_root(origin)?;
668 Self::do_unreserve(item_index)?;
669 Ok(Pays::No.into())
670 }
671
672 /// Reserve a core for a single task workload for a limited period.
673 ///
674 /// In the interlude and sale period where Bulk Coretime is sold for the period immediately
675 /// after `until`, then the same workload may be renewed.
676 ///
677 /// - `origin`: Must be Root or pass `AdminOrigin`.
678 /// - `task`: The workload which should be placed on a core.
679 /// - `until`: The timeslice now earlier than which `task` should be placed as a workload on
680 /// a core.
681 #[pallet::call_index(3)]
682 pub fn set_lease(
683 origin: OriginFor<T>,
684 task: TaskId,
685 until: Timeslice,
686 ) -> DispatchResultWithPostInfo {
687 T::AdminOrigin::ensure_origin_or_root(origin)?;
688 Self::do_set_lease(task, until)?;
689 Ok(Pays::No.into())
690 }
691
692 /// Begin the Bulk Coretime sales rotation.
693 ///
694 /// - `origin`: Must be Root or pass `AdminOrigin`.
695 /// - `end_price`: The price after the leadin period of Bulk Coretime in the first sale.
696 /// - `extra_cores`: Number of extra cores that should be requested on top of the cores
697 /// required for `Reservations` and `Leases`.
698 ///
699 /// This will call [`Self::request_core_count`] internally to set the correct core count on
700 /// the relay chain.
701 #[pallet::call_index(4)]
702 #[pallet::weight(T::WeightInfo::start_sales(
703 T::MaxLeasedCores::get() + T::MaxReservedCores::get() + *extra_cores as u32
704 ))]
705 pub fn start_sales(
706 origin: OriginFor<T>,
707 end_price: BalanceOf<T>,
708 extra_cores: CoreIndex,
709 ) -> DispatchResultWithPostInfo {
710 T::AdminOrigin::ensure_origin_or_root(origin)?;
711 Self::do_start_sales(end_price, extra_cores)?;
712 Ok(Pays::No.into())
713 }
714
715 /// Purchase Bulk Coretime in the ongoing Sale.
716 ///
717 /// - `origin`: Must be a Signed origin with at least enough funds to pay the current price
718 /// of Bulk Coretime.
719 /// - `price_limit`: An amount no more than which should be paid.
720 #[pallet::call_index(5)]
721 pub fn purchase(
722 origin: OriginFor<T>,
723 price_limit: BalanceOf<T>,
724 ) -> DispatchResultWithPostInfo {
725 let who = ensure_signed(origin)?;
726 Self::do_purchase(who, price_limit)?;
727 Ok(Pays::No.into())
728 }
729
730 /// Renew Bulk Coretime in the ongoing Sale or its prior Interlude Period.
731 ///
732 /// - `origin`: Must be a Signed origin with at least enough funds to pay the renewal price
733 /// of the core.
734 /// - `core`: The core which should be renewed.
735 #[pallet::call_index(6)]
736 pub fn renew(origin: OriginFor<T>, core: CoreIndex) -> DispatchResultWithPostInfo {
737 let who = ensure_signed(origin)?;
738 Self::do_renew(who, core)?;
739 Ok(Pays::No.into())
740 }
741
742 /// Transfer a Bulk Coretime Region to a new owner.
743 ///
744 /// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
745 /// - `region_id`: The Region whose ownership should change.
746 /// - `new_owner`: The new owner for the Region.
747 #[pallet::call_index(7)]
748 pub fn transfer(
749 origin: OriginFor<T>,
750 region_id: RegionId,
751 new_owner: T::AccountId,
752 ) -> DispatchResult {
753 let who = ensure_signed(origin)?;
754 Self::do_transfer(region_id, Some(who), new_owner)?;
755 Ok(())
756 }
757
758 /// Split a Bulk Coretime Region into two non-overlapping Regions at a particular time into
759 /// the region.
760 ///
761 /// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
762 /// - `region_id`: The Region which should be partitioned into two non-overlapping Regions.
763 /// - `pivot`: The offset in time into the Region at which to make the split.
764 #[pallet::call_index(8)]
765 pub fn partition(
766 origin: OriginFor<T>,
767 region_id: RegionId,
768 pivot: Timeslice,
769 ) -> DispatchResult {
770 let who = ensure_signed(origin)?;
771 Self::do_partition(region_id, Some(who), pivot)?;
772 Ok(())
773 }
774
775 /// Split a Bulk Coretime Region into two wholly-overlapping Regions with complementary
776 /// interlace masks which together make up the original Region's interlace mask.
777 ///
778 /// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
779 /// - `region_id`: The Region which should become two interlaced Regions of incomplete
780 /// regularity.
781 /// - `pivot`: The interlace mask of one of the two new regions (the other is its partial
782 /// complement).
783 #[pallet::call_index(9)]
784 pub fn interlace(
785 origin: OriginFor<T>,
786 region_id: RegionId,
787 pivot: CoreMask,
788 ) -> DispatchResult {
789 let who = ensure_signed(origin)?;
790 Self::do_interlace(region_id, Some(who), pivot)?;
791 Ok(())
792 }
793
794 /// Assign a Bulk Coretime Region to a task.
795 ///
796 /// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
797 /// - `region_id`: The Region which should be assigned to the task.
798 /// - `task`: The task to assign.
799 /// - `finality`: Indication of whether this assignment is final (in which case it may be
800 /// eligible for renewal) or provisional (in which case it may be manipulated and/or
801 /// reassigned at a later stage).
802 #[pallet::call_index(10)]
803 pub fn assign(
804 origin: OriginFor<T>,
805 region_id: RegionId,
806 task: TaskId,
807 finality: Finality,
808 ) -> DispatchResultWithPostInfo {
809 let who = ensure_signed(origin)?;
810 Self::do_assign(region_id, Some(who), task, finality)?;
811 Ok(if finality == Finality::Final { Pays::No } else { Pays::Yes }.into())
812 }
813
814 /// Place a Bulk Coretime Region into the Instantaneous Coretime Pool.
815 ///
816 /// - `origin`: Must be a Signed origin of the account which owns the Region `region_id`.
817 /// - `region_id`: The Region which should be assigned to the Pool.
818 /// - `payee`: The account which is able to collect any revenue due for the usage of this
819 /// Coretime.
820 #[pallet::call_index(11)]
821 pub fn pool(
822 origin: OriginFor<T>,
823 region_id: RegionId,
824 payee: T::AccountId,
825 finality: Finality,
826 ) -> DispatchResultWithPostInfo {
827 let who = ensure_signed(origin)?;
828 Self::do_pool(region_id, Some(who), payee, finality)?;
829 Ok(if finality == Finality::Final { Pays::No } else { Pays::Yes }.into())
830 }
831
832 /// Claim the revenue owed from inclusion in the Instantaneous Coretime Pool.
833 ///
834 /// - `origin`: Must be a Signed origin.
835 /// - `region_id`: The Region which was assigned to the Pool.
836 /// - `max_timeslices`: The maximum number of timeslices which should be processed. This
837 /// must be greater than 0. This may affect the weight of the call but should be ideally
838 /// made equivalent to the length of the Region `region_id`. If less, further dispatches
839 /// will be required with the same `region_id` to claim revenue for the remainder.
840 #[pallet::call_index(12)]
841 #[pallet::weight(T::WeightInfo::claim_revenue(*max_timeslices))]
842 pub fn claim_revenue(
843 origin: OriginFor<T>,
844 region_id: RegionId,
845 max_timeslices: Timeslice,
846 ) -> DispatchResultWithPostInfo {
847 ensure_signed(origin)?;
848 Self::do_claim_revenue(region_id, max_timeslices)?;
849 Ok(Pays::No.into())
850 }
851
852 /// Purchase credit for use in the Instantaneous Coretime Pool.
853 ///
854 /// - `origin`: Must be a Signed origin able to pay at least `amount`.
855 /// - `amount`: The amount of credit to purchase.
856 /// - `beneficiary`: The account on the Relay-chain which controls the credit (generally
857 /// this will be the collator's hot wallet).
858 #[pallet::call_index(13)]
859 pub fn purchase_credit(
860 origin: OriginFor<T>,
861 amount: BalanceOf<T>,
862 beneficiary: RelayAccountIdOf<T>,
863 ) -> DispatchResult {
864 let who = ensure_signed(origin)?;
865 Self::do_purchase_credit(who, amount, beneficiary)?;
866 Ok(())
867 }
868
869 /// Drop an expired Region from the chain.
870 ///
871 /// - `origin`: Can be any kind of origin.
872 /// - `region_id`: The Region which has expired.
873 #[pallet::call_index(14)]
874 pub fn drop_region(
875 _origin: OriginFor<T>,
876 region_id: RegionId,
877 ) -> DispatchResultWithPostInfo {
878 Self::do_drop_region(region_id)?;
879 Ok(Pays::No.into())
880 }
881
882 /// Drop an expired Instantaneous Pool Contribution record from the chain.
883 ///
884 /// - `origin`: Can be any kind of origin.
885 /// - `region_id`: The Region identifying the Pool Contribution which has expired.
886 #[pallet::call_index(15)]
887 pub fn drop_contribution(
888 _origin: OriginFor<T>,
889 region_id: RegionId,
890 ) -> DispatchResultWithPostInfo {
891 Self::do_drop_contribution(region_id)?;
892 Ok(Pays::No.into())
893 }
894
895 /// Drop an expired Instantaneous Pool History record from the chain.
896 ///
897 /// - `origin`: Can be any kind of origin.
898 /// - `region_id`: The time of the Pool History record which has expired.
899 #[pallet::call_index(16)]
900 pub fn drop_history(_origin: OriginFor<T>, when: Timeslice) -> DispatchResultWithPostInfo {
901 Self::do_drop_history(when)?;
902 Ok(Pays::No.into())
903 }
904
905 /// Drop an expired Allowed Renewal record from the chain.
906 ///
907 /// - `origin`: Can be any kind of origin.
908 /// - `core`: The core to which the expired renewal refers.
909 /// - `when`: The timeslice to which the expired renewal refers. This must have passed.
910 #[pallet::call_index(17)]
911 pub fn drop_renewal(
912 _origin: OriginFor<T>,
913 core: CoreIndex,
914 when: Timeslice,
915 ) -> DispatchResultWithPostInfo {
916 Self::do_drop_renewal(core, when)?;
917 Ok(Pays::No.into())
918 }
919
920 /// Request a change to the number of cores available for scheduling work.
921 ///
922 /// - `origin`: Must be Root or pass `AdminOrigin`.
923 /// - `core_count`: The desired number of cores to be made available.
924 #[pallet::call_index(18)]
925 #[pallet::weight(T::WeightInfo::request_core_count((*core_count).into()))]
926 pub fn request_core_count(origin: OriginFor<T>, core_count: CoreIndex) -> DispatchResult {
927 T::AdminOrigin::ensure_origin_or_root(origin)?;
928 Self::do_request_core_count(core_count)?;
929 Ok(())
930 }
931
932 #[pallet::call_index(19)]
933 #[pallet::weight(T::WeightInfo::notify_core_count())]
934 pub fn notify_core_count(origin: OriginFor<T>, core_count: CoreIndex) -> DispatchResult {
935 T::AdminOrigin::ensure_origin_or_root(origin)?;
936 Self::do_notify_core_count(core_count)?;
937 Ok(())
938 }
939
940 #[pallet::call_index(20)]
941 #[pallet::weight(T::WeightInfo::notify_revenue())]
942 pub fn notify_revenue(
943 origin: OriginFor<T>,
944 revenue: OnDemandRevenueRecordOf<T>,
945 ) -> DispatchResult {
946 T::AdminOrigin::ensure_origin_or_root(origin)?;
947 Self::do_notify_revenue(revenue)?;
948 Ok(())
949 }
950
951 /// Extrinsic for enabling auto renewal.
952 ///
953 /// Callable by the sovereign account of the task on the specified core. This account
954 /// will be charged at the start of every bulk period for renewing core time.
955 ///
956 /// - `origin`: Must be the sovereign account of the task
957 /// - `core`: The core to which the task to be renewed is currently assigned.
958 /// - `task`: The task for which we want to enable auto renewal.
959 /// - `workload_end_hint`: should be used when enabling auto-renewal for a core that is not
960 /// expiring in the upcoming bulk period (e.g., due to holding a lease) since it would be
961 /// inefficient to look up when the core expires to schedule the next renewal. Also used
962 /// when the core is expiring with another task's workload, in which case it must point at
963 /// the task's own renewal record.
964 #[pallet::call_index(21)]
965 #[pallet::weight(T::WeightInfo::enable_auto_renew())]
966 pub fn enable_auto_renew(
967 origin: OriginFor<T>,
968 core: CoreIndex,
969 task: TaskId,
970 workload_end_hint: Option<Timeslice>,
971 ) -> DispatchResult {
972 let who = ensure_signed(origin)?;
973
974 let sovereign_account = T::SovereignAccountOf::maybe_convert(task)
975 .ok_or(Error::<T>::SovereignAccountNotFound)?;
976 // Only the sovereign account of a task can enable auto renewal for its own core.
977 ensure!(who == sovereign_account, Error::<T>::NoPermission);
978
979 Self::do_enable_auto_renew(sovereign_account, core, task, workload_end_hint)?;
980 Ok(())
981 }
982
983 /// Extrinsic for disabling auto renewal.
984 ///
985 /// Callable by the sovereign account of the task on the specified core.
986 ///
987 /// - `origin`: Must be the sovereign account of the task.
988 /// - `core`: The core for which we want to disable auto renewal.
989 /// - `task`: The task for which we want to disable auto renewal.
990 #[pallet::call_index(22)]
991 #[pallet::weight(T::WeightInfo::disable_auto_renew())]
992 pub fn disable_auto_renew(
993 origin: OriginFor<T>,
994 core: CoreIndex,
995 task: TaskId,
996 ) -> DispatchResult {
997 let who = ensure_signed(origin)?;
998
999 let sovereign_account = T::SovereignAccountOf::maybe_convert(task)
1000 .ok_or(Error::<T>::SovereignAccountNotFound)?;
1001 // Only the sovereign account of the task can disable auto-renewal.
1002 ensure!(who == sovereign_account, Error::<T>::NoPermission);
1003
1004 Self::do_disable_auto_renew(core, task)?;
1005
1006 Ok(())
1007 }
1008
1009 /// Reserve a core for a workload immediately.
1010 ///
1011 /// - `origin`: Must be Root or pass `AdminOrigin`.
1012 /// - `workload`: The workload which should be permanently placed on a core starting
1013 /// immediately.
1014 /// - `core`: The core to which the assignment should be made until the reservation takes
1015 /// effect. It is left to the caller to either add this new core or reassign any other
1016 /// tasks to this existing core.
1017 ///
1018 /// This reserves the workload and then injects the workload into the Workplan for the next
1019 /// two sale periods. This overwrites any existing assignments for this core at the start of
1020 /// the next sale period.
1021 #[pallet::call_index(23)]
1022 pub fn force_reserve(
1023 origin: OriginFor<T>,
1024 workload: Schedule,
1025 core: CoreIndex,
1026 ) -> DispatchResultWithPostInfo {
1027 T::AdminOrigin::ensure_origin_or_root(origin)?;
1028 Self::do_force_reserve(workload, core)?;
1029 Ok(Pays::No.into())
1030 }
1031
1032 /// Remove a lease.
1033 ///
1034 /// - `origin`: Must be Root or pass `AdminOrigin`.
1035 /// - `task`: The task id of the lease which should be removed.
1036 #[pallet::call_index(24)]
1037 pub fn remove_lease(origin: OriginFor<T>, task: TaskId) -> DispatchResult {
1038 T::AdminOrigin::ensure_origin_or_root(origin)?;
1039 Self::do_remove_lease(task)
1040 }
1041
1042 /// Remove an assignment from the Workplan.
1043 ///
1044 /// - `origin`: Must be Root or pass `AdminOrigin`.
1045 /// - `region_id`: The Region to be removed from the workplan.
1046 #[pallet::call_index(26)]
1047 pub fn remove_assignment(origin: OriginFor<T>, region_id: RegionId) -> DispatchResult {
1048 T::AdminOrigin::ensure_origin_or_root(origin)?;
1049 Self::do_remove_assignment(region_id)
1050 }
1051
1052 /// Forcefully remove a potential renewal record from chain.
1053 ///
1054 /// Note that only the specified potential renewal will be removed while any related auto
1055 /// renewals will stay intact and will fail.
1056 ///
1057 /// - `origin`: Must be Root or pass `AdminOrigin`.
1058 /// - `core`: Core which the target potential renewal record refers to.
1059 /// - `when`: Timeslice which the target potential renewal record refers to.
1060 #[pallet::call_index(27)]
1061 pub fn remove_potential_renewal(
1062 origin: OriginFor<T>,
1063 core: CoreIndex,
1064 when: Timeslice,
1065 ) -> DispatchResult {
1066 T::AdminOrigin::ensure_origin_or_root(origin)?;
1067 Self::do_remove_potential_renewal(core, when)
1068 }
1069
1070 /// Transfer a Bulk Coretime Region to a new owner, ignoring the previous owner.
1071 ///
1072 /// This can also be used to recover regions that have been "burned" (e.g., from an
1073 /// XCM reserve transfer).
1074 ///
1075 /// - `origin`: Must be Root or pass `AdminOrigin`.
1076 /// - `region_id`: The Region whose ownership should change.
1077 /// - `new_owner`: The new owner for the Region.
1078 #[pallet::call_index(28)]
1079 pub fn force_transfer(
1080 origin: OriginFor<T>,
1081 region_id: RegionId,
1082 new_owner: T::AccountId,
1083 ) -> DispatchResult {
1084 T::AdminOrigin::ensure_origin_or_root(origin)?;
1085 Self::do_transfer(region_id, None, new_owner)?;
1086 Ok(())
1087 }
1088
1089 #[pallet::call_index(99)]
1090 #[pallet::weight(T::WeightInfo::swap_leases())]
1091 pub fn swap_leases(origin: OriginFor<T>, id: TaskId, other: TaskId) -> DispatchResult {
1092 T::AdminOrigin::ensure_origin_or_root(origin)?;
1093 Self::do_swap_leases(id, other)?;
1094 Ok(())
1095 }
1096 }
1097}