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//! Extrinsics implementing the relay chain side of the Coretime interface.
18//!
19//! <https://github.com/polkadot-fellows/RFCs/blob/main/text/0005-coretime-interface.md>
20
21use 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
55/// A weight info that is only suitable for testing.
56pub 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
73/// Shorthand for the Balance type the runtime is using.
74pub type BalanceOf<T> = <<T as on_demand::Config>::Currency as Currency<
75	<T as frame_system::Config>::AccountId,
76>>::Balance;
77
78/// Broker pallet index on the coretime chain. Used to
79///
80/// construct remote calls. The codec index must correspond to the index of `Broker` in the
81/// `construct_runtime` of the coretime chain.
82#[derive(Encode, Decode)]
83enum BrokerRuntimePallets {
84	#[codec(index = 50)]
85	Broker(CoretimeCalls),
86}
87
88/// Call encoding for the calls needed from the Broker pallet.
89#[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		/// The ParaId of the coretime chain.
124		#[pallet::constant]
125		type BrokerId: Get<u32>;
126		/// The coretime chain pot location.
127		#[pallet::constant]
128		type BrokerPotLocation: Get<InteriorLocation>;
129		/// Something that provides the weight of this pallet.
130		type WeightInfo: WeightInfo;
131		/// The XCM sender.
132		type SendXcm: SendXcm;
133		/// The asset transactor.
134		type AssetTransactor: TransactAsset;
135		/// AccountId to Location converter
136		type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>;
137
138		/// Maximum weight for any XCM transact call that should be executed on the coretime chain.
139		///
140		/// Basically should be `max_weight(set_leases, reserve, notify_core_count)`.
141		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		/// The broker chain has asked for revenue information for a specific block.
148		RevenueInfoRequested { when: BlockNumberFor<T> },
149		/// A core has received a new assignment from the broker chain.
150		CoreAssigned { core: CoreIndex },
151	}
152
153	#[pallet::error]
154	pub enum Error<T> {
155		/// The paraid making the call is not the coretime brokerage system parachain.
156		NotBroker,
157		/// Requested revenue information `when` parameter was in the future from the current
158		/// block height.
159		RequestedFutureRevenue,
160		/// Failed to transfer assets to the coretime chain
161		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		/// Request the configuration to be updated with the specified number of cores. Warning:
170		/// Since this only schedules a configuration update, it takes two sessions to come into
171		/// effect.
172		///
173		/// - `origin`: Root or the Coretime Chain
174		/// - `count`: total number of cores
175		#[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			// Ignore requests not coming from the coretime chain or root.
179			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		/// Request to claim the instantaneous coretime sales revenue starting from the block it was
185		/// last claimed until and up to the block specified. The claimed amount value is sent back
186		/// to the Coretime chain in a `notify_revenue` message. At the same time, the amount is
187		/// teleported to the Coretime chain.
188		#[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			// Ignore requests not coming from the Coretime Chain or Root.
192			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			// Ignore requests not coming from the coretime chain or root.
204			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		/// Receive instructions from the `ExternalBrokerOrigin`, detailing how a specific core is
211		/// to be used.
212		///
213		/// Parameters:
214		/// -`origin`: The `ExternalBrokerOrigin`, assumed to be the coretime chain.
215		/// -`core`: The core that should be scheduled.
216		/// -`begin`: The starting blockheight of the instruction.
217		/// -`assignment`: How the blockspace should be utilised.
218		/// -`end_hint`: An optional hint as to when this particular set of instructions will end.
219		// The broker pallet's `CoreIndex` definition is `u16` but on the relay chain it's `struct
220		// CoreIndex(u32)`
221		#[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			// Ignore requests not coming from the coretime chain or root.
231			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	/// Ensure the origin is one of Root or the `para` itself.
244	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			// Check if matching para id...
251			ensure!(caller_id == id, Error::<T>::NotBroker);
252		} else {
253			// Check if root...
254			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	/// Provide the amount of revenue accumulated from Instantaneous Coretime Sales from Relay-chain
281	/// block number last_until to until, not including until itself. last_until is defined as being
282	/// the until argument of the last notify_revenue message sent, or zero for the first call. If
283	/// revenue is None, this indicates that the information is no longer available. This explicitly
284	/// disregards the possibility of multiple parachains requesting and being notified of revenue
285	/// information.
286	///
287	/// The Relay-chain must be configured to ensure that only a single revenue information
288	/// destination exists.
289	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		// When cannot be in the future.
294		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	// Handle legacy swaps in coretime. Notifies coretime chain that a lease swap has occurred via
313	// XCM message. This function is meant to be used in an implementation of `OnSwap` trait.
314	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}