common_primitives/
messages.rs

1#[cfg(feature = "std")]
2use crate::utils;
3use crate::{msa::MessageSourceId, node::BlockNumber};
4use parity_scale_codec::{Decode, Encode};
5use scale_info::TypeInfo;
6#[cfg(feature = "std")]
7use serde::{Deserialize, Serialize};
8use sp_runtime::traits::One;
9extern crate alloc;
10use alloc::{vec, vec::Vec};
11#[cfg(feature = "std")]
12use utils::*;
13
14/// A type for responding with an single Message in an RPC-call dependent on schema model
15/// IPFS, Parquet: { index, block_number, provider_msa_id, cid, payload_length }
16/// Avro, OnChain: { index, block_number, provider_msa_id, msa_id, payload }
17#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
18#[derive(Default, Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq)]
19pub struct MessageResponse {
20	/// Message source account id of the Provider. This may be the same id as contained in `msa_id`,
21	/// indicating that the original source MSA is acting as its own provider. An id differing from that
22	/// of `msa_id` indicates that `provider_msa_id` was delegated by `msa_id` to send this message on
23	/// its behalf .
24	pub provider_msa_id: MessageSourceId,
25	/// Index in block to get total order.
26	pub index: u16,
27	/// Block-number for which the message was stored.
28	pub block_number: BlockNumber,
29	///  Message source account id (the original source).
30	#[cfg_attr(feature = "std", serde(skip_serializing_if = "Option::is_none", default))]
31	pub msa_id: Option<MessageSourceId>,
32	/// Serialized data in a the schemas.
33	#[cfg_attr(
34		feature = "std",
35		serde(with = "as_hex_option", skip_serializing_if = "Option::is_none", default)
36	)]
37	pub payload: Option<Vec<u8>>,
38	/// The content address for an IPFS payload in Base32. Will always be CIDv1.
39	#[cfg_attr(
40		feature = "std",
41		serde(with = "as_string_option", skip_serializing_if = "Option::is_none", default)
42	)]
43	pub cid: Option<Vec<u8>>,
44	///  Offchain payload length (IPFS).
45	#[cfg_attr(feature = "std", serde(skip_serializing_if = "Option::is_none", default))]
46	pub payload_length: Option<u32>,
47}
48/// A type for requesting paginated messages.
49#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
50#[derive(Default, Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq)]
51pub struct BlockPaginationRequest {
52	/// Starting block-number (inclusive).
53	pub from_block: BlockNumber,
54	/// Current page index starting from 0.
55	pub from_index: u32,
56	/// Ending block-number (exclusive).
57	pub to_block: BlockNumber,
58	/// The number of messages in a single page.
59	pub page_size: u32,
60}
61
62impl BlockPaginationRequest {
63	/// Hard limit on the number of items per page that can be returned
64	pub const MAX_PAGE_SIZE: u32 = 10000;
65	/// Hard limit on the block range for a request (~7 days at 12 sec per block)
66	pub const MAX_BLOCK_RANGE: u32 = 50000; // ~3 days (6 sec per block)= ~7 days (12 sec per block)
67
68	/// Helper function for request validation.
69	/// * Page size should not exceed MAX_PAGE_SIZE.
70	/// * Block range [from_block:to_block) should not exceed MAX_BLOCK_RANGE.
71	pub fn validate(&self) -> bool {
72		self.page_size > 0 &&
73			self.page_size <= Self::MAX_PAGE_SIZE &&
74			self.from_block < self.to_block &&
75			self.to_block - self.from_block <= Self::MAX_BLOCK_RANGE
76	}
77}
78
79/// A type for responding with a collection of paginated messages.
80#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
81#[derive(Default, Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Eq)]
82pub struct BlockPaginationResponse<T> {
83	/// Collection of messages for a given [`BlockPaginationRequest`].
84	pub content: Vec<T>,
85	/// Flag to indicate the end of paginated messages.
86	pub has_next: bool,
87	#[cfg_attr(feature = "std", serde(skip_serializing_if = "Option::is_none"))]
88	/// Flag to indicate the starting block number for the next page.
89	pub next_block: Option<BlockNumber>,
90	#[cfg_attr(feature = "std", serde(skip_serializing_if = "Option::is_none"))]
91	/// Flag to indicate the next index for the following request.
92	pub next_index: Option<u32>,
93}
94
95impl<T> BlockPaginationResponse<T> {
96	/// Generates a new empty Pagination request
97	pub const fn new() -> BlockPaginationResponse<T> {
98		BlockPaginationResponse {
99			content: vec![],
100			has_next: false,
101			next_block: None,
102			next_index: None,
103		}
104	}
105
106	/// Checks if we are at the end of the pagination
107	/// if we are, update the response with the correct next information
108	pub fn check_end_condition_and_set_next_pagination(
109		&mut self,
110		block_number: BlockNumber,
111		current_index: u32,
112		list_size: u32,
113		request: &BlockPaginationRequest,
114	) -> bool {
115		if self.content.len() as u32 == request.page_size {
116			let mut next_block = block_number;
117			let mut next_index = current_index + 1;
118
119			// checking if it's end of current list
120			if next_index == list_size {
121				next_block = block_number + BlockNumber::one();
122				next_index = 0;
123			}
124
125			if next_block < request.to_block {
126				self.has_next = true;
127				self.next_block = Some(next_block);
128				self.next_index = Some(next_index);
129			}
130			return true
131		}
132
133		false
134	}
135}
136
137#[cfg(test)]
138mod tests {
139	use crate::{
140		messages::{BlockPaginationRequest, BlockPaginationResponse, MessageResponse},
141		node::BlockNumber,
142	};
143
144	struct TestCase<T> {
145		input: BlockPaginationRequest,
146		expected: T,
147		message: String,
148	}
149
150	#[test]
151	fn as_hex_option_msg_ipfs_serialize_deserialize_test() {
152		// skip deserialize if Option::none works
153		let msg = MessageResponse {
154			payload: None,
155			msa_id: None,
156			provider_msa_id: 1,
157			index: 1,
158			block_number: 1,
159			cid: Some(
160				"bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq"
161					.as_bytes()
162					.to_vec(),
163			),
164			payload_length: Some(42),
165		};
166		let serialized = serde_json::to_string(&msg).unwrap();
167		assert_eq!(serialized, "{\"provider_msa_id\":1,\"index\":1,\"block_number\":1,\"cid\":\"bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq\",\"payload_length\":42}");
168
169		let deserialized: MessageResponse = serde_json::from_str(&serialized).unwrap();
170		assert_eq!(deserialized, msg);
171	}
172
173	#[test]
174	fn as_hex_option_empty_payload_deserialize_as_default_value() {
175		let expected_msg = MessageResponse {
176			payload: None,
177			msa_id: Some(1),
178			provider_msa_id: 1,
179			index: 1,
180			block_number: 1,
181			cid: None,
182			payload_length: None,
183		};
184
185		// Notice Payload field is missing
186		let serialized_msg_without_payload =
187			"{\"provider_msa_id\":1,\"index\":1,\"block_number\":1,\"msa_id\":1}";
188
189		let deserialized_result: MessageResponse =
190			serde_json::from_str(serialized_msg_without_payload).unwrap();
191		assert_eq!(deserialized_result, expected_msg);
192	}
193
194	#[test]
195	fn block_pagination_request_validation_test() {
196		let test_cases: Vec<TestCase<bool>> = vec![
197			TestCase {
198				input: BlockPaginationRequest { from_block: 10, from_index: 0, to_block: 12, page_size: 1 },
199				expected: true,
200				message: "Should be valid".to_string(),
201			},
202			TestCase {
203				input: BlockPaginationRequest { from_block: 10, from_index: 0, to_block: 12, page_size: 0 },
204				expected: false,
205				message: "Page with size 0 is invalid".to_string(),
206			},
207			TestCase {
208				input: BlockPaginationRequest { from_block: 10, from_index: 0, to_block: 8, page_size: 1 },
209				expected: false,
210				message: "from_block should be less than to_block".to_string(),
211			},
212			TestCase {
213				input: BlockPaginationRequest { from_block: 10, from_index: 0, to_block: 8, page_size: 10000 + 1 },
214				expected: false,
215				message: "page_size should be less than MAX_PAGE_SIZE".to_string(),
216			},
217			TestCase {
218				input: BlockPaginationRequest { from_block: 1, from_index: 0, to_block: 50000 + 2, page_size: 1 },
219				expected: false,
220				message: "the difference between from_block and to_block should be less than MAX_BLOCK_RANGE".to_string(),
221			},
222		];
223
224		for tc in test_cases {
225			assert_eq!(tc.expected, tc.input.validate(), "{}", tc.message);
226		}
227	}
228
229	#[test]
230	fn check_end_condition_does_not_mutate_when_at_the_end() {
231		let mut resp = BlockPaginationResponse::<u32> {
232			content: vec![1, 2, 3],
233			has_next: false,
234			next_block: None,
235			next_index: None,
236		};
237
238		let total_data_length: u32 = resp.content.len() as u32;
239
240		let request = BlockPaginationRequest {
241			from_block: 1 as BlockNumber,
242			from_index: 0,
243			to_block: 5,
244			// Page is LARGER
245			page_size: total_data_length + 10,
246		};
247		// We are at the LAST block
248		let current_block = 5;
249		// Index after content
250		let current_index = total_data_length - 1;
251		// Critical Bit: NO more data than index
252		let list_size = current_index;
253		let is_full = resp.check_end_condition_and_set_next_pagination(
254			current_block,
255			current_index,
256			list_size,
257			&request,
258		);
259		// NOT FULL
260		assert!(!is_full);
261		// NOTHING MORE
262		assert!(!resp.has_next);
263		// None
264		assert_eq!(None, resp.next_block);
265		assert_eq!(None, resp.next_index);
266	}
267
268	#[test]
269	fn check_end_condition_mutates_when_more_in_list_than_page() {
270		let mut resp = BlockPaginationResponse::<u32> {
271			content: vec![1, 2, 3],
272			has_next: false,
273			next_block: None,
274			next_index: None,
275		};
276
277		let total_data_length: u32 = resp.content.len() as u32;
278
279		let request = BlockPaginationRequest {
280			from_block: 1 as BlockNumber,
281			from_index: 0,
282			to_block: 5,
283			page_size: total_data_length,
284		};
285		// We have not completed the block yet
286		let current_block = 1;
287		// End of the Block
288		let current_index = total_data_length - 1;
289		// Critical Bit: MORE Data to go in length than page_size
290		let list_size = total_data_length + 1;
291		let is_full = resp.check_end_condition_and_set_next_pagination(
292			current_block,
293			current_index,
294			list_size,
295			&request,
296		);
297		assert!(is_full);
298		assert!(resp.has_next);
299		// SAME block
300		assert_eq!(Some(1), resp.next_block);
301		// NEXT index
302		assert_eq!(Some(current_index + 1), resp.next_index);
303	}
304
305	#[test]
306	fn check_end_condition_mutates_when_more_than_page_but_none_left_in_block() {
307		let mut resp = BlockPaginationResponse::<u32> {
308			content: vec![1, 2, 3],
309			has_next: false,
310			next_block: None,
311			next_index: None,
312		};
313
314		let total_data_length: u32 = resp.content.len() as u32;
315
316		let request = BlockPaginationRequest {
317			from_block: 1 as BlockNumber,
318			from_index: 0,
319			to_block: 5,
320			page_size: total_data_length,
321		};
322		// We have not completed the block yet
323		let current_block = 1;
324		// End of the Block
325		let current_index = total_data_length - 1;
326		// SAME in length than page_size
327		let list_size = total_data_length;
328		let is_full = resp.check_end_condition_and_set_next_pagination(
329			current_block,
330			current_index,
331			list_size,
332			&request,
333		);
334		assert!(is_full);
335		assert!(resp.has_next);
336		// NEXT block
337		assert_eq!(Some(current_block + 1), resp.next_block);
338		// ZERO index
339		assert_eq!(Some(0), resp.next_index);
340	}
341}