pallet_schemas/migration/
v5.rs

1use crate::{
2	migration::v4,
3	pallet::{CurrentIntentIdentifierMaximum, IntentInfos, NameToMappedEntityIds, SchemaInfos},
4	Config, IntentInfo, Pallet, SchemaInfo, SchemaVersionId, SCHEMA_NAME_BYTES_MAX,
5	SCHEMA_STORAGE_VERSION,
6};
7use alloc::{format, vec::Vec};
8use common_primitives::schema::{IntentId, MappedEntityIdentifier, SchemaId, SchemaStatus};
9use core::marker::PhantomData;
10#[cfg(feature = "try-runtime")]
11use frame_support::ensure;
12use frame_support::{
13	pallet_prelude::{Get, GetStorageVersion, Weight},
14	traits::{Len, UncheckedOnRuntimeUpgrade},
15	BoundedVec,
16};
17#[cfg(feature = "try-runtime")]
18use parity_scale_codec::{Decode, Encode};
19#[cfg(feature = "try-runtime")]
20use sp_runtime::TryRuntimeError;
21
22const LOG_TARGET: &str = "pallet::schemas::migration::v5";
23
24fn convert_from_old(id: SchemaId, old_info: &v4::SchemaInfo) -> crate::SchemaInfo {
25	SchemaInfo {
26		intent_id: id as IntentId,
27		model_type: old_info.model_type,
28		payload_location: old_info.payload_location,
29		settings: old_info.settings,
30		status: SchemaStatus::Active,
31	}
32}
33
34fn convert_to_intent(old_info: &v4::SchemaInfo) -> IntentInfo {
35	IntentInfo { payload_location: old_info.payload_location, settings: old_info.settings }
36}
37
38fn append_or_overlay<S1: Get<u32>, S2: Get<u32>>(
39	target: &mut BoundedVec<u8, S1>,
40	source: &[u8],
41	protocol: &BoundedVec<u8, S2>,
42) -> Result<(), ()> {
43	let max_len = (SCHEMA_NAME_BYTES_MAX as usize - protocol.len() - 1).min(S1::get() as usize);
44	let additional = source.len();
45	let len = target.len();
46	if len + additional > max_len {
47		target.truncate(max_len - additional);
48	}
49	target.try_append(&mut source.to_vec())
50}
51
52/// Migrate from v4 to v5.
53pub struct InnerMigrateV4ToV5<T: Config>(PhantomData<T>);
54
55impl<T: Config> UncheckedOnRuntimeUpgrade for InnerMigrateV4ToV5<T> {
56	fn on_runtime_upgrade() -> Weight {
57		let onchain_version = Pallet::<T>::on_chain_storage_version();
58		let current_version = Pallet::<T>::in_code_storage_version();
59		log::info!(target: LOG_TARGET, "onchain_version={onchain_version:?}, current_version={current_version:?}");
60		if SCHEMA_STORAGE_VERSION != current_version {
61			log::error!(target: LOG_TARGET, "storage version mismatch: expected {SCHEMA_STORAGE_VERSION:?}, found {current_version:?}");
62			return T::DbWeight::get().reads(1)
63		}
64		if onchain_version < current_version {
65			log::info!(target: LOG_TARGET, "Migrating from v4 to v5");
66			let mut reads = 0;
67			let mut writes = 0;
68			let mut schemas_migrated = 0;
69			let mut intents_created = 0;
70			let mut new_names = 0;
71			let mut old_names = 0;
72			let mut max_intent_id = 0u16;
73
74			// Translate schema infos and create corresponding intent infos.
75			SchemaInfos::<T>::translate(|schema_id, old_schema_info: v4::SchemaInfo| {
76				reads += 1;
77				writes += 2;
78				max_intent_id = max_intent_id.max(schema_id);
79				IntentInfos::<T>::insert(
80					schema_id as IntentId,
81					convert_to_intent(&old_schema_info),
82				);
83				intents_created += 1;
84				schemas_migrated += 1;
85				Some(convert_from_old(schema_id, &old_schema_info))
86			});
87
88			// Remove old schema name map and create new entity name map, appending version id to names
89			v4::SchemaNameToIds::<T>::translate(
90				|schema_id, old_schema_name, version_id: SchemaVersionId| {
91					reads += 1;
92					old_names += 1;
93					let max_version = version_id.ids.len();
94					for (index, id) in version_id.ids.iter().enumerate() {
95						let version = index + 1;
96						let mut name = old_schema_name.clone();
97						// Let the latest schema version be the original name, and postfix previous versions
98						// with "-<version>"
99						if version < max_version {
100							let version_str = Vec::<u8>::from(format!("-{version}").as_bytes());
101							match append_or_overlay(&mut name, &version_str, &schema_id) {
102								Ok(_) => (),
103								Err(e) => {
104									log::error!(target: LOG_TARGET, "{e:?} unable to append id {version_str:?} to name: {name:?}");
105									return Some(version_id)
106								},
107							};
108						}
109						NameToMappedEntityIds::<T>::insert(
110							&schema_id,
111							name,
112							MappedEntityIdentifier::Intent(*id as IntentId),
113						);
114						writes += 1;
115						new_names += 1;
116					}
117					writes += 1;
118					None
119				},
120			);
121
122			CurrentIntentIdentifierMaximum::<T>::set(max_intent_id);
123
124			log::info!(target: LOG_TARGET,
125				"Migration complete. \
126				schemas_migrated={schemas_migrated:?}, intents_created={intents_created:?}, \
127				new_names={new_names:?}, old_names={old_names:?}"
128			);
129
130			// Set storage version to current version
131			SCHEMA_STORAGE_VERSION.put::<Pallet<T>>();
132			let weight = T::DbWeight::get().reads_writes(reads, writes);
133			log::info!(target: LOG_TARGET,
134				"Schemas storage migrated to version {current_version:?}. reads={reads:?}, writes={writes:?}, proof_size={:?}, ref_time={:?}",
135				weight.proof_size(), weight.ref_time());
136
137			weight
138		} else {
139			log::info!(target: LOG_TARGET,
140			"Migration did not execute; storage version is already up to date. \
141			onchain_version={onchain_version:?}, current_version={current_version:?}"
142			);
143			T::DbWeight::get().reads(1)
144		}
145	}
146
147	#[cfg(feature = "try-runtime")]
148	fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
149		let schemas_count = v4::SchemaInfos::<T>::iter_values().count() as u32;
150		let mut named_schemas_count = 0u32;
151		v4::SchemaNameToIds::<T>::iter_values().for_each(|schema_versions| {
152			named_schemas_count += schema_versions.ids.len() as u32;
153		});
154		Ok((schemas_count, named_schemas_count).encode())
155	}
156
157	#[cfg(feature = "try-runtime")]
158	fn post_upgrade(state: Vec<u8>) -> Result<(), TryRuntimeError> {
159		let (old_count, old_named_schemas_count) = <(u32, u32)>::decode(&mut &state[..])
160			.map_err(|_| TryRuntimeError::Other("unable to decode old schema count"))?;
161		let new_schema_count = SchemaInfos::<T>::iter_values().count() as u32;
162		let new_intent_count = IntentInfos::<T>::iter_values().count() as u32;
163		let new_names_count = NameToMappedEntityIds::<T>::iter_values().count() as u32;
164		let remaining_schema_names = v4::SchemaNameToIds::<T>::iter_values().count() as u32;
165
166		ensure!(remaining_schema_names == 0, "named schemas still exist");
167		ensure!(old_count == new_schema_count, "schema count mismatch");
168		ensure!(old_count == new_intent_count, "intent count mismatch");
169		ensure!(old_named_schemas_count == new_names_count, "name count mismatch");
170		Ok(())
171	}
172}
173
174/// [`UncheckedOnRuntimeUpgrade`] implementation [`InnerMigrateV4ToV5`] wrapped in a
175/// [`VersionedMigration`](frame_support::migrations::VersionedMigration), which ensures that:
176/// - The migration only runs once when the on-chain storage version is 4
177/// - The on-chain storage version is updated to `5` after the migration executes
178/// - Reads/Writes from checking/settings the on-chain storage version are accounted for
179pub type MigrateV4ToV5<T> = frame_support::migrations::VersionedMigration<
180	4, // The migration will only execute when the on-chain storage version is 4
181	5, // The on-chain storage version will be set to 5 after the migration is complete
182	InnerMigrateV4ToV5<T>,
183	crate::pallet::Pallet<T>,
184	<T as frame_system::Config>::DbWeight,
185>;