referrerpolicy=no-referrer-when-downgrade

polkadot_runtime_parachains/shared/
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
14use super::*;
15use frame_support::{
16	pallet_prelude::ValueQuery, traits::UncheckedOnRuntimeUpgrade, weights::Weight,
17};
18use polkadot_primitives::vstaging::RelayParentInfo;
19
20#[cfg(feature = "try-runtime")]
21const LOG_TARGET: &str = "runtime::shared";
22
23pub mod v1 {
24	use super::*;
25	use alloc::collections::vec_deque::VecDeque;
26	use codec::{Decode, Encode};
27	use frame_support::storage_alias;
28
29	/// The old `AllowedRelayParents` storage at version 1 (a StorageValue).
30	/// This occupied the storage key `twox128("ParasShared") ++ twox128("AllowedRelayParents")`.
31	/// In v2, this storage name is reused as a StorageDoubleMap, so the old value must be removed.
32	#[storage_alias]
33	pub(crate) type AllowedRelayParents<T: Config> = StorageValue<
34		Pallet<T>,
35		AllowedRelayParentsTracker<<T as frame_system::Config>::Hash, BlockNumberFor<T>>,
36		ValueQuery,
37	>;
38
39	/// The v1 relay parent info stored in the old tracker's buffer.
40	#[derive(Encode, Decode, Default, TypeInfo, Debug)]
41	pub struct RelayParentInfo<Hash> {
42		pub relay_parent: Hash,
43		pub state_root: Hash,
44		pub claim_queue: BTreeMap<Id, BTreeMap<u8, BTreeSet<CoreIndex>>>,
45	}
46
47	/// The v1 allowed relay parents tracker (StorageValue format).
48	#[derive(Encode, Decode, Default, TypeInfo)]
49	pub struct AllowedRelayParentsTracker<Hash, BlockNumber> {
50		pub buffer: VecDeque<RelayParentInfo<Hash>>,
51		pub latest_number: BlockNumber,
52	}
53
54	impl<Hash: PartialEq, BlockNumber: AtLeast32BitUnsigned + Copy>
55		AllowedRelayParentsTracker<Hash, BlockNumber>
56	{
57		pub(crate) fn hypothetical_earliest_block_number(
58			&self,
59			now: BlockNumber,
60			max_ancestry_len: u32,
61		) -> BlockNumber {
62			let allowed_ancestry_len = max_ancestry_len.min(self.buffer.len() as u32);
63
64			now - allowed_ancestry_len.into()
65		}
66
67		pub(crate) fn get_number(&self, relay_parent: Hash) -> Option<BlockNumber> {
68			let pos = self.buffer.iter().position(|info| info.relay_parent == relay_parent)?;
69			let age = (self.buffer.len() - 1) - pos;
70			let number = self.latest_number - BlockNumber::from(age as u32);
71
72			Some(number)
73		}
74	}
75}
76
77mod v2 {
78	use super::*;
79
80	#[cfg(feature = "try-runtime")]
81	use frame_support::{
82		ensure,
83		traits::{GetStorageVersion, StorageVersion},
84	};
85
86	pub struct VersionUncheckedMigrateToV2<T>(core::marker::PhantomData<T>);
87
88	impl<T: Config> UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateToV2<T> {
89		#[cfg(feature = "try-runtime")]
90		fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
91			log::trace!(target: LOG_TARGET, "Running pre_upgrade() for shared MigrateToV2");
92
93			let old_tracker = v1::AllowedRelayParents::<T>::get();
94			let buf_len = old_tracker.buffer.len() as u32;
95			log::trace!(
96				target: LOG_TARGET,
97				"Old AllowedRelayParents tracker has {} entries",
98				buf_len
99			);
100
101			Ok(buf_len.to_ne_bytes().to_vec())
102		}
103
104		fn on_runtime_upgrade() -> Weight {
105			let mut weight: Weight = Weight::zero();
106
107			// Remove the old AllowedRelayParents StorageValue (v1 format).
108			let old_tracker = v1::AllowedRelayParents::<T>::take();
109			weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
110
111			let latest_number = old_tracker.latest_number;
112			let buf_len = old_tracker.buffer.len();
113
114			// Convert v1 AllowedRelayParentsTracker to new AllowedSchedulingParentsTracker.
115			// The scheduling tracker keeps: scheduling_parent (was relay_parent), claim_queue.
116			// It drops: state_root (now stored in the relay parent DoubleMap).
117			let mut new_buffer: VecDeque<SchedulingParentInfo<T::Hash>> =
118				VecDeque::with_capacity(buf_len);
119
120			// Populate the new AllowedRelayParents DoubleMap from the old tracker entries.
121			// All existing entries are from the current session (the old tracker was cleared
122			// on session changes). Block numbers are derived from buffer position and
123			// latest_number.
124			let current_session = CurrentSessionIndex::<T>::get();
125			weight = weight.saturating_add(T::DbWeight::get().reads(1));
126
127			for (idx, info) in old_tracker.buffer.into_iter().enumerate() {
128				// Compute block number from position in the buffer.
129				let age = (buf_len - 1) - idx;
130				let block_number = latest_number - BlockNumberFor::<T>::from(age as u32);
131
132				// Insert into the new AllowedRelayParents DoubleMap.
133				AllowedRelayParents::<T>::insert(
134					current_session,
135					info.relay_parent,
136					RelayParentInfo { number: block_number, state_root: info.state_root },
137				);
138
139				// Build the scheduling parents buffer entry.
140				new_buffer.push_back(SchedulingParentInfo {
141					scheduling_parent: info.relay_parent,
142					claim_queue: info.claim_queue,
143				});
144			}
145			weight = weight.saturating_add(T::DbWeight::get().writes(buf_len as u64));
146
147			AllowedSchedulingParents::<T>::set(AllowedSchedulingParentsTracker {
148				buffer: new_buffer,
149				latest_number,
150			});
151			weight = weight.saturating_add(T::DbWeight::get().writes(1));
152
153			OldestRelayParentSession::<T>::set(current_session);
154			weight = weight.saturating_add(T::DbWeight::get().writes(1));
155
156			// Initialize MinimumRelayParentNumber for the current session.
157			// The oldest entry in the buffer has the smallest block number.
158			if buf_len > 0 {
159				let min_block_number =
160					latest_number - BlockNumberFor::<T>::from((buf_len - 1) as u32);
161				MinimumRelayParentNumber::<T>::insert(current_session, min_block_number);
162			} else {
163				MinimumRelayParentNumber::<T>::insert(current_session, latest_number);
164			}
165
166			weight = weight.saturating_add(T::DbWeight::get().writes(1));
167
168			weight
169		}
170
171		#[cfg(feature = "try-runtime")]
172		fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
173			log::trace!(target: LOG_TARGET, "Running post_upgrade() for shared MigrateToV2");
174
175			ensure!(
176				Pallet::<T>::on_chain_storage_version() >= StorageVersion::new(2),
177				"Storage version should be >= 2 after the migration"
178			);
179
180			let old_buf_len = u32::from_ne_bytes(
181				state
182					.try_into()
183					.expect("u32::from_ne_bytes(to_ne_bytes(u32)) always works; qed"),
184			);
185
186			// The scheduling parents tracker should have the same number of entries.
187			let new_tracker = AllowedSchedulingParents::<T>::get();
188			ensure!(
189				old_buf_len as usize == new_tracker.buffer.len(),
190				"AllowedSchedulingParents buffer length should match the old tracker"
191			);
192
193			// The old AllowedRelayParents StorageValue should be gone (taken).
194			ensure!(
195				v1::AllowedRelayParents::<T>::get().buffer.is_empty(),
196				"Old AllowedRelayParents StorageValue should be empty after migration"
197			);
198
199			// OldestRelayParentSession should be set.
200			let oldest = OldestRelayParentSession::<T>::get();
201			let current = CurrentSessionIndex::<T>::get();
202			ensure!(
203				oldest == current,
204				"OldestRelayParentSession should equal current session after migration"
205			);
206
207			// The AllowedRelayParents DoubleMap should have the same number of entries.
208			let double_map_count = AllowedRelayParents::<T>::iter_prefix(current).count();
209			ensure!(
210				old_buf_len as usize == double_map_count,
211				"AllowedRelayParents DoubleMap should have the same number of entries as the old tracker"
212			);
213
214			// MinimumRelayParentNumber should be set if the buffer was non-empty.
215			if old_buf_len > 0 {
216				ensure!(
217					MinimumRelayParentNumber::<T>::contains_key(current),
218					"MinimumRelayParentNumber should be set for current session after migration"
219				);
220			}
221
222			Ok(())
223		}
224	}
225}
226
227/// Migrate shared module storage from v1 to v2.
228pub type MigrateToV2<T> = frame_support::migrations::VersionedMigration<
229	1,
230	2,
231	v2::VersionUncheckedMigrateToV2<T>,
232	Pallet<T>,
233	<T as frame_system::Config>::DbWeight,
234>;
235
236#[cfg(test)]
237mod tests {
238	use super::{v1, v2::VersionUncheckedMigrateToV2, *};
239	use crate::mock::{new_test_ext, MockGenesisConfig, Test};
240	use frame_support::traits::UncheckedOnRuntimeUpgrade;
241	use polkadot_primitives::Hash;
242
243	#[test]
244	fn migrate_v1_to_v2() {
245		new_test_ext(MockGenesisConfig::default()).execute_with(|| {
246			// Set up v1 state: populate the old AllowedRelayParents StorageValue.
247			let old_tracker = v1::AllowedRelayParentsTracker {
248				latest_number: 200u32,
249				buffer: (0..10u32)
250					.map(|idx| v1::RelayParentInfo {
251						relay_parent: Hash::from_low_u64_ne(idx as u64),
252						state_root: Hash::from_low_u64_ne(100 + idx as u64),
253						claim_queue: [(Id::from(idx), BTreeMap::new())].into_iter().collect(),
254					})
255					.collect(),
256			};
257			v1::AllowedRelayParents::<Test>::put(old_tracker);
258
259			// Set session index.
260			let session_index = 5;
261			CurrentSessionIndex::<Test>::set(session_index);
262
263			// Run migration.
264			<VersionUncheckedMigrateToV2<Test> as UncheckedOnRuntimeUpgrade>::on_runtime_upgrade();
265
266			// Verify AllowedSchedulingParents was populated correctly.
267			let new_tracker = AllowedSchedulingParents::<Test>::get();
268			assert_eq!(new_tracker.buffer.len(), 10);
269			assert_eq!(new_tracker.latest_number, 200u32);
270
271			for idx in 0..10u32 {
272				let expected_hash = Hash::from_low_u64_ne(idx as u64);
273				assert_eq!(new_tracker.buffer[idx as usize].scheduling_parent, expected_hash);
274				let expected_cq = [(Id::from(idx), BTreeMap::new())].into_iter().collect();
275				assert_eq!(new_tracker.buffer[idx as usize].claim_queue, expected_cq);
276			}
277
278			// Verify old AllowedRelayParents StorageValue was removed.
279			assert!(v1::AllowedRelayParents::<Test>::get().buffer.is_empty());
280
281			// Verify OldestRelayParentSession was initialized.
282			assert_eq!(OldestRelayParentSession::<Test>::get(), 5);
283
284			// Verify AllowedRelayParents DoubleMap was populated with all entries
285			// under the current session (5).
286			assert_eq!(AllowedRelayParents::<Test>::iter_prefix(session_index).count(), 10);
287			assert_eq!(AllowedRelayParents::<Test>::iter().count(), 10);
288
289			for idx in 0..10u32 {
290				let relay_parent = Hash::from_low_u64_ne(idx as u64);
291				let expected_state_root = Hash::from_low_u64_ne(100 + idx as u64);
292
293				let info = AllowedRelayParents::<Test>::get(session_index, relay_parent)
294					.expect("relay parent should be in DoubleMap");
295				assert_eq!(info.state_root, expected_state_root);
296				assert_eq!(info.number, 200 - 10 + idx + 1);
297			}
298
299			// Verify the MinimumRelayParentNumber was set correctly.
300			assert_eq!(MinimumRelayParentNumber::<Test>::get(session_index).unwrap(), 191);
301		});
302	}
303
304	#[test]
305	fn migrate_v1_to_v2_empty_tracker() {
306		new_test_ext(MockGenesisConfig::default()).execute_with(|| {
307			// v1 state with empty tracker.
308			v1::AllowedRelayParents::<Test>::put(v1::AllowedRelayParentsTracker::<Hash, u32> {
309				buffer: Default::default(),
310				latest_number: 300,
311			});
312
313			CurrentSessionIndex::<Test>::set(1);
314
315			<VersionUncheckedMigrateToV2<Test> as UncheckedOnRuntimeUpgrade>::on_runtime_upgrade();
316
317			let new_tracker = AllowedSchedulingParents::<Test>::get();
318			assert!(new_tracker.buffer.is_empty());
319			assert_eq!(OldestRelayParentSession::<Test>::get(), 1);
320
321			assert_eq!(AllowedRelayParents::<Test>::iter().count(), 0);
322
323			// Verify the MinimumRelayParentNumber was set correctly.
324			assert_eq!(MinimumRelayParentNumber::<Test>::get(1).unwrap(), 300);
325		});
326	}
327}