1use super::*;
21use frame_support::traits::OnRuntimeUpgrade;
22
23#[cfg(feature = "try-runtime")]
24use sp_runtime::TryRuntimeError;
25
26const TARGET: &'static str = "runtime::scheduler::migration";
28
29pub mod v1 {
30 use super::*;
31 use frame_support::pallet_prelude::*;
32
33 #[frame_support::storage_alias]
34 pub(crate) type Agenda<T: Config> = StorageMap<
35 Pallet<T>,
36 Twox64Concat,
37 BlockNumberFor<T>,
38 Vec<Option<ScheduledV1<<T as Config>::RuntimeCall, BlockNumberFor<T>>>>,
39 ValueQuery,
40 >;
41
42 #[frame_support::storage_alias]
43 pub(crate) type Lookup<T: Config> =
44 StorageMap<Pallet<T>, Twox64Concat, Vec<u8>, TaskAddress<BlockNumberFor<T>>>;
45}
46
47pub mod v2 {
48 use super::*;
49 use frame_support::pallet_prelude::*;
50
51 #[frame_support::storage_alias]
52 pub(crate) type Agenda<T: Config> = StorageMap<
53 Pallet<T>,
54 Twox64Concat,
55 BlockNumberFor<T>,
56 Vec<Option<ScheduledV2Of<T>>>,
57 ValueQuery,
58 >;
59
60 #[frame_support::storage_alias]
61 pub(crate) type Lookup<T: Config> =
62 StorageMap<Pallet<T>, Twox64Concat, Vec<u8>, TaskAddress<BlockNumberFor<T>>>;
63}
64
65pub mod v3 {
66 use super::*;
67 use frame_support::pallet_prelude::*;
68
69 #[frame_support::storage_alias]
70 pub(crate) type Agenda<T: Config> = StorageMap<
71 Pallet<T>,
72 Twox64Concat,
73 BlockNumberFor<T>,
74 Vec<Option<ScheduledV3Of<T>>>,
75 ValueQuery,
76 >;
77
78 #[frame_support::storage_alias]
79 pub(crate) type Lookup<T: Config> =
80 StorageMap<Pallet<T>, Twox64Concat, Vec<u8>, TaskAddress<BlockNumberFor<T>>>;
81
82 pub struct MigrateToV4<T>(core::marker::PhantomData<T>);
84
85 impl<T: Config> OnRuntimeUpgrade for MigrateToV4<T> {
86 #[cfg(feature = "try-runtime")]
87 fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
88 ensure!(StorageVersion::get::<Pallet<T>>() == 3, "Can only upgrade from version 3");
89
90 let agendas = Agenda::<T>::iter_keys().count() as u32;
91 let decodable_agendas = Agenda::<T>::iter_values().count() as u32;
92 if agendas != decodable_agendas {
93 log::error!(
96 target: TARGET,
97 "Can only decode {} of {} agendas - others will be dropped",
98 decodable_agendas,
99 agendas
100 );
101 }
102 log::info!(target: TARGET, "Trying to migrate {} agendas...", decodable_agendas);
103
104 let max_scheduled_per_block = T::MaxScheduledPerBlock::get() as usize;
106 for (block_number, agenda) in Agenda::<T>::iter() {
107 if agenda.iter().cloned().flatten().count() > max_scheduled_per_block {
108 log::error!(
109 target: TARGET,
110 "Would truncate agenda of block {:?} from {} items to {} items.",
111 block_number,
112 agenda.len(),
113 max_scheduled_per_block,
114 );
115 return Err("Agenda would overflow `MaxScheduledPerBlock`.".into())
116 }
117 }
118 let max_length = T::Preimages::MAX_LENGTH as usize;
120 for (block_number, agenda) in Agenda::<T>::iter() {
121 for schedule in agenda.iter().cloned().flatten() {
122 match schedule.call {
123 frame_support::traits::schedule::MaybeHashed::Value(call) => {
124 let l = call.using_encoded(|c| c.len());
125 if l > max_length {
126 log::error!(
127 target: TARGET,
128 "Call in agenda of block {:?} is too large: {} byte",
129 block_number,
130 l,
131 );
132 return Err("Call is too large.".into())
133 }
134 },
135 _ => (),
136 }
137 }
138 }
139
140 Ok((decodable_agendas as u32).encode())
141 }
142
143 fn on_runtime_upgrade() -> Weight {
144 let version = StorageVersion::get::<Pallet<T>>();
145 if version != 3 {
146 log::warn!(
147 target: TARGET,
148 "skipping v3 to v4 migration: executed on wrong storage version.\
149 Expected version 3, found {:?}",
150 version,
151 );
152 return T::DbWeight::get().reads(1)
153 }
154
155 crate::Pallet::<T>::migrate_v3_to_v4()
156 }
157
158 #[cfg(feature = "try-runtime")]
159 fn post_upgrade(state: Vec<u8>) -> Result<(), TryRuntimeError> {
160 ensure!(StorageVersion::get::<Pallet<T>>() == 4, "Must upgrade");
161
162 for k in crate::Agenda::<T>::iter_keys() {
164 ensure!(crate::Agenda::<T>::try_get(k).is_ok(), "Cannot decode V4 Agenda");
165 }
166
167 let old_agendas: u32 =
168 Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed");
169 let new_agendas = crate::Agenda::<T>::iter_keys().count() as u32;
170 if old_agendas != new_agendas {
171 log::error!(
174 target: TARGET,
175 "Did not migrate all Agendas. Previous {}, Now {}",
176 old_agendas,
177 new_agendas,
178 );
179 } else {
180 log::info!(target: TARGET, "Migrated {} agendas.", new_agendas);
181 }
182
183 Ok(())
184 }
185 }
186}
187
188pub mod v4 {
189 use super::*;
190 use frame_support::pallet_prelude::*;
191
192 pub struct CleanupAgendas<T>(core::marker::PhantomData<T>);
197
198 impl<T: Config> OnRuntimeUpgrade for CleanupAgendas<T> {
199 #[cfg(feature = "try-runtime")]
200 fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
201 assert_eq!(
202 StorageVersion::get::<Pallet<T>>(),
203 4,
204 "Can only cleanup agendas of the V4 scheduler"
205 );
206
207 let agendas = Agenda::<T>::iter_keys().count();
208 let non_empty_agendas =
209 Agenda::<T>::iter_values().filter(|a| a.iter().any(|s| s.is_some())).count();
210 log::info!(
211 target: TARGET,
212 "There are {} total and {} non-empty agendas",
213 agendas,
214 non_empty_agendas
215 );
216
217 Ok((agendas as u32, non_empty_agendas as u32).encode())
218 }
219
220 fn on_runtime_upgrade() -> Weight {
221 let version = StorageVersion::get::<Pallet<T>>();
222 if version != 4 {
223 log::warn!(target: TARGET, "Skipping CleanupAgendas migration since it was run on the wrong version: {:?} != 4", version);
224 return T::DbWeight::get().reads(1)
225 }
226
227 let keys = Agenda::<T>::iter_keys().collect::<Vec<_>>();
228 let mut writes = 0;
229 for k in &keys {
230 let mut schedules = Agenda::<T>::get(k);
231 let all_schedules = schedules.len();
232 let suffix_none_schedules =
233 schedules.iter().rev().take_while(|s| s.is_none()).count();
234
235 match all_schedules.checked_sub(suffix_none_schedules) {
236 Some(0) => {
237 log::info!(
238 "Deleting None-only agenda {:?} with {} entries",
239 k,
240 all_schedules
241 );
242 Agenda::<T>::remove(k);
243 writes.saturating_inc();
244 },
245 Some(ne) if ne > 0 => {
246 log::info!(
247 "Removing {} schedules of {} from agenda {:?}, now {:?}",
248 suffix_none_schedules,
249 all_schedules,
250 ne,
251 k
252 );
253 schedules.truncate(ne);
254 Agenda::<T>::insert(k, schedules);
255 writes.saturating_inc();
256 },
257 Some(_) => {
258 frame_support::defensive!(
259 "Cannot have more None suffix schedules that schedules in total"
261 );
262 },
263 None => {
264 log::info!("Agenda {:?} does not have any None suffix schedules", k);
265 },
266 }
267 }
268
269 T::DbWeight::get().reads_writes(1 + keys.len().saturating_mul(2) as u64, writes)
272 }
273
274 #[cfg(feature = "try-runtime")]
275 fn post_upgrade(state: Vec<u8>) -> Result<(), TryRuntimeError> {
276 ensure!(StorageVersion::get::<Pallet<T>>() == 4, "Version must not change");
277
278 let (old_agendas, non_empty_agendas): (u32, u32) =
279 Decode::decode(&mut state.as_ref()).expect("Must decode pre_upgrade state");
280 let new_agendas = Agenda::<T>::iter_keys().count() as u32;
281
282 match old_agendas.checked_sub(new_agendas) {
283 Some(0) => log::warn!(
284 target: TARGET,
285 "Did not clean up any agendas. v4::CleanupAgendas can be removed."
286 ),
287 Some(n) => {
288 log::info!(target: TARGET, "Cleaned up {} agendas, now {}", n, new_agendas)
289 },
290 None => unreachable!(
291 "Number of agendas cannot increase, old {} new {}",
292 old_agendas, new_agendas
293 ),
294 }
295 ensure!(new_agendas == non_empty_agendas, "Expected to keep all non-empty agendas");
296
297 Ok(())
298 }
299 }
300}
301
302#[cfg(test)]
303#[cfg(feature = "try-runtime")]
304mod test {
305 use super::*;
306 use crate::mock::*;
307 use alloc::borrow::Cow;
308 use frame_support::Hashable;
309 use substrate_test_utils::assert_eq_uvec;
310
311 #[test]
312 #[allow(deprecated)]
313 fn migration_v3_to_v4_works() {
314 new_test_ext().execute_with(|| {
315 StorageVersion::new(3).put::<Scheduler>();
317
318 let large_call =
320 RuntimeCall::System(frame_system::Call::remark { remark: vec![0; 1024] });
321 let small_call =
323 RuntimeCall::System(frame_system::Call::remark { remark: vec![0; 10] });
324 let hashed_call =
326 RuntimeCall::System(frame_system::Call::remark { remark: vec![0; 2048] });
327 let bound_hashed_call = Preimage::bound(hashed_call.clone()).unwrap();
328 assert!(bound_hashed_call.lookup_needed());
329 let trash_data = vec![255u8; 1024];
331 let undecodable_hash = Preimage::note(Cow::Borrowed(&trash_data)).unwrap();
332
333 for i in 0..2u64 {
334 let k = i.twox_64_concat();
335 let old = vec![
336 Some(ScheduledV3Of::<Test> {
337 maybe_id: None,
338 priority: i as u8 + 10,
339 call: small_call.clone().into(),
340 maybe_periodic: None, origin: root(),
342 _phantom: PhantomData::<u64>::default(),
343 }),
344 None,
345 Some(ScheduledV3Of::<Test> {
346 maybe_id: Some(vec![i as u8; 32]),
347 priority: 123,
348 call: large_call.clone().into(),
349 maybe_periodic: Some((4u64, 20)),
350 origin: signed(i),
351 _phantom: PhantomData::<u64>::default(),
352 }),
353 Some(ScheduledV3Of::<Test> {
354 maybe_id: Some(vec![255 - i as u8; 320]),
355 priority: 123,
356 call: MaybeHashed::Hash(bound_hashed_call.hash()),
357 maybe_periodic: Some((8u64, 10)),
358 origin: signed(i),
359 _phantom: PhantomData::<u64>::default(),
360 }),
361 Some(ScheduledV3Of::<Test> {
362 maybe_id: Some(vec![i as u8; 320]),
363 priority: 123,
364 call: MaybeHashed::Hash(undecodable_hash),
365 maybe_periodic: Some((4u64, 20)),
366 origin: root(),
367 _phantom: PhantomData::<u64>::default(),
368 }),
369 ];
370 frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old);
371 }
372
373 let state = v3::MigrateToV4::<Test>::pre_upgrade().unwrap();
374 let _w = v3::MigrateToV4::<Test>::on_runtime_upgrade();
375 v3::MigrateToV4::<Test>::post_upgrade(state).unwrap();
376
377 let mut x = Agenda::<Test>::iter().map(|x| (x.0, x.1.into_inner())).collect::<Vec<_>>();
378 x.sort_by_key(|x| x.0);
379
380 let bound_large_call = Preimage::bound(large_call).unwrap();
381 assert!(bound_large_call.lookup_needed());
382 let bound_small_call = Preimage::bound(small_call).unwrap();
383 assert!(!bound_small_call.lookup_needed());
384
385 let expected = vec![
386 (
387 0,
388 vec![
389 Some(ScheduledOf::<Test> {
390 maybe_id: None,
391 priority: 10,
392 call: bound_small_call.clone(),
393 maybe_periodic: None,
394 origin: root(),
395 _phantom: PhantomData::<u64>::default(),
396 }),
397 None,
398 Some(ScheduledOf::<Test> {
399 maybe_id: Some(blake2_256(&[0u8; 32])),
400 priority: 123,
401 call: bound_large_call.clone(),
402 maybe_periodic: Some((4u64, 20)),
403 origin: signed(0),
404 _phantom: PhantomData::<u64>::default(),
405 }),
406 Some(ScheduledOf::<Test> {
407 maybe_id: Some(blake2_256(&[255u8; 320])),
408 priority: 123,
409 call: Bounded::from_legacy_hash(bound_hashed_call.hash()),
410 maybe_periodic: Some((8u64, 10)),
411 origin: signed(0),
412 _phantom: PhantomData::<u64>::default(),
413 }),
414 None,
415 ],
416 ),
417 (
418 1,
419 vec![
420 Some(ScheduledOf::<Test> {
421 maybe_id: None,
422 priority: 11,
423 call: bound_small_call.clone(),
424 maybe_periodic: None,
425 origin: root(),
426 _phantom: PhantomData::<u64>::default(),
427 }),
428 None,
429 Some(ScheduledOf::<Test> {
430 maybe_id: Some(blake2_256(&[1u8; 32])),
431 priority: 123,
432 call: bound_large_call.clone(),
433 maybe_periodic: Some((4u64, 20)),
434 origin: signed(1),
435 _phantom: PhantomData::<u64>::default(),
436 }),
437 Some(ScheduledOf::<Test> {
438 maybe_id: Some(blake2_256(&[254u8; 320])),
439 priority: 123,
440 call: Bounded::from_legacy_hash(bound_hashed_call.hash()),
441 maybe_periodic: Some((8u64, 10)),
442 origin: signed(1),
443 _phantom: PhantomData::<u64>::default(),
444 }),
445 None,
446 ],
447 ),
448 ];
449 for (outer, (i, j)) in x.iter().zip(expected.iter()).enumerate() {
450 assert_eq!(i.0, j.0);
451 for (inner, (x, y)) in i.1.iter().zip(j.1.iter()).enumerate() {
452 assert_eq!(x, y, "at index: outer {} inner {}", outer, inner);
453 }
454 }
455 assert_eq_uvec!(x, expected);
456
457 assert_eq!(StorageVersion::get::<Scheduler>(), 4);
458 });
459 }
460
461 #[test]
462 #[allow(deprecated)]
463 fn migration_v3_to_v4_too_large_calls_are_ignored() {
464 new_test_ext().execute_with(|| {
465 StorageVersion::new(3).put::<Scheduler>();
467
468 let too_large_call = RuntimeCall::System(frame_system::Call::remark {
469 remark: vec![0; <Test as Config>::Preimages::MAX_LENGTH + 1],
470 });
471
472 let i = 0u64;
473 let k = i.twox_64_concat();
474 let old = vec![Some(ScheduledV3Of::<Test> {
475 maybe_id: None,
476 priority: 1,
477 call: too_large_call.clone().into(),
478 maybe_periodic: None,
479 origin: root(),
480 _phantom: PhantomData::<u64>::default(),
481 })];
482 frame_support::migration::put_storage_value(b"Scheduler", b"Agenda", &k, old);
483
484 let err = v3::MigrateToV4::<Test>::pre_upgrade().unwrap_err();
486 assert_eq!(DispatchError::from("Call is too large."), err);
487 let _w = v3::MigrateToV4::<Test>::on_runtime_upgrade();
489
490 let mut x = Agenda::<Test>::iter().map(|x| (x.0, x.1.into_inner())).collect::<Vec<_>>();
491 x.sort_by_key(|x| x.0);
492 let expected = vec![(0, vec![None])];
494 assert_eq_uvec!(x, expected);
495
496 assert_eq!(StorageVersion::get::<Scheduler>(), 4);
497 });
498 }
499
500 #[test]
501 fn cleanup_agendas_works() {
502 use sp_core::bounded_vec;
503 new_test_ext().execute_with(|| {
504 StorageVersion::new(4).put::<Scheduler>();
505
506 let call = RuntimeCall::System(frame_system::Call::remark { remark: vec![] });
507 let bounded_call = Preimage::bound(call).unwrap();
508 let some = Some(ScheduledOf::<Test> {
509 maybe_id: None,
510 priority: 1,
511 call: bounded_call,
512 maybe_periodic: None,
513 origin: root(),
514 _phantom: Default::default(),
515 });
516
517 let test_data: Vec<(
519 BoundedVec<Option<ScheduledOf<Test>>, <Test as Config>::MaxScheduledPerBlock>,
520 Option<
521 BoundedVec<Option<ScheduledOf<Test>>, <Test as Config>::MaxScheduledPerBlock>,
522 >,
523 )> = vec![
524 (bounded_vec![some.clone()], Some(bounded_vec![some.clone()])),
525 (bounded_vec![None, some.clone()], Some(bounded_vec![None, some.clone()])),
526 (bounded_vec![None, some.clone(), None], Some(bounded_vec![None, some.clone()])),
527 (bounded_vec![some.clone(), None, None], Some(bounded_vec![some.clone()])),
528 (bounded_vec![None, None], None),
529 (bounded_vec![None, None, None], None),
530 (bounded_vec![], None),
531 ];
532
533 for (i, test) in test_data.iter().enumerate() {
535 Agenda::<Test>::insert(i as u64, test.0.clone());
536 }
537
538 let data = v4::CleanupAgendas::<Test>::pre_upgrade().unwrap();
540 let _w = v4::CleanupAgendas::<Test>::on_runtime_upgrade();
541 v4::CleanupAgendas::<Test>::post_upgrade(data).unwrap();
542
543 for (i, test) in test_data.iter().enumerate() {
545 match test.1.clone() {
546 None => assert!(
547 !Agenda::<Test>::contains_key(i as u64),
548 "Agenda {} should be removed",
549 i
550 ),
551 Some(new) => {
552 assert_eq!(Agenda::<Test>::get(i as u64), new, "Agenda wrong {}", i)
553 },
554 }
555 }
556 });
557 }
558
559 fn signed(i: u64) -> OriginCaller {
560 system::RawOrigin::Signed(i).into()
561 }
562}