@@ -198,6 +198,119 @@ pub mod v3 {
198
198
}
199
199
}
200
200
201
+ mod v4 {
202
+ use super :: * ;
203
+ use frame_support:: pallet_prelude:: * ;
204
+
205
+ /// This migration cleans up empty agendas of the V4 scheduler.
206
+ ///
207
+ /// This should be run on a scheduler that does not have
208
+ /// <https://github.com/paritytech/substrate/pull/12989> since it piles up `None`-only agendas. This does not modify the pallet version.
209
+ pub struct CleanupAgendas < T > ( sp_std:: marker:: PhantomData < T > ) ;
210
+
211
+ impl < T : Config > OnRuntimeUpgrade for CleanupAgendas < T > {
212
+ #[ cfg( feature = "try-runtime" ) ]
213
+ fn pre_upgrade ( ) -> Result < Vec < u8 > , & ' static str > {
214
+ assert_eq ! (
215
+ StorageVersion :: get:: <Pallet <T >>( ) ,
216
+ 4 ,
217
+ "Can only cleanup agendas of the V4 scheduler"
218
+ ) ;
219
+
220
+ let agendas = Agenda :: < T > :: iter_keys ( ) . count ( ) ;
221
+ let non_empty_agendas =
222
+ Agenda :: < T > :: iter_values ( ) . filter ( |a| a. iter ( ) . any ( |s| s. is_some ( ) ) ) . count ( ) ;
223
+ log:: info!(
224
+ target: TARGET ,
225
+ "There are {} total and {} non-empty agendas" ,
226
+ agendas,
227
+ non_empty_agendas
228
+ ) ;
229
+
230
+ Ok ( ( agendas as u32 , non_empty_agendas as u32 ) . encode ( ) )
231
+ }
232
+
233
+ fn on_runtime_upgrade ( ) -> Weight {
234
+ let version = StorageVersion :: get :: < Pallet < T > > ( ) ;
235
+ if version != 4 {
236
+ log:: warn!( target: TARGET , "Skipping CleanupAgendas migration since it was run on the wrong version: {:?} != 4" , version) ;
237
+ return T :: DbWeight :: get ( ) . reads ( 1 )
238
+ }
239
+
240
+ let keys = Agenda :: < T > :: iter_keys ( ) . collect :: < Vec < _ > > ( ) ;
241
+ let mut writes = 0 ;
242
+ for k in & keys {
243
+ let mut schedules = Agenda :: < T > :: get ( k) ;
244
+ let all_schedules = schedules. len ( ) ;
245
+ let suffix_none_schedules =
246
+ schedules. iter ( ) . rev ( ) . take_while ( |s| s. is_none ( ) ) . count ( ) ;
247
+
248
+ match all_schedules. checked_sub ( suffix_none_schedules) {
249
+ Some ( 0 ) => {
250
+ log:: info!(
251
+ "Deleting None-only agenda {:?} with {} entries" ,
252
+ k,
253
+ all_schedules
254
+ ) ;
255
+ Agenda :: < T > :: remove ( k) ;
256
+ writes. saturating_inc ( ) ;
257
+ } ,
258
+ Some ( ne) if ne > 0 => {
259
+ log:: info!(
260
+ "Removing {} schedules of {} from agenda {:?}, now {:?}" ,
261
+ suffix_none_schedules,
262
+ all_schedules,
263
+ ne,
264
+ k
265
+ ) ;
266
+ schedules. truncate ( ne) ;
267
+ Agenda :: < T > :: insert ( k, schedules) ;
268
+ writes. saturating_inc ( ) ;
269
+ } ,
270
+ Some ( _) => {
271
+ frame_support:: defensive!(
272
+ // Bad but let's not panic.
273
+ "Cannot have more None suffix schedules that schedules in total"
274
+ ) ;
275
+ } ,
276
+ None => {
277
+ log:: info!( "Agenda {:?} does not have any None suffix schedules" , k) ;
278
+ } ,
279
+ }
280
+ }
281
+
282
+ // We don't modify the pallet version.
283
+
284
+ T :: DbWeight :: get ( ) . reads_writes ( 1 + keys. len ( ) . saturating_mul ( 2 ) as u64 , writes)
285
+ }
286
+
287
+ #[ cfg( feature = "try-runtime" ) ]
288
+ fn post_upgrade ( state : Vec < u8 > ) -> Result < ( ) , & ' static str > {
289
+ assert_eq ! ( StorageVersion :: get:: <Pallet <T >>( ) , 4 , "Version must not change" ) ;
290
+
291
+ let ( old_agendas, non_empty_agendas) : ( u32 , u32 ) =
292
+ Decode :: decode ( & mut state. as_ref ( ) ) . expect ( "Must decode pre_upgrade state" ) ;
293
+ let new_agendas = Agenda :: < T > :: iter_keys ( ) . count ( ) as u32 ;
294
+
295
+ match old_agendas. checked_sub ( new_agendas) {
296
+ Some ( 0 ) => log:: warn!(
297
+ target: TARGET ,
298
+ "Did not clean up any agendas. v4::CleanupAgendas can be removed."
299
+ ) ,
300
+ Some ( n) =>
301
+ log:: info!( target: TARGET , "Cleaned up {} agendas, now {}" , n, new_agendas) ,
302
+ None => unreachable ! (
303
+ "Number of agendas cannot increase, old {} new {}" ,
304
+ old_agendas, new_agendas
305
+ ) ,
306
+ }
307
+ assert_eq ! ( new_agendas, non_empty_agendas, "Expected to keep all non-empty agendas" ) ;
308
+
309
+ Ok ( ( ) )
310
+ }
311
+ }
312
+ }
313
+
201
314
#[ cfg( test) ]
202
315
#[ cfg( feature = "try-runtime" ) ]
203
316
mod test {
@@ -396,6 +509,64 @@ mod test {
396
509
} ) ;
397
510
}
398
511
512
+ #[ test]
513
+ fn cleanup_agendas_works ( ) {
514
+ use sp_core:: bounded_vec;
515
+ new_test_ext ( ) . execute_with ( || {
516
+ StorageVersion :: new ( 4 ) . put :: < Scheduler > ( ) ;
517
+
518
+ let call = RuntimeCall :: System ( frame_system:: Call :: remark { remark : vec ! [ ] } ) ;
519
+ let bounded_call = Preimage :: bound ( call) . unwrap ( ) ;
520
+ let some = Some ( ScheduledOf :: < Test > {
521
+ maybe_id : None ,
522
+ priority : 1 ,
523
+ call : bounded_call,
524
+ maybe_periodic : None ,
525
+ origin : root ( ) ,
526
+ _phantom : Default :: default ( ) ,
527
+ } ) ;
528
+
529
+ // Put some empty, and some non-empty agendas in there.
530
+ let test_data: Vec < (
531
+ BoundedVec < Option < ScheduledOf < Test > > , <Test as Config >:: MaxScheduledPerBlock > ,
532
+ Option <
533
+ BoundedVec < Option < ScheduledOf < Test > > , <Test as Config >:: MaxScheduledPerBlock > ,
534
+ > ,
535
+ ) > = vec ! [
536
+ ( bounded_vec![ some. clone( ) ] , Some ( bounded_vec![ some. clone( ) ] ) ) ,
537
+ ( bounded_vec![ None , some. clone( ) ] , Some ( bounded_vec![ None , some. clone( ) ] ) ) ,
538
+ ( bounded_vec![ None , some. clone( ) , None ] , Some ( bounded_vec![ None , some. clone( ) ] ) ) ,
539
+ ( bounded_vec![ some. clone( ) , None , None ] , Some ( bounded_vec![ some. clone( ) ] ) ) ,
540
+ ( bounded_vec![ None , None ] , None ) ,
541
+ ( bounded_vec![ None , None , None ] , None ) ,
542
+ ( bounded_vec![ ] , None ) ,
543
+ ] ;
544
+
545
+ // Insert all the agendas.
546
+ for ( i, test) in test_data. iter ( ) . enumerate ( ) {
547
+ Agenda :: < Test > :: insert ( i as u64 , test. 0 . clone ( ) ) ;
548
+ }
549
+
550
+ // Run the migration.
551
+ let data = v4:: CleanupAgendas :: < Test > :: pre_upgrade ( ) . unwrap ( ) ;
552
+ let _w = v4:: CleanupAgendas :: < Test > :: on_runtime_upgrade ( ) ;
553
+ v4:: CleanupAgendas :: < Test > :: post_upgrade ( data) . unwrap ( ) ;
554
+
555
+ // Check that the post-state is correct.
556
+ for ( i, test) in test_data. iter ( ) . enumerate ( ) {
557
+ match test. 1 . clone ( ) {
558
+ None => assert ! (
559
+ !Agenda :: <Test >:: contains_key( i as u64 ) ,
560
+ "Agenda {} should be removed" ,
561
+ i
562
+ ) ,
563
+ Some ( new) =>
564
+ assert_eq ! ( Agenda :: <Test >:: get( i as u64 ) , new, "Agenda wrong {}" , i) ,
565
+ }
566
+ }
567
+ } ) ;
568
+ }
569
+
399
570
fn signed ( i : u64 ) -> OriginCaller {
400
571
system:: RawOrigin :: Signed ( i) . into ( )
401
572
}
0 commit comments