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}
56
57pub struct TestWeightInfo;
59
60impl WeightInfo for TestWeightInfo {
61 fn request_core_count() -> Weight {
62 Weight::MAX
63 }
64 fn request_revenue_at() -> Weight {
65 Weight::MAX
66 }
67 fn credit_account() -> Weight {
68 Weight::MAX
69 }
70 fn assign_core(_s: u32) -> Weight {
71 Weight::MAX
72 }
73}
74
75pub type BalanceOf<T> = <<T as on_demand::Config>::Currency as Currency<
77 <T as frame_system::Config>::AccountId,
78>>::Balance;
79
80#[derive(Encode, Decode)]
85enum BrokerRuntimePallets {
86 #[codec(index = 50)]
87 Broker(CoretimeCalls),
88}
89
90#[derive(Encode, Decode)]
92enum CoretimeCalls {
93 #[codec(index = 1)]
94 Reserve(pallet_broker::Schedule),
95 #[codec(index = 3)]
96 SetLease(pallet_broker::TaskId, pallet_broker::Timeslice),
97 #[codec(index = 19)]
98 NotifyCoreCount(u16),
99 #[codec(index = 20)]
100 NotifyRevenue((BlockNumber, Balance)),
101 #[codec(index = 99)]
102 SwapLeases(ParaId, ParaId),
103}
104
105#[frame_support::pallet]
106pub mod pallet {
107
108 use crate::configuration;
109 use sp_runtime::traits::TryConvert;
110 use xcm::latest::InteriorLocation;
111 use xcm_executor::traits::TransactAsset;
112
113 use super::*;
114
115 #[pallet::pallet]
116 #[pallet::without_storage_info]
117 pub struct Pallet<T>(_);
118
119 #[pallet::config]
120 pub trait Config: frame_system::Config + scheduler::Config + on_demand::Config {
121 type RuntimeOrigin: From<<Self as frame_system::Config>::RuntimeOrigin>
122 + Into<result::Result<Origin, <Self as Config>::RuntimeOrigin>>;
123 #[allow(deprecated)]
124 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
125 #[pallet::constant]
127 type BrokerId: Get<u32>;
128 #[pallet::constant]
130 type BrokerPotLocation: Get<InteriorLocation>;
131 type WeightInfo: WeightInfo;
133 type SendXcm: SendXcm;
135 type AssetTransactor: TransactAsset;
137 type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>;
139
140 type MaxXcmTransactWeight: Get<Weight>;
144 }
145
146 #[pallet::event]
147 #[pallet::generate_deposit(pub(super) fn deposit_event)]
148 pub enum Event<T: Config> {
149 RevenueInfoRequested { when: BlockNumberFor<T> },
151 CoreAssigned { core: CoreIndex },
153 }
154
155 #[pallet::error]
156 pub enum Error<T> {
157 NotBroker,
159 RequestedFutureRevenue,
162 AssetTransferFailed,
164 }
165
166 #[pallet::hooks]
167 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
168
169 impl<T: Config> OnNewSession<BlockNumberFor<T>> for Pallet<T> {
170 fn on_new_session(notification: &SessionChangeNotification<BlockNumberFor<T>>) {
171 Self::initializer_on_new_session(notification);
172 }
173 }
174
175 #[pallet::call]
176 impl<T: Config> Pallet<T> {
178 #[pallet::weight(<T as Config>::WeightInfo::request_core_count())]
185 #[pallet::call_index(1)]
186 pub fn request_core_count(origin: OriginFor<T>, count: u16) -> DispatchResult {
187 Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
189
190 configuration::Pallet::<T>::set_coretime_cores_unchecked(u32::from(count))
191 }
192
193 #[pallet::weight(<T as Config>::WeightInfo::request_revenue_at())]
198 #[pallet::call_index(2)]
199 pub fn request_revenue_at(origin: OriginFor<T>, when: BlockNumber) -> DispatchResult {
200 Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
202 Self::notify_revenue(when)
203 }
204
205 #[pallet::weight(<T as Config>::WeightInfo::credit_account())]
206 #[pallet::call_index(3)]
207 pub fn credit_account(
208 origin: OriginFor<T>,
209 who: T::AccountId,
210 amount: BalanceOf<T>,
211 ) -> DispatchResult {
212 Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
214
215 on_demand::Pallet::<T>::credit_account(who, amount.saturated_into());
216 Ok(())
217 }
218
219 #[pallet::call_index(4)]
231 #[pallet::weight(<T as Config>::WeightInfo::assign_core(assignment.len() as u32))]
232 pub fn assign_core(
233 origin: OriginFor<T>,
234 core: BrokerCoreIndex,
235 begin: BlockNumberFor<T>,
236 assignment: Vec<(CoreAssignment, PartsOf57600)>,
237 end_hint: Option<BlockNumberFor<T>>,
238 ) -> DispatchResult {
239 Self::ensure_root_or_para(origin, T::BrokerId::get().into())?;
241
242 let core = u32::from(core).into();
243
244 <scheduler::Pallet<T>>::assign_core(core, begin, assignment, end_hint)?;
245 Self::deposit_event(Event::<T>::CoreAssigned { core });
246 Ok(())
247 }
248 }
249}
250
251impl<T: Config> Pallet<T> {
253 fn ensure_root_or_para(
255 origin: <T as frame_system::Config>::RuntimeOrigin,
256 id: ParaId,
257 ) -> DispatchResult {
258 if let Ok(caller_id) = ensure_parachain(<T as Config>::RuntimeOrigin::from(origin.clone()))
259 {
260 ensure!(caller_id == id, Error::<T>::NotBroker);
262 } else {
263 ensure_root(origin.clone())?;
265 }
266 Ok(())
267 }
268
269 pub fn initializer_on_new_session(notification: &SessionChangeNotification<BlockNumberFor<T>>) {
270 let old_core_count = notification.prev_config.scheduler_params.num_cores;
271 let new_core_count = notification.new_config.scheduler_params.num_cores;
272 if new_core_count != old_core_count {
273 let core_count: u16 = new_core_count.saturated_into();
274 let message = Xcm(vec![
275 Instruction::UnpaidExecution {
276 weight_limit: WeightLimit::Unlimited,
277 check_origin: None,
278 },
279 mk_coretime_call::<T>(crate::coretime::CoretimeCalls::NotifyCoreCount(core_count)),
280 ]);
281 if let Err(err) = send_xcm::<T::SendXcm>(
282 Location::new(0, [Junction::Parachain(T::BrokerId::get())]),
283 message,
284 ) {
285 log::error!(target: LOG_TARGET, "Sending `NotifyCoreCount` to coretime chain failed: {:?}", err);
286 }
287 }
288 }
289
290 pub fn notify_revenue(until: BlockNumber) -> DispatchResult {
300 let now = <frame_system::Pallet<T>>::block_number();
301 let until_bnf: BlockNumberFor<T> = until.into();
302
303 ensure!(until_bnf <= now, Error::<T>::RequestedFutureRevenue);
305
306 let amount = <on_demand::Pallet<T>>::claim_revenue_until(until_bnf);
307 log::debug!(target: LOG_TARGET, "Revenue info requested: {:?}", amount);
308
309 let raw_revenue: Balance = amount.try_into().map_err(|_| {
310 log::error!(target: LOG_TARGET, "Converting on demand revenue for `NotifyRevenue` failed");
311 Error::<T>::AssetTransferFailed
312 })?;
313
314 do_notify_revenue::<T>(until, raw_revenue).map_err(|err| {
315 log::error!(target: LOG_TARGET, "notify_revenue failed: {err:?}");
316 Error::<T>::AssetTransferFailed
317 })?;
318
319 Ok(())
320 }
321
322 pub fn on_legacy_lease_swap(one: ParaId, other: ParaId) {
325 let message = Xcm(vec![
326 Instruction::UnpaidExecution {
327 weight_limit: WeightLimit::Unlimited,
328 check_origin: None,
329 },
330 mk_coretime_call::<T>(crate::coretime::CoretimeCalls::SwapLeases(one, other)),
331 ]);
332 if let Err(err) = send_xcm::<T::SendXcm>(
333 Location::new(0, [Junction::Parachain(T::BrokerId::get())]),
334 message,
335 ) {
336 log::error!(target: LOG_TARGET, "Sending `SwapLeases` to coretime chain failed: {:?}", err);
337 }
338 }
339}
340
341fn mk_coretime_call<T: Config>(call: crate::coretime::CoretimeCalls) -> Instruction<()> {
342 Instruction::Transact {
343 origin_kind: OriginKind::Superuser,
344 fallback_max_weight: Some(T::MaxXcmTransactWeight::get()),
345 call: BrokerRuntimePallets::Broker(call).encode().into(),
346 }
347}
348
349fn do_notify_revenue<T: Config>(when: BlockNumber, raw_revenue: Balance) -> Result<(), XcmError> {
350 let dest = Junction::Parachain(T::BrokerId::get()).into_location();
351 let mut message = vec![Instruction::UnpaidExecution {
352 weight_limit: WeightLimit::Unlimited,
353 check_origin: None,
354 }];
355 let asset = Asset { id: Location::here().into(), fun: Fungible(raw_revenue) };
356 let dummy_xcm_context = XcmContext { origin: None, message_id: [0; 32], topic: None };
357
358 if raw_revenue > 0 {
359 let on_demand_pot =
360 T::AccountToLocation::try_convert(&<on_demand::Pallet<T>>::account_id()).map_err(
361 |err| {
362 log::error!(
363 target: LOG_TARGET,
364 "Failed to convert on-demand pot account to XCM location: {err:?}",
365 );
366 XcmError::InvalidLocation
367 },
368 )?;
369
370 let withdrawn = T::AssetTransactor::withdraw_asset(&asset, &on_demand_pot, None)?;
371
372 T::AssetTransactor::can_check_out(&dest, &asset, &dummy_xcm_context)?;
373
374 let assets: Vec<Asset> = withdrawn.into_assets_iter().collect();
376 let assets_reanchored = Into::<Assets>::into(assets)
377 .reanchored(&dest, &Here.into())
378 .defensive_map_err(|_| XcmError::ReanchorFailed)?;
379
380 message.extend(
381 [
382 ReceiveTeleportedAsset(assets_reanchored),
383 DepositAsset {
384 assets: Wild(AllCounted(1)),
385 beneficiary: T::BrokerPotLocation::get().into_location(),
386 },
387 ]
388 .into_iter(),
389 );
390 }
391
392 message.push(mk_coretime_call::<T>(CoretimeCalls::NotifyRevenue((when, raw_revenue))));
393
394 send_xcm::<T::SendXcm>(dest.clone(), Xcm(message))?;
395
396 if raw_revenue > 0 {
397 T::AssetTransactor::check_out(&dest, &asset, &dummy_xcm_context);
398 }
399
400 Ok(())
401}