referrerpolicy=no-referrer-when-downgrade

pallet_im_online/
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 im-online pallet.
19
20use super::*;
21use alloc::vec::Vec;
22use frame_support::{storage_alias, traits::OnRuntimeUpgrade};
23
24#[cfg(feature = "try-runtime")]
25use frame_support::ensure;
26#[cfg(feature = "try-runtime")]
27use sp_runtime::TryRuntimeError;
28
29/// The log target.
30const TARGET: &str = "runtime::im-online::migration::v1";
31
32/// The original data layout of the im-online pallet (`ReceivedHeartbeats` storage item).
33mod v0 {
34	use super::*;
35	use frame_support::traits::WrapperOpaque;
36
37	#[derive(Encode, Decode, Default)]
38	pub(super) struct BoundedOpaqueNetworkState {
39		/// PeerId of the local node in SCALE encoded.
40		pub peer_id: Vec<u8>,
41		/// List of addresses the node knows it can be reached as.
42		pub external_addresses: Vec<Vec<u8>>,
43	}
44
45	#[storage_alias]
46	pub(super) type ReceivedHeartbeats<T: Config> = StorageDoubleMap<
47		Pallet<T>,
48		Twox64Concat,
49		SessionIndex,
50		Twox64Concat,
51		AuthIndex,
52		WrapperOpaque<BoundedOpaqueNetworkState>,
53	>;
54}
55
56pub mod v1 {
57	use super::*;
58
59	/// Simple migration that replaces `ReceivedHeartbeats` values with `true`.
60	pub struct Migration<T>(core::marker::PhantomData<T>);
61
62	impl<T: Config> OnRuntimeUpgrade for Migration<T> {
63		#[cfg(feature = "try-runtime")]
64		fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
65			let count = v0::ReceivedHeartbeats::<T>::iter().count();
66			log::info!(target: TARGET, "Migrating {} received heartbeats", count);
67
68			Ok((count as u32).encode())
69		}
70
71		fn on_runtime_upgrade() -> Weight {
72			let mut weight = T::DbWeight::get().reads(1);
73			if StorageVersion::get::<Pallet<T>>() != 0 {
74				log::warn!(
75					target: TARGET,
76					"Skipping migration because in-code storage version is not 0"
77				);
78				return weight
79			}
80
81			let heartbeats = v0::ReceivedHeartbeats::<T>::drain().collect::<Vec<_>>();
82
83			weight.saturating_accrue(T::DbWeight::get().reads(heartbeats.len() as u64));
84			weight.saturating_accrue(T::DbWeight::get().writes(heartbeats.len() as u64));
85
86			for (session_index, auth_index, _) in heartbeats {
87				log::trace!(
88					target: TARGET,
89					"Migrated received heartbeat for {:?}...",
90					(session_index, auth_index)
91				);
92				crate::ReceivedHeartbeats::<T>::insert(session_index, auth_index, true);
93			}
94
95			StorageVersion::new(1).put::<Pallet<T>>();
96			weight.saturating_add(T::DbWeight::get().writes(1))
97		}
98
99		#[cfg(feature = "try-runtime")]
100		fn post_upgrade(state: Vec<u8>) -> DispatchResult {
101			let old_received_heartbeats: u32 =
102				Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed");
103			let new_received_heartbeats = crate::ReceivedHeartbeats::<T>::iter().count();
104
105			if new_received_heartbeats != old_received_heartbeats as usize {
106				log::error!(
107					target: TARGET,
108					"migrated {} received heartbeats, expected {}",
109					new_received_heartbeats,
110					old_received_heartbeats
111				);
112			}
113			ensure!(StorageVersion::get::<Pallet<T>>() >= 1, "must upgrade");
114
115			Ok(())
116		}
117	}
118}
119
120/// Clears the pallet's offchain storage.
121///
122/// Must be put in `OffchainWorkerApi::offchain_worker` after
123/// the pallet was removed.
124pub fn clear_offchain_storage(validator_set_size: u32) {
125	(0..validator_set_size).for_each(|idx| {
126		let key = {
127			let mut key = DB_PREFIX.to_vec();
128			key.extend(idx.encode());
129			key
130		};
131		sp_runtime::offchain::storage::StorageValueRef::persistent(&key).clear();
132	});
133}
134
135#[cfg(all(feature = "try-runtime", test))]
136mod test {
137	use super::*;
138	use crate::mock::{new_test_ext, Runtime as T};
139	use frame_support::traits::WrapperOpaque;
140
141	#[test]
142	fn migration_works() {
143		new_test_ext().execute_with(|| {
144			assert_eq!(StorageVersion::get::<Pallet<T>>(), 0);
145
146			// Insert some received heartbeats into the v0 storage:
147			let current_session = <T as pallet::Config>::ValidatorSet::session_index();
148			v0::ReceivedHeartbeats::<T>::insert(
149				&current_session,
150				0,
151				WrapperOpaque(v0::BoundedOpaqueNetworkState::default()),
152			);
153			v0::ReceivedHeartbeats::<T>::insert(
154				&current_session,
155				1,
156				WrapperOpaque(v0::BoundedOpaqueNetworkState::default()),
157			);
158
159			// Check that the v0 storage is populated
160			assert_eq!(v0::ReceivedHeartbeats::<T>::iter().count(), 2);
161			assert_eq!(crate::ReceivedHeartbeats::<T>::iter().count(), 0, "V1 storage corrupted");
162
163			// Perform the migration
164			let state = v1::Migration::<T>::pre_upgrade().unwrap();
165			let _w = v1::Migration::<T>::on_runtime_upgrade();
166			v1::Migration::<T>::post_upgrade(state).unwrap();
167
168			// Check that the v1 storage is populated and v0 storage is empty
169			assert_eq!(v0::ReceivedHeartbeats::<T>::iter().count(), 0);
170			assert_eq!(crate::ReceivedHeartbeats::<T>::iter().count(), 2);
171			assert!(crate::ReceivedHeartbeats::<T>::contains_key(&current_session, 0));
172			assert_eq!(Some(true), crate::ReceivedHeartbeats::<T>::get(&current_session, 1));
173
174			assert_eq!(StorageVersion::get::<Pallet<T>>(), 1);
175		});
176	}
177}