polkadot_runtime_parachains/coretime/
mod.rs1use alloc::{vec, vec::Vec};
22use core::result;
23use frame_support::{
24 pallet_prelude::*,
25 traits::{defensive_prelude::*, Currency},
26};
27use frame_system::pallet_prelude::*;
28pub use pallet::*;
29use pallet_broker::{CoreAssignment, CoreIndex as BrokerCoreIndex};
30use polkadot_primitives::{Balance, BlockNumber, CoreIndex, Id as ParaId};
31use sp_arithmetic::traits::SaturatedConversion;
32use sp_runtime::traits::TryConvert;
33use xcm::prelude::*;
34use xcm_executor::traits::TransactAsset;
35
36use crate::{
37 assigner_coretime::{self, PartsOf57600},
38 initializer::{OnNewSession, SessionChangeNotification},
39 on_demand,
40 origin::{ensure_parachain, Origin},
41};
42
43mod benchmarking;
44pub mod migration;
45
46const LOG_TARGET: &str = "runtime::parachains::coretime";
47
48pub trait WeightInfo {
49 fn request_core_count() -> Weight;
50 fn request_revenue_at() -> Weight;
51 fn credit_account() -> Weight;
52 fn assign_core(s: u32) -> Weight;
53}
54
55pub struct TestWeightInfo;
57
58impl WeightInfo for TestWeightInfo {
59 fn request_core_count() -> Weight {
60 Weight::MAX
61 }
62 fn request_revenue_at() -> Weight {
63 Weight::MAX
64 }
65 fn credit_account() -> Weight {
66 Weight::MAX
67 }
68 fn assign_core(_s: u32) -> Weight {
69 Weight::MAX
70 }
71}
72
73pub type BalanceOf<T> = <<T as on_demand::Config>::Currency as Currency<
75 <T as frame_system::Config>::AccountId,
76>>::Balance;
77
78#[derive(Encode, Decode)]
83enum BrokerRuntimePallets {
84 #[codec(index = 50)]
85 Broker(CoretimeCalls),
86}
87
88#[derive(Encode, Decode)]
90enum CoretimeCalls {
91 #[codec(index = 1)]
92 Reserve(pallet_broker::Schedule),
93 #[codec(index = 3)]
94 SetLease(pallet_broker::TaskId, pallet_broker::Timeslice),
95 #[codec(index = 19)]
96 NotifyCoreCount(u16),
97 #[codec(index = 20)]
98 NotifyRevenue((BlockNumber, Balance)),
99 #[codec(index = 99)]
100 SwapLeases(ParaId, ParaId),
101}
102
103#[frame_support::pallet]
104pub mod pallet {
105
106 use crate::configuration;
107 use sp_runtime::traits::TryConvert;
108 use xcm::latest::InteriorLocation;
109 use xcm_executor::traits::TransactAsset;
110
111 use super::*;
112
113 #[pallet::pallet]
114 #[pallet::without_storage_info]
115 pub struct Pallet<T>(_);
116
117 #[pallet::config]
118 pub trait Config: frame_system::Config + assigner_coretime::Config + on_demand::Config {
119 type RuntimeOrigin: From<<Self as frame_system::Config>::RuntimeOrigin>
120 + Into<result::Result<Origin, <Self as Config>::RuntimeOrigin>>;
121 #[allow(deprecated)]
122 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
123 #[pallet::constant]
125 type BrokerId: Get<u32>;
126 #[pallet::constant]
128 type BrokerPotLocation: Get<InteriorLocation>;
129 type WeightInfo: WeightInfo;
131 type SendXcm: SendXcm;
133 type AssetTransactor: TransactAsset;
135 type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>;
137
138 type MaxXcmTransactWeight: Get<Weight>;
142 }
143
144 #[pallet::event]
145 #[pallet::generate_deposit(pub(super) fn deposit_event)]
146 pub enum Event<T: Config> {
147 RevenueInfoRequested { when: BlockNumberFor<T> },
149 CoreAssigned { core: CoreIndex },
151 }
152
153 #[pallet::error]
154 pub enum Error<T> {
155 NotBroker,
157 RequestedFutureRevenue,
160 AssetTransferFailed,
162 }
163
164 #[pallet::hooks]
165 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
166
167 #[pallet::call]
168 impl<T: Config> Pallet<T> {
169 #[pallet::weight(<T as Config>::WeightInfo::request_core_count())]
176 #[pallet::call_index(1)]
177 pub fn request_core_count(origin: OriginFor<T>, count: u16) -> DispatchResult {
178 Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
180
181 configuration::Pallet::<T>::set_coretime_cores_unchecked(u32::from(count))
182 }
183
184 #[pallet::weight(<T as Config>::WeightInfo::request_revenue_at())]
189 #[pallet::call_index(2)]
190 pub fn request_revenue_at(origin: OriginFor<T>, when: BlockNumber) -> DispatchResult {
191 Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
193 Self::notify_revenue(when)
194 }
195
196 #[pallet::weight(<T as Config>::WeightInfo::credit_account())]
197 #[pallet::call_index(3)]
198 pub fn credit_account(
199 origin: OriginFor<T>,
200 who: T::AccountId,
201 amount: BalanceOf<T>,
202 ) -> DispatchResult {
203 Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
205
206 on_demand::Pallet::<T>::credit_account(who, amount.saturated_into());
207 Ok(())
208 }
209
210 #[pallet::call_index(4)]
222 #[pallet::weight(<T as Config>::WeightInfo::assign_core(assignment.len() as u32))]
223 pub fn assign_core(
224 origin: OriginFor<T>,
225 core: BrokerCoreIndex,
226 begin: BlockNumberFor<T>,
227 assignment: Vec<(CoreAssignment, PartsOf57600)>,
228 end_hint: Option<BlockNumberFor<T>>,
229 ) -> DispatchResult {
230 Self::ensure_root_or_para(origin, T::BrokerId::get().into())?;
232
233 let core = u32::from(core).into();
234
235 <assigner_coretime::Pallet<T>>::assign_core(core, begin, assignment, end_hint)?;
236 Self::deposit_event(Event::<T>::CoreAssigned { core });
237 Ok(())
238 }
239 }
240}
241
242impl<T: Config> Pallet<T> {
243 fn ensure_root_or_para(
245 origin: <T as frame_system::Config>::RuntimeOrigin,
246 id: ParaId,
247 ) -> DispatchResult {
248 if let Ok(caller_id) = ensure_parachain(<T as Config>::RuntimeOrigin::from(origin.clone()))
249 {
250 ensure!(caller_id == id, Error::<T>::NotBroker);
252 } else {
253 ensure_root(origin.clone())?;
255 }
256 Ok(())
257 }
258
259 pub fn initializer_on_new_session(notification: &SessionChangeNotification<BlockNumberFor<T>>) {
260 let old_core_count = notification.prev_config.scheduler_params.num_cores;
261 let new_core_count = notification.new_config.scheduler_params.num_cores;
262 if new_core_count != old_core_count {
263 let core_count: u16 = new_core_count.saturated_into();
264 let message = Xcm(vec![
265 Instruction::UnpaidExecution {
266 weight_limit: WeightLimit::Unlimited,
267 check_origin: None,
268 },
269 mk_coretime_call::<T>(crate::coretime::CoretimeCalls::NotifyCoreCount(core_count)),
270 ]);
271 if let Err(err) = send_xcm::<T::SendXcm>(
272 Location::new(0, [Junction::Parachain(T::BrokerId::get())]),
273 message,
274 ) {
275 log::error!(target: LOG_TARGET, "Sending `NotifyCoreCount` to coretime chain failed: {:?}", err);
276 }
277 }
278 }
279
280 pub fn notify_revenue(until: BlockNumber) -> DispatchResult {
290 let now = <frame_system::Pallet<T>>::block_number();
291 let until_bnf: BlockNumberFor<T> = until.into();
292
293 ensure!(until_bnf <= now, Error::<T>::RequestedFutureRevenue);
295
296 let amount = <on_demand::Pallet<T>>::claim_revenue_until(until_bnf);
297 log::debug!(target: LOG_TARGET, "Revenue info requested: {:?}", amount);
298
299 let raw_revenue: Balance = amount.try_into().map_err(|_| {
300 log::error!(target: LOG_TARGET, "Converting on demand revenue for `NotifyRevenue` failed");
301 Error::<T>::AssetTransferFailed
302 })?;
303
304 do_notify_revenue::<T>(until, raw_revenue).map_err(|err| {
305 log::error!(target: LOG_TARGET, "notify_revenue failed: {err:?}");
306 Error::<T>::AssetTransferFailed
307 })?;
308
309 Ok(())
310 }
311
312 pub fn on_legacy_lease_swap(one: ParaId, other: ParaId) {
315 let message = Xcm(vec![
316 Instruction::UnpaidExecution {
317 weight_limit: WeightLimit::Unlimited,
318 check_origin: None,
319 },
320 mk_coretime_call::<T>(crate::coretime::CoretimeCalls::SwapLeases(one, other)),
321 ]);
322 if let Err(err) = send_xcm::<T::SendXcm>(
323 Location::new(0, [Junction::Parachain(T::BrokerId::get())]),
324 message,
325 ) {
326 log::error!(target: LOG_TARGET, "Sending `SwapLeases` to coretime chain failed: {:?}", err);
327 }
328 }
329}
330
331impl<T: Config> OnNewSession<BlockNumberFor<T>> for Pallet<T> {
332 fn on_new_session(notification: &SessionChangeNotification<BlockNumberFor<T>>) {
333 Self::initializer_on_new_session(notification);
334 }
335}
336
337fn mk_coretime_call<T: Config>(call: crate::coretime::CoretimeCalls) -> Instruction<()> {
338 Instruction::Transact {
339 origin_kind: OriginKind::Superuser,
340 fallback_max_weight: Some(T::MaxXcmTransactWeight::get()),
341 call: BrokerRuntimePallets::Broker(call).encode().into(),
342 }
343}
344
345fn do_notify_revenue<T: Config>(when: BlockNumber, raw_revenue: Balance) -> Result<(), XcmError> {
346 let dest = Junction::Parachain(T::BrokerId::get()).into_location();
347 let mut message = vec![Instruction::UnpaidExecution {
348 weight_limit: WeightLimit::Unlimited,
349 check_origin: None,
350 }];
351 let asset = Asset { id: Location::here().into(), fun: Fungible(raw_revenue) };
352 let dummy_xcm_context = XcmContext { origin: None, message_id: [0; 32], topic: None };
353
354 if raw_revenue > 0 {
355 let on_demand_pot =
356 T::AccountToLocation::try_convert(&<on_demand::Pallet<T>>::account_id()).map_err(
357 |err| {
358 log::error!(
359 target: LOG_TARGET,
360 "Failed to convert on-demand pot account to XCM location: {err:?}",
361 );
362 XcmError::InvalidLocation
363 },
364 )?;
365
366 let withdrawn = T::AssetTransactor::withdraw_asset(&asset, &on_demand_pot, None)?;
367
368 T::AssetTransactor::can_check_out(&dest, &asset, &dummy_xcm_context)?;
369
370 let assets_reanchored = Into::<Assets>::into(withdrawn)
371 .reanchored(&dest, &Here.into())
372 .defensive_map_err(|_| XcmError::ReanchorFailed)?;
373
374 message.extend(
375 [
376 ReceiveTeleportedAsset(assets_reanchored),
377 DepositAsset {
378 assets: Wild(AllCounted(1)),
379 beneficiary: T::BrokerPotLocation::get().into_location(),
380 },
381 ]
382 .into_iter(),
383 );
384 }
385
386 message.push(mk_coretime_call::<T>(CoretimeCalls::NotifyRevenue((when, raw_revenue))));
387
388 send_xcm::<T::SendXcm>(dest.clone(), Xcm(message))?;
389
390 if raw_revenue > 0 {
391 T::AssetTransactor::check_out(&dest, &asset, &dummy_xcm_context);
392 }
393
394 Ok(())
395}