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 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 assign_core(_s: u32) -> Weight {
71 Weight::MAX
72 }
73}
74
75pub type BalanceOf<T> =
77 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
78
79#[derive(Encode, Decode)]
84enum BrokerRuntimePallets {
85 #[codec(index = 50)]
86 Broker(CoretimeCalls),
87}
88
89#[derive(Encode, Decode)]
91enum CoretimeCalls {
92 #[codec(index = 1)]
93 Reserve(pallet_broker::Schedule),
94 #[codec(index = 3)]
95 SetLease(pallet_broker::TaskId, pallet_broker::Timeslice),
96 #[codec(index = 19)]
97 NotifyCoreCount(u16),
98 #[codec(index = 20)]
99 NotifyRevenue((BlockNumber, Balance)),
100 #[codec(index = 99)]
101 SwapLeases(ParaId, ParaId),
102}
103
104#[frame_support::pallet]
105pub mod pallet {
106
107 use crate::configuration;
108 use sp_runtime::traits::TryConvert;
109 use xcm::latest::InteriorLocation;
110 use xcm_executor::traits::TransactAsset;
111
112 use super::*;
113
114 #[pallet::pallet]
115 #[pallet::without_storage_info]
116 pub struct Pallet<T>(_);
117
118 #[pallet::config]
119 pub trait Config: frame_system::Config + assigner_coretime::Config + on_demand::Config {
120 type RuntimeOrigin: From<<Self as frame_system::Config>::RuntimeOrigin>
121 + Into<result::Result<Origin, <Self as Config>::RuntimeOrigin>>;
122 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
123 type Currency: Currency<Self::AccountId>;
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 #[pallet::call]
170 impl<T: Config> Pallet<T> {
171 #[pallet::weight(<T as Config>::WeightInfo::request_core_count())]
178 #[pallet::call_index(1)]
179 pub fn request_core_count(origin: OriginFor<T>, count: u16) -> DispatchResult {
180 Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
182
183 configuration::Pallet::<T>::set_coretime_cores_unchecked(u32::from(count))
184 }
185
186 #[pallet::weight(<T as Config>::WeightInfo::request_revenue_at())]
191 #[pallet::call_index(2)]
192 pub fn request_revenue_at(origin: OriginFor<T>, when: BlockNumber) -> DispatchResult {
193 Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
195 Self::notify_revenue(when)
196 }
197
198 #[pallet::call_index(4)]
223 #[pallet::weight(<T as Config>::WeightInfo::assign_core(assignment.len() as u32))]
224 pub fn assign_core(
225 origin: OriginFor<T>,
226 core: BrokerCoreIndex,
227 begin: BlockNumberFor<T>,
228 assignment: Vec<(CoreAssignment, PartsOf57600)>,
229 end_hint: Option<BlockNumberFor<T>>,
230 ) -> DispatchResult {
231 Self::ensure_root_or_para(origin, T::BrokerId::get().into())?;
233
234 let core = u32::from(core).into();
235
236 <assigner_coretime::Pallet<T>>::assign_core(core, begin, assignment, end_hint)?;
237 Self::deposit_event(Event::<T>::CoreAssigned { core });
238 Ok(())
239 }
240 }
241}
242
243impl<T: Config> Pallet<T> {
244 fn ensure_root_or_para(
246 origin: <T as frame_system::Config>::RuntimeOrigin,
247 id: ParaId,
248 ) -> DispatchResult {
249 if let Ok(caller_id) = ensure_parachain(<T as Config>::RuntimeOrigin::from(origin.clone()))
250 {
251 ensure!(caller_id == id, Error::<T>::NotBroker);
253 } else {
254 ensure_root(origin.clone())?;
256 }
257 Ok(())
258 }
259
260 pub fn initializer_on_new_session(notification: &SessionChangeNotification<BlockNumberFor<T>>) {
261 let old_core_count = notification.prev_config.scheduler_params.num_cores;
262 let new_core_count = notification.new_config.scheduler_params.num_cores;
263 if new_core_count != old_core_count {
264 let core_count: u16 = new_core_count.saturated_into();
265 let message = Xcm(vec![
266 Instruction::UnpaidExecution {
267 weight_limit: WeightLimit::Unlimited,
268 check_origin: None,
269 },
270 mk_coretime_call::<T>(crate::coretime::CoretimeCalls::NotifyCoreCount(core_count)),
271 ]);
272 if let Err(err) = send_xcm::<T::SendXcm>(
273 Location::new(0, [Junction::Parachain(T::BrokerId::get())]),
274 message,
275 ) {
276 log::error!(target: LOG_TARGET, "Sending `NotifyCoreCount` to coretime chain failed: {:?}", err);
277 }
278 }
279 }
280
281 pub fn notify_revenue(until: BlockNumber) -> DispatchResult {
291 let now = <frame_system::Pallet<T>>::block_number();
292 let until_bnf: BlockNumberFor<T> = until.into();
293
294 ensure!(until_bnf <= now, Error::<T>::RequestedFutureRevenue);
296
297 let amount = <on_demand::Pallet<T>>::claim_revenue_until(until_bnf);
298 log::debug!(target: LOG_TARGET, "Revenue info requested: {:?}", amount);
299
300 let raw_revenue: Balance = amount.try_into().map_err(|_| {
301 log::error!(target: LOG_TARGET, "Converting on demand revenue for `NotifyRevenue` failed");
302 Error::<T>::AssetTransferFailed
303 })?;
304
305 do_notify_revenue::<T>(until, raw_revenue).map_err(|err| {
306 log::error!(target: LOG_TARGET, "notify_revenue failed: {err:?}");
307 Error::<T>::AssetTransferFailed
308 })?;
309
310 Ok(())
311 }
312
313 pub fn on_legacy_lease_swap(one: ParaId, other: ParaId) {
316 let message = Xcm(vec![
317 Instruction::UnpaidExecution {
318 weight_limit: WeightLimit::Unlimited,
319 check_origin: None,
320 },
321 mk_coretime_call::<T>(crate::coretime::CoretimeCalls::SwapLeases(one, other)),
322 ]);
323 if let Err(err) = send_xcm::<T::SendXcm>(
324 Location::new(0, [Junction::Parachain(T::BrokerId::get())]),
325 message,
326 ) {
327 log::error!(target: LOG_TARGET, "Sending `SwapLeases` to coretime chain failed: {:?}", err);
328 }
329 }
330}
331
332impl<T: Config> OnNewSession<BlockNumberFor<T>> for Pallet<T> {
333 fn on_new_session(notification: &SessionChangeNotification<BlockNumberFor<T>>) {
334 Self::initializer_on_new_session(notification);
335 }
336}
337
338fn mk_coretime_call<T: Config>(call: crate::coretime::CoretimeCalls) -> Instruction<()> {
339 Instruction::Transact {
340 origin_kind: OriginKind::Superuser,
341 fallback_max_weight: Some(T::MaxXcmTransactWeight::get()),
342 call: BrokerRuntimePallets::Broker(call).encode().into(),
343 }
344}
345
346fn do_notify_revenue<T: Config>(when: BlockNumber, raw_revenue: Balance) -> Result<(), XcmError> {
347 let dest = Junction::Parachain(T::BrokerId::get()).into_location();
348 let mut message = vec![Instruction::UnpaidExecution {
349 weight_limit: WeightLimit::Unlimited,
350 check_origin: None,
351 }];
352 let asset = Asset { id: Location::here().into(), fun: Fungible(raw_revenue) };
353 let dummy_xcm_context = XcmContext { origin: None, message_id: [0; 32], topic: None };
354
355 if raw_revenue > 0 {
356 let on_demand_pot =
357 T::AccountToLocation::try_convert(&<on_demand::Pallet<T>>::account_id()).map_err(
358 |err| {
359 log::error!(
360 target: LOG_TARGET,
361 "Failed to convert on-demand pot account to XCM location: {err:?}",
362 );
363 XcmError::InvalidLocation
364 },
365 )?;
366
367 let withdrawn = T::AssetTransactor::withdraw_asset(&asset, &on_demand_pot, None)?;
368
369 T::AssetTransactor::can_check_out(&dest, &asset, &dummy_xcm_context)?;
370
371 let assets_reanchored = Into::<Assets>::into(withdrawn)
372 .reanchored(&dest, &Here.into())
373 .defensive_map_err(|_| XcmError::ReanchorFailed)?;
374
375 message.extend(
376 [
377 ReceiveTeleportedAsset(assets_reanchored),
378 DepositAsset {
379 assets: Wild(AllCounted(1)),
380 beneficiary: T::BrokerPotLocation::get().into_location(),
381 },
382 ]
383 .into_iter(),
384 );
385 }
386
387 message.push(mk_coretime_call::<T>(CoretimeCalls::NotifyRevenue((when, raw_revenue))));
388
389 send_xcm::<T::SendXcm>(dest.clone(), Xcm(message))?;
390
391 if raw_revenue > 0 {
392 T::AssetTransactor::check_out(&dest, &asset, &dummy_xcm_context);
393 }
394
395 Ok(())
396}