referrerpolicy=no-referrer-when-downgrade

polkadot_runtime_parachains/on_demand/
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
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! A module that is responsible for migration of storage.
18use super::*;
19use frame_support::{
20	migrations::VersionedMigration, pallet_prelude::ValueQuery, storage_alias,
21	traits::UncheckedOnRuntimeUpgrade, weights::Weight,
22};
23
24mod v0 {
25	use super::*;
26	use alloc::collections::vec_deque::VecDeque;
27
28	#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Clone)]
29	pub(super) struct EnqueuedOrder {
30		pub para_id: ParaId,
31	}
32
33	/// Keeps track of the multiplier used to calculate the current spot price for the on demand
34	/// assigner.
35	/// NOTE: Ignoring the `OnEmpty` field for the migration.
36	#[storage_alias]
37	pub(super) type SpotTraffic<T: Config> = StorageValue<Pallet<T>, FixedU128, ValueQuery>;
38
39	/// The order storage entry. Uses a VecDeque to be able to push to the front of the
40	/// queue from the scheduler on session boundaries.
41	/// NOTE: Ignoring the `OnEmpty` field for the migration.
42	#[storage_alias]
43	pub(super) type OnDemandQueue<T: Config> =
44		StorageValue<Pallet<T>, VecDeque<EnqueuedOrder>, ValueQuery>;
45}
46
47mod v1 {
48	use super::*;
49
50	use crate::on_demand::LOG_TARGET;
51
52	/// Migration to V1
53	pub struct UncheckedMigrateToV1<T>(core::marker::PhantomData<T>);
54	impl<T: Config> UncheckedOnRuntimeUpgrade for UncheckedMigrateToV1<T> {
55		fn on_runtime_upgrade() -> Weight {
56			let mut weight: Weight = Weight::zero();
57
58			// Migrate the current traffic value
59			let config = configuration::ActiveConfig::<T>::get();
60			QueueStatus::<T>::mutate(|mut queue_status| {
61				Pallet::<T>::update_spot_traffic(&config, &mut queue_status);
62
63				let v0_queue = v0::OnDemandQueue::<T>::take();
64				// Process the v0 queue into v1.
65				v0_queue.into_iter().for_each(|enqueued_order| {
66					// Readding the old orders will use the new systems.
67					Pallet::<T>::add_on_demand_order(
68						queue_status,
69						enqueued_order.para_id,
70						QueuePushDirection::Back,
71					);
72				});
73			});
74
75			// Remove the old storage.
76			v0::OnDemandQueue::<T>::kill(); // 1 write
77			v0::SpotTraffic::<T>::kill(); // 1 write
78
79			// Config read
80			weight.saturating_accrue(T::DbWeight::get().reads(1));
81			// QueueStatus read write (update_spot_traffic)
82			weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
83			// Kill x 2
84			weight.saturating_accrue(T::DbWeight::get().writes(2));
85
86			log::info!(target: LOG_TARGET, "Migrated on demand assigner storage to v1");
87			weight
88		}
89
90		#[cfg(feature = "try-runtime")]
91		fn pre_upgrade() -> Result<alloc::vec::Vec<u8>, sp_runtime::TryRuntimeError> {
92			let n: u32 = v0::OnDemandQueue::<T>::get().len() as u32;
93
94			log::info!(
95				target: LOG_TARGET,
96				"Number of orders waiting in the queue before: {n}",
97			);
98
99			Ok(n.encode())
100		}
101
102		#[cfg(feature = "try-runtime")]
103		fn post_upgrade(state: alloc::vec::Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
104			log::info!(target: LOG_TARGET, "Running post_upgrade()");
105
106			ensure!(
107				v0::OnDemandQueue::<T>::get().is_empty(),
108				"OnDemandQueue should be empty after the migration"
109			);
110
111			let expected_len = u32::decode(&mut &state[..]).unwrap();
112			let queue_status_size = QueueStatus::<T>::get().size();
113			ensure!(
114				expected_len == queue_status_size,
115				"Number of orders should be the same before and after migration"
116			);
117
118			let n_affinity_entries: u32 =
119				AffinityEntries::<T>::iter().map(|(_index, heap)| heap.len() as u32).sum();
120			let n_para_id_affinity: u32 = ParaIdAffinity::<T>::iter()
121				.map(|(_para_id, affinity)| affinity.count as u32)
122				.sum();
123			ensure!(
124				n_para_id_affinity == n_affinity_entries,
125				"Number of affinity entries should be the same as the counts in ParaIdAffinity"
126			);
127
128			Ok(())
129		}
130	}
131}
132
133/// Migrate `V0` to `V1` of the storage format.
134pub type MigrateV0ToV1<T> = VersionedMigration<
135	0,
136	1,
137	v1::UncheckedMigrateToV1<T>,
138	Pallet<T>,
139	<T as frame_system::Config>::DbWeight,
140>;
141
142#[cfg(test)]
143mod tests {
144	use super::{v0, v1, UncheckedOnRuntimeUpgrade, Weight};
145	use crate::mock::{new_test_ext, MockGenesisConfig, OnDemand, Test};
146	use polkadot_primitives::Id as ParaId;
147
148	#[test]
149	fn migration_to_v1_preserves_queue_ordering() {
150		new_test_ext(MockGenesisConfig::default()).execute_with(|| {
151			// Place orders for paraids 1..5
152			for i in 1..=5 {
153				v0::OnDemandQueue::<Test>::mutate(|queue| {
154					queue.push_back(v0::EnqueuedOrder { para_id: ParaId::new(i) })
155				});
156			}
157
158			// Queue has 5 orders
159			let old_queue = v0::OnDemandQueue::<Test>::get();
160			assert_eq!(old_queue.len(), 5);
161			// New queue has 0 orders
162			assert_eq!(OnDemand::get_queue_status().size(), 0);
163
164			// For tests, db weight is zero.
165			assert_eq!(
166				<v1::UncheckedMigrateToV1<Test> as UncheckedOnRuntimeUpgrade>::on_runtime_upgrade(),
167				Weight::zero()
168			);
169
170			// New queue has 5 orders
171			assert_eq!(OnDemand::get_queue_status().size(), 5);
172
173			// Compare each entry from the old queue with the entry in the new queue.
174			old_queue.iter().zip(OnDemand::get_free_entries().iter()).for_each(
175				|(old_enq, new_enq)| {
176					assert_eq!(old_enq.para_id, new_enq.para_id);
177				},
178			);
179		});
180	}
181}