referrerpolicy=no-referrer-when-downgrade

polkadot_runtime_parachains/coretime/
mod.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! This pallet exposes the relay chain coretime functionality to the broker/coretime chain.
18//!
19//! It depends on the scheduler pallet, which does the actual ground work of handling
20//! received core assignments.
21//!
22//! <https://github.com/polkadot-fellows/RFCs/blob/main/text/0005-coretime-interface.md>
23
24use 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
58/// A weight info that is only suitable for testing.
59pub 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
79/// Shorthand for the Balance type the runtime is using.
80pub type BalanceOf<T> = <<T as on_demand::Config>::Currency as Currency<
81	<T as frame_system::Config>::AccountId,
82>>::Balance;
83
84/// Broker pallet index on the coretime chain. Used to
85///
86/// construct remote calls. The codec index must correspond to the index of `Broker` in the
87/// `construct_runtime` of the coretime chain.
88#[derive(Encode, Decode)]
89enum BrokerRuntimePallets {
90	#[codec(index = 50)]
91	Broker(CoretimeCalls),
92}
93
94/// Call encoding for the calls needed from the Broker pallet.
95#[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		/// The ParaId of the coretime chain.
130		#[pallet::constant]
131		type BrokerId: Get<u32>;
132		/// The coretime chain pot location.
133		#[pallet::constant]
134		type BrokerPotLocation: Get<InteriorLocation>;
135		/// Something that provides the weight of this pallet.
136		type WeightInfo: WeightInfo;
137		/// The XCM sender.
138		type SendXcm: SendXcm;
139		/// The asset transactor.
140		type AssetTransactor: TransactAsset;
141		/// AccountId to Location converter
142		type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>;
143
144		/// Maximum weight for any XCM transact call that should be executed on the coretime chain.
145		///
146		/// Basically should be `max_weight(set_leases, reserve, notify_core_count)`.
147		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		/// The broker chain has asked for revenue information for a specific block.
154		RevenueInfoRequested { when: BlockNumberFor<T> },
155		/// A core has received a new assignment from the broker chain.
156		CoreAssigned { core: CoreIndex },
157	}
158
159	#[pallet::error]
160	pub enum Error<T> {
161		/// The paraid making the call is not the coretime brokerage system parachain.
162		NotBroker,
163		/// Requested revenue information `when` parameter was in the future from the current
164		/// block height.
165		RequestedFutureRevenue,
166		/// Failed to transfer assets to the coretime chain
167		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	/// Extrinsics to be called by the Coretime chain.
181	impl<T: Config> Pallet<T> {
182		/// Request the configuration to be updated with the specified number of cores. Warning:
183		/// Since this only schedules a configuration update, it takes two sessions to come into
184		/// effect.
185		///
186		/// - `origin`: Root or the Coretime Chain
187		/// - `count`: total number of cores
188		#[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			// Ignore requests not coming from the coretime chain or root.
192			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		/// Request to claim the instantaneous coretime sales revenue starting from the block it was
198		/// last claimed until and up to the block specified. The claimed amount value is sent back
199		/// to the Coretime chain in a `notify_revenue` message. At the same time, the amount is
200		/// teleported to the Coretime chain.
201		#[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			// Ignore requests not coming from the Coretime Chain or Root.
205			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			// Ignore requests not coming from the coretime chain or root.
217			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		/// Receive instructions from the `ExternalBrokerOrigin`, detailing how a specific core is
224		/// to be used.
225		///
226		/// Parameters:
227		/// -`origin`: The `ExternalBrokerOrigin`, assumed to be the coretime chain.
228		/// -`core`: The core that should be scheduled.
229		/// -`begin`: The starting blockheight of the instruction.
230		/// -`assignment`: How the blockspace should be utilised.
231		/// -`end_hint`: An optional hint as to when this particular set of instructions will end.
232		// The broker pallet's `CoreIndex` definition is `u16` but on the relay chain it's `struct
233		// CoreIndex(u32)`
234		#[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			// Ignore requests not coming from the coretime chain or root.
244			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		/// Receive on-demand coretime orders from the `ExternalBrokerOrigin`.
254		///
255		/// Parameters:
256		/// -`origin`: The `ExternalBrokerOrigin`, assumed to be the coretime chain.
257		/// -`batch`: The batch of on-demand orders.
258		#[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			// Ignore requests not coming from the coretime chain or root.
265			Self::ensure_root_or_para(origin, T::BrokerId::get().into())?;
266
267			// It is possible for the call below to not always queue the full batch. In such a case,
268			// instead of returning an error, which would revert any changes made to storage, we
269			// queue as much as possible, so that the orders don't have to wait unnecessarily long,
270			// and emit an `UnexpectedQueueFull` event for better observability.
271			<on_demand::Pallet<T>>::queue_order_batch(&batch);
272			Ok(())
273		}
274	}
275}
276
277/// Coretime chain communiation.
278impl<T: Config> Pallet<T> {
279	/// Ensure the origin is one of Root or the `para` itself.
280	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			// Check if matching para id...
287			ensure!(caller_id == id, Error::<T>::NotBroker);
288		} else {
289			// Check if root...
290			ensure_root(origin.clone())?;
291		}
292		Ok(())
293	}
294
295	/// Helper function for benchmarks to ensure the broker parachain is reachable.
296	#[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	/// Provide the amount of revenue accumulated from Instantaneous Coretime Sales from Relay-chain
326	/// block number last_until to until, not including until itself. last_until is defined as being
327	/// the until argument of the last notify_revenue message sent, or zero for the first call. If
328	/// revenue is None, this indicates that the information is no longer available. This explicitly
329	/// disregards the possibility of multiple parachains requesting and being notified of revenue
330	/// information.
331	///
332	/// The Relay-chain must be configured to ensure that only a single revenue information
333	/// destination exists.
334	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		// When cannot be in the future.
339		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	// Handle legacy swaps in coretime. Notifies coretime chain that a lease swap has occurred via
358	// XCM message. This function is meant to be used in an implementation of `OnSwap` trait.
359	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		// dropping `withdrawn` effectively burns the inner imbalance
410		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}