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 intent.schema_ids = Some(
1033 SchemaInfos::<T>::iter()
1034 .filter(|(_, info)| info.intent_id == intent_id)
1035 .map(|(id, _)| id)
1036 .collect::<Vec<SchemaId>>(),
1037 );
1038 intent
1039 })
1040 }
1041
1042 pub fn get_intent_group_by_id(
1044 intent_group_id: IntentGroupId,
1045 ) -> Option<IntentGroupResponse> {
1046 if let Some(intent_group) = IntentGroups::<T>::get(intent_group_id) {
1047 return Some(IntentGroupResponse {
1048 intent_group_id,
1049 intent_ids: intent_group.intent_ids.into(),
1050 });
1051 }
1052 None
1053 }
1054
1055 pub fn ensure_valid_model(
1062 model_type: &ModelType,
1063 model: &BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
1064 ) -> DispatchResult {
1065 match *model_type {
1066 ModelType::Parquet => {
1067 serde_json::from_slice::<ParquetModel>(model)
1068 .map_err(|_| Error::<T>::InvalidSchema)?;
1069 },
1070 ModelType::AvroBinary => serde::validate_json_model(model.clone().into_inner())
1071 .map_err(|_| Error::<T>::InvalidSchema)?,
1072 };
1073 Ok(())
1074 }
1075
1076 fn get_next_schema_id() -> Result<SchemaId, DispatchError> {
1082 let next = CurrentSchemaIdentifierMaximum::<T>::get()
1083 .checked_add(1)
1084 .ok_or(Error::<T>::SchemaCountOverflow)?;
1085
1086 Ok(next)
1087 }
1088
1089 fn get_next_intent_id() -> Result<IntentId, DispatchError> {
1095 let next = CurrentIntentIdentifierMaximum::<T>::get()
1096 .checked_add(1)
1097 .ok_or(Error::<T>::IntentCountOverflow)?;
1098
1099 Ok(next)
1100 }
1101
1102 fn get_next_intent_group_id() -> Result<IntentGroupId, DispatchError> {
1108 let next = CurrentIntentGroupIdentifierMaximum::<T>::get()
1109 .checked_add(1)
1110 .ok_or(Error::<T>::IntentGroupCountOverflow)?;
1111 Ok(next)
1112 }
1113
1114 pub fn create_schema_for(
1127 intent_id: IntentId,
1128 model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit>,
1129 model_type: ModelType,
1130 ) -> Result<SchemaId, DispatchError> {
1131 Self::ensure_valid_model(&model_type, &model)?;
1132 ensure!(
1133 model.len() >= T::MinSchemaModelSizeBytes::get() as usize,
1134 Error::<T>::LessThanMinSchemaModelBytes
1135 );
1136 ensure!(
1137 model.len() <= GovernanceSchemaModelMaxBytes::<T>::get() as usize,
1138 Error::<T>::ExceedsMaxSchemaModelBytes
1139 );
1140 let intent_info =
1141 IntentInfos::<T>::get(intent_id).ok_or(Error::<T>::InvalidIntentId)?;
1142 let schema_id = Self::add_schema(
1143 intent_id,
1144 model,
1145 model_type,
1146 intent_info.payload_location,
1147 intent_info.settings,
1148 SchemaStatus::Active,
1149 )?;
1150 Ok(schema_id)
1151 }
1152
1153 pub fn create_intent_for(
1168 intent_name_payload: SchemaNamePayload,
1169 payload_location: PayloadLocation,
1170 settings: BoundedVec<IntentSetting, T::MaxSchemaSettingsPerSchema>,
1171 ) -> Result<(IntentId, SchemaName), DispatchError> {
1172 ensure!(
1174 !settings.contains(&IntentSetting::AppendOnly) ||
1175 payload_location == PayloadLocation::Itemized,
1176 Error::<T>::InvalidSetting
1177 );
1178 ensure!(
1180 !settings.contains(&IntentSetting::SignatureRequired) ||
1181 payload_location == PayloadLocation::Itemized ||
1182 payload_location == PayloadLocation::Paginated,
1183 Error::<T>::InvalidSetting
1184 );
1185 let parsed_name = Self::parse_and_verify_new_name(&intent_name_payload)?;
1186 let intent_id = Self::add_intent(payload_location, settings, &parsed_name)?;
1187 Ok((intent_id, parsed_name))
1188 }
1189
1190 pub fn create_intent_group_for(
1205 intent_group_name: SchemaNamePayload,
1206 intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
1207 ) -> Result<(IntentGroupId, SchemaName), DispatchError> {
1208 let intent_group_name = Self::parse_and_verify_new_name(&intent_group_name)?;
1209 Self::validate_intent_ids(&intent_ids)?;
1210 let intent_id = Self::add_intent_group(&intent_group_name, intent_ids)?;
1211 Ok((intent_id, intent_group_name))
1212 }
1213
1214 pub fn update_intent_group_for(
1220 intent_group_id: IntentGroupId,
1221 intent_ids: BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
1222 ) -> Result<(), DispatchError> {
1223 ensure!(
1224 IntentGroups::<T>::contains_key(intent_group_id),
1225 Error::<T>::InvalidIntentGroupId
1226 );
1227 Self::validate_intent_ids(&intent_ids)?;
1228 Self::update_intent_group_storage(intent_group_id, intent_ids)?;
1229 Ok(())
1230 }
1231
1232 pub fn validate_intent_ids(
1238 intent_ids: &BoundedVec<IntentId, T::MaxIntentsPerIntentGroup>,
1239 ) -> Result<(), DispatchError> {
1240 let max_intent_id = CurrentIntentIdentifierMaximum::<T>::get();
1241 intent_ids.iter().try_for_each::<_, _>(|intent_id| {
1242 ensure!(*intent_id <= max_intent_id, Error::<T>::InvalidIntentId);
1243 Ok::<(), DispatchError>(())
1244 })?;
1245 Ok(())
1246 }
1247
1248 pub fn get_schema_versions(schema_name: Vec<u8>) -> Option<Vec<SchemaVersionResponse>> {
1251 if let Some(mut entities) = Self::get_intent_or_group_ids_by_name(schema_name.clone()) {
1252 entities.retain(|e| matches!(e.entity_id, MappedEntityIdentifier::Intent(_)));
1253 return Some(
1254 entities
1255 .iter()
1256 .flat_map(|e| {
1257 let schema_ids: Vec<SchemaId> = SchemaInfos::<T>::iter()
1258 .filter_map(|(id, info)| match e.entity_id {
1259 MappedEntityIdentifier::Intent(intent_id) => {
1260 if info.intent_id == intent_id {
1261 Some(id)
1262 } else {
1263 None
1264 }
1265 },
1266 _ => None,
1267 })
1268 .collect();
1269 schema_ids.convert_to_response(&e.name)
1270 })
1271 .collect::<Vec<SchemaVersionResponse>>(),
1272 );
1273 }
1274 None
1275 }
1276
1277 pub fn get_intent_or_group_ids_by_name(
1280 entity_name: Vec<u8>,
1281 ) -> Option<Vec<NameLookupResponse>> {
1282 let parsed_name =
1283 FullyQualifiedName::try_parse::<T>(BoundedVec::try_from(entity_name).ok()?, false)
1284 .ok()?;
1285
1286 if parsed_name.descriptor_exists() {
1287 if let Ok(id) = NameToMappedEntityIds::<T>::try_get(
1288 &parsed_name.namespace,
1289 &parsed_name.descriptor,
1290 ) {
1291 return Some(vec![id.convert_to_response(&parsed_name)]);
1292 }
1293 return None;
1294 }
1295
1296 let responses: Vec<NameLookupResponse> =
1297 NameToMappedEntityIds::<T>::iter_prefix(&parsed_name.namespace)
1298 .map(|(descriptor, val)| {
1299 val.convert_to_response(&parsed_name.new_with_descriptor(descriptor))
1300 })
1301 .collect();
1302
1303 (!responses.is_empty()).then_some(responses)
1304 }
1305
1306 fn parse_and_verify_new_name(
1316 name: &SchemaNamePayload,
1317 ) -> Result<SchemaName, DispatchError> {
1318 let parsed_name = SchemaName::try_parse::<T>(name.clone(), true)?;
1319
1320 ensure!(
1321 !NameToMappedEntityIds::<T>::contains_key(
1322 &parsed_name.namespace,
1323 &parsed_name.descriptor,
1324 ),
1325 Error::<T>::NameAlreadyExists,
1326 );
1327
1328 Ok(parsed_name)
1329 }
1330 }
1331}
1332
1333#[allow(clippy::unwrap_used)]
1334#[cfg(feature = "runtime-benchmarks")]
1335impl<T: Config> SchemaBenchmarkHelper for Pallet<T> {
1336 fn set_schema_count(schema_id: SchemaId) {
1338 Self::set_schema_count(schema_id);
1339 }
1340
1341 fn set_intent_count(intent_id: IntentId) {
1343 Self::set_intent_count(intent_id)
1344 }
1345
1346 fn create_schema(
1348 intent_id: IntentId,
1349 model: Vec<u8>,
1350 model_type: ModelType,
1351 payload_location: PayloadLocation,
1352 ) -> Result<SchemaId, DispatchError> {
1353 let model: BoundedVec<u8, T::SchemaModelMaxBytesBoundedVecLimit> =
1354 model.try_into().unwrap();
1355 Self::ensure_valid_model(&model_type, &model)?;
1356 let schema_id = Self::add_schema(
1357 intent_id,
1358 model,
1359 model_type,
1360 payload_location,
1361 IntentSettings::default(),
1362 SchemaStatus::Active,
1363 )?;
1364 Ok(schema_id)
1365 }
1366
1367 fn create_intent(
1369 name_payload: Vec<u8>,
1370 payload_location: PayloadLocation,
1371 settings: Vec<IntentSetting>,
1372 ) -> Result<IntentId, DispatchError> {
1373 let name = BoundedVec::try_from(name_payload).unwrap();
1374 let (intent_id, _) =
1375 Self::create_intent_for(name, payload_location, settings.try_into().unwrap())?;
1376 Ok(intent_id)
1377 }
1378}
1379
1380impl<T: Config> SchemaValidator<SchemaId> for Pallet<T> {
1381 fn are_all_intent_ids_valid(intent_ids: &[IntentId]) -> bool {
1382 let latest_issue_intent_id = CurrentIntentIdentifierMaximum::<T>::get();
1383 intent_ids.iter().all(|id| id <= &latest_issue_intent_id)
1384 }
1385
1386 #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
1387 fn set_schema_count(n: SchemaId) {
1388 Self::set_schema_count(n);
1389 }
1390
1391 #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))]
1392 fn set_intent_count(n: IntentId) {
1393 Self::set_intent_count(n);
1394 }
1395}
1396
1397impl<T: Config> SchemaProvider<SchemaId> for Pallet<T> {
1398 fn get_schema_by_id(schema_id: SchemaId) -> Option<SchemaResponseV2> {
1399 Self::get_schema_by_id(schema_id)
1400 }
1401
1402 fn get_schema_info_by_id(schema_id: SchemaId) -> Option<SchemaInfoResponse> {
1403 Self::get_schema_info_by_id(schema_id)
1404 }
1405
1406 fn get_intent_by_id(intent_id: IntentId) -> Option<IntentResponse> {
1407 Self::get_intent_by_id(intent_id)
1408 }
1409}