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}
56
57/// A weight info that is only suitable for testing.
58pub 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
75/// Shorthand for the Balance type the runtime is using.
76pub type BalanceOf<T> = <<T as on_demand::Config>::Currency as Currency<
77	<T as frame_system::Config>::AccountId,
78>>::Balance;
79
80/// Broker pallet index on the coretime chain. Used to
81///
82/// construct remote calls. The codec index must correspond to the index of `Broker` in the
83/// `construct_runtime` of the coretime chain.
84#[derive(Encode, Decode)]
85enum BrokerRuntimePallets {
86	#[codec(index = 50)]
87	Broker(CoretimeCalls),
88}
89
90/// Call encoding for the calls needed from the Broker pallet.
91#[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		/// The ParaId of the coretime chain.
126		#[pallet::constant]
127		type BrokerId: Get<u32>;
128		/// The coretime chain pot location.
129		#[pallet::constant]
130		type BrokerPotLocation: Get<InteriorLocation>;
131		/// Something that provides the weight of this pallet.
132		type WeightInfo: WeightInfo;
133		/// The XCM sender.
134		type SendXcm: SendXcm;
135		/// The asset transactor.
136		type AssetTransactor: TransactAsset;
137		/// AccountId to Location converter
138		type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>;
139
140		/// Maximum weight for any XCM transact call that should be executed on the coretime chain.
141		///
142		/// Basically should be `max_weight(set_leases, reserve, notify_core_count)`.
143		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		/// The broker chain has asked for revenue information for a specific block.
150		RevenueInfoRequested { when: BlockNumberFor<T> },
151		/// A core has received a new assignment from the broker chain.
152		CoreAssigned { core: CoreIndex },
153	}
154
155	#[pallet::error]
156	pub enum Error<T> {
157		/// The paraid making the call is not the coretime brokerage system parachain.
158		NotBroker,
159		/// Requested revenue information `when` parameter was in the future from the current
160		/// block height.
161		RequestedFutureRevenue,
162		/// Failed to transfer assets to the coretime chain
163		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	/// Extrinsics to be called by the Coretime chain.
177	impl<T: Config> Pallet<T> {
178		/// Request the configuration to be updated with the specified number of cores. Warning:
179		/// Since this only schedules a configuration update, it takes two sessions to come into
180		/// effect.
181		///
182		/// - `origin`: Root or the Coretime Chain
183		/// - `count`: total number of cores
184		#[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			// Ignore requests not coming from the coretime chain or root.
188			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		/// Request to claim the instantaneous coretime sales revenue starting from the block it was
194		/// last claimed until and up to the block specified. The claimed amount value is sent back
195		/// to the Coretime chain in a `notify_revenue` message. At the same time, the amount is
196		/// teleported to the Coretime chain.
197		#[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			// Ignore requests not coming from the Coretime Chain or Root.
201			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			// Ignore requests not coming from the coretime chain or root.
213			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		/// Receive instructions from the `ExternalBrokerOrigin`, detailing how a specific core is
220		/// to be used.
221		///
222		/// Parameters:
223		/// -`origin`: The `ExternalBrokerOrigin`, assumed to be the coretime chain.
224		/// -`core`: The core that should be scheduled.
225		/// -`begin`: The starting blockheight of the instruction.
226		/// -`assignment`: How the blockspace should be utilised.
227		/// -`end_hint`: An optional hint as to when this particular set of instructions will end.
228		// The broker pallet's `CoreIndex` definition is `u16` but on the relay chain it's `struct
229		// CoreIndex(u32)`
230		#[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			// Ignore requests not coming from the coretime chain or root.
240			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
251/// Coretime chain communiation.
252impl<T: Config> Pallet<T> {
253	/// Ensure the origin is one of Root or the `para` itself.
254	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			// Check if matching para id...
261			ensure!(caller_id == id, Error::<T>::NotBroker);
262		} else {
263			// Check if root...
264			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	/// Provide the amount of revenue accumulated from Instantaneous Coretime Sales from Relay-chain
291	/// block number last_until to until, not including until itself. last_until is defined as being
292	/// the until argument of the last notify_revenue message sent, or zero for the first call. If
293	/// revenue is None, this indicates that the information is no longer available. This explicitly
294	/// disregards the possibility of multiple parachains requesting and being notified of revenue
295	/// information.
296	///
297	/// The Relay-chain must be configured to ensure that only a single revenue information
298	/// destination exists.
299	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		// When cannot be in the future.
304		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	// Handle legacy swaps in coretime. Notifies coretime chain that a lease swap has occurred via
323	// XCM message. This function is meant to be used in an implementation of `OnSwap` trait.
324	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		// dropping `withdrawn` effectively burns the inner imbalance
375		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}