pallet_schemas/
lib.rs

1//! Schema Management for Message and Stateful Storage
2//!
3//! ## Quick Links
4//! - [Configuration: `Config`](Config)
5//! - [Extrinsics: `Call`](Call)
6//! - [Runtime API: `SchemasRuntimeApi`](../pallet_schemas_runtime_api/trait.SchemasRuntimeApi.html)
7//! - [Custom RPC API: `SchemasApiServer`](../pallet_schemas_rpc/trait.SchemasApiServer.html)
8//! - [Event Enum: `Event`](Event)
9//! - [Error Enum: `Error`](Error)
10#![doc = include_str!("../README.md")]
11//!
12//! ## Implementations
13//!
14//! - [`SchemaValidator`](../common_primitives/schema/trait.SchemaValidator.html): Functions for accessing and validating Schemas. This implementation is what is used in the runtime.
15//! - [`SchemaProvider`](../common_primitives/schema/trait.SchemaProvider.html): Allows another pallet to resolve schema information.
16// Substrate macros are tripping the clippy::expect_used lint.
17#![allow(clippy::expect_used)]
18#![cfg_attr(not(feature = "std"), no_std)]
19#![allow(rustdoc::private_intra_doc_links)]
20// Strong Documentation Lints
21#![deny(
22	rustdoc::broken_intra_doc_links,
23	rustdoc::missing_crate_level_docs,
24	rustdoc::invalid_codeblock_attributes,
25	missing_docs
26)]
27
28use common_primitives::{
29	node::ProposalProvider,
30	parquet::ParquetModel,
31	schema::{
32		ModelType, PayloadLocation, SchemaId, SchemaProvider, SchemaResponse, SchemaSetting,
33		SchemaSettings, SchemaValidator,
34	},
35};
36use frame_support::{
37	dispatch::{DispatchResult, PostDispatchInfo},
38	ensure,
39	traits::{BuildGenesisConfig, Get},
40};
41use sp_runtime::{traits::Dispatchable, BoundedVec, DispatchError};
42extern crate alloc;
43use alloc::{boxed::Box, vec::Vec};
44
45#[cfg(test)]
46mod tests;
47
48#[cfg(feature = "runtime-benchmarks")]
49mod benchmarking;
50#[cfg(feature = "runtime-benchmarks")]
51use common_primitives::benchmarks::SchemaBenchmarkHelper;
52use common_primitives::schema::{SchemaInfoResponse, SchemaVersionResponse};
53mod types;
54
55pub use pallet::*;
56pub mod weights;
57pub use types::*;
58pub use weights::*;
59
60mod serde;
61
62#[frame_support::pallet]
63pub mod pallet {
64	use super::*;
65	use frame_support::pallet_prelude::*;
66	use frame_system::pallet_prelude::*;
67
68	#[pallet::config]
69	pub trait Config: frame_system::Config {
70		/// The overarching event type.
71		#[allow(deprecated)]
72		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
73
74		/// Weight information for extrinsics in this pallet.
75		type WeightInfo: WeightInfo;
76
77		/// Minimum length of Schema model size in bytes
78		#[pallet::constant]
79		type MinSchemaModelSizeBytes: Get<u32>;
80
81		/// Maximum length of a Schema model Bounded Vec
82		#[pallet::constant]
83		type SchemaModelMaxBytesBoundedVecLimit: Get<u32> + MaxEncodedLen;
84
85		/// Maximum number of schemas that can be registered
86		#[pallet::constant]
87		type MaxSchemaRegistrations: Get<SchemaId>;
88
89		/// The origin that is allowed to create providers via governance
90		type CreateSchemaViaGovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
91
92		/// The runtime call dispatch type.
93		type Proposal: Parameter
94			+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
95			+ From<Call<Self>>;
96
97		/// The Council proposal provider interface
98		type ProposalProvider: ProposalProvider<Self::AccountId, Self::Proposal>;
99
100		/// Maximum number of schema settings that can be registered per schema (if any)
101		#[pallet::constant]
102		type MaxSchemaSettingsPerSchema: Get<u32>;
103	}
104
105	#[pallet::event]
106	#[pallet::generate_deposit(pub (super) fn deposit_event)]
107	pub enum Event<T: Config> {
108		/// Emitted when a schema is registered. [who, schemas id]
109		SchemaCreated {
110			/// Account ID
111			key: T::AccountId,
112
113			/// Schema ID of newly-created schema
114			schema_id: SchemaId,
115		},
116
117		/// Emitted when maximum size for schema model is changed.
118		SchemaMaxSizeChanged {
119			/// Max size of schema document
120			max_size: u32,
121		},
122
123		/// Emitted when a schema is assigned a name
124		SchemaNameCreated {
125			/// Schema ID which a name is assigned
126			schema_id: SchemaId,
127			/// ASCII string in bytes of the assigned name
128			name: Vec<u8>,
129		},
130	}
131
132	#[derive(PartialEq, Eq)] // for testing
133	#[pallet::error]
134	pub enum Error<T> {
135		/// Schema is malformed
136		InvalidSchema,
137
138		/// The schema model exceeds the maximum length allowed
139		ExceedsMaxSchemaModelBytes,
140
141		/// The schema is less than the minimum length allowed
142		LessThanMinSchemaModelBytes,
143
144		/// CurrentSchemaIdentifierMaximum was attempted to overflow max, means MaxSchemaRegistrations is too big
145		SchemaCountOverflow,
146
147		/// Invalid setting for schema
148		InvalidSetting,
149
150		/// Invalid schema name encoding
151		InvalidSchemaNameEncoding,
152
153		/// Invalid schema name characters
154		InvalidSchemaNameCharacters,
155
156		/// Invalid schema name structure
157		InvalidSchemaNameStructure,
158
159		/// Invalid schema name length
160		InvalidSchemaNameLength,
161
162		/// Invalid schema namespace length
163		InvalidSchemaNamespaceLength,
164
165		/// Invalid schema descriptor length
166		InvalidSchemaDescriptorLength,
167
168		/// Schema version exceeds the maximum allowed number
169		ExceedsMaxNumberOfVersions,
170
171		/// Inserted schema id already exists
172		SchemaIdAlreadyExists,
173
174		///  SchemaId does not exist
175		SchemaIdDoesNotExist,
176
177		/// SchemaId has a name already
178		SchemaIdAlreadyHasName,
179	}
180
181	#[pallet::pallet]
182	#[pallet::storage_version(SCHEMA_STORAGE_VERSION)]
183	pub struct Pallet<T>(_);
184
185	/// Storage for the Governance managed max bytes for schema model
186	/// Allows for altering the max bytes without a full chain upgrade
187	/// - Value: Max Bytes
188	#[pallet::storage]
189	pub(super) type GovernanceSchemaModelMaxBytes<T: Config> = StorageValue<_, u32, ValueQuery>;
190
191	/// Storage type for current number of schemas
192	/// Useful for retrieving latest schema id
193	/// - Value: Last Schema Id
194	#[pallet::storage]
195	pub(super) type CurrentSchemaIdentifierMaximum<T: Config> =
196		StorageValue<_, SchemaId, ValueQuery>;
197
198	/// Storage for message schema info struct data
199	/// - Key: Schema Id
200	/// - Value: [`SchemaInfo`](SchemaInfo)
201	#[pallet::storage]
202	pub(super) type SchemaInfos<T: Config> =
203		StorageMap<_, Twox64Concat, SchemaId, SchemaInfo, OptionQuery>;
204
205	/// Storage for message schema struct data
206	/// - Key: Schema Id
207	/// - Value: [`BoundedVec`](BoundedVec<T::SchemaModelMaxBytesBoundedVecLimit>)
208	#[pallet::storage]
209	pub(super) type SchemaPayloads<T: Config> = StorageMap<
210		_,
211		Twox64Concat,
212		SchemaId,
213		BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
214		OptionQuery,
215	>;
216
217	/// Storage for message schema info struct data
218	/// - Key: Schema Id
219	/// - Value: [`SchemaInfo`](SchemaInfo)
220	#[pallet::storage]
221	pub(super) type SchemaNameToIds<T: Config> = StorageDoubleMap<
222		_,
223		Blake2_128Concat,
224		SchemaNamespace,
225		Blake2_128Concat,
226		SchemaDescriptor,
227		SchemaVersionId,
228		ValueQuery,
229	>;
230
231	#[pallet::genesis_config]
232	pub struct GenesisConfig<T: Config> {
233		/// Maximum schema size in bytes at genesis
234		pub initial_schema_identifier_max: u16,
235		/// Maximum schema size in bytes at genesis
236		pub initial_max_schema_model_size: u32,
237		/// Genesis Schemas to load for development
238		pub initial_schemas: Vec<GenesisSchema>,
239		/// Phantom type
240		#[serde(skip)]
241		pub _config: PhantomData<T>,
242	}
243
244	impl<T: Config> core::default::Default for GenesisConfig<T> {
245		fn default() -> Self {
246			Self {
247				initial_schema_identifier_max: 16_000,
248				initial_max_schema_model_size: 1024,
249				initial_schemas: Default::default(),
250				_config: Default::default(),
251			}
252		}
253	}
254	#[pallet::genesis_build]
255	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
256		fn build(&self) {
257			GovernanceSchemaModelMaxBytes::<T>::put(self.initial_max_schema_model_size);
258
259			// Load in the Genesis Schemas
260			for schema in self.initial_schemas.iter() {
261				let model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit> =
262					BoundedVec::try_from(schema.model.clone().into_bytes()).expect(
263						"Genesis Schema Model larger than SchemaModelMaxBytesBoundedVecLimit",
264					);
265				let name_payload: SchemaNamePayload =
266					BoundedVec::try_from(schema.name.clone().into_bytes())
267						.expect("Genesis Schema Name larger than SCHEMA_NAME_BYTES_MAX");
268				let parsed_name: Option<SchemaName> = if name_payload.len() > 0 {
269					Some(
270						SchemaName::try_parse::<T>(name_payload, true)
271							.expect("Bad Genesis Schema Name"),
272					)
273				} else {
274					None
275				};
276				let settings: BoundedVec<SchemaSetting, T::MaxSchemaSettingsPerSchema> =
277					BoundedVec::try_from(schema.settings.clone()).expect(
278						"Bad Genesis Schema Settings. Perhaps larger than MaxSchemaSettingsPerSchema"
279					);
280
281				let _ = Pallet::<T>::add_schema(
282					model,
283					schema.model_type,
284					schema.payload_location,
285					settings,
286					parsed_name,
287				)
288				.expect("Failed to set Schema in Genesis!");
289			}
290
291			// Set the maximum manually
292			CurrentSchemaIdentifierMaximum::<T>::put(self.initial_schema_identifier_max);
293		}
294	}
295
296	#[pallet::call]
297	impl<T: Config> Pallet<T> {
298		/// REMOVED create_schema() at call index 0
299
300		/// Root and Governance can set a new max value for Schema bytes.
301		/// Must be <= the limit of the Schema BoundedVec used for registration.
302		///
303		/// # Requires
304		/// * Root Origin
305		///
306		/// # Events
307		/// * [`Event::SchemaMaxSizeChanged`]
308		///
309		/// # Errors
310		/// * [`Error::ExceedsMaxSchemaModelBytes`] - Cannot set to above the hard coded maximum [`Config::SchemaModelMaxBytesBoundedVecLimit`]
311		///
312		#[pallet::call_index(1)]
313		#[pallet::weight((T::WeightInfo::set_max_schema_model_bytes(), DispatchClass::Operational))]
314		pub fn set_max_schema_model_bytes(
315			origin: OriginFor<T>,
316			#[pallet::compact] max_size: u32,
317		) -> DispatchResult {
318			ensure_root(origin)?;
319			ensure!(
320				max_size <= T::SchemaModelMaxBytesBoundedVecLimit::get(),
321				Error::<T>::ExceedsMaxSchemaModelBytes
322			);
323			GovernanceSchemaModelMaxBytes::<T>::set(max_size);
324			Self::deposit_event(Event::SchemaMaxSizeChanged { max_size });
325			Ok(())
326		}
327
328		// REMOVED propose_to_create_schema() at call index 2
329		// REMOVED create_schema_via_governance() at call index 3
330		// REMOVED create_schema_v2() at call index 4
331
332		/// Propose to create a schema.  Creates a proposal for council approval to create a schema
333		///
334		#[pallet::call_index(5)]
335		#[pallet::weight(
336			match schema_name {
337				Some(_) => T::WeightInfo::propose_to_create_schema_v2_with_name(model.len() as u32),
338				None => T::WeightInfo::propose_to_create_schema_v2_without_name(model.len() as u32)
339			}
340		)]
341		pub fn propose_to_create_schema_v2(
342			origin: OriginFor<T>,
343			model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
344			model_type: ModelType,
345			payload_location: PayloadLocation,
346			settings: BoundedVec<SchemaSetting, T::MaxSchemaSettingsPerSchema>,
347			schema_name: Option<SchemaNamePayload>,
348		) -> DispatchResult {
349			let proposer = ensure_signed(origin)?;
350
351			let proposal: Box<T::Proposal> = Box::new(
352				(Call::<T>::create_schema_via_governance_v2 {
353					creator_key: proposer.clone(),
354					model,
355					model_type,
356					payload_location,
357					settings,
358					schema_name,
359				})
360				.into(),
361			);
362			T::ProposalProvider::propose_with_simple_majority(proposer, proposal)?;
363			Ok(())
364		}
365
366		/// Create a schema by means of council approval
367		///
368		/// # Events
369		/// * [`Event::SchemaCreated`]
370		/// * [`Event::SchemaNameCreated`]
371		///
372		/// # Errors
373		/// * [`Error::LessThanMinSchemaModelBytes`] - The schema's length is less than the minimum schema length
374		/// * [`Error::ExceedsMaxSchemaModelBytes`] - The schema's length is greater than the maximum schema length
375		/// * [`Error::InvalidSchema`] - Schema is malformed in some way
376		/// * [`Error::SchemaCountOverflow`] - The schema count has exceeded its bounds
377		/// * [`Error::InvalidSchemaNameEncoding`] - The schema name has invalid encoding
378		/// * [`Error::InvalidSchemaNameCharacters`] - The schema name has invalid characters
379		/// * [`Error::InvalidSchemaNameStructure`] - The schema name has invalid structure
380		/// * [`Error::InvalidSchemaNameLength`] - The schema name has invalid length
381		/// * [`Error::InvalidSchemaNamespaceLength`] - The schema namespace has invalid length
382		/// * [`Error::InvalidSchemaDescriptorLength`] - The schema descriptor has invalid length
383		/// * [`Error::ExceedsMaxNumberOfVersions`] - The schema name reached max number of versions
384		///
385		#[pallet::call_index(6)]
386		#[pallet::weight(
387			match schema_name {
388				Some(_) => T::WeightInfo::create_schema_via_governance_v2_with_name(model.len() as u32+ settings.len() as u32),
389				None => T::WeightInfo::create_schema_via_governance_v2_without_name(model.len() as u32+ settings.len() as u32)
390			}
391		)]
392		pub fn create_schema_via_governance_v2(
393			origin: OriginFor<T>,
394			creator_key: T::AccountId,
395			model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
396			model_type: ModelType,
397			payload_location: PayloadLocation,
398			settings: BoundedVec<SchemaSetting, T::MaxSchemaSettingsPerSchema>,
399			schema_name: Option<SchemaNamePayload>,
400		) -> DispatchResult {
401			T::CreateSchemaViaGovernanceOrigin::ensure_origin(origin)?;
402			let (schema_id, schema_name) = Self::create_schema_for(
403				model,
404				model_type,
405				payload_location,
406				settings,
407				schema_name,
408			)?;
409
410			Self::deposit_event(Event::SchemaCreated { key: creator_key, schema_id });
411			if let Some(inner_name) = schema_name {
412				Self::deposit_event(Event::SchemaNameCreated {
413					schema_id,
414					name: inner_name.get_combined_name(),
415				});
416			}
417			Ok(())
418		}
419
420		/// Adds a given schema to storage. The schema in question must be of length
421		/// between the min and max model size allowed for schemas (see pallet
422		/// constants above). If the pallet's maximum schema limit has been
423		/// fulfilled by the time this extrinsic is called, a SchemaCountOverflow error
424		/// will be thrown.
425		///
426		/// # Events
427		/// * [`Event::SchemaCreated`]
428		/// * [`Event::SchemaNameCreated`]
429		///
430		/// # Errors
431		/// * [`Error::LessThanMinSchemaModelBytes`] - The schema's length is less than the minimum schema length
432		/// * [`Error::ExceedsMaxSchemaModelBytes`] - The schema's length is greater than the maximum schema length
433		/// * [`Error::InvalidSchema`] - Schema is malformed in some way
434		/// * [`Error::SchemaCountOverflow`] - The schema count has exceeded its bounds
435		/// * [`Error::InvalidSetting`] - Invalid setting is provided
436		/// * [`Error::InvalidSchemaNameEncoding`] - The schema name has invalid encoding
437		/// * [`Error::InvalidSchemaNameCharacters`] - The schema name has invalid characters
438		/// * [`Error::InvalidSchemaNameStructure`] - The schema name has invalid structure
439		/// * [`Error::InvalidSchemaNameLength`] - The schema name has invalid length
440		/// * [`Error::InvalidSchemaNamespaceLength`] - The schema namespace has invalid length
441		/// * [`Error::InvalidSchemaDescriptorLength`] - The schema descriptor has invalid length
442		/// * [`Error::ExceedsMaxNumberOfVersions`] - The schema name reached max number of versions
443		///
444		#[pallet::call_index(7)]
445		#[pallet::weight(
446			match schema_name {
447				Some(_) => T::WeightInfo::create_schema_v3_with_name(model.len() as u32 + settings.len() as u32),
448				None => T::WeightInfo::create_schema_v3_without_name(model.len() as u32 + settings.len() as u32)
449			}
450		)]
451		pub fn create_schema_v3(
452			origin: OriginFor<T>,
453			model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
454			model_type: ModelType,
455			payload_location: PayloadLocation,
456			settings: BoundedVec<SchemaSetting, T::MaxSchemaSettingsPerSchema>,
457			schema_name: Option<SchemaNamePayload>,
458		) -> DispatchResult {
459			let sender = ensure_signed(origin)?;
460
461			let (schema_id, schema_name) = Self::create_schema_for(
462				model,
463				model_type,
464				payload_location,
465				settings,
466				schema_name,
467			)?;
468
469			Self::deposit_event(Event::SchemaCreated { key: sender, schema_id });
470			if let Some(inner_name) = schema_name {
471				Self::deposit_event(Event::SchemaNameCreated {
472					schema_id,
473					name: inner_name.get_combined_name(),
474				});
475			}
476			Ok(())
477		}
478
479		/// Propose to create a schema name.  Creates a proposal for council approval to create a schema name
480		/// * [`Error::LessThanMinSchemaModelBytes`] - The schema's length is less than the minimum schema length
481		/// * [`Error::ExceedsMaxSchemaModelBytes`] - The schema's length is greater than the maximum schema length
482		/// * [`Error::InvalidSchema`] - Schema is malformed in some way
483		/// * [`Error::InvalidSchemaNameEncoding`] - The schema name has invalid encoding
484		/// * [`Error::InvalidSchemaNameCharacters`] - The schema name has invalid characters
485		/// * [`Error::InvalidSchemaNameStructure`] - The schema name has invalid structure
486		/// * [`Error::InvalidSchemaNameLength`] - The schema name has invalid length
487		/// * [`Error::InvalidSchemaNamespaceLength`] - The schema namespace has invalid length
488		/// * [`Error::InvalidSchemaDescriptorLength`] - The schema descriptor has invalid length
489		/// * [`Error::ExceedsMaxNumberOfVersions`] - The schema name reached max number of versions
490		/// * [`Error::SchemaIdDoesNotExist`] - The schema id does not exist
491		/// * [`Error::SchemaIdAlreadyHasName`] - The schema id already has a name
492		#[pallet::call_index(8)]
493		#[pallet::weight(T::WeightInfo::propose_to_create_schema_name())]
494		pub fn propose_to_create_schema_name(
495			origin: OriginFor<T>,
496			schema_id: SchemaId,
497			schema_name: SchemaNamePayload,
498		) -> DispatchResult {
499			let proposer = ensure_signed(origin)?;
500
501			let _ = Self::parse_and_verify_schema_name(schema_id, &schema_name)?;
502
503			let proposal: Box<T::Proposal> = Box::new(
504				(Call::<T>::create_schema_name_via_governance { schema_id, schema_name }).into(),
505			);
506			T::ProposalProvider::propose_with_simple_majority(proposer, proposal)?;
507			Ok(())
508		}
509
510		/// Assigns a name to a schema without any name
511		///
512		/// # Events
513		/// * [`Event::SchemaNameCreated`]
514		///
515		/// # Errors
516		/// * [`Error::LessThanMinSchemaModelBytes`] - The schema's length is less than the minimum schema length
517		/// * [`Error::ExceedsMaxSchemaModelBytes`] - The schema's length is greater than the maximum schema length
518		/// * [`Error::InvalidSchema`] - Schema is malformed in some way
519		/// * [`Error::SchemaCountOverflow`] - The schema count has exceeded its bounds
520		/// * [`Error::InvalidSchemaNameEncoding`] - The schema name has invalid encoding
521		/// * [`Error::InvalidSchemaNameCharacters`] - The schema name has invalid characters
522		/// * [`Error::InvalidSchemaNameStructure`] - The schema name has invalid structure
523		/// * [`Error::InvalidSchemaNameLength`] - The schema name has invalid length
524		/// * [`Error::InvalidSchemaNamespaceLength`] - The schema namespace has invalid length
525		/// * [`Error::InvalidSchemaDescriptorLength`] - The schema descriptor has invalid length
526		/// * [`Error::ExceedsMaxNumberOfVersions`] - The schema name reached max number of versions
527		/// * [`Error::SchemaIdDoesNotExist`] - The schema id does not exist
528		/// * [`Error::SchemaIdAlreadyHasName`] - The schema id already has a name
529		///
530		#[pallet::call_index(9)]
531		#[pallet::weight(T::WeightInfo::create_schema_name_via_governance())]
532		pub fn create_schema_name_via_governance(
533			origin: OriginFor<T>,
534			schema_id: SchemaId,
535			schema_name: SchemaNamePayload,
536		) -> DispatchResult {
537			T::CreateSchemaViaGovernanceOrigin::ensure_origin(origin)?;
538
539			let parsed_name = Self::parse_and_verify_schema_name(schema_id, &schema_name)?;
540			SchemaNameToIds::<T>::try_mutate(
541				&parsed_name.namespace,
542				&parsed_name.descriptor,
543				|schema_version_id| -> DispatchResult {
544					schema_version_id.add::<T>(schema_id)?;
545
546					Self::deposit_event(Event::SchemaNameCreated {
547						schema_id,
548						name: parsed_name.get_combined_name(),
549					});
550					Ok(())
551				},
552			)
553		}
554	}
555
556	impl<T: Config> Pallet<T> {
557		/// Set the schema count to something in particular.
558		#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
559		pub fn set_schema_count(n: SchemaId) {
560			<CurrentSchemaIdentifierMaximum<T>>::set(n);
561		}
562
563		/// Inserts both the [`SchemaInfo`] and Schema Payload into storage
564		/// Updates the `CurrentSchemaIdentifierMaximum` storage
565		pub fn add_schema(
566			model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
567			model_type: ModelType,
568			payload_location: PayloadLocation,
569			settings: BoundedVec<SchemaSetting, T::MaxSchemaSettingsPerSchema>,
570			schema_name_option: Option<SchemaName>,
571		) -> Result<SchemaId, DispatchError> {
572			let schema_id = Self::get_next_schema_id()?;
573			let has_name = schema_name_option.is_some();
574			let mut set_settings = SchemaSettings::all_disabled();
575			if !settings.is_empty() {
576				for i in settings.into_inner() {
577					set_settings.set(i);
578				}
579			}
580
581			if let Some(schema_name) = schema_name_option {
582				SchemaNameToIds::<T>::try_mutate(
583					schema_name.namespace,
584					schema_name.descriptor,
585					|schema_version_id| -> Result<(), DispatchError> {
586						schema_version_id.add::<T>(schema_id)?;
587						Ok(())
588					},
589				)?;
590			};
591
592			let schema_info =
593				SchemaInfo { model_type, payload_location, settings: set_settings, has_name };
594			<CurrentSchemaIdentifierMaximum<T>>::set(schema_id);
595			<SchemaInfos<T>>::insert(schema_id, schema_info);
596			<SchemaPayloads<T>>::insert(schema_id, model);
597
598			Ok(schema_id)
599		}
600
601		/// Retrieve a schema by id
602		pub fn get_schema_by_id(schema_id: SchemaId) -> Option<SchemaResponse> {
603			match (SchemaInfos::<T>::get(schema_id), SchemaPayloads::<T>::get(schema_id)) {
604				(Some(schema_info), Some(payload)) => {
605					let model_vec: Vec<u8> = payload.into_inner();
606					let saved_settings = schema_info.settings;
607					let settings = saved_settings.0.iter().collect::<Vec<SchemaSetting>>();
608					let response = SchemaResponse {
609						schema_id,
610						model: model_vec,
611						model_type: schema_info.model_type,
612						payload_location: schema_info.payload_location,
613						settings,
614					};
615					Some(response)
616				},
617				(None, Some(_)) | (Some(_), None) => {
618					log::error!("Corrupted state for schema {schema_id:?}, Should never happen!");
619					None
620				},
621				(None, None) => None,
622			}
623		}
624
625		/// Retrieve a schema info by id
626		pub fn get_schema_info_by_id(schema_id: SchemaId) -> Option<SchemaInfoResponse> {
627			if let Some(schema_info) = SchemaInfos::<T>::get(schema_id) {
628				let saved_settings = schema_info.settings;
629				let settings = saved_settings.0.iter().collect::<Vec<SchemaSetting>>();
630				let response = SchemaInfoResponse {
631					schema_id,
632					model_type: schema_info.model_type,
633					payload_location: schema_info.payload_location,
634					settings,
635				};
636				return Some(response);
637			}
638			None
639		}
640
641		/// Ensures that a given u8 Vector conforms to a recognized Parquet shape
642		///
643		/// # Errors
644		/// * [`Error::InvalidSchema`]
645		/// * [`Error::SchemaCountOverflow`]
646		///
647		pub fn ensure_valid_model(
648			model_type: &ModelType,
649			model: &BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
650		) -> DispatchResult {
651			match *model_type {
652				ModelType::Parquet => {
653					serde_json::from_slice::<ParquetModel>(model)
654						.map_err(|_| Error::<T>::InvalidSchema)?;
655				},
656				ModelType::AvroBinary => serde::validate_json_model(model.clone().into_inner())
657					.map_err(|_| Error::<T>::InvalidSchema)?,
658			};
659			Ok(())
660		}
661
662		/// Get the next available schema id
663		///
664		/// # Errors
665		/// * [`Error::SchemaCountOverflow`]
666		///
667		fn get_next_schema_id() -> Result<SchemaId, DispatchError> {
668			let next = CurrentSchemaIdentifierMaximum::<T>::get()
669				.checked_add(1)
670				.ok_or(Error::<T>::SchemaCountOverflow)?;
671
672			Ok(next)
673		}
674
675		/// Adds a given schema to storage. The schema in question must be of length
676		/// between the min and max model size allowed for schemas (see pallet
677		/// constants above). If the pallet's maximum schema limit has been
678		/// fulfilled by the time this extrinsic is called, a SchemaCountOverflow error
679		/// will be thrown.
680		///
681		/// # Errors
682		/// * [`Error::LessThanMinSchemaModelBytes`] - The schema's length is less than the minimum schema length
683		/// * [`Error::ExceedsMaxSchemaModelBytes`] - The schema's length is greater than the maximum schema length
684		/// * [`Error::InvalidSchema`] - Schema is malformed in some way
685		/// * [`Error::SchemaCountOverflow`] - The schema count has exceeded its bounds
686		/// * [`Error::InvalidSetting`] - Invalid setting is provided
687		pub fn create_schema_for(
688			model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
689			model_type: ModelType,
690			payload_location: PayloadLocation,
691			settings: BoundedVec<SchemaSetting, T::MaxSchemaSettingsPerSchema>,
692			optional_schema_name: Option<SchemaNamePayload>,
693		) -> Result<(SchemaId, Option<SchemaName>), DispatchError> {
694			Self::ensure_valid_model(&model_type, &model)?;
695			ensure!(
696				model.len() >= T::MinSchemaModelSizeBytes::get() as usize,
697				Error::<T>::LessThanMinSchemaModelBytes
698			);
699			ensure!(
700				model.len() <= GovernanceSchemaModelMaxBytes::<T>::get() as usize,
701				Error::<T>::ExceedsMaxSchemaModelBytes
702			);
703			// AppendOnly is only valid for Itemized payload location
704			ensure!(
705				!settings.contains(&SchemaSetting::AppendOnly) ||
706					payload_location == PayloadLocation::Itemized,
707				Error::<T>::InvalidSetting
708			);
709			// SignatureRequired is only valid for Itemized and Paginated payload locations
710			ensure!(
711				!settings.contains(&SchemaSetting::SignatureRequired) ||
712					payload_location == PayloadLocation::Itemized ||
713					payload_location == PayloadLocation::Paginated,
714				Error::<T>::InvalidSetting
715			);
716			let schema_name = match optional_schema_name {
717				None => None,
718				Some(name_payload) => {
719					let parsed_name = SchemaName::try_parse::<T>(name_payload, true)?;
720					Some(parsed_name)
721				},
722			};
723			let schema_id = Self::add_schema(
724				model,
725				model_type,
726				payload_location,
727				settings,
728				schema_name.clone(),
729			)?;
730			Ok((schema_id, schema_name))
731		}
732
733		/// a method to return all versions of a schema name with their schemaIds
734		/// Warning: Must only get called from RPC, since the number of DB accesses is not deterministic
735		pub fn get_schema_versions(schema_name: Vec<u8>) -> Option<Vec<SchemaVersionResponse>> {
736			let bounded_name = BoundedVec::try_from(schema_name).ok()?;
737			let parsed_name = SchemaName::try_parse::<T>(bounded_name, false).ok()?;
738			let versions: Vec<_> = match parsed_name.descriptor_exists() {
739				true => SchemaNameToIds::<T>::get(&parsed_name.namespace, &parsed_name.descriptor)
740					.convert_to_response(&parsed_name),
741				false => SchemaNameToIds::<T>::iter_prefix(&parsed_name.namespace)
742					.flat_map(|(descriptor, val)| {
743						val.convert_to_response(&parsed_name.new_with_descriptor(descriptor))
744					})
745					.collect(),
746			};
747			Some(versions)
748		}
749
750		/// Parses the schema name and makes sure the schema does not have a name
751		fn parse_and_verify_schema_name(
752			schema_id: SchemaId,
753			schema_name: &SchemaNamePayload,
754		) -> Result<SchemaName, DispatchError> {
755			let schema_option = SchemaInfos::<T>::get(schema_id);
756			ensure!(schema_option.is_some(), Error::<T>::SchemaIdDoesNotExist);
757			if let Some(info) = schema_option {
758				ensure!(!info.has_name, Error::<T>::SchemaIdAlreadyHasName);
759			}
760			let parsed_name = SchemaName::try_parse::<T>(schema_name.clone(), true)?;
761			Ok(parsed_name)
762		}
763	}
764}
765
766#[allow(clippy::unwrap_used)]
767#[cfg(feature = "runtime-benchmarks")]
768impl<T: Config> SchemaBenchmarkHelper for Pallet<T> {
769	/// Sets schema count.
770	fn set_schema_count(schema_id: SchemaId) {
771		Self::set_schema_count(schema_id);
772	}
773
774	/// Creates a schema.
775	fn create_schema(
776		model: Vec<u8>,
777		model_type: ModelType,
778		payload_location: PayloadLocation,
779	) -> DispatchResult {
780		let model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit> =
781			model.try_into().unwrap();
782		Self::ensure_valid_model(&model_type, &model)?;
783		Self::add_schema(model, model_type, payload_location, BoundedVec::default(), None)?;
784		Ok(())
785	}
786}
787
788impl<T: Config> SchemaValidator<SchemaId> for Pallet<T> {
789	fn are_all_schema_ids_valid(schema_ids: &[SchemaId]) -> bool {
790		let latest_issue_schema_id = CurrentSchemaIdentifierMaximum::<T>::get();
791		schema_ids.iter().all(|id| id <= &latest_issue_schema_id)
792	}
793
794	#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
795	fn set_schema_count(n: SchemaId) {
796		Self::set_schema_count(n);
797	}
798}
799
800impl<T: Config> SchemaProvider<SchemaId> for Pallet<T> {
801	fn get_schema_by_id(schema_id: SchemaId) -> Option<SchemaResponse> {
802		Self::get_schema_by_id(schema_id)
803	}
804
805	fn get_schema_info_by_id(schema_id: SchemaId) -> Option<SchemaInfoResponse> {
806		Self::get_schema_info_by_id(schema_id)
807	}
808}