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