1
// Copyright Moonsong Labs
2
// This file is part of Moonkit.
3

            
4
// Moonkit 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
// Moonkit 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 Moonkit.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! # Migration Pallet
18

            
19
#![allow(non_camel_case_types)]
20
#![cfg_attr(not(feature = "std"), no_std)]
21

            
22
#[cfg(test)]
23
mod mock;
24
#[cfg(test)]
25
mod tests;
26

            
27
use frame_support::{pallet, weights::Weight};
28

            
29
pub use pallet::*;
30

            
31
#[cfg(feature = "try-runtime")]
32
extern crate alloc;
33
#[cfg(feature = "try-runtime")]
34
use alloc::{
35
	format,
36
	string::{String, ToString},
37
};
38

            
39
#[cfg(test)]
40
#[macro_use]
41
extern crate environmental;
42

            
43
use sp_std::prelude::*;
44

            
45
/// A Migration that must happen on-chain upon a runtime-upgrade
46
pub trait Migration {
47
	/// A human-readable name for this migration. Also used as storage key.
48
	fn friendly_name(&self) -> &str;
49

            
50
	/// Perform the required migration and return the weight consumed.
51
	///
52
	/// Currently there is no way to migrate across blocks, so this method must (1) perform its full
53
	/// migration and (2) not produce a block that has gone over-weight. Not meeting these strict
54
	/// constraints will lead to a bricked chain upon a runtime upgrade because the parachain will
55
	/// not be able to produce a block that the relay chain will accept.
56
	fn migrate(&self, available_weight: Weight) -> Weight;
57

            
58
	/// Run a standard pre-runtime test. This works the same way as in a normal runtime upgrade.
59
	#[cfg(feature = "try-runtime")]
60
	fn pre_upgrade(&self) -> Result<Vec<u8>, sp_runtime::DispatchError> {
61
		Ok(Vec::new())
62
	}
63

            
64
	/// Run a standard post-runtime test. This works the same way as in a normal runtime upgrade.
65
	#[cfg(feature = "try-runtime")]
66
	fn post_upgrade(&self, _state: Vec<u8>) -> Result<(), sp_runtime::DispatchError> {
67
		Ok(())
68
	}
69
}
70

            
71
// The migration trait
72
pub trait GetMigrations {
73
	// Migration list Getter
74
	fn get_migrations() -> Vec<Box<dyn Migration>>;
75
}
76

            
77
#[impl_trait_for_tuples::impl_for_tuples(30)]
78
impl GetMigrations for Tuple {
79
3
	fn get_migrations() -> Vec<Box<dyn Migration>> {
80
3
		let mut migrations = Vec::new();
81
3

            
82
3
		for_tuples!( #( migrations.extend(Tuple::get_migrations()); )* );
83
3

            
84
3
		migrations
85
3
	}
86
}
87

            
88
#[pallet]
89
pub mod pallet {
90
	use super::*;
91
	use frame_support::pallet_prelude::*;
92
	use frame_system::pallet_prelude::*;
93
	use xcm_primitives::PauseXcmExecution;
94

            
95
	/// Pallet for migrations
96
1698
	#[pallet::pallet]
97
	#[pallet::without_storage_info]
98
	pub struct Pallet<T>(PhantomData<T>);
99

            
100
	/// Configuration trait of this pallet.
101
	#[pallet::config]
102
	pub trait Config: frame_system::Config {
103
		/// Overarching event type
104
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
105
		/// The list of migrations that will be performed
106
		type MigrationsList: GetMigrations;
107

            
108
		/// Handler to suspend and resume XCM execution
109
		type XcmExecutionManager: PauseXcmExecution;
110
	}
111

            
112
	#[pallet::event]
113
	#[pallet::generate_deposit(pub(crate) fn deposit_event)]
114
	pub enum Event<T: Config> {
115
		/// Runtime upgrade started
116
		RuntimeUpgradeStarted(),
117
		/// Runtime upgrade completed
118
		RuntimeUpgradeCompleted { weight: Weight },
119
		/// Migration started
120
		MigrationStarted { migration_name: Vec<u8> },
121
		/// Migration completed
122
		MigrationCompleted {
123
			migration_name: Vec<u8>,
124
			consumed_weight: Weight,
125
		},
126
		/// XCM execution suspension failed with inner error
127
		FailedToSuspendIdleXcmExecution { error: DispatchError },
128
		/// XCM execution resume failed with inner error
129
		FailedToResumeIdleXcmExecution { error: DispatchError },
130
	}
131

            
132
554367
	#[pallet::hooks]
133
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
134
		/// on_runtime_upgrade is expected to be called exactly once after a runtime upgrade.
135
		/// We use this as a chance to flag that we are now in upgrade-mode and begin our
136
		/// migrations.
137
		fn on_runtime_upgrade() -> Weight {
138
			log::warn!("Performing on_runtime_upgrade");
139

            
140
			// Store the fact that we should pause xcm execution for this block
141
			ShouldPauseXcm::<T>::put(true);
142

            
143
			let mut weight = Weight::zero();
144
			// TODO: derive a suitable value here, which is probably something < max_block
145
			let available_weight: Weight = T::BlockWeights::get().max_block;
146

            
147
			// start by flagging that we are not fully upgraded
148
			<FullyUpgraded<T>>::put(false);
149
			weight = weight.saturating_add(T::DbWeight::get().writes(1));
150
			Self::deposit_event(Event::RuntimeUpgradeStarted());
151

            
152
			weight = weight.saturating_add(perform_runtime_upgrades::<T>(
153
				available_weight.saturating_sub(weight),
154
			));
155

            
156
			if !<FullyUpgraded<T>>::get() {
157
				log::error!(
158
					"migrations weren't completed in on_runtime_upgrade(), but we're not
159
				configured for multi-block migrations; state is potentially inconsistent!"
160
				);
161
			}
162

            
163
			log::info!("Migrations consumed weight: {}", weight);
164

            
165
			// Consume all block weight to ensure no user transactions inclusion.
166
			T::BlockWeights::get().max_block
167
		}
168

            
169
194763
		fn on_initialize(_: BlockNumberFor<T>) -> Weight {
170
194763
			if ShouldPauseXcm::<T>::get() {
171
				// Suspend XCM execution
172
				if let Err(error) = T::XcmExecutionManager::suspend_xcm_execution() {
173
					<Pallet<T>>::deposit_event(Event::FailedToSuspendIdleXcmExecution { error });
174
				}
175
				// Account on_finalize write
176
				T::DbWeight::get().reads_writes(1, 1)
177
			} else {
178
194763
				T::DbWeight::get().reads(1)
179
			}
180
194763
		}
181

            
182
194763
		fn on_finalize(_: BlockNumberFor<T>) {
183
194763
			if ShouldPauseXcm::<T>::get() {
184
				// Resume XCM execution
185
				if let Err(error) = T::XcmExecutionManager::resume_xcm_execution() {
186
					<Pallet<T>>::deposit_event(Event::FailedToResumeIdleXcmExecution { error });
187
				}
188
				ShouldPauseXcm::<T>::put(false);
189
194763
			}
190
194763
		}
191

            
192
		#[cfg(feature = "try-runtime")]
193
		fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::DispatchError> {
194
			use sp_std::collections::btree_map::BTreeMap;
195
			let mut state_map: BTreeMap<String, bool> = BTreeMap::new();
196
			let mut migration_states_map: BTreeMap<String, Vec<u8>> = BTreeMap::new();
197

            
198
			for migration in &T::MigrationsList::get_migrations() {
199
				let migration_name = migration.friendly_name();
200
				let migration_name_as_bytes = migration_name.as_bytes();
201

            
202
				let migration_done = <MigrationState<T>>::get(migration_name_as_bytes);
203
				if migration_done {
204
					continue;
205
				}
206
				log::debug!(
207
					target: "pallet-migrations",
208
					"invoking pre_upgrade() on migration {}", migration_name
209
				);
210

            
211
				// dump the migration name to state_map so post_upgrade will know which
212
				// migrations were performed (as opposed to skipped)
213
				state_map.insert(migration_name.to_string(), true);
214
				let state = migration
215
					.pre_upgrade()
216
					.expect(&format!("migration {} pre_upgrade()", migration_name));
217
				migration_states_map.insert(migration_name.to_string(), state);
218
			}
219
			Ok((state_map, migration_states_map).encode())
220
		}
221

            
222
		/// Run a standard post-runtime test. This works the same way as in a normal runtime upgrade.
223
		#[cfg(feature = "try-runtime")]
224
		fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::DispatchError> {
225
			use sp_std::collections::btree_map::BTreeMap;
226

            
227
			let (state_map, migration_states_map): (
228
				BTreeMap<String, bool>,
229
				BTreeMap<String, Vec<u8>>,
230
			) = Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed");
231

            
232
			// TODO: my desire to DRY all the things feels like this code is very repetitive...
233
			let mut failed = false;
234
			for migration in &T::MigrationsList::get_migrations() {
235
				let migration_name = migration.friendly_name();
236

            
237
				// we can't query MigrationState because on_runtime_upgrade() would have
238
				// unconditionally set it to true, so we read a hint from temp storage which was
239
				// left for us by pre_upgrade()
240

            
241
				match state_map.get(&migration_name.to_string()) {
242
					Some(value) => assert!(value.clone(), "our dummy value might as well be true"),
243
					None => continue,
244
				}
245

            
246
				log::debug!(
247
					target: "pallet-migrations",
248
					"invoking post_upgrade() on migration {}", migration_name
249
				);
250

            
251
				if let Some(state) = migration_states_map.get(&migration_name.to_string()) {
252
					let result = migration.post_upgrade(state.clone());
253
					match result {
254
						Ok(()) => {
255
							log::info!("migration {} post_upgrade() => Ok()", migration_name);
256
						}
257
						Err(msg) => {
258
							log::error!(
259
								"migration {} post_upgrade() => Err({:?})",
260
								migration_name,
261
								msg
262
							);
263
							failed = true;
264
						}
265
					}
266
				}
267
			}
268

            
269
			if failed {
270
				Err(sp_runtime::DispatchError::Other(
271
					"One or more post_upgrade tests failed; see output above.",
272
				))
273
			} else {
274
				Ok(())
275
			}
276
		}
277
	}
278

            
279
	#[pallet::storage]
280
	#[pallet::getter(fn is_fully_upgraded)]
281
	/// True if all required migrations have completed
282
	type FullyUpgraded<T: Config> = StorageValue<_, bool, ValueQuery>;
283

            
284
6
	#[pallet::storage]
285
	#[pallet::getter(fn migration_state)]
286
	/// MigrationState tracks the progress of a migration.
287
	/// Maps name (Vec<u8>) -> whether or not migration has been completed (bool)
288
	pub(crate) type MigrationState<T: Config> =
289
		StorageMap<_, Twox64Concat, Vec<u8>, bool, ValueQuery>;
290

            
291
779052
	#[pallet::storage]
292
	#[pallet::getter(fn should_pause_xcm)]
293
	/// Temporary value that is set to true at the beginning of the block during which the execution
294
	/// of xcm messages must be paused.
295
	type ShouldPauseXcm<T: Config> = StorageValue<_, bool, ValueQuery>;
296

            
297
	#[pallet::error]
298
	pub enum Error<T> {
299
		/// Missing preimage in original democracy storage
300
		PreimageMissing,
301
		/// Provided upper bound is too low.
302
		WrongUpperBound,
303
		/// Preimage is larger than the new max size.
304
		PreimageIsTooBig,
305
		/// Preimage already exists in the new storage.
306
		PreimageAlreadyExists,
307
	}
308

            
309
	#[pallet::genesis_config]
310
	#[derive(frame_support::DefaultNoBound)]
311
	pub struct GenesisConfig<T: Config> {
312
		#[serde(skip)]
313
		pub _config: sp_std::marker::PhantomData<T>,
314
	}
315

            
316
	#[pallet::genesis_build]
317
	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
318
3
		fn build(&self) {
319
			// When building a new genesis, all listed migrations should be considered as already
320
			// applied, they only make sense for networks that had been launched in the past.
321
6
			for migration_name in T::MigrationsList::get_migrations()
322
3
				.into_iter()
323
6
				.map(|migration| migration.friendly_name().as_bytes().to_vec())
324
6
			{
325
6
				<MigrationState<T>>::insert(migration_name, true);
326
6
			}
327
3
		}
328
	}
329

            
330
	fn perform_runtime_upgrades<T: Config>(available_weight: Weight) -> Weight {
331
		let mut weight = Weight::zero();
332

            
333
		for migration in &T::MigrationsList::get_migrations() {
334
			let migration_name = migration.friendly_name();
335
			let migration_name_as_bytes = migration_name.as_bytes();
336
			log::debug!( target: "pallet-migrations", "evaluating migration {}", migration_name);
337

            
338
			let migration_done = <MigrationState<T>>::get(migration_name_as_bytes);
339

            
340
			if !migration_done {
341
				<Pallet<T>>::deposit_event(Event::MigrationStarted {
342
					migration_name: migration_name_as_bytes.into(),
343
				});
344

            
345
				// when we go overweight, leave a warning... there's nothing we can really do about
346
				// this scenario other than hope that the block is actually accepted.
347
				let available_for_step = if available_weight.ref_time() > weight.ref_time() {
348
					available_weight - weight
349
				} else {
350
					log::error!(
351
						"previous migration went overweight;
352
						ignoring and providing migration {} 0 weight.",
353
						migration_name,
354
					);
355

            
356
					Weight::zero()
357
				};
358

            
359
				log::info!( target: "pallet-migrations",
360
					"performing migration {}, available weight: {}",
361
					migration_name,
362
					available_for_step
363
				);
364

            
365
				let consumed_weight = migration.migrate(available_for_step);
366
				<Pallet<T>>::deposit_event(Event::MigrationCompleted {
367
					migration_name: migration_name_as_bytes.into(),
368
					consumed_weight: consumed_weight,
369
				});
370
				<MigrationState<T>>::insert(migration_name_as_bytes, true);
371

            
372
				weight = weight.saturating_add(consumed_weight);
373
				if weight.ref_time() > available_weight.ref_time() {
374
					log::error!(
375
						"Migration {} consumed more weight than it was given! ({} > {})",
376
						migration_name,
377
						consumed_weight,
378
						available_for_step
379
					);
380
				}
381
			}
382
		}
383

            
384
		<FullyUpgraded<T>>::put(true);
385
		weight = weight.saturating_add(T::DbWeight::get().writes(1));
386
		<Pallet<T>>::deposit_event(Event::RuntimeUpgradeCompleted { weight });
387

            
388
		weight
389
	}
390
}