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