referrerpolicy=no-referrer-when-downgrade

pallet_broker/
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
18use super::*;
19use crate::types::RegionRecord;
20use codec::{Decode, Encode};
21use core::marker::PhantomData;
22use frame_support::traits::{Get, UncheckedOnRuntimeUpgrade};
23use sp_runtime::Saturating;
24
25#[cfg(feature = "try-runtime")]
26use alloc::vec::Vec;
27#[cfg(feature = "try-runtime")]
28use frame_support::ensure;
29
30mod v1 {
31	use super::*;
32
33	/// V0 region record.
34	#[derive(Encode, Decode)]
35	struct RegionRecordV0<AccountId, Balance> {
36		/// The end of the Region.
37		pub end: Timeslice,
38		/// The owner of the Region.
39		pub owner: AccountId,
40		/// The amount paid to Polkadot for this Region, or `None` if renewal is not allowed.
41		pub paid: Option<Balance>,
42	}
43
44	pub struct MigrateToV1Impl<T>(PhantomData<T>);
45
46	impl<T: Config> UncheckedOnRuntimeUpgrade for MigrateToV1Impl<T> {
47		fn on_runtime_upgrade() -> frame_support::weights::Weight {
48			let mut count: u64 = 0;
49
50			<Regions<T>>::translate::<RegionRecordV0<T::AccountId, BalanceOf<T>>, _>(|_, v0| {
51				count.saturating_inc();
52				Some(RegionRecord { end: v0.end, owner: Some(v0.owner), paid: v0.paid })
53			});
54
55			log::info!(
56				target: LOG_TARGET,
57				"Storage migration v1 for pallet-broker finished.",
58			);
59
60			// calculate and return migration weights
61			T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1)
62		}
63
64		#[cfg(feature = "try-runtime")]
65		fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
66			Ok((Regions::<T>::iter_keys().count() as u32).encode())
67		}
68
69		#[cfg(feature = "try-runtime")]
70		fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
71			let old_count = u32::decode(&mut &state[..]).expect("Known good");
72			let new_count = Regions::<T>::iter_values().count() as u32;
73
74			ensure!(old_count == new_count, "Regions count should not change");
75			Ok(())
76		}
77	}
78}
79
80mod v2 {
81	use super::*;
82	use frame_support::{
83		pallet_prelude::{OptionQuery, Twox64Concat},
84		storage_alias,
85	};
86
87	#[storage_alias]
88	pub type AllowedRenewals<T: Config> = StorageMap<
89		Pallet<T>,
90		Twox64Concat,
91		PotentialRenewalId,
92		PotentialRenewalRecordOf<T>,
93		OptionQuery,
94	>;
95
96	pub struct MigrateToV2Impl<T>(PhantomData<T>);
97
98	impl<T: Config> UncheckedOnRuntimeUpgrade for MigrateToV2Impl<T> {
99		fn on_runtime_upgrade() -> frame_support::weights::Weight {
100			let mut count = 0;
101			for (renewal_id, renewal) in AllowedRenewals::<T>::drain() {
102				PotentialRenewals::<T>::insert(renewal_id, renewal);
103				count += 1;
104			}
105
106			log::info!(
107				target: LOG_TARGET,
108				"Storage migration v2 for pallet-broker finished.",
109			);
110
111			// calculate and return migration weights
112			T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1)
113		}
114
115		#[cfg(feature = "try-runtime")]
116		fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
117			Ok((AllowedRenewals::<T>::iter_keys().count() as u32).encode())
118		}
119
120		#[cfg(feature = "try-runtime")]
121		fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
122			let old_count = u32::decode(&mut &state[..]).expect("Known good");
123			let new_count = PotentialRenewals::<T>::iter_values().count() as u32;
124
125			ensure!(old_count == new_count, "Renewal count should not change");
126			Ok(())
127		}
128	}
129}
130
131mod v3 {
132	use super::*;
133	use codec::MaxEncodedLen;
134	use frame_support::{
135		pallet_prelude::{Debug, OptionQuery, TypeInfo},
136		storage_alias,
137	};
138	use frame_system::Pallet as System;
139	use sp_arithmetic::Perbill;
140
141	pub struct MigrateToV3Impl<T>(PhantomData<T>);
142
143	impl<T: Config> UncheckedOnRuntimeUpgrade for MigrateToV3Impl<T> {
144		fn on_runtime_upgrade() -> frame_support::weights::Weight {
145			let acc = Pallet::<T>::account_id();
146			System::<T>::inc_providers(&acc);
147			// calculate and return migration weights
148			T::DbWeight::get().writes(1)
149		}
150
151		#[cfg(feature = "try-runtime")]
152		fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
153			Ok(System::<T>::providers(&Pallet::<T>::account_id()).encode())
154		}
155
156		#[cfg(feature = "try-runtime")]
157		fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
158			let old_providers = u32::decode(&mut &state[..]).expect("Known good");
159			let new_providers = System::<T>::providers(&Pallet::<T>::account_id()) as u32;
160
161			ensure!(new_providers == old_providers + 1, "Providers count should increase by one");
162			Ok(())
163		}
164	}
165
166	#[storage_alias]
167	pub type Configuration<T: Config> = StorageValue<Pallet<T>, ConfigRecordOf<T>, OptionQuery>;
168	pub type ConfigRecordOf<T> =
169		ConfigRecord<frame_system::pallet_prelude::BlockNumberFor<T>, RelayBlockNumberOf<T>>;
170
171	// types added here for v4 migration
172	#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
173	pub struct ConfigRecord<BlockNumber, RelayBlockNumber> {
174		/// The number of Relay-chain blocks in advance which scheduling should be fixed and the
175		/// `Coretime::assign` API used to inform the Relay-chain.
176		pub advance_notice: RelayBlockNumber,
177		/// The length in blocks of the Interlude Period for forthcoming sales.
178		pub interlude_length: BlockNumber,
179		/// The length in blocks of the Leadin Period for forthcoming sales.
180		pub leadin_length: BlockNumber,
181		/// The length in timeslices of Regions which are up for sale in forthcoming sales.
182		pub region_length: Timeslice,
183		/// The proportion of cores available for sale which should be sold in order for the price
184		/// to remain the same in the next sale.
185		pub ideal_bulk_proportion: Perbill,
186		/// An artificial limit to the number of cores which are allowed to be sold. If `Some` then
187		/// no more cores will be sold than this.
188		pub limit_cores_offered: Option<CoreIndex>,
189		/// The amount by which the renewal price increases each sale period.
190		pub renewal_bump: Perbill,
191		/// The duration by which rewards for contributions to the InstaPool must be collected.
192		pub contribution_timeout: Timeslice,
193	}
194
195	#[storage_alias]
196	pub type SaleInfo<T: Config> = StorageValue<Pallet<T>, SaleInfoRecordOf<T>, OptionQuery>;
197	pub type SaleInfoRecordOf<T> =
198		SaleInfoRecord<BalanceOf<T>, frame_system::pallet_prelude::BlockNumberFor<T>>;
199
200	/// The status of a Bulk Coretime Sale.
201	#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
202	pub struct SaleInfoRecord<Balance, BlockNumber> {
203		/// The relay block number at which the sale will/did start.
204		pub sale_start: BlockNumber,
205		/// The length in relay chain blocks of the Leadin Period (where the price is decreasing).
206		pub leadin_length: BlockNumber,
207		/// The price of Bulk Coretime after the Leadin Period.
208		pub price: Balance,
209		/// The first timeslice of the Regions which are being sold in this sale.
210		pub region_begin: Timeslice,
211		/// The timeslice on which the Regions which are being sold in the sale terminate. (i.e.
212		/// One after the last timeslice which the Regions control.)
213		pub region_end: Timeslice,
214		/// The number of cores we want to sell, ideally. Selling this amount would result in no
215		/// change to the price for the next sale.
216		pub ideal_cores_sold: CoreIndex,
217		/// Number of cores which are/have been offered for sale.
218		pub cores_offered: CoreIndex,
219		/// The index of the first core which is for sale. Core of Regions which are sold have
220		/// incrementing indices from this.
221		pub first_core: CoreIndex,
222		/// The latest price at which Bulk Coretime was purchased until surpassing the ideal number
223		/// of cores were sold.
224		pub sellout_price: Option<Balance>,
225		/// Number of cores which have been sold; never more than cores_offered.
226		pub cores_sold: CoreIndex,
227	}
228}
229
230pub mod v4 {
231	use super::*;
232
233	type BlockNumberFor<T> = frame_system::pallet_prelude::BlockNumberFor<T>;
234
235	pub trait BlockToRelayHeightConversion<T: Config> {
236		/// Converts absolute value of parachain block number to relay chain block number
237		fn convert_block_number_to_relay_height(
238			block_number: BlockNumberFor<T>,
239		) -> RelayBlockNumberOf<T>;
240
241		/// Converts parachain block length into equivalent relay chain block length
242		fn convert_block_length_to_relay_length(
243			block_number: BlockNumberFor<T>,
244		) -> RelayBlockNumberOf<T>;
245	}
246
247	pub struct MigrateToV4Impl<T, BlockConversion>(PhantomData<T>, PhantomData<BlockConversion>);
248	impl<T: Config, BlockConversion: BlockToRelayHeightConversion<T>> UncheckedOnRuntimeUpgrade
249		for MigrateToV4Impl<T, BlockConversion>
250	{
251		#[cfg(feature = "try-runtime")]
252		fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
253			let (interlude_length, configuration_leadin_length) =
254				if let Some(config_record) = v3::Configuration::<T>::get() {
255					(config_record.interlude_length, config_record.leadin_length)
256				} else {
257					((0 as u32).into(), (0 as u32).into())
258				};
259
260			let updated_interlude_length: RelayBlockNumberOf<T> =
261				BlockConversion::convert_block_length_to_relay_length(interlude_length);
262			let updated_leadin_length: RelayBlockNumberOf<T> =
263				BlockConversion::convert_block_length_to_relay_length(configuration_leadin_length);
264			log::info!(target: LOG_TARGET, "Configuration Pre-Migration: Interlude Length {:?}->{:?} Leadin Length {:?}->{:?}", interlude_length, updated_interlude_length, configuration_leadin_length, updated_leadin_length);
265
266			let (sale_start, sale_info_leadin_length) =
267				if let Some(sale_info_record) = v3::SaleInfo::<T>::get() {
268					(sale_info_record.sale_start, sale_info_record.leadin_length)
269				} else {
270					((0 as u32).into(), (0 as u32).into())
271				};
272
273			let updated_sale_start: RelayBlockNumberOf<T> =
274				BlockConversion::convert_block_number_to_relay_height(sale_start);
275			let updated_sale_info_leadin_length: RelayBlockNumberOf<T> =
276				BlockConversion::convert_block_length_to_relay_length(sale_info_leadin_length);
277			log::info!(target: LOG_TARGET, "SaleInfo Pre-Migration: Sale Start {:?}->{:?} Interlude Length {:?}->{:?}", sale_start, updated_sale_start, sale_info_leadin_length, updated_sale_info_leadin_length);
278
279			Ok((interlude_length, configuration_leadin_length, sale_start, sale_info_leadin_length)
280				.encode())
281		}
282
283		fn on_runtime_upgrade() -> frame_support::weights::Weight {
284			let mut weight = T::DbWeight::get().reads(1);
285
286			if let Some(config_record) = v3::Configuration::<T>::take() {
287				log::info!(target: LOG_TARGET, "migrating Configuration record");
288
289				let updated_interlude_length: RelayBlockNumberOf<T> =
290					BlockConversion::convert_block_length_to_relay_length(
291						config_record.interlude_length,
292					);
293				let updated_leadin_length: RelayBlockNumberOf<T> =
294					BlockConversion::convert_block_length_to_relay_length(
295						config_record.leadin_length,
296					);
297
298				let updated_config_record = ConfigRecord {
299					interlude_length: updated_interlude_length,
300					leadin_length: updated_leadin_length,
301					advance_notice: config_record.advance_notice,
302					region_length: config_record.region_length,
303					ideal_bulk_proportion: config_record.ideal_bulk_proportion,
304					limit_cores_offered: config_record.limit_cores_offered,
305					renewal_bump: config_record.renewal_bump,
306					contribution_timeout: config_record.contribution_timeout,
307				};
308				Configuration::<T>::put(updated_config_record);
309			}
310			weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
311
312			if let Some(sale_info) = v3::SaleInfo::<T>::take() {
313				log::info!(target: LOG_TARGET, "migrating SaleInfo record");
314
315				let updated_sale_start: RelayBlockNumberOf<T> =
316					BlockConversion::convert_block_number_to_relay_height(sale_info.sale_start);
317				let updated_leadin_length: RelayBlockNumberOf<T> =
318					BlockConversion::convert_block_length_to_relay_length(sale_info.leadin_length);
319
320				let updated_sale_info = SaleInfoRecord {
321					sale_start: updated_sale_start,
322					leadin_length: updated_leadin_length,
323					end_price: sale_info.price,
324					region_begin: sale_info.region_begin,
325					region_end: sale_info.region_end,
326					ideal_cores_sold: sale_info.ideal_cores_sold,
327					cores_offered: sale_info.cores_offered,
328					first_core: sale_info.first_core,
329					sellout_price: sale_info.sellout_price,
330					cores_sold: sale_info.cores_sold,
331					sale_index: 0,
332				};
333				SaleInfo::<T>::put(updated_sale_info);
334			}
335
336			weight.saturating_add(T::DbWeight::get().reads_writes(1, 2))
337		}
338
339		#[cfg(feature = "try-runtime")]
340		fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
341			let (
342				old_interlude_length,
343				old_configuration_leadin_length,
344				old_sale_start,
345				old_sale_info_leadin_length,
346			): (BlockNumberFor<T>, BlockNumberFor<T>, BlockNumberFor<T>, BlockNumberFor<T>) =
347				Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed");
348
349			if let Some(config_record) = Configuration::<T>::get() {
350				ensure!(
351					Self::verify_updated_block_length(
352						old_configuration_leadin_length,
353						config_record.leadin_length
354					),
355					"must migrate configuration leadin_length"
356				);
357
358				ensure!(
359					Self::verify_updated_block_length(
360						old_interlude_length,
361						config_record.interlude_length
362					),
363					"must migrate configuration interlude_length"
364				);
365			}
366
367			if let Some(sale_info) = SaleInfo::<T>::get() {
368				ensure!(
369					Self::verify_updated_block_time(old_sale_start, sale_info.sale_start),
370					"must migrate sale info sale_start"
371				);
372
373				ensure!(
374					Self::verify_updated_block_length(
375						old_sale_info_leadin_length,
376						sale_info.leadin_length
377					),
378					"must migrate sale info leadin_length"
379				);
380			}
381
382			Ok(())
383		}
384	}
385
386	#[cfg(feature = "try-runtime")]
387	impl<T: Config, BlockConversion: BlockToRelayHeightConversion<T>>
388		MigrateToV4Impl<T, BlockConversion>
389	{
390		fn verify_updated_block_time(
391			old_value: BlockNumberFor<T>,
392			new_value: RelayBlockNumberOf<T>,
393		) -> bool {
394			BlockConversion::convert_block_number_to_relay_height(old_value) == new_value
395		}
396
397		fn verify_updated_block_length(
398			old_value: BlockNumberFor<T>,
399			new_value: RelayBlockNumberOf<T>,
400		) -> bool {
401			BlockConversion::convert_block_length_to_relay_length(old_value) == new_value
402		}
403	}
404}
405
406pub mod v5 {
407	use super::*;
408	use frame_support::traits::Get;
409
410	/// Pre-v5 `SaleInfo` storage: the `SaleInfoRecord` layout without `sale_index`.
411	pub(crate) mod old {
412		use super::*;
413		use codec::MaxEncodedLen;
414		use frame_support::{
415			pallet_prelude::{Debug, OptionQuery, TypeInfo},
416			storage_alias,
417		};
418
419		#[storage_alias]
420		pub type SaleInfo<T: Config> = StorageValue<Pallet<T>, SaleInfoRecordOf<T>, OptionQuery>;
421		pub type SaleInfoRecordOf<T> = SaleInfoRecord<BalanceOf<T>, RelayBlockNumberOf<T>>;
422
423		/// Pre-v5 `SaleInfoRecord`: the current layout without `sale_index`.
424		#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
425		pub struct SaleInfoRecord<Balance, RelayBlockNumber> {
426			pub sale_start: RelayBlockNumber,
427			pub leadin_length: RelayBlockNumber,
428			pub end_price: Balance,
429			pub region_begin: Timeslice,
430			pub region_end: Timeslice,
431			pub ideal_cores_sold: CoreIndex,
432			pub cores_offered: CoreIndex,
433			pub first_core: CoreIndex,
434			pub sellout_price: Option<Balance>,
435			pub cores_sold: CoreIndex,
436		}
437	}
438
439	/// Supplies the first sale's `region_begin` (timeslice), used to reconstruct `sale_index`.
440	/// Historical data, not recoverable from on-chain storage.
441	pub trait FirstSaleRegion {
442		fn region_begin() -> Timeslice;
443	}
444
445	/// Adds the `sale_index` field to `SaleInfoRecord`.
446	pub struct MigrateToV5Impl<T, F>(PhantomData<T>, PhantomData<F>);
447
448	impl<T: Config, F: FirstSaleRegion> UncheckedOnRuntimeUpgrade for MigrateToV5Impl<T, F> {
449		#[cfg(feature = "try-runtime")]
450		fn pre_upgrade() -> Result<Vec<u8>, sp_runtime::TryRuntimeError> {
451			let sale_info_state = old::SaleInfo::<T>::get()
452				.map(|sale| (sale.sale_start, sale.region_begin, sale.region_end));
453			Ok(sale_info_state.encode())
454		}
455
456		fn on_runtime_upgrade() -> frame_support::weights::Weight {
457			let mut weight = T::DbWeight::get().reads(1);
458
459			if let Some(old_sale) = old::SaleInfo::<T>::get() {
460				let first_region_begin = F::region_begin();
461
462				// `region_length` from Configuration; defaults to 0 (sale_index 1) if absent, so
463				// the record is rewritten rather than the migration panicking.
464				let region_length = if let Some(config) = Configuration::<T>::get() {
465					config.region_length
466				} else {
467					log::error!(
468						target: LOG_TARGET,
469						"Migrating SaleInfo: Configuration missing while SaleInfo exists; \
470						defaulting region_length to 0 and sale_index to 1",
471					);
472					0
473				};
474				weight.saturating_accrue(T::DbWeight::get().reads(1));
475
476				// `region_begin` advances by `region_length` timeslices each sale with no gaps, so
477				// completed sales = `(region_begin - first_region_begin) / region_length` and the
478				// current index is that plus one. The bootstrap sale in `do_start_sales` is index
479				// 0, so the first stored sale is index 1. Counted in timeslices, which are
480				// exact.
481				let completed_regions = if region_length > 0 {
482					old_sale.region_begin.saturating_sub(first_region_begin) / region_length
483				} else {
484					0
485				};
486				let sale_index: SaleIndex = completed_regions.saturating_add(1);
487
488				log::info!(
489					target: LOG_TARGET,
490					"Migrating SaleInfo: first_region_begin={:?}, current_region_begin={:?}, \
491					region_length={:?}, sale_index={}",
492					first_region_begin,
493					old_sale.region_begin,
494					region_length,
495					sale_index,
496				);
497
498				let updated_sale = SaleInfoRecord {
499					sale_start: old_sale.sale_start,
500					leadin_length: old_sale.leadin_length,
501					end_price: old_sale.end_price,
502					region_begin: old_sale.region_begin,
503					region_end: old_sale.region_end,
504					ideal_cores_sold: old_sale.ideal_cores_sold,
505					cores_offered: old_sale.cores_offered,
506					first_core: old_sale.first_core,
507					sellout_price: old_sale.sellout_price,
508					cores_sold: old_sale.cores_sold,
509					sale_index,
510				};
511				SaleInfo::<T>::put(updated_sale);
512				weight.saturating_accrue(T::DbWeight::get().writes(1));
513
514				log::info!(
515					target: LOG_TARGET,
516					"Storage migration v5 for pallet-broker completed: added sale_index = {}",
517					sale_index,
518				);
519			} else {
520				log::info!(
521					target: LOG_TARGET,
522					"No SaleInfo found, skipping v5 migration",
523				);
524			}
525
526			weight
527		}
528
529		#[cfg(feature = "try-runtime")]
530		fn post_upgrade(state: Vec<u8>) -> Result<(), sp_runtime::TryRuntimeError> {
531			let old_state: Option<(RelayBlockNumberOf<T>, Timeslice, Timeslice)> =
532				Decode::decode(&mut &state[..])
533					.map_err(|_| "Failed to decode pre_upgrade state")?;
534
535			// on_runtime_upgrade skips a missing SaleInfo, so only check when one existed.
536			if let Some((old_sale_start, old_region_begin, old_region_end)) = old_state {
537				let new_sale =
538					SaleInfo::<T>::get().ok_or("SaleInfo should still exist after migration")?;
539
540				ensure!(
541					old_sale_start == new_sale.sale_start &&
542						old_region_begin == new_sale.region_begin &&
543						old_region_end == new_sale.region_end,
544					"Core SaleInfo fields should not have changed during migration"
545				);
546				ensure!(
547					new_sale.sale_index >= 1,
548					"An existing sale must have a sale_index of at least 1"
549				);
550			}
551
552			Ok(())
553		}
554	}
555}
556
557/// Migrate the pallet storage from `0` to `1`.
558pub type MigrateV0ToV1<T> = frame_support::migrations::VersionedMigration<
559	0,
560	1,
561	v1::MigrateToV1Impl<T>,
562	Pallet<T>,
563	<T as frame_system::Config>::DbWeight,
564>;
565
566pub type MigrateV1ToV2<T> = frame_support::migrations::VersionedMigration<
567	1,
568	2,
569	v2::MigrateToV2Impl<T>,
570	Pallet<T>,
571	<T as frame_system::Config>::DbWeight,
572>;
573
574pub type MigrateV2ToV3<T> = frame_support::migrations::VersionedMigration<
575	2,
576	3,
577	v3::MigrateToV3Impl<T>,
578	Pallet<T>,
579	<T as frame_system::Config>::DbWeight,
580>;
581
582pub type MigrateV3ToV4<T, BlockConversion> = frame_support::migrations::VersionedMigration<
583	3,
584	4,
585	v4::MigrateToV4Impl<T, BlockConversion>,
586	Pallet<T>,
587	<T as frame_system::Config>::DbWeight,
588>;
589
590pub type MigrateV4ToV5<T, FirstSaleRegion> = frame_support::migrations::VersionedMigration<
591	4,
592	5,
593	v5::MigrateToV5Impl<T, FirstSaleRegion>,
594	Pallet<T>,
595	<T as frame_system::Config>::DbWeight,
596>;