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