polkadot_runtime_parachains/coretime/
mod.rs1use alloc::{vec, vec::Vec};
25use core::result;
26use frame_support::{
27 pallet_prelude::*,
28 traits::{defensive_prelude::*, Currency},
29};
30use frame_system::pallet_prelude::*;
31pub use pallet::*;
32use pallet_broker::{CoreAssignment, CoreIndex as BrokerCoreIndex};
33use polkadot_primitives::{Balance, BlockNumber, CoreIndex, Id as ParaId};
34use sp_arithmetic::traits::SaturatedConversion;
35use sp_runtime::traits::TryConvert;
36use xcm::prelude::*;
37use xcm_executor::traits::TransactAsset;
38
39use crate::{
40 initializer::{OnNewSession, SessionChangeNotification},
41 on_demand,
42 origin::{ensure_parachain, Origin},
43 scheduler::{self, PartsOf57600},
44};
45
46mod benchmarking;
47
48const LOG_TARGET: &str = "runtime::parachains::coretime";
49
50pub trait WeightInfo {
51 fn request_core_count() -> Weight;
52 fn request_revenue_at() -> Weight;
53 fn credit_account() -> Weight;
54 fn assign_core(s: u32) -> Weight;
55 fn queue_on_demand_batch(s: u32) -> Weight;
56}
57
58pub struct TestWeightInfo;
60
61impl WeightInfo for TestWeightInfo {
62 fn request_core_count() -> Weight {
63 Weight::MAX
64 }
65 fn request_revenue_at() -> Weight {
66 Weight::MAX
67 }
68 fn credit_account() -> Weight {
69 Weight::MAX
70 }
71 fn assign_core(_s: u32) -> Weight {
72 Weight::MAX
73 }
74 fn queue_on_demand_batch(_s: u32) -> Weight {
75 Weight::MAX
76 }
77}
78
79pub type BalanceOf<T> = <<T as on_demand::Config>::Currency as Currency<
81 <T as frame_system::Config>::AccountId,
82>>::Balance;
83
84#[derive(Encode, Decode)]
89enum BrokerRuntimePallets {
90 #[codec(index = 50)]
91 Broker(CoretimeCalls),
92}
93
94#[derive(Encode, Decode)]
96enum CoretimeCalls {
97 #[codec(index = 1)]
98 Reserve(pallet_broker::Schedule),
99 #[codec(index = 3)]
100 SetLease(pallet_broker::TaskId, pallet_broker::Timeslice),
101 #[codec(index = 19)]
102 NotifyCoreCount(u16),
103 #[codec(index = 20)]
104 NotifyRevenue((BlockNumber, Balance)),
105 #[codec(index = 99)]
106 SwapLeases(ParaId, ParaId),
107}
108
109#[frame_support::pallet]
110pub mod pallet {
111
112 use crate::configuration;
113 use sp_runtime::traits::TryConvert;
114 use xcm::latest::InteriorLocation;
115 use xcm_executor::traits::TransactAsset;
116
117 use super::*;
118
119 #[pallet::pallet]
120 #[pallet::without_storage_info]
121 pub struct Pallet<T>(_);
122
123 #[pallet::config]
124 pub trait Config: frame_system::Config + scheduler::Config + on_demand::Config {
125 type RuntimeOrigin: From<<Self as frame_system::Config>::RuntimeOrigin>
126 + Into<result::Result<Origin, <Self as Config>::RuntimeOrigin>>;
127 #[allow(deprecated)]
128 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
129 #[pallet::constant]
131 type BrokerId: Get<u32>;
132 #[pallet::constant]
134 type BrokerPotLocation: Get<InteriorLocation>;
135 type WeightInfo: WeightInfo;
137 type SendXcm: SendXcm;
139 type AssetTransactor: TransactAsset;
141 type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>;
143
144 type MaxXcmTransactWeight: Get<Weight>;
148 }
149
150 #[pallet::event]
151 #[pallet::generate_deposit(pub(super) fn deposit_event)]
152 pub enum Event<T: Config> {
153 RevenueInfoRequested { when: BlockNumberFor<T> },
155 CoreAssigned { core: CoreIndex },
157 }
158
159 #[pallet::error]
160 pub enum Error<T> {
161 NotBroker,
163 RequestedFutureRevenue,
166 AssetTransferFailed,
168 }
169
170 #[pallet::hooks]
171 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
172
173 impl<T: Config> OnNewSession<BlockNumberFor<T>> for Pallet<T> {
174 fn on_new_session(notification: &SessionChangeNotification<BlockNumberFor<T>>) {
175 Self::initializer_on_new_session(notification);
176 }
177 }
178
179 #[pallet::call]
180 impl<T: Config> Pallet<T> {
182 #[pallet::weight(<T as Config>::WeightInfo::request_core_count())]
189 #[pallet::call_index(1)]
190 pub fn request_core_count(origin: OriginFor<T>, count: u16) -> DispatchResult {
191 Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
193
194 configuration::Pallet::<T>::set_coretime_cores_unchecked(u32::from(count))
195 }
196
197 #[pallet::weight(<T as Config>::WeightInfo::request_revenue_at())]
202 #[pallet::call_index(2)]
203 pub fn request_revenue_at(origin: OriginFor<T>, when: BlockNumber) -> DispatchResult {
204 Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
206 Self::notify_revenue(when)
207 }
208
209 #[pallet::weight(<T as Config>::WeightInfo::credit_account())]
210 #[pallet::call_index(3)]
211 pub fn credit_account(
212 origin: OriginFor<T>,
213 who: T::AccountId,
214 amount: BalanceOf<T>,
215 ) -> DispatchResult {
216 Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
218
219 on_demand::Pallet::<T>::credit_account(who, amount.saturated_into());
220 Ok(())
221 }
222
223 #[pallet::call_index(4)]
235 #[pallet::weight(<T as Config>::WeightInfo::assign_core(assignment.len() as u32))]
236 pub fn assign_core(
237 origin: OriginFor<T>,
238 core: BrokerCoreIndex,
239 begin: BlockNumberFor<T>,
240 assignment: Vec<(CoreAssignment, PartsOf57600)>,
241 end_hint: Option<BlockNumberFor<T>>,
242 ) -> DispatchResult {
243 Self::ensure_root_or_para(origin, T::BrokerId::get().into())?;
245
246 let core = u32::from(core).into();
247
248 <scheduler::Pallet<T>>::assign_core(core, begin, assignment, end_hint)?;
249 Self::deposit_event(Event::<T>::CoreAssigned { core });
250 Ok(())
251 }
252
253 #[pallet::call_index(5)]
259 #[pallet::weight(<T as Config>::WeightInfo::queue_on_demand_batch(batch.len() as u32))]
260 pub fn queue_on_demand_batch(
261 origin: OriginFor<T>,
262 batch: Vec<(ParaId, BlockNumberFor<T>)>,
263 ) -> DispatchResult {
264 Self::ensure_root_or_para(origin, T::BrokerId::get().into())?;
266
267 <on_demand::Pallet<T>>::queue_order_batch(&batch);
272 Ok(())
273 }
274 }
275}
276
277impl<T: Config> Pallet<T> {
279 fn ensure_root_or_para(
281 origin: <T as frame_system::Config>::RuntimeOrigin,
282 id: ParaId,
283 ) -> DispatchResult {
284 if let Ok(caller_id) = ensure_parachain(<T as Config>::RuntimeOrigin::from(origin.clone()))
285 {
286 ensure!(caller_id == id, Error::<T>::NotBroker);
288 } else {
289 ensure_root(origin.clone())?;
291 }
292 Ok(())
293 }
294
295 #[cfg(feature = "runtime-benchmarks")]
297 pub fn ensure_broker_parachain_reachable() {
298 <T as Config>::SendXcm::ensure_successful_delivery(Some(Location::new(
299 0,
300 [Junction::Parachain(T::BrokerId::get())],
301 )));
302 }
303
304 pub fn initializer_on_new_session(notification: &SessionChangeNotification<BlockNumberFor<T>>) {
305 let old_core_count = notification.prev_config.scheduler_params.num_cores;
306 let new_core_count = notification.new_config.scheduler_params.num_cores;
307 if new_core_count != old_core_count {
308 let core_count: u16 = new_core_count.saturated_into();
309 let message = Xcm(vec![
310 Instruction::UnpaidExecution {
311 weight_limit: WeightLimit::Unlimited,
312 check_origin: None,
313 },
314 mk_coretime_call::<T>(crate::coretime::CoretimeCalls::NotifyCoreCount(core_count)),
315 ]);
316 if let Err(err) = send_xcm::<T::SendXcm>(
317 Location::new(0, [Junction::Parachain(T::BrokerId::get())]),
318 message,
319 ) {
320 log::error!(target: LOG_TARGET, "Sending `NotifyCoreCount` to coretime chain failed: {:?}", err);
321 }
322 }
323 }
324
325 pub fn notify_revenue(until: BlockNumber) -> DispatchResult {
335 let now = <frame_system::Pallet<T>>::block_number();
336 let until_bnf: BlockNumberFor<T> = until.into();
337
338 ensure!(until_bnf <= now, Error::<T>::RequestedFutureRevenue);
340
341 let amount = <on_demand::Pallet<T>>::claim_revenue_until(until_bnf);
342 log::debug!(target: LOG_TARGET, "Revenue info requested: {:?}", amount);
343
344 let raw_revenue: Balance = amount.try_into().map_err(|_| {
345 log::error!(target: LOG_TARGET, "Converting on demand revenue for `NotifyRevenue` failed");
346 Error::<T>::AssetTransferFailed
347 })?;
348
349 do_notify_revenue::<T>(until, raw_revenue).map_err(|err| {
350 log::error!(target: LOG_TARGET, "notify_revenue failed: {err:?}");
351 Error::<T>::AssetTransferFailed
352 })?;
353
354 Ok(())
355 }
356
357 pub fn on_legacy_lease_swap(one: ParaId, other: ParaId) {
360 let message = Xcm(vec![
361 Instruction::UnpaidExecution {
362 weight_limit: WeightLimit::Unlimited,
363 check_origin: None,
364 },
365 mk_coretime_call::<T>(crate::coretime::CoretimeCalls::SwapLeases(one, other)),
366 ]);
367 if let Err(err) = send_xcm::<T::SendXcm>(
368 Location::new(0, [Junction::Parachain(T::BrokerId::get())]),
369 message,
370 ) {
371 log::error!(target: LOG_TARGET, "Sending `SwapLeases` to coretime chain failed: {:?}", err);
372 }
373 }
374}
375
376fn mk_coretime_call<T: Config>(call: crate::coretime::CoretimeCalls) -> Instruction<()> {
377 Instruction::Transact {
378 origin_kind: OriginKind::Superuser,
379 fallback_max_weight: Some(T::MaxXcmTransactWeight::get()),
380 call: BrokerRuntimePallets::Broker(call).encode().into(),
381 }
382}
383
384fn do_notify_revenue<T: Config>(when: BlockNumber, raw_revenue: Balance) -> Result<(), XcmError> {
385 let dest = Junction::Parachain(T::BrokerId::get()).into_location();
386 let mut message = vec![Instruction::UnpaidExecution {
387 weight_limit: WeightLimit::Unlimited,
388 check_origin: None,
389 }];
390 let asset = Asset { id: Location::here().into(), fun: Fungible(raw_revenue) };
391 let dummy_xcm_context = XcmContext { origin: None, message_id: [0; 32], topic: None };
392
393 if raw_revenue > 0 {
394 let on_demand_pot =
395 T::AccountToLocation::try_convert(&<on_demand::Pallet<T>>::account_id()).map_err(
396 |err| {
397 log::error!(
398 target: LOG_TARGET,
399 "Failed to convert on-demand pot account to XCM location: {err:?}",
400 );
401 XcmError::InvalidLocation
402 },
403 )?;
404
405 let withdrawn = T::AssetTransactor::withdraw_asset(&asset, &on_demand_pot, None)?;
406
407 T::AssetTransactor::can_check_out(&dest, &asset, &dummy_xcm_context)?;
408
409 let assets: Vec<Asset> = withdrawn.into_assets_iter().collect();
411 let assets_reanchored = Into::<Assets>::into(assets)
412 .reanchored(&dest, &Here.into())
413 .defensive_map_err(|_| XcmError::ReanchorFailed)?;
414
415 message.extend(
416 [
417 ReceiveTeleportedAsset(assets_reanchored),
418 DepositAsset {
419 assets: Wild(AllCounted(1)),
420 beneficiary: T::BrokerPotLocation::get().into_location(),
421 },
422 ]
423 .into_iter(),
424 );
425 }
426
427 message.push(mk_coretime_call::<T>(CoretimeCalls::NotifyRevenue((when, raw_revenue))));
428
429 send_xcm::<T::SendXcm>(dest.clone(), Xcm(message))?;
430
431 if raw_revenue > 0 {
432 T::AssetTransactor::check_out(&dest, &asset, &dummy_xcm_context);
433 }
434
435 Ok(())
436}