referrerpolicy=no-referrer-when-downgrade

pallet_preimage/
migration.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Storage migrations for the preimage pallet.
19
20use super::*;
21use alloc::collections::btree_map::BTreeMap;
22use frame_support::{
23	storage_alias,
24	traits::{ConstU32, OnRuntimeUpgrade},
25};
26
27#[cfg(feature = "try-runtime")]
28use frame_support::ensure;
29#[cfg(feature = "try-runtime")]
30use sp_runtime::TryRuntimeError;
31
32/// The log target.
33const TARGET: &'static str = "runtime::preimage::migration::v1";
34
35/// The original data layout of the preimage pallet without a specific version number.
36mod v0 {
37	use super::*;
38
39	#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, Debug)]
40	pub enum OldRequestStatus<AccountId, Balance> {
41		Unrequested(Option<(AccountId, Balance)>),
42		Requested(u32),
43	}
44
45	#[storage_alias]
46	pub type PreimageFor<T: Config> = StorageMap<
47		Pallet<T>,
48		Identity,
49		<T as frame_system::Config>::Hash,
50		BoundedVec<u8, ConstU32<MAX_SIZE>>,
51	>;
52
53	#[storage_alias]
54	pub type StatusFor<T: Config> = StorageMap<
55		Pallet<T>,
56		Identity,
57		<T as frame_system::Config>::Hash,
58		OldRequestStatus<<T as frame_system::Config>::AccountId, BalanceOf<T>>,
59	>;
60
61	/// Returns the number of images or `None` if the storage is corrupted.
62	#[cfg(feature = "try-runtime")]
63	pub fn image_count<T: Config>() -> Option<u32> {
64		let images = v0::PreimageFor::<T>::iter_values().count() as u32;
65		let status = v0::StatusFor::<T>::iter_values().count() as u32;
66
67		if images == status {
68			Some(images)
69		} else {
70			None
71		}
72	}
73}
74
75pub mod v1 {
76	use super::*;
77
78	/// Migration for moving preimage from V0 to V1 storage.
79	///
80	/// Note: This needs to be run with the same hashing algorithm as before
81	/// since it is not re-hashing the preimages.
82	pub struct Migration<T>(core::marker::PhantomData<T>);
83
84	impl<T: Config> OnRuntimeUpgrade for Migration<T> {
85		#[cfg(feature = "try-runtime")]
86		fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
87			ensure!(StorageVersion::get::<Pallet<T>>() == 0, "can only upgrade from version 0");
88
89			let images = v0::image_count::<T>().expect("v0 storage corrupted");
90			log::info!(target: TARGET, "Migrating {} images", &images);
91			Ok((images as u32).encode())
92		}
93
94		fn on_runtime_upgrade() -> Weight {
95			let mut weight = T::DbWeight::get().reads(1);
96			if StorageVersion::get::<Pallet<T>>() != 0 {
97				log::warn!(
98					target: TARGET,
99					"skipping MovePreimagesIntoBuckets: executed on wrong storage version.\
100				Expected version 0"
101				);
102				return weight;
103			}
104
105			let status = v0::StatusFor::<T>::drain().collect::<Vec<_>>();
106			weight.saturating_accrue(T::DbWeight::get().reads(status.len() as u64));
107
108			let preimages = v0::PreimageFor::<T>::drain().collect::<BTreeMap<_, _>>();
109			weight.saturating_accrue(T::DbWeight::get().reads(preimages.len() as u64));
110
111			for (hash, status) in status.into_iter() {
112				let preimage = if let Some(preimage) = preimages.get(&hash) {
113					preimage
114				} else {
115					log::error!(target: TARGET, "preimage not found for hash {:?}", &hash);
116					continue;
117				};
118				let len = preimage.len() as u32;
119				if len > MAX_SIZE {
120					log::error!(
121						target: TARGET,
122						"preimage too large for hash {:?}, len: {}",
123						&hash,
124						len
125					);
126					continue;
127				}
128
129				let status = match status {
130					v0::OldRequestStatus::Unrequested(deposit) => match deposit {
131						Some(deposit) => OldRequestStatus::Unrequested { deposit, len },
132						// `None` depositor becomes system-requested.
133						None => {
134							OldRequestStatus::Requested { deposit: None, count: 1, len: Some(len) }
135						},
136					},
137					v0::OldRequestStatus::Requested(0) => {
138						log::error!(target: TARGET, "preimage has counter of zero: {:?}", hash);
139						continue;
140					},
141					v0::OldRequestStatus::Requested(count) => {
142						OldRequestStatus::Requested { deposit: None, count, len: Some(len) }
143					},
144				};
145				log::trace!(target: TARGET, "Moving preimage {:?} with len {}", hash, len);
146
147				#[allow(deprecated)]
148				crate::StatusFor::<T>::insert(hash, status);
149				crate::PreimageFor::<T>::insert(&(hash, len), preimage);
150
151				weight.saturating_accrue(T::DbWeight::get().writes(2));
152			}
153			StorageVersion::new(1).put::<Pallet<T>>();
154
155			weight.saturating_add(T::DbWeight::get().writes(1))
156		}
157
158		#[cfg(feature = "try-runtime")]
159		fn post_upgrade(state: Vec<u8>) -> DispatchResult {
160			let old_images: u32 =
161				Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed");
162			let new_images = image_count::<T>().expect("V1 storage corrupted");
163
164			if new_images != old_images {
165				log::error!(
166					target: TARGET,
167					"migrated {} images, expected {}",
168					new_images,
169					old_images
170				);
171			}
172			ensure!(StorageVersion::get::<Pallet<T>>() == 1, "must upgrade");
173			Ok(())
174		}
175	}
176
177	/// Returns the number of images or `None` if the storage is corrupted.
178	#[cfg(feature = "try-runtime")]
179	pub fn image_count<T: Config>() -> Option<u32> {
180		// Use iter_values() to ensure that the values are decodable.
181		let images = crate::PreimageFor::<T>::iter_values().count() as u32;
182		#[allow(deprecated)]
183		let status = crate::StatusFor::<T>::iter_values().count() as u32;
184
185		if images == status {
186			Some(images)
187		} else {
188			None
189		}
190	}
191}
192
193#[cfg(test)]
194#[cfg(feature = "try-runtime")]
195mod test {
196	#![allow(deprecated)]
197	use super::*;
198	use crate::mock::{Test as T, *};
199
200	use sp_runtime::bounded_vec;
201
202	#[test]
203	fn migration_works() {
204		new_test_ext().execute_with(|| {
205			assert_eq!(StorageVersion::get::<Pallet<T>>(), 0);
206			// Insert some preimages into the v0 storage:
207
208			// Case 1: Unrequested without deposit
209			let (p, h) = preimage::<T>(128);
210			v0::PreimageFor::<T>::insert(h, p);
211			v0::StatusFor::<T>::insert(h, v0::OldRequestStatus::Unrequested(None));
212			// Case 2: Unrequested with deposit
213			let (p, h) = preimage::<T>(1024);
214			v0::PreimageFor::<T>::insert(h, p);
215			v0::StatusFor::<T>::insert(h, v0::OldRequestStatus::Unrequested(Some((1, 1))));
216			// Case 3: Requested by 0 (invalid)
217			let (p, h) = preimage::<T>(8192);
218			v0::PreimageFor::<T>::insert(h, p);
219			v0::StatusFor::<T>::insert(h, v0::OldRequestStatus::Requested(0));
220			// Case 4: Requested by 10
221			let (p, h) = preimage::<T>(65536);
222			v0::PreimageFor::<T>::insert(h, p);
223			v0::StatusFor::<T>::insert(h, v0::OldRequestStatus::Requested(10));
224
225			assert_eq!(v0::image_count::<T>(), Some(4));
226			assert_eq!(v1::image_count::<T>(), None, "V1 storage should be corrupted");
227
228			let state = v1::Migration::<T>::pre_upgrade().unwrap();
229			let _w = v1::Migration::<T>::on_runtime_upgrade();
230			v1::Migration::<T>::post_upgrade(state).unwrap();
231
232			// V0 and V1 share the same prefix, so `iter_values` still counts the same.
233			assert_eq!(v0::image_count::<T>(), Some(3));
234			assert_eq!(v1::image_count::<T>(), Some(3)); // One gets skipped therefore 3.
235			assert_eq!(StorageVersion::get::<Pallet<T>>(), 1);
236
237			// Case 1: Unrequested without deposit becomes system-requested
238			let (p, h) = preimage::<T>(128);
239			assert_eq!(crate::PreimageFor::<T>::get(&(h, 128)), Some(p));
240			assert_eq!(
241				crate::StatusFor::<T>::get(h),
242				Some(OldRequestStatus::Requested { deposit: None, count: 1, len: Some(128) })
243			);
244			// Case 2: Unrequested with deposit becomes unrequested
245			let (p, h) = preimage::<T>(1024);
246			assert_eq!(crate::PreimageFor::<T>::get(&(h, 1024)), Some(p));
247			assert_eq!(
248				crate::StatusFor::<T>::get(h),
249				Some(OldRequestStatus::Unrequested { deposit: (1, 1), len: 1024 })
250			);
251			// Case 3: Requested by 0 should be skipped
252			let (_, h) = preimage::<T>(8192);
253			assert_eq!(crate::PreimageFor::<T>::get(&(h, 8192)), None);
254			assert_eq!(crate::StatusFor::<T>::get(h), None);
255			// Case 4: Requested by 10 becomes requested by 10
256			let (p, h) = preimage::<T>(65536);
257			assert_eq!(crate::PreimageFor::<T>::get(&(h, 65536)), Some(p));
258			assert_eq!(
259				crate::StatusFor::<T>::get(h),
260				Some(OldRequestStatus::Requested { deposit: None, count: 10, len: Some(65536) })
261			);
262		});
263	}
264
265	/// Returns a preimage with a given size and its hash.
266	fn preimage<T: Config>(
267		len: usize,
268	) -> (BoundedVec<u8, ConstU32<MAX_SIZE>>, <T as frame_system::Config>::Hash) {
269		let p = bounded_vec![1; len];
270		let h = <T as frame_system::Config>::Hashing::hash_of(&p);
271		(p, h)
272	}
273}