polkadot_runtime_parachains/shared/
migration.rs1use 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 #[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 #[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 #[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 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 let mut new_buffer: VecDeque<SchedulingParentInfo<T::Hash>> =
118 VecDeque::with_capacity(buf_len);
119
120 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 let age = (buf_len - 1) - idx;
130 let block_number = latest_number - BlockNumberFor::<T>::from(age as u32);
131
132 AllowedRelayParents::<T>::insert(
134 current_session,
135 info.relay_parent,
136 RelayParentInfo { number: block_number, state_root: info.state_root },
137 );
138
139 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 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 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 ensure!(
195 v1::AllowedRelayParents::<T>::get().buffer.is_empty(),
196 "Old AllowedRelayParents StorageValue should be empty after migration"
197 );
198
199 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 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 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
227pub 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 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 let session_index = 5;
261 CurrentSessionIndex::<Test>::set(session_index);
262
263 <VersionUncheckedMigrateToV2<Test> as UncheckedOnRuntimeUpgrade>::on_runtime_upgrade();
265
266 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 assert!(v1::AllowedRelayParents::<Test>::get().buffer.is_empty());
280
281 assert_eq!(OldestRelayParentSession::<Test>::get(), 5);
283
284 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 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::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 assert_eq!(MinimumRelayParentNumber::<Test>::get(1).unwrap(), 300);
325 });
326 }
327}