1#![doc = include_str!("../README.md")]
11#![allow(clippy::expect_used)]
13#![cfg_attr(not(feature = "std"), no_std)]
15#![deny(
17 rustdoc::broken_intra_doc_links,
18 rustdoc::missing_crate_level_docs,
19 rustdoc::invalid_codeblock_attributes,
20 missing_docs
21)]
22
23#[cfg(feature = "runtime-benchmarks")]
24mod benchmarking;
25#[cfg(test)]
26mod tests;
27
28pub mod weights;
29
30mod types;
31
32use core::{convert::TryInto, fmt::Debug};
33use frame_support::{ensure, pallet_prelude::Weight, traits::Get, BoundedVec};
34use sp_runtime::DispatchError;
35
36extern crate alloc;
37use alloc::vec::Vec;
38use common_primitives::{
39 messages::*,
40 msa::{
41 DelegatorId, MessageSourceId, MsaLookup, MsaValidator, ProviderId, SchemaGrantValidator,
42 },
43 schema::*,
44};
45use frame_support::dispatch::DispatchResult;
46use parity_scale_codec::Encode;
47
48#[cfg(feature = "runtime-benchmarks")]
49use common_primitives::benchmarks::{MsaBenchmarkHelper, SchemaBenchmarkHelper};
50
51pub use pallet::*;
52pub use types::*;
53pub use weights::*;
54
55use cid::Cid;
56use frame_system::pallet_prelude::*;
57
58#[frame_support::pallet]
59pub mod pallet {
60 use super::*;
61 use frame_support::pallet_prelude::*;
62
63 pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
65
66 #[pallet::config]
67 pub trait Config: frame_system::Config {
68 #[allow(deprecated)]
70 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
71
72 type WeightInfo: WeightInfo;
74
75 type MsaInfoProvider: MsaLookup + MsaValidator<AccountId = Self::AccountId>;
77
78 type SchemaGrantValidator: SchemaGrantValidator<BlockNumberFor<Self>>;
80
81 type SchemaProvider: SchemaProvider<SchemaId>;
83
84 #[pallet::constant]
86 type MessagesMaxPayloadSizeBytes: Get<u32> + Clone + Debug + MaxEncodedLen;
87
88 #[cfg(feature = "runtime-benchmarks")]
89 type MsaBenchmarkHelper: MsaBenchmarkHelper<Self::AccountId>;
91
92 #[cfg(feature = "runtime-benchmarks")]
93 type SchemaBenchmarkHelper: SchemaBenchmarkHelper;
95 }
96
97 #[pallet::pallet]
98 #[pallet::storage_version(STORAGE_VERSION)]
99 pub struct Pallet<T>(_);
100
101 #[pallet::storage]
104 #[pallet::whitelist_storage]
105 pub(super) type BlockMessageIndex<T: Config> = StorageValue<_, MessageIndex, ValueQuery>;
106
107 #[pallet::storage]
108 pub(super) type MessagesV2<T: Config> = StorageNMap<
109 _,
110 (
111 storage::Key<Twox64Concat, BlockNumberFor<T>>,
112 storage::Key<Twox64Concat, SchemaId>,
113 storage::Key<Twox64Concat, MessageIndex>,
114 ),
115 Message<T::MessagesMaxPayloadSizeBytes>,
116 OptionQuery,
117 >;
118
119 #[pallet::error]
120 pub enum Error<T> {
121 TooManyMessagesInBlock,
123
124 ExceedsMaxMessagePayloadSizeBytes,
126
127 TypeConversionOverflow,
129
130 InvalidMessageSourceAccount,
132
133 InvalidSchemaId,
135
136 UnAuthorizedDelegate,
138
139 InvalidPayloadLocation,
141
142 UnsupportedCidVersion,
144
145 InvalidCid,
147 }
148
149 #[pallet::event]
150 #[pallet::generate_deposit(pub(super) fn deposit_event)]
151 pub enum Event<T: Config> {
152 MessagesStored {
155 schema_id: SchemaId,
157 block_number: BlockNumberFor<T>,
159 },
160 MessagesInBlock,
162 }
163
164 #[pallet::hooks]
165 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
166 fn on_initialize(_current: BlockNumberFor<T>) -> Weight {
167 <BlockMessageIndex<T>>::set(0u16);
168 T::DbWeight::get().reads(1u64).saturating_add(T::DbWeight::get().writes(1u64))
170 }
172 }
173
174 #[pallet::call]
175 impl<T: Config> Pallet<T> {
176 #[pallet::call_index(0)]
195 #[pallet::weight(T::WeightInfo::add_ipfs_message())]
196 pub fn add_ipfs_message(
197 origin: OriginFor<T>,
198 #[pallet::compact] schema_id: SchemaId,
199 cid: Vec<u8>,
200 #[pallet::compact] payload_length: u32,
201 ) -> DispatchResult {
202 let provider_key = ensure_signed(origin)?;
203 let cid_binary = Self::validate_cid(&cid)?;
204 let payload_tuple: OffchainPayloadType = (cid_binary, payload_length);
205 let bounded_payload: BoundedVec<u8, T::MessagesMaxPayloadSizeBytes> = payload_tuple
206 .encode()
207 .try_into()
208 .map_err(|_| Error::<T>::ExceedsMaxMessagePayloadSizeBytes)?;
209
210 if let Some(schema) = T::SchemaProvider::get_schema_info_by_id(schema_id) {
211 ensure!(
212 schema.payload_location == PayloadLocation::IPFS,
213 Error::<T>::InvalidPayloadLocation
214 );
215
216 let provider_msa_id = Self::find_msa_id(&provider_key)?;
217 let current_block = frame_system::Pallet::<T>::block_number();
218 if Self::add_message(
219 provider_msa_id,
220 None,
221 bounded_payload,
222 schema_id,
223 current_block,
224 )? {
225 Self::deposit_event(Event::MessagesInBlock);
226 }
227 Ok(())
228 } else {
229 Err(Error::<T>::InvalidSchemaId.into())
230 }
231 }
232
233 #[pallet::call_index(1)]
247 #[pallet::weight(T::WeightInfo::add_onchain_message(payload.len() as u32))]
248 pub fn add_onchain_message(
249 origin: OriginFor<T>,
250 on_behalf_of: Option<MessageSourceId>,
251 #[pallet::compact] schema_id: SchemaId,
252 payload: Vec<u8>,
253 ) -> DispatchResult {
254 let provider_key = ensure_signed(origin)?;
255
256 let bounded_payload: BoundedVec<u8, T::MessagesMaxPayloadSizeBytes> =
257 payload.try_into().map_err(|_| Error::<T>::ExceedsMaxMessagePayloadSizeBytes)?;
258
259 if let Some(schema) = T::SchemaProvider::get_schema_info_by_id(schema_id) {
260 ensure!(
261 schema.payload_location == PayloadLocation::OnChain,
262 Error::<T>::InvalidPayloadLocation
263 );
264
265 let provider_msa_id = Self::find_msa_id(&provider_key)?;
266 let provider_id = ProviderId(provider_msa_id);
267
268 let current_block = frame_system::Pallet::<T>::block_number();
269 let maybe_delegator = match on_behalf_of {
271 Some(delegator_msa_id) => {
272 let delegator_id = DelegatorId(delegator_msa_id);
273 T::SchemaGrantValidator::ensure_valid_schema_grant(
274 provider_id,
275 delegator_id,
276 schema_id,
277 current_block,
278 )
279 .map_err(|_| Error::<T>::UnAuthorizedDelegate)?;
280 delegator_id
281 },
282 None => DelegatorId(provider_msa_id), };
284
285 if Self::add_message(
286 provider_msa_id,
287 Some(maybe_delegator.into()),
288 bounded_payload,
289 schema_id,
290 current_block,
291 )? {
292 Self::deposit_event(Event::MessagesInBlock);
293 }
294
295 Ok(())
296 } else {
297 Err(Error::<T>::InvalidSchemaId.into())
298 }
299 }
300 }
301}
302
303impl<T: Config> Pallet<T> {
304 pub fn add_message(
310 provider_msa_id: MessageSourceId,
311 msa_id: Option<MessageSourceId>,
312 payload: BoundedVec<u8, T::MessagesMaxPayloadSizeBytes>,
313 schema_id: SchemaId,
314 current_block: BlockNumberFor<T>,
315 ) -> Result<bool, DispatchError> {
316 let index = BlockMessageIndex::<T>::get();
317 let first = index == 0;
318 let msg = Message {
319 payload, provider_msa_id,
321 msa_id,
322 };
323
324 <MessagesV2<T>>::insert((current_block, schema_id, index), msg);
325 BlockMessageIndex::<T>::set(index.saturating_add(1));
326 Ok(first)
327 }
328
329 pub fn find_msa_id(key: &T::AccountId) -> Result<MessageSourceId, DispatchError> {
336 Ok(T::MsaInfoProvider::ensure_valid_msa_key(key)
337 .map_err(|_| Error::<T>::InvalidMessageSourceAccount)?)
338 }
339
340 pub fn get_messages_by_schema_and_block(
347 schema_id: SchemaId,
348 schema_payload_location: PayloadLocation,
349 block_number: BlockNumberFor<T>,
350 ) -> Vec<MessageResponse> {
351 let block_number_value: u32 = block_number.try_into().unwrap_or_default();
352
353 match schema_payload_location {
354 PayloadLocation::Itemized | PayloadLocation::Paginated => Vec::new(),
355 _ => {
356 let mut messages: Vec<_> = <MessagesV2<T>>::iter_prefix((block_number, schema_id))
357 .map(|(index, msg)| {
358 msg.map_to_response(block_number_value, schema_payload_location, index)
359 })
360 .collect();
361 messages.sort_by(|a, b| a.index.cmp(&b.index));
362 messages
363 },
364 }
365 }
366
367 pub fn validate_cid(in_cid: &[u8]) -> Result<Vec<u8>, DispatchError> {
374 let cid_str: &str = core::str::from_utf8(in_cid).map_err(|_| Error::<T>::InvalidCid)?;
376 ensure!(cid_str.len() > 2, Error::<T>::InvalidCid);
377 ensure!(!cid_str.starts_with("Qm"), Error::<T>::UnsupportedCidVersion);
379
380 let cid_b = multibase::decode(cid_str).map_err(|_| Error::<T>::InvalidCid)?.1;
382 ensure!(Cid::read_bytes(&cid_b[..]).is_ok(), Error::<T>::InvalidCid);
383
384 Ok(cid_b)
385 }
386}