referrerpolicy=no-referrer-when-downgrade

polkadot_runtime_parachains/coretime/
migration.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//! Migrations for the Coretime pallet.
18
19pub use v_coretime::{GetLegacyLease, MigrateToCoretime};
20
21mod v_coretime {
22	use crate::{
23		assigner_coretime, configuration,
24		coretime::{mk_coretime_call, Config, PartsOf57600, WeightInfo},
25	};
26	use alloc::{vec, vec::Vec};
27	#[cfg(feature = "try-runtime")]
28	use codec::Decode;
29	#[cfg(feature = "try-runtime")]
30	use codec::Encode;
31	use core::{iter, result};
32	#[cfg(feature = "try-runtime")]
33	use frame_support::ensure;
34	use frame_support::{
35		traits::{OnRuntimeUpgrade, PalletInfoAccess, StorageVersion},
36		weights::Weight,
37	};
38	use frame_system::pallet_prelude::BlockNumberFor;
39	use pallet_broker::{CoreAssignment, CoreMask, ScheduleItem};
40	use polkadot_parachain_primitives::primitives::IsSystem;
41	use polkadot_primitives::{CoreIndex, Id as ParaId};
42	use sp_arithmetic::traits::SaturatedConversion;
43	use sp_core::Get;
44	use sp_runtime::BoundedVec;
45	use xcm::prelude::{
46		send_xcm, Instruction, Junction, Location, SendError, SendXcm, WeightLimit, Xcm,
47	};
48
49	/// Return information about a legacy lease of a parachain.
50	pub trait GetLegacyLease<N> {
51		/// If parachain is a lease holding parachain, return the block at which the lease expires.
52		fn get_parachain_lease_in_blocks(para: ParaId) -> Option<N>;
53		// All parachains holding a lease, no matter if there are gaps in the slots or not.
54		fn get_all_parachains_with_leases() -> Vec<ParaId>;
55	}
56
57	/// Migrate a chain to use coretime.
58	///
59	/// This assumes that the `Coretime` and the `AssignerCoretime` pallets are added at the same
60	/// time to a runtime.
61	pub struct MigrateToCoretime<T, SendXcm, LegacyLease, const TIMESLICE_PERIOD: u32>(
62		core::marker::PhantomData<(T, SendXcm, LegacyLease)>,
63	);
64
65	impl<
66			T: Config,
67			XcmSender: SendXcm,
68			LegacyLease: GetLegacyLease<BlockNumberFor<T>>,
69			const TIMESLICE_PERIOD: u32,
70		> MigrateToCoretime<T, XcmSender, LegacyLease, TIMESLICE_PERIOD>
71	{
72		fn already_migrated() -> bool {
73			// We are using the assigner coretime because the coretime pallet doesn't has any
74			// storage data. But both pallets are introduced at the same time, so this is fine.
75			let name_hash = assigner_coretime::Pallet::<T>::name_hash();
76			let mut next_key = name_hash.to_vec();
77			let storage_version_key = StorageVersion::storage_key::<assigner_coretime::Pallet<T>>();
78
79			loop {
80				match sp_io::storage::next_key(&next_key) {
81					// StorageVersion is initialized before, so we need to ignore it.
82					Some(key) if &key == &storage_version_key => {
83						next_key = key;
84					},
85					// If there is any other key with the prefix of the pallet,
86					// we already have executed the migration.
87					Some(key) if key.starts_with(&name_hash) => {
88						log::info!("`MigrateToCoretime` already executed!");
89						return true
90					},
91					// Any other key/no key means that we did not yet have migrated.
92					None | Some(_) => return false,
93				}
94			}
95		}
96	}
97
98	impl<
99			T: Config + crate::dmp::Config,
100			XcmSender: SendXcm,
101			LegacyLease: GetLegacyLease<BlockNumberFor<T>>,
102			const TIMESLICE_PERIOD: u32,
103		> OnRuntimeUpgrade for MigrateToCoretime<T, XcmSender, LegacyLease, TIMESLICE_PERIOD>
104	{
105		fn on_runtime_upgrade() -> Weight {
106			if Self::already_migrated() {
107				return Weight::zero()
108			}
109
110			log::info!("Migrating existing parachains to coretime.");
111			migrate_to_coretime::<T, XcmSender, LegacyLease, TIMESLICE_PERIOD>()
112		}
113
114		#[cfg(feature = "try-runtime")]
115		fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::DispatchError> {
116			if Self::already_migrated() {
117				return Ok(Vec::new())
118			}
119
120			let legacy_paras = LegacyLease::get_all_parachains_with_leases();
121			let config = configuration::ActiveConfig::<T>::get();
122			let total_core_count = config.scheduler_params.num_cores + legacy_paras.len() as u32;
123
124			let dmp_queue_size =
125				crate::dmp::Pallet::<T>::dmq_contents(T::BrokerId::get().into()).len() as u32;
126
127			let total_core_count = total_core_count as u32;
128
129			Ok((total_core_count, dmp_queue_size).encode())
130		}
131
132		#[cfg(feature = "try-runtime")]
133		fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::DispatchError> {
134			if state.is_empty() {
135				return Ok(())
136			}
137
138			log::trace!("Running post_upgrade()");
139
140			let (prev_core_count, prev_dmp_queue_size) =
141				<(u32, u32)>::decode(&mut &state[..]).unwrap();
142
143			let dmp_queue_size =
144				crate::dmp::Pallet::<T>::dmq_contents(T::BrokerId::get().into()).len() as u32;
145			let config = configuration::ActiveConfig::<T>::get();
146			let new_core_count = config.scheduler_params.num_cores;
147			ensure!(new_core_count == prev_core_count, "Total number of cores need to not change.");
148			ensure!(
149				dmp_queue_size > prev_dmp_queue_size,
150				"There should have been enqueued at least one DMP messages."
151			);
152
153			Ok(())
154		}
155	}
156
157	// Migrate to Coretime.
158	//
159	// NOTE: Also migrates `num_cores` config value in configuration::ActiveConfig.
160	fn migrate_to_coretime<
161		T: Config,
162		XcmSender: SendXcm,
163		LegacyLease: GetLegacyLease<BlockNumberFor<T>>,
164		const TIMESLICE_PERIOD: u32,
165	>() -> Weight {
166		let legacy_paras = LegacyLease::get_all_parachains_with_leases();
167		let legacy_count = legacy_paras.len() as u32;
168		let now = frame_system::Pallet::<T>::block_number();
169		for (core, para_id) in legacy_paras.into_iter().enumerate() {
170			let r = assigner_coretime::Pallet::<T>::assign_core(
171				CoreIndex(core as u32),
172				now,
173				vec![(CoreAssignment::Task(para_id.into()), PartsOf57600::FULL)],
174				None,
175			);
176			if let Err(err) = r {
177				log::error!(
178					"Creating assignment for existing para failed: {:?}, error: {:?}",
179					para_id,
180					err
181				);
182			}
183		}
184
185		let config = configuration::ActiveConfig::<T>::get();
186		for on_demand in 0..config.scheduler_params.num_cores {
187			let core = CoreIndex(legacy_count.saturating_add(on_demand as _));
188			let r = assigner_coretime::Pallet::<T>::assign_core(
189				core,
190				now,
191				vec![(CoreAssignment::Pool, PartsOf57600::FULL)],
192				None,
193			);
194			if let Err(err) = r {
195				log::error!("Creating assignment for existing on-demand core, failed: {:?}", err);
196			}
197		}
198		let total_cores = config.scheduler_params.num_cores + legacy_count;
199		configuration::ActiveConfig::<T>::mutate(|c| {
200			c.scheduler_params.num_cores = total_cores;
201		});
202
203		if let Err(err) = migrate_send_assignments_to_coretime_chain::<
204			T,
205			XcmSender,
206			LegacyLease,
207			TIMESLICE_PERIOD,
208		>() {
209			log::error!("Sending legacy chain data to coretime chain failed: {:?}", err);
210		}
211
212		let single_weight = <T as Config>::WeightInfo::assign_core(1);
213		single_weight
214			.saturating_mul(u64::from(
215				legacy_count.saturating_add(config.scheduler_params.num_cores),
216			))
217			// Second read from sending assignments to the coretime chain.
218			.saturating_add(T::DbWeight::get().reads_writes(2, 1))
219	}
220
221	fn migrate_send_assignments_to_coretime_chain<
222		T: Config,
223		XcmSender: SendXcm,
224		LegacyLease: GetLegacyLease<BlockNumberFor<T>>,
225		const TIMESLICE_PERIOD: u32,
226	>() -> result::Result<(), SendError> {
227		let legacy_paras = LegacyLease::get_all_parachains_with_leases();
228		let legacy_paras_count = legacy_paras.len();
229		let (system_chains, lease_holding): (Vec<_>, Vec<_>) =
230			legacy_paras.into_iter().partition(IsSystem::is_system);
231
232		let reservations = system_chains.into_iter().map(|p| {
233			let schedule = BoundedVec::truncate_from(vec![ScheduleItem {
234				mask: CoreMask::complete(),
235				assignment: CoreAssignment::Task(p.into()),
236			}]);
237			mk_coretime_call::<T>(crate::coretime::CoretimeCalls::Reserve(schedule))
238		});
239
240		let mut leases = lease_holding.into_iter().filter_map(|p| {
241			log::trace!(target: "coretime-migration", "Preparing sending of lease holding para {:?}", p);
242			let Some(valid_until) = LegacyLease::get_parachain_lease_in_blocks(p) else {
243				log::error!("Lease holding chain with no lease information?!");
244				return None
245			};
246			let valid_until: u32 = match valid_until.try_into() {
247				Ok(val) => val,
248				Err(_) => {
249					log::error!("Converting block number to u32 failed!");
250					return None
251				},
252			};
253			let time_slice = valid_until.div_ceil(TIMESLICE_PERIOD);
254			log::trace!(target: "coretime-migration", "Sending of lease holding para {:?}, valid_until: {:?}, time_slice: {:?}", p, valid_until, time_slice);
255			Some(mk_coretime_call::<T>(crate::coretime::CoretimeCalls::SetLease(p.into(), time_slice)))
256		});
257
258		let core_count: u16 = configuration::ActiveConfig::<T>::get()
259			.scheduler_params
260			.num_cores
261			.saturated_into();
262		let set_core_count = iter::once(mk_coretime_call::<T>(
263			crate::coretime::CoretimeCalls::NotifyCoreCount(core_count),
264		));
265
266		let pool = (legacy_paras_count..core_count.into()).map(|_| {
267			let schedule = BoundedVec::truncate_from(vec![ScheduleItem {
268				mask: CoreMask::complete(),
269				assignment: CoreAssignment::Pool,
270			}]);
271			// Reserved cores will come before lease cores, so cores will change their assignments
272			// when coretime chain sends us their assign_core calls -> Good test.
273			mk_coretime_call::<T>(crate::coretime::CoretimeCalls::Reserve(schedule))
274		});
275
276		let message_content = iter::once(Instruction::UnpaidExecution {
277			weight_limit: WeightLimit::Unlimited,
278			check_origin: None,
279		});
280
281		let reservation_content = message_content.clone().chain(reservations).collect();
282		let leases_content_1 = message_content
283			.clone()
284			.chain(leases.by_ref().take(legacy_paras_count / 2)) // split in two messages to avoid overweighted XCM
285			.collect();
286		let leases_content_2 = message_content.clone().chain(leases).collect();
287		let set_core_count_content = message_content.clone().chain(set_core_count).collect();
288		// If `pool_content` is empty don't send a blank XCM message
289		let messages = if core_count as usize > legacy_paras_count {
290			let pool_content = message_content.clone().chain(pool).collect();
291			vec![
292				Xcm(reservation_content),
293				Xcm(pool_content),
294				Xcm(leases_content_1),
295				Xcm(leases_content_2),
296				Xcm(set_core_count_content),
297			]
298		} else {
299			vec![
300				Xcm(reservation_content),
301				Xcm(leases_content_1),
302				Xcm(leases_content_2),
303				Xcm(set_core_count_content),
304			]
305		};
306
307		for message in messages {
308			send_xcm::<XcmSender>(
309				Location::new(0, Junction::Parachain(T::BrokerId::get())),
310				message,
311			)?;
312		}
313
314		Ok(())
315	}
316}