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