1#![doc = include_str!("../README.md")]
11#![allow(clippy::expect_used)]
18#![cfg_attr(not(feature = "std"), no_std)]
19#![allow(rustdoc::private_intra_doc_links)]
20#![deny(
22 rustdoc::broken_intra_doc_links,
23 rustdoc::missing_crate_level_docs,
24 rustdoc::invalid_codeblock_attributes,
25 missing_docs
26)]
27
28extern crate alloc;
29use alloc::{boxed::Box, vec, vec::Vec};
30use common_primitives::{
31 node::ProposalProvider,
32 parquet::ParquetModel,
33 schema::{
34 IntentGroupId, IntentGroupResponse, IntentId, IntentResponse, IntentSetting,
35 IntentSettings, MappedEntityIdentifier, ModelType, NameLookupResponse, PayloadLocation,
36 SchemaId, SchemaInfoResponse, SchemaProvider, SchemaStatus, SchemaValidator,
37 SchemaVersionResponse,
38 },
39};
40use frame_support::{
41 dispatch::{DispatchResult, PostDispatchInfo},
42 ensure,
43 traits::{BuildGenesisConfig, Get},
44};
45use sp_runtime::{traits::Dispatchable, BoundedVec, DispatchError};
46
47#[cfg(test)]
48mod tests;
49
50#[cfg(feature = "runtime-benchmarks")]
51mod benchmarking;
52#[cfg(feature = "runtime-benchmarks")]
53use common_primitives::benchmarks::SchemaBenchmarkHelper;
54use common_primitives::schema::SchemaResponseV2;
55
56mod types;
57
58pub use pallet::*;
59pub mod weights;
60pub use types::*;
61pub use weights::*;
62
63pub mod migration;
65mod serde;
66
67#[frame_support::pallet]
68pub mod pallet {
69 use super::*;
70 use common_primitives::schema::SchemaResponseV2;
71 use frame_support::pallet_prelude::*;
72 use frame_system::pallet_prelude::*;
73
74 #[pallet::config]
75 pub trait Config: frame_system::Config {
76 #[allow(deprecated)]
78 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
79
80 type WeightInfo: WeightInfo;
82
83 #[pallet::constant]
85 type MinSchemaModelSizeBytes: Get<u32>;
86
87 #[pallet::constant]
89 type SchemaModelMaxBytesBoundedVecLimit: Get<u32> + MaxEncodedLen;
90
91 #[pallet::constant]
93 type MaxIntentsPerIntentGroup: Get<u32>;
94
95 type CreateSchemaViaGovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
97
98 type Proposal: Parameter
100 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
101 + From<Call<Self>>;
102
103 type ProposalProvider: ProposalProvider<Self::AccountId, Self::Proposal>;
105
106 #[pallet::constant]
108 type MaxSchemaSettingsPerSchema: Get<u32>;
109 }
110
111 #[pallet::event]
112 #[pallet::generate_deposit(pub (super) fn deposit_event)]
113 pub enum Event<T: Config> {
114 SchemaCreated {
116 key: T::AccountId,
118
119 schema_id: SchemaId,
121 },
122
123 SchemaMaxSizeChanged {
125 max_size: u32,
127 },
128
129 SchemaNameCreated {
131 schema_id: SchemaId,
133 name: Vec<u8>,
135 },
136
137 IntentCreated {
139 key: T::AccountId,
141
142 intent_id: IntentId,
144
145 intent_name: Vec<u8>,
147 },
148
149 IntentGroupCreated {
151 key: T::AccountId,
153
154 intent_group_id: IntentGroupId,
156
157 intent_group_name: Vec<u8>,
159 },
160
161 IntentGroupUpdated {
163 key: T::AccountId,
165
166 intent_group_id: IntentGroupId,
168 },
169 }
170
171 #[derive(PartialEq, Eq)] #[pallet::error]
173 pub enum Error<T> {
174 InvalidSchema,
176
177 ExceedsMaxSchemaModelBytes,
179
180 LessThanMinSchemaModelBytes,
182
183 SchemaCountOverflow,
186
187 InvalidSetting,
189
190 InvalidSchemaNameEncoding,
192
193 InvalidSchemaNameCharacters,
195
196 InvalidSchemaNameStructure,
198
199 InvalidSchemaNameLength,
201
202 InvalidSchemaNamespaceLength,
204
205 InvalidSchemaDescriptorLength,
207
208 ExceedsMaxNumberOfVersions,
210
211 SchemaIdAlreadyExists,
213
214 SchemaIdDoesNotExist,
216
217 SchemaIdAlreadyHasName,
219
220 NameAlreadyExists,
222
223 IntentCountOverflow,
226
227 InvalidIntentId,
229
230 InvalidIntentGroupId,
232
233 IntentGroupCountOverflow,
236
237 TooManyIntentsInGroup,
239 }
240
241 #[pallet::pallet]
242 #[pallet::storage_version(SCHEMA_STORAGE_VERSION)]
243 pub struct Pallet<T>(_);
244
245 #[pallet::storage]
249 pub(super) type GovernanceSchemaModelMaxBytes<T: Config> = StorageValue<_, u32, ValueQuery>;
250
251 #[pallet::storage]
255 pub(super) type CurrentSchemaIdentifierMaximum<T: Config> =
256 StorageValue<_, SchemaId, ValueQuery>;
257
258 #[pallet::storage]
262 pub(super) type SchemaInfos<T: Config> =
263 StorageMap<_, Twox64Concat, SchemaId, SchemaInfo, OptionQuery>;
264
265 #[pallet::storage]
269 pub(super) type SchemaPayloads<T: Config> =
270 StorageMap<_, Twox64Concat, SchemaId, SchemaPayload<T>, OptionQuery>;
271
272 #[pallet::storage]
277 pub(super) type NameToMappedEntityIds<T: Config> = StorageDoubleMap<
278 _,
279 Blake2_128Concat,
280 SchemaProtocolName,
281 Blake2_128Concat,
282 SchemaDescriptor,
283 MappedEntityIdentifier,
284 ValueQuery,
285 >;
286
287 #[pallet::storage]
291 pub(super) type CurrentIntentIdentifierMaximum<T: Config> =
292 StorageValue<_, IntentId, ValueQuery>;
293
294 #[pallet::storage]
298 pub(super) type IntentInfos<T: Config> =
299 StorageMap<_, Twox64Concat, IntentId, IntentInfo, OptionQuery>;
300
301 #[pallet::storage]
305 pub(super) type CurrentIntentGroupIdentifierMaximum<T: Config> =
306 StorageValue<_, IntentGroupId, ValueQuery>;
307
308 #[pallet::storage]
312 pub(super) type IntentGroups<T: Config> =
313 StorageMap<_, Twox64Concat, IntentGroupId, IntentGroup<T>, OptionQuery>;
314
315 #[pallet::genesis_config]
316 pub struct GenesisConfig<T: Config> {
317 pub initial_schema_identifier_max: SchemaId,
319 pub initial_intent_identifier_max: IntentId,
321 pub initial_intent_group_identifier_max: IntentGroupId,
323 pub initial_max_schema_model_size: u32,
325 pub initial_intents: Vec<GenesisIntent>,
327 pub initial_schemas: Vec<GenesisSchema>,
329 pub initial_intent_groups: Vec<GenesisIntentGroup>,
331 #[serde(skip)]
333 pub _config: PhantomData<T>,
334 }
335
336 impl<T: Config> core::default::Default for GenesisConfig<T> {
337 fn default() -> Self {
338 Self {
339 initial_intent_identifier_max: 16_000,
340 initial_schema_identifier_max: 16_000,
341 initial_intent_group_identifier_max: 16_000,
342 initial_max_schema_model_size: 1024,
343 initial_intents: Default::default(),
344 initial_schemas: Default::default(),
345 initial_intent_groups: Default::default(),
346 _config: Default::default(),
347 }
348 }
349 }
350
351 impl<T: Config> Into<GenesisConfig<T>> for GenesisSchemasPalletConfig {
352 fn into(self) -> GenesisConfig<T> {
353 GenesisConfig::<T> {
354 initial_intent_identifier_max: self.intent_identifier_max.unwrap_or(16_000),
355 initial_schema_identifier_max: self.schema_identifier_max.unwrap_or(16_000),
356 initial_intent_group_identifier_max: self
357 .intent_group_identifier_max
358 .unwrap_or(16_000),
359 initial_max_schema_model_size: self.max_schema_model_size.unwrap_or(1024),
360 initial_intents: self.intents.unwrap_or_default(),
361 initial_schemas: self.schemas.unwrap_or_default(),
362 initial_intent_groups: self.intent_groups.unwrap_or_default(),
363 _config: Default::default(),
364 }
365 }
366 }
367
368 #[pallet::genesis_build]
369 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
370 fn build(&self) {
371 GovernanceSchemaModelMaxBytes::<T>::put(self.initial_max_schema_model_size);
372
373 for intent in self.initial_intents.iter() {
374 let name_payload: SchemaNamePayload =
375 BoundedVec::try_from(intent.name.clone().into_bytes())
376 .expect("Genesis Intent name larger than max bytes");
377 let parsed_name = SchemaName::try_parse::<T>(name_payload, true)
378 .expect("Bad Genesis Intent name");
379 let settings_vec = BoundedVec::<IntentSetting, T::MaxSchemaSettingsPerSchema>::try_from(intent.settings.clone()).expect("Bad Genesis Intent settings. Perhaps larger than MaxSchemaSettingsPerSchema");
380 let mut settings = IntentSettings::default();
381 settings_vec.iter().for_each(|setting| settings.set(*setting));
382
383 let intent_info =
384 IntentInfo { payload_location: intent.payload_location, settings };
385 Pallet::<T>::store_intent_info(intent.intent_id, intent_info, &parsed_name)
386 .expect("Failed to set Intent in Genesis!");
387 }
388
389 for schema in self.initial_schemas.iter() {
390 let intent = IntentInfos::<T>::try_get(schema.intent_id)
391 .expect("Genesis Schema has no matching Intent.");
392 let model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit> =
393 BoundedVec::try_from(schema.model.clone().into_bytes()).expect(
394 "Genesis Schema Model larger than SchemaModelMaxBytesBoundedVecLimit",
395 );
396 let schema_info = SchemaInfo {
397 intent_id: schema.intent_id,
398 payload_location: intent.payload_location,
399 model_type: schema.model_type,
400 settings: intent.settings,
401 status: schema.status,
402 };
403
404 Pallet::<T>::store_schema_info_and_payload(schema.schema_id, schema_info, model)
405 .expect("Failed to set Schema in Genesis!");
406 }
407
408 for intent_group in self.initial_intent_groups.iter() {
409 let intent_ids = BoundedVec::try_from(intent_group.intent_ids.clone())
410 .expect("Too many Intents in IntentGroup");
411 intent_ids.iter().for_each(|intent_id| {
412 assert!(
413 IntentInfos::<T>::contains_key(intent_id),
414 "Genesis IntentGroup has no matching Intent."
415 );
416 });
417 let name_payload: SchemaNamePayload = BoundedVec::try_from(
418 intent_group.name.clone().into_bytes(),
419 )
420 .expect("Genesis IntentGroup name larger than {SCHEMA_NAME_BYTES_MAX} bytes}");
421 let parsed_name = SchemaName::try_parse::<T>(name_payload, true)
422 .expect("Bad Genesis IntentGroup name");
423 Pallet::<T>::store_intent_group(
424 intent_group.intent_group_id,
425 intent_ids,
426 &parsed_name,
427 )
428 .expect("Failed to set Schema in Genesis!");
429 }
430
431 CurrentIntentIdentifierMaximum::<T>::put(self.initial_intent_identifier_max);
433 CurrentSchemaIdentifierMaximum::<T>::put(self.initial_schema_identifier_max);
434 CurrentIntentGroupIdentifierMaximum::<T>::put(self.initial_intent_group_identifier_max);
435 }
436 }
437
438 #[pallet::call]
439 impl<T: Config> Pallet<T> {
440 #[pallet::call_index(1)]
455 #[pallet::weight((T::WeightInfo::set_max_schema_model_bytes(), DispatchClass::Operational))]
456 pub fn set_max_schema_model_bytes(
457 origin: OriginFor<T>,
458 #[pallet::compact] max_size: u32,
459 ) -> DispatchResult {
460 ensure_root(origin)?;
461 ensure!(
462 max_size <= T::SchemaModelMaxBytesBoundedVecLimit::get(),
463 Error::<T>::ExceedsMaxSchemaModelBytes
464 );
465 GovernanceSchemaModelMaxBytes::<T>::set(max_size);
466 Self::deposit_event(Event::SchemaMaxSizeChanged { max_size });
467 Ok(())
468 }
469
470 #[pallet::call_index(10)]
496 #[pallet::weight(T::WeightInfo::create_intent(settings.len() as u32))]
497 pub fn create_intent(
498 origin: OriginFor<T>,
499 intent_name: SchemaNamePayload,
500 payload_location: PayloadLocation,
501 settings: BoundedVec<IntentSetting, T::MaxSchemaSettingsPerSchema>,
502 ) -> DispatchResult {
503 let sender = ensure_signed(origin)?;
504
505 let (intent_id, parsed_name) =
506 Self::create_intent_for(intent_name, payload_location, settings)?;
507
508 Self::deposit_event(Event::IntentCreated {
509 key: sender,
510 intent_id,
511 intent_name: parsed_name.get_combined_name(),
512 });
513
514 Ok(())
515 }
516
517 #[pallet::call_index(11)]
534 #[pallet::weight(T::WeightInfo::create_intent_via_governance(settings.len() as u32))]
535 pub fn create_intent_via_governance(
536 origin: OriginFor<T>,
537 creator_key: T::AccountId,
538 payload_location: PayloadLocation,
539 settings: BoundedVec<IntentSetting, T::MaxSchemaSettingsPerSchema>,
540 intent_name: SchemaNamePayload,
541 ) -> DispatchResult {
542 T::CreateSchemaViaGovernanceOrigin::ensure_origin(origin)?;
543 let (intent_id, parsed_name) =
544 Self::create_intent_for(intent_name, payload_location, settings)?;
545
546 Self::deposit_event(Event::IntentCreated {
547 key: creator_key,
548 intent_id,
549 intent_name: parsed_name.get_combined_name(),
550 });
551 Ok(())
552 }
553
554 #[pallet::call_index(12)]
557 #[pallet::weight(T::WeightInfo::propose_to_create_intent())]
558 pub fn propose_to_create_intent(
559 origin: OriginFor<T>,
560 payload_location: PayloadLocation,
561 settings: BoundedVec<IntentSetting, T::MaxSchemaSettingsPerSchema>,
562 intent_name: SchemaNamePayload,
563 ) -> DispatchResult {
564 let proposer = ensure_signed(origin)?;
565
566 let proposal: Box<T::Proposal> = Box::new(
567 (Call::<T>::create_intent_via_governance {
568 creator_key: proposer.clone(),
569 payload_location,
570 settings,
571 intent_name,
572 })
573 .into(),
574 );
575 T::ProposalProvider::propose_with_simple_majority(proposer, proposal)?;
576 Ok(())
577 }
578
579 #[pallet::call_index(13)]
596 #[pallet::weight(T::WeightInfo::create_intent_group(intent_ids.len() as u32))]
597 pub fn create_intent_group(
598 origin: OriginFor<T>,
599 intent_group_name: SchemaNamePayload,
600 intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
601 ) -> DispatchResult {
602 let sender = ensure_signed(origin)?;
603
604 let (intent_group_id, parsed_name) =
605 Self::create_intent_group_for(intent_group_name, intent_ids)?;
606
607 Self::deposit_event(Event::IntentGroupCreated {
608 key: sender,
609 intent_group_id,
610 intent_group_name: parsed_name.get_combined_name(),
611 });
612 Ok(())
613 }
614
615 #[pallet::call_index(14)]
632 #[pallet::weight(
633 T::WeightInfo::create_intent_group_via_governance(intent_ids.len() as u32)
634 )]
635 pub fn create_intent_group_via_governance(
636 origin: OriginFor<T>,
637 creator_key: T::AccountId,
638 intent_group_name: SchemaNamePayload,
639 intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
640 ) -> DispatchResult {
641 T::CreateSchemaViaGovernanceOrigin::ensure_origin(origin)?;
642 let (intent_group_id, parsed_name) =
643 Self::create_intent_group_for(intent_group_name, intent_ids)?;
644
645 Self::deposit_event(Event::IntentGroupCreated {
646 key: creator_key,
647 intent_group_id,
648 intent_group_name: parsed_name.get_combined_name(),
649 });
650 Ok(())
651 }
652
653 #[pallet::call_index(15)]
656 #[pallet::weight(T::WeightInfo::propose_to_create_intent_group())]
657 pub fn propose_to_create_intent_group(
658 origin: OriginFor<T>,
659 intent_group_name: crate::types::SchemaNamePayload,
660 intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
661 ) -> DispatchResult {
662 let proposer = ensure_signed(origin)?;
663
664 let proposal: Box<T::Proposal> = Box::new(
665 (Call::<T>::create_intent_group_via_governance {
666 creator_key: proposer.clone(),
667 intent_group_name,
668 intent_ids,
669 })
670 .into(),
671 );
672 T::ProposalProvider::propose_with_simple_majority(proposer, proposal)?;
673 Ok(())
674 }
675
676 #[pallet::call_index(16)]
687 #[pallet::weight(T::WeightInfo::update_intent_group(intent_ids.len() as u32))]
688 pub fn update_intent_group(
689 origin: OriginFor<T>,
690 intent_group_id: IntentGroupId,
691 intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
692 ) -> DispatchResult {
693 let sender = ensure_signed(origin)?;
694
695 Self::update_intent_group_for(intent_group_id, intent_ids)?;
696 Self::deposit_event(Event::IntentGroupUpdated { key: sender, intent_group_id });
697 Ok(())
698 }
699
700 #[pallet::call_index(17)]
712 #[pallet::weight(
713 T::WeightInfo::update_intent_group_via_governance(intent_ids.len() as u32)
714 )]
715 pub fn update_intent_group_via_governance(
716 origin: OriginFor<T>,
717 updater_key: T::AccountId,
718 intent_group_id: IntentGroupId,
719 intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
720 ) -> DispatchResult {
721 T::CreateSchemaViaGovernanceOrigin::ensure_origin(origin)?;
722 ensure!(
723 IntentGroups::<T>::contains_key(intent_group_id),
724 Error::<T>::InvalidIntentGroupId
725 );
726 Self::update_intent_group_for(intent_group_id, intent_ids)?;
727
728 Self::deposit_event(Event::IntentGroupUpdated { key: updater_key, intent_group_id });
729 Ok(())
730 }
731
732 #[pallet::call_index(18)]
735 #[pallet::weight(T::WeightInfo::propose_to_update_intent_group())]
736 pub fn propose_to_update_intent_group(
737 origin: OriginFor<T>,
738 intent_group_id: IntentGroupId,
739 intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
740 ) -> DispatchResult {
741 let proposer = ensure_signed(origin)?;
742
743 let proposal: Box<T::Proposal> = Box::new(
744 (Call::<T>::update_intent_group_via_governance {
745 updater_key: proposer.clone(),
746 intent_group_id,
747 intent_ids,
748 })
749 .into(),
750 );
751 T::ProposalProvider::propose_with_simple_majority(proposer, proposal)?;
752 Ok(())
753 }
754
755 #[pallet::call_index(19)]
758 #[pallet::weight(T::WeightInfo::propose_to_create_schema_v3())]
759 pub fn propose_to_create_schema_v3(
760 origin: OriginFor<T>,
761 intent_id: IntentId,
762 model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
763 model_type: ModelType,
764 ) -> DispatchResult {
765 let proposer = ensure_signed(origin)?;
766
767 let proposal: Box<T::Proposal> = Box::new(
768 (Call::<T>::create_schema_via_governance_v3 {
769 creator_key: proposer.clone(),
770 intent_id,
771 model,
772 model_type,
773 })
774 .into(),
775 );
776 T::ProposalProvider::propose_with_simple_majority(proposer, proposal)?;
777 Ok(())
778 }
779
780 #[pallet::call_index(20)]
792 #[pallet::weight(T::WeightInfo::create_schema_via_governance_v3(model.len() as u32))]
793 pub fn create_schema_via_governance_v3(
794 origin: OriginFor<T>,
795 creator_key: T::AccountId,
796 intent_id: IntentId,
797 model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
798 model_type: ModelType,
799 ) -> DispatchResult {
800 T::CreateSchemaViaGovernanceOrigin::ensure_origin(origin)?;
801 let schema_id = Self::create_schema_for(intent_id, model, model_type)?;
802
803 Self::deposit_event(Event::SchemaCreated { key: creator_key, schema_id });
804 Ok(())
805 }
806
807 #[pallet::call_index(21)]
825 #[pallet::weight(T::WeightInfo::create_schema_v4(model.len() as u32))]
826 pub fn create_schema_v4(
827 origin: OriginFor<T>,
828 intent_id: IntentId,
829 model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
830 model_type: ModelType,
831 ) -> DispatchResult {
832 let sender = ensure_signed(origin)?;
833
834 let schema_id = Self::create_schema_for(intent_id, model, model_type)?;
835
836 Self::deposit_event(Event::SchemaCreated { key: sender, schema_id });
837 Ok(())
838 }
839 }
840
841 impl<T: Config> Pallet<T> {
842 #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
844 pub fn set_schema_count(n: SchemaId) {
845 <CurrentSchemaIdentifierMaximum<T>>::set(n);
846 }
847
848 #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
850 pub fn set_intent_count(n: IntentId) {
851 <CurrentIntentIdentifierMaximum<T>>::set(n);
852 }
853
854 pub fn store_schema_info_and_payload(
857 schema_id: SchemaId,
858 schema_info: SchemaInfo,
859 schema_payload: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
860 ) -> Result<(), DispatchError> {
861 <SchemaInfos<T>>::insert(schema_id, schema_info);
862 <SchemaPayloads<T>>::insert(schema_id, schema_payload);
863 Ok(())
864 }
865
866 pub fn add_schema(
869 intent_id: IntentId,
870 model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
871 model_type: ModelType,
872 payload_location: PayloadLocation,
873 settings: IntentSettings,
874 status: SchemaStatus,
875 ) -> Result<SchemaId, DispatchError> {
876 let schema_id = Self::get_next_schema_id()?;
877 let schema_info =
878 SchemaInfo { intent_id, model_type, payload_location, settings, status };
879 <CurrentSchemaIdentifierMaximum<T>>::set(schema_id);
880 Self::store_schema_info_and_payload(schema_id, schema_info, model)?;
881
882 Ok(schema_id)
883 }
884
885 pub fn store_intent_info(
888 intent_id: IntentId,
889 intent_info: IntentInfo,
890 intent_name: &SchemaName,
891 ) -> Result<(), DispatchError> {
892 NameToMappedEntityIds::<T>::insert(
893 &intent_name.namespace,
894 &intent_name.descriptor,
895 MappedEntityIdentifier::Intent(intent_id),
896 );
897 <IntentInfos<T>>::insert(intent_id, intent_info);
898
899 Ok(())
900 }
901
902 pub fn add_intent(
910 payload_location: PayloadLocation,
911 settings: BoundedVec<IntentSetting, T::MaxSchemaSettingsPerSchema>,
912 intent_name: &SchemaName,
913 ) -> Result<IntentId, DispatchError> {
914 let intent_id = Self::get_next_intent_id()?;
915 let mut set_settings = IntentSettings::all_disabled();
916 if !settings.is_empty() {
917 for i in settings.into_inner() {
918 set_settings.set(i);
919 }
920 }
921
922 let intent_info = IntentInfo { payload_location, settings: set_settings };
923 <CurrentIntentIdentifierMaximum<T>>::set(intent_id);
924 Self::store_intent_info(intent_id, intent_info, intent_name)?;
925
926 Ok(intent_id)
927 }
928
929 pub fn store_intent_group(
932 intent_group_id: IntentGroupId,
933 intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
934 intent_group_name: &SchemaName,
935 ) -> Result<(), DispatchError> {
936 NameToMappedEntityIds::<T>::insert(
937 &intent_group_name.namespace,
938 &intent_group_name.descriptor,
939 MappedEntityIdentifier::IntentGroup(intent_group_id),
940 );
941 Self::update_intent_group_storage(intent_group_id, intent_ids)?;
942
943 Ok(())
944 }
945
946 pub fn add_intent_group(
955 intent_group_name: &SchemaName,
956 intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
957 ) -> Result<IntentGroupId, DispatchError> {
958 let intent_group_id = Self::get_next_intent_group_id()?;
959
960 <CurrentIntentGroupIdentifierMaximum<T>>::set(intent_group_id);
961 Self::store_intent_group(intent_group_id, intent_ids, intent_group_name)?;
962 Ok(intent_group_id)
963 }
964
965 pub fn update_intent_group_storage(
970 intent_group_id: IntentGroupId,
971 intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
972 ) -> DispatchResult {
973 IntentGroups::<T>::set(intent_group_id, Some(IntentGroup { intent_ids }));
974 Ok(())
975 }
976
977 pub fn get_schema_by_id(schema_id: SchemaId) -> Option<SchemaResponseV2> {
979 match (SchemaInfos::<T>::get(schema_id), SchemaPayloads::<T>::get(schema_id)) {
980 (Some(schema_info), Some(payload)) => {
981 let model_vec: Vec<u8> = payload.into_inner();
982 let response = SchemaResponseV2 {
983 schema_id,
984 intent_id: schema_info.intent_id,
985 model: model_vec,
986 model_type: schema_info.model_type,
987 payload_location: schema_info.payload_location,
988 settings: schema_info.settings.0.iter().collect::<Vec<IntentSetting>>(),
989 status: schema_info.status,
990 };
991 Some(response)
992 },
993 (None, Some(_)) | (Some(_), None) => {
994 log::error!("Corrupted state for schema {schema_id:?}, Should never happen!");
995 None
996 },
997 (None, None) => None,
998 }
999 }
1000
1001 pub fn get_schema_info_by_id(schema_id: SchemaId) -> Option<SchemaInfoResponse> {
1003 if let Some(schema_info) = SchemaInfos::<T>::get(schema_id) {
1004 let response = SchemaInfoResponse {
1005 schema_id,
1006 intent_id: schema_info.intent_id,
1007 model_type: schema_info.model_type,
1008 payload_location: schema_info.payload_location,
1009 settings: schema_info.settings.0.iter().collect::<Vec<IntentSetting>>(),
1010 status: schema_info.status,
1011 };
1012 return Some(response);
1013 }
1014 None
1015 }
1016
1017 pub fn get_intent_by_id(intent_id: IntentId) -> Option<IntentResponse> {
1019 IntentInfos::<T>::get(intent_id).map(|intent_info| IntentResponse {
1020 intent_id,
1021 payload_location: intent_info.payload_location,
1022 settings: intent_info.settings.0.iter().collect::<Vec<IntentSetting>>(),
1023 schema_ids: None,
1024 })
1025 }
1026
1027 pub fn get_intent_by_id_with_schemas(intent_id: IntentId) -> Option<IntentResponse> {
1030 let response = Self::get_intent_by_id(intent_id);
1031 response.map(|mut intent| {
1032 let mut schema_ids = SchemaInfos::<T>::iter()
1033 .filter(|(_, info)| {
1034 info.intent_id == intent_id && info.status != SchemaStatus::Unsupported
1035 })
1036 .map(|(id, _)| id)
1037 .collect::<Vec<SchemaId>>();
1038 schema_ids.sort();
1039 intent.schema_ids = Some(schema_ids);
1040 intent
1041 })
1042 }
1043
1044 pub fn get_intent_group_by_id(
1046 intent_group_id: IntentGroupId,
1047 ) -> Option<IntentGroupResponse> {
1048 if let Some(intent_group) = IntentGroups::<T>::get(intent_group_id) {
1049 return Some(IntentGroupResponse {
1050 intent_group_id,
1051 intent_ids: intent_group.intent_ids.into(),
1052 });
1053 }
1054 None
1055 }
1056
1057 pub fn ensure_valid_model(
1064 model_type: &ModelType,
1065 model: &BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
1066 ) -> DispatchResult {
1067 match *model_type {
1068 ModelType::Parquet => {
1069 serde_json::from_slice::<ParquetModel>(model)
1070 .map_err(|_| Error::<T>::InvalidSchema)?;
1071 },
1072 ModelType::AvroBinary => serde::validate_json_model(model.clone().into_inner())
1073 .map_err(|_| Error::<T>::InvalidSchema)?,
1074 };
1075 Ok(())
1076 }
1077
1078 fn get_next_schema_id() -> Result<SchemaId, DispatchError> {
1084 let next = CurrentSchemaIdentifierMaximum::<T>::get()
1085 .checked_add(1)
1086 .ok_or(Error::<T>::SchemaCountOverflow)?;
1087
1088 Ok(next)
1089 }
1090
1091 fn get_next_intent_id() -> Result<IntentId, DispatchError> {
1097 let next = CurrentIntentIdentifierMaximum::<T>::get()
1098 .checked_add(1)
1099 .ok_or(Error::<T>::IntentCountOverflow)?;
1100
1101 Ok(next)
1102 }
1103
1104 fn get_next_intent_group_id() -> Result<IntentGroupId, DispatchError> {
1110 let next = CurrentIntentGroupIdentifierMaximum::<T>::get()
1111 .checked_add(1)
1112 .ok_or(Error::<T>::IntentGroupCountOverflow)?;
1113 Ok(next)
1114 }
1115
1116 pub fn create_schema_for(
1129 intent_id: IntentId,
1130 model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
1131 model_type: ModelType,
1132 ) -> Result<SchemaId, DispatchError> {
1133 Self::ensure_valid_model(&model_type, &model)?;
1134 ensure!(
1135 model.len() >= T::MinSchemaModelSizeBytes::get() as usize,
1136 Error::<T>::LessThanMinSchemaModelBytes
1137 );
1138 ensure!(
1139 model.len() <= GovernanceSchemaModelMaxBytes::<T>::get() as usize,
1140 Error::<T>::ExceedsMaxSchemaModelBytes
1141 );
1142 let intent_info =
1143 IntentInfos::<T>::get(intent_id).ok_or(Error::<T>::InvalidIntentId)?;
1144 let schema_id = Self::add_schema(
1145 intent_id,
1146 model,
1147 model_type,
1148 intent_info.payload_location,
1149 intent_info.settings,
1150 SchemaStatus::Active,
1151 )?;
1152 Ok(schema_id)
1153 }
1154
1155 pub fn create_intent_for(
1170 intent_name_payload: SchemaNamePayload,
1171 payload_location: PayloadLocation,
1172 settings: BoundedVec<IntentSetting, T::MaxSchemaSettingsPerSchema>,
1173 ) -> Result<(IntentId, SchemaName), DispatchError> {
1174 ensure!(
1176 !settings.contains(&IntentSetting::AppendOnly) ||
1177 payload_location == PayloadLocation::Itemized,
1178 Error::<T>::InvalidSetting
1179 );
1180 ensure!(
1182 !settings.contains(&IntentSetting::SignatureRequired) ||
1183 payload_location == PayloadLocation::Itemized ||
1184 payload_location == PayloadLocation::Paginated,
1185 Error::<T>::InvalidSetting
1186 );
1187 let parsed_name = Self::parse_and_verify_new_name(&intent_name_payload)?;
1188 let intent_id = Self::add_intent(payload_location, settings, &parsed_name)?;
1189 Ok((intent_id, parsed_name))
1190 }
1191
1192 pub fn create_intent_group_for(
1207 intent_group_name: SchemaNamePayload,
1208 intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
1209 ) -> Result<(IntentGroupId, SchemaName), DispatchError> {
1210 let intent_group_name = Self::parse_and_verify_new_name(&intent_group_name)?;
1211 Self::validate_intent_ids(&intent_ids)?;
1212 let intent_id = Self::add_intent_group(&intent_group_name, intent_ids)?;
1213 Ok((intent_id, intent_group_name))
1214 }
1215
1216 pub fn update_intent_group_for(
1222 intent_group_id: IntentGroupId,
1223 intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
1224 ) -> Result<(), DispatchError> {
1225 ensure!(
1226 IntentGroups::<T>::contains_key(intent_group_id),
1227 Error::<T>::InvalidIntentGroupId
1228 );
1229 Self::validate_intent_ids(&intent_ids)?;
1230 Self::update_intent_group_storage(intent_group_id, intent_ids)?;
1231 Ok(())
1232 }
1233
1234 pub fn validate_intent_ids(
1240 intent_ids: &BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
1241 ) -> Result<(), DispatchError> {
1242 let max_intent_id = CurrentIntentIdentifierMaximum::<T>::get();
1243 intent_ids.iter().try_for_each::<_, _>(|intent_id| {
1244 ensure!(*intent_id <= max_intent_id, Error::<T>::InvalidIntentId);
1245 Ok::<(), DispatchError>(())
1246 })?;
1247 Ok(())
1248 }
1249
1250 pub fn get_schema_versions(schema_name: Vec<u8>) -> Option<Vec<SchemaVersionResponse>> {
1253 if let Some(mut entities) = Self::get_intent_or_group_ids_by_name(schema_name.clone()) {
1254 entities.retain(|e| matches!(e.entity_id, MappedEntityIdentifier::Intent(_)));
1255 return Some(
1256 entities
1257 .iter()
1258 .flat_map(|e| {
1259 let schema_ids: Vec<SchemaId> = SchemaInfos::<T>::iter()
1260 .filter_map(|(id, info)| match e.entity_id {
1261 MappedEntityIdentifier::Intent(intent_id) => {
1262 if info.intent_id == intent_id {
1263 Some(id)
1264 } else {
1265 None
1266 }
1267 },
1268 _ => None,
1269 })
1270 .collect();
1271 schema_ids.convert_to_response(&e.name)
1272 })
1273 .collect::<Vec<SchemaVersionResponse>>(),
1274 );
1275 }
1276 None
1277 }
1278
1279 pub fn get_intent_or_group_ids_by_name(
1282 entity_name: Vec<u8>,
1283 ) -> Option<Vec<NameLookupResponse>> {
1284 let parsed_name =
1285 FullyQualifiedName::try_parse::<T>(BoundedVec::try_from(entity_name).ok()?, false)
1286 .ok()?;
1287
1288 if parsed_name.descriptor_exists() {
1289 if let Ok(id) = NameToMappedEntityIds::<T>::try_get(
1290 &parsed_name.namespace,
1291 &parsed_name.descriptor,
1292 ) {
1293 return Some(vec![id.convert_to_response(&parsed_name)]);
1294 }
1295 return None;
1296 }
1297
1298 let responses: Vec<NameLookupResponse> =
1299 NameToMappedEntityIds::<T>::iter_prefix(&parsed_name.namespace)
1300 .map(|(descriptor, val)| {
1301 val.convert_to_response(&parsed_name.new_with_descriptor(descriptor))
1302 })
1303 .collect();
1304
1305 (!responses.is_empty()).then_some(responses)
1306 }
1307
1308 fn parse_and_verify_new_name(
1318 name: &SchemaNamePayload,
1319 ) -> Result<SchemaName, DispatchError> {
1320 let parsed_name = SchemaName::try_parse::<T>(name.clone(), true)?;
1321
1322 ensure!(
1323 !NameToMappedEntityIds::<T>::contains_key(
1324 &parsed_name.namespace,
1325 &parsed_name.descriptor,
1326 ),
1327 Error::<T>::NameAlreadyExists,
1328 );
1329
1330 Ok(parsed_name)
1331 }
1332 }
1333}
1334
1335#[allow(clippy::unwrap_used)]
1336#[cfg(feature = "runtime-benchmarks")]
1337impl<T: Config> SchemaBenchmarkHelper for Pallet<T> {
1338 fn set_schema_count(schema_id: SchemaId) {
1340 Self::set_schema_count(schema_id);
1341 }
1342
1343 fn set_intent_count(intent_id: IntentId) {
1345 Self::set_intent_count(intent_id)
1346 }
1347
1348 fn create_schema(
1350 intent_id: IntentId,
1351 model: Vec<u8>,
1352 model_type: ModelType,
1353 payload_location: PayloadLocation,
1354 ) -> Result<SchemaId, DispatchError> {
1355 let model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit> =
1356 model.try_into().unwrap();
1357 Self::ensure_valid_model(&model_type, &model)?;
1358 let schema_id = Self::add_schema(
1359 intent_id,
1360 model,
1361 model_type,
1362 payload_location,
1363 IntentSettings::default(),
1364 SchemaStatus::Active,
1365 )?;
1366 Ok(schema_id)
1367 }
1368
1369 fn create_intent(
1371 name_payload: Vec<u8>,
1372 payload_location: PayloadLocation,
1373 settings: Vec<IntentSetting>,
1374 ) -> Result<IntentId, DispatchError> {
1375 let name = BoundedVec::try_from(name_payload).unwrap();
1376 let (intent_id, _) =
1377 Self::create_intent_for(name, payload_location, settings.try_into().unwrap())?;
1378 Ok(intent_id)
1379 }
1380}
1381
1382impl<T: Config> SchemaValidator<SchemaId> for Pallet<T> {
1383 fn are_all_intent_ids_valid(intent_ids: &[IntentId]) -> bool {
1384 let latest_issue_intent_id = CurrentIntentIdentifierMaximum::<T>::get();
1385 intent_ids.iter().all(|id| id <= &latest_issue_intent_id)
1386 }
1387
1388 #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
1389 fn set_schema_count(n: SchemaId) {
1390 Self::set_schema_count(n);
1391 }
1392
1393 #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
1394 fn set_intent_count(n: IntentId) {
1395 Self::set_intent_count(n);
1396 }
1397}
1398
1399impl<T: Config> SchemaProvider<SchemaId> for Pallet<T> {
1400 fn get_schema_by_id(schema_id: SchemaId) -> Option<SchemaResponseV2> {
1401 Self::get_schema_by_id(schema_id)
1402 }
1403
1404 fn get_schema_info_by_id(schema_id: SchemaId) -> Option<SchemaInfoResponse> {
1405 Self::get_schema_info_by_id(schema_id)
1406 }
1407
1408 fn get_intent_by_id(intent_id: IntentId) -> Option<IntentResponse> {
1409 Self::get_intent_by_id(intent_id)
1410 }
1411}