referrerpolicy=no-referrer-when-downgrade

polkadot_runtime_parachains/inclusion/
migration.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot 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// Polkadot 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
14pub use v1::MigrateToV1;
15
16pub mod v0 {
17	use crate::inclusion::{Config, Pallet};
18	use bitvec::{order::Lsb0 as BitOrderLsb0, vec::BitVec};
19	use codec::{Decode, Encode};
20	use frame_support::{storage_alias, Twox64Concat};
21	use frame_system::pallet_prelude::BlockNumberFor;
22	use polkadot_primitives::{
23		AvailabilityBitfield, CandidateCommitments, CandidateDescriptorV2 as CandidateDescriptor,
24		CandidateHash, CoreIndex, GroupIndex, Id as ParaId, ValidatorIndex,
25	};
26	use scale_info::TypeInfo;
27
28	#[derive(Encode, Decode, PartialEq, TypeInfo, Clone, Debug)]
29	pub struct CandidatePendingAvailability<H, N> {
30		pub core: CoreIndex,
31		pub hash: CandidateHash,
32		pub descriptor: CandidateDescriptor<H>,
33		pub availability_votes: BitVec<u8, BitOrderLsb0>,
34		pub backers: BitVec<u8, BitOrderLsb0>,
35		pub relay_parent_number: N,
36		pub backed_in_number: N,
37		pub backing_group: GroupIndex,
38	}
39
40	#[derive(Encode, Decode, TypeInfo, Debug, PartialEq)]
41	pub struct AvailabilityBitfieldRecord<N> {
42		pub bitfield: AvailabilityBitfield,
43		pub submitted_at: N,
44	}
45
46	#[storage_alias]
47	pub type PendingAvailability<T: Config> = StorageMap<
48		Pallet<T>,
49		Twox64Concat,
50		ParaId,
51		CandidatePendingAvailability<<T as frame_system::Config>::Hash, BlockNumberFor<T>>,
52	>;
53
54	#[storage_alias]
55	pub type PendingAvailabilityCommitments<T: Config> =
56		StorageMap<Pallet<T>, Twox64Concat, ParaId, CandidateCommitments>;
57
58	#[storage_alias]
59	pub type AvailabilityBitfields<T: Config> = StorageMap<
60		Pallet<T>,
61		Twox64Concat,
62		ValidatorIndex,
63		AvailabilityBitfieldRecord<BlockNumberFor<T>>,
64	>;
65}
66
67mod v1 {
68	use super::v0::{
69		AvailabilityBitfields, PendingAvailability as V0PendingAvailability,
70		PendingAvailabilityCommitments as V0PendingAvailabilityCommitments,
71	};
72	use crate::inclusion::{
73		CandidatePendingAvailability as V1CandidatePendingAvailability, Config, Pallet,
74		PendingAvailability as V1PendingAvailability,
75	};
76	use alloc::{collections::vec_deque::VecDeque, vec::Vec};
77	use frame_support::{traits::UncheckedOnRuntimeUpgrade, weights::Weight};
78	use sp_core::Get;
79
80	#[cfg(feature = "try-runtime")]
81	use codec::{Decode, Encode};
82	#[cfg(feature = "try-runtime")]
83	use frame_support::{
84		ensure,
85		traits::{GetStorageVersion, StorageVersion},
86	};
87
88	pub struct VersionUncheckedMigrateToV1<T>(core::marker::PhantomData<T>);
89
90	impl<T: Config> UncheckedOnRuntimeUpgrade for VersionUncheckedMigrateToV1<T> {
91		#[cfg(feature = "try-runtime")]
92		fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
93			log::trace!(target: crate::inclusion::LOG_TARGET, "Running pre_upgrade() for inclusion MigrateToV1");
94			let candidates_before_upgrade = V0PendingAvailability::<T>::iter().count();
95			let commitments_before_upgrade = V0PendingAvailabilityCommitments::<T>::iter().count();
96
97			if candidates_before_upgrade != commitments_before_upgrade {
98				log::warn!(
99					target: crate::inclusion::LOG_TARGET,
100					"Number of pending candidates differ from the number of pending commitments. {} vs {}",
101					candidates_before_upgrade,
102					commitments_before_upgrade
103				);
104			}
105
106			Ok((candidates_before_upgrade as u32).encode())
107		}
108
109		fn on_runtime_upgrade() -> Weight {
110			let mut weight: Weight = Weight::zero();
111
112			let v0_candidates: Vec<_> = V0PendingAvailability::<T>::drain().collect();
113
114			for (para_id, candidate) in v0_candidates {
115				let commitments = V0PendingAvailabilityCommitments::<T>::take(para_id);
116				// One write for each removal (one candidate and one commitment).
117				weight = weight.saturating_add(T::DbWeight::get().writes(2));
118
119				if let Some(commitments) = commitments {
120					let mut per_para = VecDeque::new();
121					per_para.push_back(V1CandidatePendingAvailability {
122						core: candidate.core,
123						hash: candidate.hash,
124						descriptor: candidate.descriptor,
125						availability_votes: candidate.availability_votes,
126						backers: candidate.backers,
127						relay_parent_number: candidate.relay_parent_number,
128						backed_in_number: candidate.backed_in_number,
129						backing_group: candidate.backing_group,
130						commitments,
131					});
132					V1PendingAvailability::<T>::insert(para_id, per_para);
133
134					weight = weight.saturating_add(T::DbWeight::get().writes(1));
135				}
136			}
137
138			// should've already been drained by the above for loop, but as a sanity check, in case
139			// there are more commitments than candidates.
140			// V0PendingAvailabilityCommitments should not contain too many keys so removing
141			// everything at once should be safe
142			let res = V0PendingAvailabilityCommitments::<T>::clear(u32::MAX, None);
143			weight = weight.saturating_add(
144				T::DbWeight::get().reads_writes(res.loops as u64, res.backend as u64),
145			);
146
147			// AvailabilityBitfields should not contain too many keys so removing everything at once
148			// should be safe.
149			let res = AvailabilityBitfields::<T>::clear(u32::MAX, None);
150			weight = weight.saturating_add(
151				T::DbWeight::get().reads_writes(res.loops as u64, res.backend as u64),
152			);
153
154			weight
155		}
156
157		#[cfg(feature = "try-runtime")]
158		fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
159			log::trace!(target: crate::inclusion::LOG_TARGET, "Running post_upgrade() for inclusion MigrateToV1");
160			ensure!(
161				Pallet::<T>::on_chain_storage_version() >= StorageVersion::new(1),
162				"Storage version should be >= 1 after the migration"
163			);
164
165			let candidates_before_upgrade =
166				u32::decode(&mut &state[..]).expect("Was properly encoded") as usize;
167			let candidates_after_upgrade = V1PendingAvailability::<T>::iter().fold(
168				0usize,
169				|mut acc, (_paraid, para_candidates)| {
170					acc += para_candidates.len();
171					acc
172				},
173			);
174
175			ensure!(
176				candidates_before_upgrade == candidates_after_upgrade,
177				"Number of pending candidates should be the same as the one before the upgrade."
178			);
179			ensure!(
180				V0PendingAvailability::<T>::iter().next() == None,
181				"Pending availability candidates storage v0 should have been removed"
182			);
183			ensure!(
184				V0PendingAvailabilityCommitments::<T>::iter().next() == None,
185				"Pending availability commitments storage should have been removed"
186			);
187			ensure!(
188				AvailabilityBitfields::<T>::iter().next() == None,
189				"Availability bitfields storage should have been removed"
190			);
191
192			Ok(())
193		}
194	}
195
196	/// Migrate to v1 inclusion module storage.
197	/// - merges the `PendingAvailabilityCommitments` into the `CandidatePendingAvailability`
198	///   storage
199	/// - removes the `AvailabilityBitfields` storage, which was never read.
200	pub type MigrateToV1<T> = frame_support::migrations::VersionedMigration<
201		0,
202		1,
203		VersionUncheckedMigrateToV1<T>,
204		Pallet<T>,
205		<T as frame_system::Config>::DbWeight,
206	>;
207}
208
209#[cfg(test)]
210mod tests {
211	use super::{v1::VersionUncheckedMigrateToV1, *};
212	use crate::{
213		inclusion::{
214			CandidatePendingAvailability as V1CandidatePendingAvailability,
215			PendingAvailability as V1PendingAvailability, *,
216		},
217		mock::{new_test_ext, MockGenesisConfig, Test},
218	};
219	use frame_support::traits::UncheckedOnRuntimeUpgrade;
220	use polkadot_primitives::{AvailabilityBitfield, Id as ParaId};
221	use polkadot_primitives_test_helpers::{
222		dummy_candidate_commitments, dummy_candidate_descriptor_v2, dummy_hash,
223	};
224
225	#[test]
226	fn migrate_to_v1() {
227		new_test_ext(MockGenesisConfig::default()).execute_with(|| {
228			// No data to migrate.
229			assert_eq!(
230				<VersionUncheckedMigrateToV1<Test> as UncheckedOnRuntimeUpgrade>::on_runtime_upgrade(),
231				Weight::zero()
232			);
233			assert!(V1PendingAvailability::<Test>::iter().next().is_none());
234
235			let mut expected = vec![];
236
237			for i in 1..5 {
238				let descriptor = dummy_candidate_descriptor_v2(dummy_hash());
239				v0::PendingAvailability::<Test>::insert(
240					ParaId::from(i),
241					v0::CandidatePendingAvailability {
242						core: CoreIndex(i),
243						descriptor: descriptor.clone(),
244						relay_parent_number: i,
245						hash: CandidateHash(dummy_hash()),
246						availability_votes: Default::default(),
247						backed_in_number: i,
248						backers: Default::default(),
249						backing_group: GroupIndex(i),
250					},
251				);
252				v0::PendingAvailabilityCommitments::<Test>::insert(
253					ParaId::from(i),
254					dummy_candidate_commitments(HeadData(vec![i as _])),
255				);
256
257				v0::AvailabilityBitfields::<Test>::insert(
258					ValidatorIndex(i),
259					v0::AvailabilityBitfieldRecord {
260						bitfield: AvailabilityBitfield(Default::default()),
261						submitted_at: i,
262					},
263				);
264
265				expected.push((
266					ParaId::from(i),
267					[V1CandidatePendingAvailability {
268						core: CoreIndex(i),
269						descriptor,
270						relay_parent_number: i,
271						hash: CandidateHash(dummy_hash()),
272						availability_votes: Default::default(),
273						backed_in_number: i,
274						backers: Default::default(),
275						backing_group: GroupIndex(i),
276						commitments: dummy_candidate_commitments(HeadData(vec![i as _])),
277					}]
278					.into_iter()
279					.collect::<VecDeque<_>>(),
280				));
281			}
282			// add some wrong data also, candidates without commitments or commitments without
283			// candidates.
284			v0::PendingAvailability::<Test>::insert(
285				ParaId::from(6),
286				v0::CandidatePendingAvailability {
287					core: CoreIndex(6),
288					descriptor: dummy_candidate_descriptor_v2(dummy_hash()),
289					relay_parent_number: 6,
290					hash: CandidateHash(dummy_hash()),
291					availability_votes: Default::default(),
292					backed_in_number: 6,
293					backers: Default::default(),
294					backing_group: GroupIndex(6),
295				},
296			);
297			v0::PendingAvailabilityCommitments::<Test>::insert(
298				ParaId::from(7),
299				dummy_candidate_commitments(HeadData(vec![7 as _])),
300			);
301
302			// For tests, db weight is zero.
303			assert_eq!(
304				<VersionUncheckedMigrateToV1<Test> as UncheckedOnRuntimeUpgrade>::on_runtime_upgrade(),
305				Weight::zero()
306			);
307
308			assert_eq!(v0::PendingAvailabilityCommitments::<Test>::iter().next(), None);
309			assert_eq!(v0::PendingAvailability::<Test>::iter().next(), None);
310			assert_eq!(v0::AvailabilityBitfields::<Test>::iter().next(), None);
311
312			let mut actual = V1PendingAvailability::<Test>::iter().collect::<Vec<_>>();
313			actual.sort_by(|(id1, _), (id2, _)| id1.cmp(id2));
314			expected.sort_by(|(id1, _), (id2, _)| id1.cmp(id2));
315
316			assert_eq!(actual, expected);
317		});
318	}
319}