pallet_handles/
lib.rs

1//! Unique human-readable strings for MSAs
2//!
3//! ## Quick Links
4//! - [Configuration: `Config`](Config)
5//! - [Extrinsics: `Call`](Call)
6//! - [Runtime API: `HandlesRuntimeApi`](../pallet_handles_runtime_api/trait.HandlesRuntimeApi.html)
7//! - [Custom RPC API: `HandlesApiServer`](../pallet_handles_rpc/trait.HandlesApiServer.html)
8//! - [Event Enum: `Event`](Event)
9//! - [Error Enum: `Error`](Error)
10#![doc = include_str!("../README.md")]
11//!
12//! ## Shuffled Sequences
13//!
14//! To limit the human value of low or particular suffixes, the pallet uses a shuffled sequence to choose a semi-randon suffix for a specific canonical handle string.
15//! The shuffle only requires storage of the current index, same as an ordered suffix system.
16//! The computational cost is greater the larger the index grows as it is lazy evaluation of the Rand32 given a deterministic seed generated from the canonical handle string.
17//! See more details at [`handles_utils::suffix`]
18//!
19// Substrate macros are tripping the clippy::expect_used lint.
20#![allow(clippy::expect_used)]
21#![cfg_attr(not(feature = "std"), no_std)]
22// Strong Documentation Lints
23#![deny(
24	rustdoc::broken_intra_doc_links,
25	rustdoc::missing_crate_level_docs,
26	rustdoc::invalid_codeblock_attributes,
27	missing_docs
28)]
29extern crate alloc;
30
31use alloc::{string::String, vec};
32
33#[cfg(feature = "runtime-benchmarks")]
34mod benchmarking;
35
36use handles_utils::{
37	converter::{convert_to_canonical, split_display_name, HANDLE_DELIMITER},
38	suffix::generate_unique_suffixes,
39	validator::{
40		consists_of_supported_unicode_character_sets, contains_blocked_characters,
41		is_reserved_canonical_handle,
42	},
43};
44
45#[cfg(test)]
46mod tests;
47
48use alloc::vec::Vec;
49#[cfg(feature = "runtime-benchmarks")]
50use common_primitives::benchmarks::MsaBenchmarkHelper;
51use common_primitives::{
52	handles::*,
53	msa::{MessageSourceId, MsaLookup, MsaValidator},
54	node::EIP712Encode,
55};
56use frame_support::{dispatch::DispatchResult, ensure, pallet_prelude::*, traits::Get};
57use frame_system::pallet_prelude::*;
58use numtoa::*;
59pub use pallet::*;
60use sp_core::crypto::AccountId32;
61use sp_runtime::{traits::Convert, DispatchError, MultiSignature};
62
63pub mod handles_signed_extension;
64
65use common_runtime::signature::check_signature;
66
67pub mod weights;
68pub use weights::*;
69
70impl<T: Config> HandleProvider for Pallet<T> {
71	fn get_handle_for_msa(msa_id: MessageSourceId) -> Option<HandleResponse> {
72		Self::get_handle_for_msa(msa_id)
73	}
74}
75
76#[frame_support::pallet]
77pub mod pallet {
78
79	use handles_utils::trim_and_collapse_whitespace;
80
81	use super::*;
82	#[pallet::config]
83	pub trait Config: frame_system::Config {
84		/// The overarching event type.
85		#[allow(deprecated)]
86		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
87
88		/// Weight information for extrinsics in this pallet.
89		type WeightInfo: WeightInfo;
90
91		/// AccountId truncated to 32 bytes
92		type ConvertIntoAccountId32: Convert<Self::AccountId, AccountId32>;
93
94		/// A type that will supply MSA related information
95		type MsaInfoProvider: MsaLookup + MsaValidator<AccountId = Self::AccountId>;
96
97		/// The minimum suffix value
98		#[pallet::constant]
99		type HandleSuffixMin: Get<SuffixRangeType>;
100
101		/// The maximum suffix value
102		#[pallet::constant]
103		type HandleSuffixMax: Get<SuffixRangeType>;
104
105		/// The number of blocks before a signature can be ejected from the PayloadSignatureRegistryList
106		#[pallet::constant]
107		type MortalityWindowSize: Get<u32>;
108
109		#[cfg(feature = "runtime-benchmarks")]
110		/// A set of helper functions for benchmarking.
111		type MsaBenchmarkHelper: MsaBenchmarkHelper<Self::AccountId>;
112	}
113
114	// Storage
115	#[pallet::pallet]
116	pub struct Pallet<T>(_);
117
118	/// - Keys: k1: `CanonicalBase`, k2: `HandleSuffix`
119	/// - Value: `MessageSourceId`
120	#[pallet::storage]
121	pub type CanonicalBaseHandleAndSuffixToMSAId<T: Config> = StorageDoubleMap<
122		_,
123		Blake2_128Concat,
124		CanonicalBase,
125		Twox64Concat,
126		HandleSuffix,
127		MessageSourceId,
128		OptionQuery,
129	>;
130
131	/// - Key: `MessageSourceId`
132	/// - Value: `DisplayHandle`
133	#[pallet::storage]
134	pub type MSAIdToDisplayName<T: Config> = StorageMap<
135		_,
136		Twox64Concat,
137		MessageSourceId,
138		(DisplayHandle, BlockNumberFor<T>),
139		OptionQuery,
140	>;
141
142	/// - Key: `CanonicalBase`
143	/// - Value: (Sequence Index, Suffix Min)
144	/// - Sequence Index: The index of the next suffix to be used for this handle
145	/// - Suffix Min: The minimum suffix value for this handle
146	#[pallet::storage]
147	pub type CanonicalBaseHandleToSuffixIndex<T: Config> = StorageMap<
148		_,
149		Blake2_128Concat,
150		CanonicalBase,
151		(SequenceIndex, SuffixRangeType),
152		OptionQuery,
153	>;
154
155	#[derive(PartialEq, Eq)] // for testing
156	#[pallet::error]
157	pub enum Error<T> {
158		/// Invalid handle encoding (should be UTF-8)
159		InvalidHandleEncoding,
160		/// Invalid handle byte length
161		InvalidHandleByteLength,
162		/// Invalid handle character length
163		InvalidHandleCharacterLength,
164		/// The handle name is reserved for chain use only
165		HandleIsNotAllowed,
166		/// The handle contains characters that are not allowed
167		HandleContainsBlockedCharacters,
168		/// The handle does not contain characters in the supported ranges of unicode characters
169		HandleDoesNotConsistOfSupportedCharacterSets,
170		/// Suffixes exhausted
171		SuffixesExhausted,
172		/// Invalid MSA
173		InvalidMessageSourceAccount,
174		/// Cryptographic signature failed verification
175		InvalidSignature,
176		/// The MSA already has a handle
177		MSAHandleAlreadyExists,
178		/// The MSA does not have a handle needed to retire it.
179		MSAHandleDoesNotExist,
180		/// The submitted proof has expired; the current block is less the expiration block
181		ProofHasExpired,
182		/// The submitted proof expiration block is too far in the future
183		ProofNotYetValid,
184		/// The handle is still in mortaility window
185		HandleWithinMortalityPeriod,
186		/// The handle is invalid
187		InvalidHandle,
188	}
189
190	#[pallet::event]
191	#[pallet::generate_deposit(pub (super) fn deposit_event)]
192	pub enum Event<T: Config> {
193		/// Deposited when a handle is claimed. [MSA id, display handle in UTF-8 bytes]
194		HandleClaimed {
195			/// MSA id of handle owner
196			msa_id: MessageSourceId,
197			/// UTF-8 string in bytes of the display handle
198			handle: Vec<u8>,
199		},
200		/// Deposited when a handle is retired. [MSA id, display handle in UTF-8 bytes]
201		HandleRetired {
202			/// MSA id of handle owner
203			msa_id: MessageSourceId,
204			/// UTF-8 string in bytes
205			handle: Vec<u8>,
206		},
207	}
208
209	impl<T: Config> Pallet<T> {
210		/// Gets the next suffix index for a canonical base.
211		///
212		/// This function takes a canonical base as input and returns the next available
213		/// suffix index for that handle. If no current suffix index is found for the handle,
214		/// it starts from 0. If a current suffix index is found, it increments it by 1 to
215		/// get the next suffix index. If the increment operation fails due to overflow, it
216		/// returns an error indicating that the suffixes are exhausted.
217		///
218		/// # Arguments
219		///
220		/// * `canonical_base` - The canonical base to get the next suffix index for.
221		///
222		/// # Returns
223		///
224		/// * `Ok(SequenceIndex)` - The next suffix index for the canonical base.
225		/// * `Err(DispatchError)` - The suffixes are exhausted.
226		pub fn get_next_suffix_index_for_canonical_handle(
227			canonical_base: CanonicalBase,
228		) -> Result<SequenceIndex, DispatchError> {
229			let next: SequenceIndex;
230			match CanonicalBaseHandleToSuffixIndex::<T>::get(canonical_base) {
231				None => {
232					next = 0;
233				},
234				Some((current, suffix_min)) => {
235					let current_suffix_min = T::HandleSuffixMin::get();
236					// if saved suffix min has changed, reset the suffix index
237					if current_suffix_min != suffix_min {
238						next = 0;
239					} else {
240						next = current.checked_add(1).ok_or(Error::<T>::SuffixesExhausted)?;
241					}
242				},
243			}
244
245			Ok(next)
246		}
247
248		/// Verifies that the signature is not expired.
249		///
250		/// This function takes a signature expiration block as input and verifies that
251		/// the signature is not expired. It returns an error if the signature is expired.
252		///
253		/// # Arguments
254		///
255		/// * `signature_expires_at` - The block number at which the signature expires.
256		///
257		/// # Returns
258		///
259		/// * `Ok(())` - The signature is not expired.
260		/// * `Err(DispatchError)` - The signature is expired.
261		pub fn verify_signature_mortality(
262			signature_expires_at: BlockNumberFor<T>,
263		) -> DispatchResult {
264			let current_block = frame_system::Pallet::<T>::block_number();
265
266			let mortality_period = Self::mortality_block_limit(current_block);
267			ensure!(mortality_period > signature_expires_at, Error::<T>::ProofNotYetValid);
268			ensure!(current_block < signature_expires_at, Error::<T>::ProofHasExpired);
269			Ok(())
270		}
271
272		/// Verifies the signature of a given payload, using the provided `signature`
273		/// and `signer` information.
274		///
275		/// # Arguments
276		///
277		/// * `signature` - The `MultiSignature` to verify against the payload.
278		/// * `signer` - The `T::AccountId` of the signer that signed the payload.
279		/// * `payload` - The payload to verify the signature against.
280		///
281		/// # Errors
282		/// * [`Error::InvalidSignature`]
283		pub fn verify_signed_payload<P>(
284			signature: &MultiSignature,
285			signer: &T::AccountId,
286			payload: &P,
287		) -> DispatchResult
288		where
289			P: Encode + EIP712Encode,
290		{
291			let key = T::ConvertIntoAccountId32::convert(signer.clone());
292
293			ensure!(check_signature(signature, key, payload), Error::<T>::InvalidSignature);
294
295			Ok(())
296		}
297
298		/// Verifies max user handle size in bytes to address potential panic condition
299		///
300		/// # Arguments
301		///
302		/// * `handle` - The user handle.
303		///
304		/// # Errors
305		/// * [`Error::InvalidHandleByteLength`]
306		pub fn verify_max_handle_byte_length(handle: Vec<u8>) -> DispatchResult {
307			ensure!(handle.len() as u32 <= HANDLE_BYTES_MAX, Error::<T>::InvalidHandleByteLength);
308			Ok(())
309		}
310
311		/// The furthest in the future a mortality_block value is allowed
312		/// to be for current_block
313		/// This is calculated to be past the risk of a replay attack
314		fn mortality_block_limit(current_block: BlockNumberFor<T>) -> BlockNumberFor<T> {
315			let mortality_size = T::MortalityWindowSize::get();
316			current_block + BlockNumberFor::<T>::from(mortality_size)
317		}
318		/// Generates a suffix for a canonical base, using the provided `canonical_base`
319		/// and `cursor` information.
320		///
321		/// # Arguments
322		///
323		/// * `canonical_base` - The canonical base to generate a suffix for.
324		/// * `cursor` - The cursor position in the sequence of suffixes to use for generation.
325		///
326		/// # Returns
327		///
328		/// The generated suffix as a `u16`.
329		///
330		pub fn generate_suffix_for_canonical_handle(
331			canonical_base: &str,
332			cursor: usize,
333		) -> Result<u16, DispatchError> {
334			let mut lazy_suffix_sequence = generate_unique_suffixes(
335				T::HandleSuffixMin::get(),
336				T::HandleSuffixMax::get(),
337				canonical_base,
338			);
339			match lazy_suffix_sequence.nth(cursor) {
340				Some(suffix) => Ok(suffix),
341				None => Err(Error::<T>::SuffixesExhausted.into()),
342			}
343		}
344	}
345
346	#[pallet::call]
347	impl<T: Config> Pallet<T> {
348		/// Claims a handle for a caller's MSA (Message Source Account) based on the provided payload.
349		/// This function performs several validations before claiming the handle, including checking
350		/// the size of the base_handle, ensuring the caller have valid MSA keys,
351		/// verifying the payload signature, and finally calling the internal `do_claim_handle` function
352		/// to claim the handle.
353		///
354		/// # Arguments
355		///
356		/// * `origin` - The origin of the caller.
357		/// * `msa_owner_key` - The public key of the MSA owner.
358		/// * `proof` - The multi-signature proof for the payload.
359		/// * `payload` - The payload containing the information needed to claim a new handle.
360		///
361		/// # Errors
362		///
363		/// This function may return an error as part of `DispatchResult` if any of the following
364		/// validations fail:
365		///
366		/// * [`Error::InvalidHandleByteLength`] - The base_handle size exceeds the maximum allowed size.
367		/// * [`Error::InvalidMessageSourceAccount`] - The caller does not have a valid  `MessageSourceId`.
368		/// * [`Error::InvalidSignature`] - The payload signature verification fails.
369		///
370		/// # Events
371		/// * [`Event::HandleClaimed`]
372		///
373		#[pallet::call_index(0)]
374		#[pallet::weight(T::WeightInfo::claim_handle(payload.base_handle.len() as u32))]
375		pub fn claim_handle(
376			origin: OriginFor<T>,
377			msa_owner_key: T::AccountId,
378			proof: MultiSignature,
379			payload: ClaimHandlePayload<BlockNumberFor<T>>,
380		) -> DispatchResult {
381			let _ = ensure_signed(origin)?;
382
383			// Validation: Check for base_handle size to address potential panic condition
384			Self::verify_max_handle_byte_length(payload.base_handle.clone())?;
385
386			// Validation: caller must already have a MSA id
387			let msa_id = T::MsaInfoProvider::ensure_valid_msa_key(&msa_owner_key)
388				.map_err(|_| Error::<T>::InvalidMessageSourceAccount)?;
389
390			// Validation: The signature is within the mortality window
391			Self::verify_signature_mortality(payload.expiration)?;
392
393			// Validation: Verify the payload was signed
394			Self::verify_signed_payload(&proof, &msa_owner_key, &payload)?;
395
396			let display_handle = Self::do_claim_handle(msa_id, payload)?;
397
398			Self::deposit_event(Event::HandleClaimed { msa_id, handle: display_handle.clone() });
399			Ok(())
400		}
401
402		/// Retire a handle for a given `DisplayHandle` owner.
403		///
404		/// # Arguments
405		///
406		/// * `origin` - The origin of the call.
407		///
408		/// # Errors
409		///
410		/// This function can return the following errors:
411		///
412		/// * `InvalidHandleByteLength` - If the length of the `payload.display_handle` exceeds the maximum allowed size.
413		/// * `InvalidMessageSourceAccount` - If caller of this extrinsic does not have a valid MSA (Message Source Account) ID.
414		///
415		/// # Events
416		/// * [`Event::HandleRetired`]
417		///
418		#[pallet::call_index(1)]
419		#[pallet::weight((T::WeightInfo::retire_handle(), DispatchClass::Normal, Pays::No))]
420		pub fn retire_handle(origin: OriginFor<T>) -> DispatchResult {
421			let msa_owner_key = ensure_signed(origin)?;
422
423			// Validation: The caller must already have a MSA id
424			let msa_id = T::MsaInfoProvider::ensure_valid_msa_key(&msa_owner_key)
425				.map_err(|_| Error::<T>::InvalidMessageSourceAccount)?;
426
427			let display_handle: Vec<u8> = Self::do_retire_handle(msa_id)?;
428
429			Self::deposit_event(Event::HandleRetired { msa_id, handle: display_handle });
430			Ok(())
431		}
432
433		/// Changes the handle for a caller's MSA (Message Source Account) based on the provided payload.
434		/// This function performs several validations before claiming the handle, including checking
435		/// the size of the base_handle, ensuring the caller has valid MSA keys,
436		/// verifying the payload signature, and finally calling the internal `do_retire_handle` and `do_claim_handle` functions
437		/// to retire the current handle and claim the new one.
438		///
439		/// # Arguments
440		///
441		/// * `origin` - The origin of the caller.
442		/// * `msa_owner_key` - The public key of the MSA owner.
443		/// * `proof` - The multi-signature proof for the payload.
444		/// * `payload` - The payload containing the information needed to change an existing handle.
445		///
446		/// # Errors
447		///
448		/// This function may return an error as part of `DispatchResult` if any of the following
449		/// validations fail:
450		///
451		/// * [`Error::InvalidHandleByteLength`] - The base_handle size exceeds the maximum allowed size.
452		/// * [`Error::InvalidMessageSourceAccount`] - The caller does not have a valid  `MessageSourceId`.
453		/// * [`Error::InvalidSignature`] - The payload signature verification fails.
454		///
455		/// # Events
456		/// * [`Event::HandleRetired`]
457		/// * [`Event::HandleClaimed`]
458		///
459		#[pallet::call_index(2)]
460		#[pallet::weight(T::WeightInfo::change_handle(payload.base_handle.len() as u32))]
461		pub fn change_handle(
462			origin: OriginFor<T>,
463			msa_owner_key: T::AccountId,
464			proof: MultiSignature,
465			payload: ClaimHandlePayload<BlockNumberFor<T>>,
466		) -> DispatchResult {
467			let _ = ensure_signed(origin)?;
468
469			// Validation: Check for base_handle size to address potential panic condition
470			Self::verify_max_handle_byte_length(payload.base_handle.clone())?;
471
472			// Validation: caller must already have a MSA id
473			let msa_id = T::MsaInfoProvider::ensure_valid_msa_key(&msa_owner_key)
474				.map_err(|_| Error::<T>::InvalidMessageSourceAccount)?;
475
476			// Validation: The signature is within the mortality window
477			Self::verify_signature_mortality(payload.expiration)?;
478
479			// Validation: Verify the payload was signed
480			Self::verify_signed_payload(&proof, &msa_owner_key, &payload)?;
481
482			// Get existing handle to retire
483			MSAIdToDisplayName::<T>::get(msa_id).ok_or(Error::<T>::MSAHandleDoesNotExist)?;
484
485			// Retire old handle
486			let old_display_handle: Vec<u8> = Self::do_retire_handle(msa_id)?;
487			Self::deposit_event(Event::HandleRetired { msa_id, handle: old_display_handle });
488
489			let display_handle = Self::do_claim_handle(msa_id, payload.clone())?;
490
491			Self::deposit_event(Event::HandleClaimed { msa_id, handle: display_handle.clone() });
492
493			Ok(())
494		}
495	}
496
497	impl<T: Config> Pallet<T> {
498		/// Retrieves a handle for a given MSA (MessageSourceId).
499		///
500		/// # Arguments
501		///
502		/// * `msa_id` - The MSA ID to retrieve the handle for.
503		///
504		/// # Returns
505		///
506		/// * `Option<HandleResponse>` - The handle response if the MSA ID is valid.
507		///
508		pub fn get_handle_for_msa(msa_id: MessageSourceId) -> Option<HandleResponse> {
509			let display_handle = match MSAIdToDisplayName::<T>::get(msa_id) {
510				Some((handle, _)) => handle,
511				_ => return None,
512			};
513			// convert to string
514			let display_handle_str = core::str::from_utf8(&display_handle)
515				.map_err(|_| Error::<T>::InvalidHandleEncoding)
516				.ok()?;
517
518			let (base_handle_str, suffix) = split_display_name(display_handle_str)?;
519			let base_handle = base_handle_str.as_bytes().to_vec();
520			// Convert base handle into a canonical base
521			let (_, canonical_base) =
522				Self::get_canonical_string_vec_from_base_handle(&base_handle_str);
523			Some(HandleResponse { base_handle, suffix, canonical_base: canonical_base.into() })
524		}
525
526		/// Get the next available suffixes for a given handle.
527		///
528		/// This function takes a `Vec<u8>` handle and generates the next available
529		/// suffixes for that handle. The number of suffixes to generate is determined
530		/// by the `count` parameter, which is of type `u16`.
531		///
532		/// # Arguments
533		///
534		/// * `handle` - The handle to generate the next available suffixes for.
535		/// * `count` - The number of suffixes to generate.
536		///
537		/// # Returns
538		///
539		/// * `PresumptiveSuffixesResponse` - The response containing the next available suffixes.
540		///
541		pub fn get_next_suffixes(
542			base_handle: BaseHandle,
543			count: u16,
544		) -> PresumptiveSuffixesResponse {
545			let base_handle_str = core::str::from_utf8(&base_handle).unwrap_or_default();
546
547			// Convert base handle into a canonical base
548			let (canonical_handle_str, canonical_base) =
549				Self::get_canonical_string_vec_from_base_handle(base_handle_str);
550
551			let suffix_index =
552				Self::get_next_suffix_index_for_canonical_handle(canonical_base.clone())
553					.unwrap_or_default();
554
555			let lazy_suffix_sequence = generate_unique_suffixes(
556				T::HandleSuffixMin::get(),
557				T::HandleSuffixMax::get(),
558				&canonical_handle_str,
559			)
560			.enumerate();
561
562			let mut suffixes: Vec<HandleSuffix> = vec![];
563
564			for (i, suffix) in lazy_suffix_sequence {
565				if i >= suffix_index as usize && i < (suffix_index as usize + count as usize) {
566					suffixes.push(suffix);
567				}
568				if suffixes.len() == count as usize {
569					break;
570				}
571			}
572			PresumptiveSuffixesResponse { base_handle: base_handle.into(), suffixes }
573		}
574
575		/// Check a base handle for validity and collect information on it
576		///
577		/// This function takes a `Vec<u8>` handle and checks to make sure it is:
578		/// - Valid
579		/// - Has suffixes remaining
580		///
581		/// It also returns the original input as well as the canonical version
582		///
583		/// # Arguments
584		///
585		/// * `handle` - The handle to check.
586		///
587		/// # Returns
588		///
589		/// * `CheckHandleResponse`
590		///
591		pub fn check_handle(base_handle: Vec<u8>) -> CheckHandleResponse {
592			let valid = Self::validate_handle(base_handle.to_vec());
593
594			if !valid {
595				return CheckHandleResponse { base_handle, ..Default::default() };
596			}
597
598			let base_handle_str = core::str::from_utf8(&base_handle).unwrap_or_default();
599
600			let base_handle_trimmed = trim_and_collapse_whitespace(base_handle_str);
601
602			// Convert base handle into a canonical base
603			let (_canonical_handle_str, canonical_base) =
604				Self::get_canonical_string_vec_from_base_handle(base_handle_str);
605
606			let suffix_index =
607				Self::get_next_suffix_index_for_canonical_handle(canonical_base.clone())
608					.unwrap_or_default();
609
610			let suffixes_available = suffix_index < T::HandleSuffixMax::get();
611
612			CheckHandleResponse {
613				base_handle: base_handle_trimmed.into(),
614				suffix_index,
615				suffixes_available,
616				valid,
617				canonical_base: canonical_base.into(),
618			}
619		}
620
621		/// Retrieve a `MessageSourceId` for a given handle.
622		///
623		/// # Arguments
624		///
625		/// * `display_handle` - The full display handle to retrieve the `MessageSourceId` for.
626		///
627		/// # Returns
628		///
629		/// * `Option<MessageSourceId>` - The `MessageSourceId` if the handle is valid.
630		///
631		pub fn get_msa_id_for_handle(display_handle: DisplayHandle) -> Option<MessageSourceId> {
632			let display_handle_str = core::str::from_utf8(&display_handle).unwrap_or_default();
633			let (base_handle_str, suffix) = split_display_name(display_handle_str)?;
634			// Convert base handle into a canonical base
635			let (_, canonical_base) =
636				Self::get_canonical_string_vec_from_base_handle(&base_handle_str);
637			CanonicalBaseHandleAndSuffixToMSAId::<T>::get(canonical_base, suffix)
638		}
639
640		/// Claims a handle for a given MSA (MessageSourceId) by validating and storing the base handle,
641		/// generating a canonical base, generating a suffix, and composing the full display handle.
642		///
643		/// # Arguments
644		///
645		/// * `msa_id` - The MSA (MessageSourceId) to claim the handle for.
646		/// * `payload` - The payload containing the base handle to claim.
647		///
648		/// # Returns
649		///
650		/// * `DisplayHandle` - The full display handle.
651		///
652		pub fn do_claim_handle(
653			msa_id: MessageSourceId,
654			payload: ClaimHandlePayload<BlockNumberFor<T>>,
655		) -> Result<Vec<u8>, DispatchError> {
656			// Validation: The MSA must not already have a handle associated with it
657			ensure!(
658				MSAIdToDisplayName::<T>::get(msa_id).is_none(),
659				Error::<T>::MSAHandleAlreadyExists
660			);
661
662			let base_handle_str = Self::validate_base_handle(payload.base_handle)?;
663
664			// Convert base handle into a canonical base
665			let (canonical_handle_str, canonical_base) =
666				Self::get_canonical_string_vec_from_base_handle(&base_handle_str);
667
668			Self::validate_canonical_handle(&canonical_handle_str)?;
669
670			// Generate suffix from the next available index into the suffix sequence
671			let suffix_sequence_index =
672				Self::get_next_suffix_index_for_canonical_handle(canonical_base.clone())?;
673
674			let suffix = Self::generate_suffix_for_canonical_handle(
675				&canonical_handle_str,
676				suffix_sequence_index as usize,
677			)?;
678
679			let display_handle = Self::build_full_display_handle(&base_handle_str, suffix)?;
680
681			// Store canonical base and suffix to MSA id
682			CanonicalBaseHandleAndSuffixToMSAId::<T>::insert(
683				canonical_base.clone(),
684				suffix,
685				msa_id,
686			);
687			// Store canonical base to suffix sequence index
688			CanonicalBaseHandleToSuffixIndex::<T>::set(
689				canonical_base.clone(),
690				Some((suffix_sequence_index, T::HandleSuffixMin::get())),
691			);
692
693			// Store the full display handle to MSA id
694			MSAIdToDisplayName::<T>::insert(msa_id, (display_handle.clone(), payload.expiration));
695
696			Ok(display_handle.into_inner())
697		}
698
699		/// Checks that handle base string is valid before canonicalization
700		fn validate_base_handle(base_handle: Vec<u8>) -> Result<String, DispatchError> {
701			// Convert base handle to UTF-8 string slice while validating.
702			let base_handle_str =
703				String::from_utf8(base_handle).map_err(|_| Error::<T>::InvalidHandleEncoding)?;
704
705			// Validation: The handle length must be valid.
706			// Note: the count() can panic but won't because the base_handle byte length is already checked
707			let len = base_handle_str.chars().count() as u32;
708
709			// Validation: `BaseHandle` character length must be within range
710			ensure!(
711				(HANDLE_CHARS_MIN..=HANDLE_CHARS_MAX).contains(&len),
712				Error::<T>::InvalidHandleCharacterLength
713			);
714
715			ensure!(
716				!contains_blocked_characters(&base_handle_str),
717				Error::<T>::HandleContainsBlockedCharacters
718			);
719			Ok(base_handle_str)
720		}
721
722		/// Validate that the canonical handle:
723		/// - contains characters ONLY in supported ranges
724		/// - Does NOT contain reserved words
725		/// - Is between (inclusive) `HANDLE_CHARS_MIN` and `HANDLE_CHARS_MAX`
726		fn validate_canonical_handle(canonical_base_handle_str: &str) -> DispatchResult {
727			// Validation: The handle must consist of characters not containing reserved words or blocked characters
728			ensure!(
729				consists_of_supported_unicode_character_sets(canonical_base_handle_str),
730				Error::<T>::HandleDoesNotConsistOfSupportedCharacterSets
731			);
732			ensure!(
733				!is_reserved_canonical_handle(canonical_base_handle_str),
734				Error::<T>::HandleIsNotAllowed
735			);
736
737			let len = canonical_base_handle_str.chars().count() as u32;
738
739			// Validation: `Canonical` character length must be within range
740			ensure!(
741				(HANDLE_CHARS_MIN..=HANDLE_CHARS_MAX).contains(&len),
742				Error::<T>::InvalidHandleCharacterLength
743			);
744
745			Ok(())
746		}
747
748		/// Creates a full display handle by combining a base handle string with supplied suffix
749		///
750		/// # Arguments
751		///
752		/// * `base_handle_str` - The base handle string.
753		/// * `suffix` - The numeric suffix .
754		///
755		/// # Returns
756		///
757		/// * `DisplayHandle` - The full display handle.
758		///
759		pub fn build_full_display_handle(
760			base_handle: &str,
761			suffix: HandleSuffix,
762		) -> Result<DisplayHandle, DispatchError> {
763			let base_handle_trimmed = trim_and_collapse_whitespace(base_handle);
764			let mut full_handle_vec: Vec<u8> = vec![];
765			full_handle_vec.extend(base_handle_trimmed.as_bytes());
766			full_handle_vec.push(HANDLE_DELIMITER as u8); // The delimiter
767			let mut buff = [0u8; SUFFIX_MAX_DIGITS];
768			full_handle_vec.extend(suffix.numtoa(10, &mut buff));
769			let res = full_handle_vec.try_into().map_err(|_| Error::<T>::InvalidHandleEncoding)?;
770			Ok(res)
771		}
772
773		/// Retires a handle associated with a given MessageSourceId (MSA) in the dispatch module.
774		///
775		/// # Arguments
776		///
777		/// * `msa_id` - The MSA (MessageSourceId) to retire the handle for.
778		///
779		/// # Returns
780		///
781		/// * `DispatchResult` - Returns `Ok` if the handle was successfully retired.
782		pub fn do_retire_handle(msa_id: MessageSourceId) -> Result<Vec<u8>, DispatchError> {
783			// Validation: The MSA must already have a handle associated with it
784			let handle_from_state =
785				MSAIdToDisplayName::<T>::get(msa_id).ok_or(Error::<T>::MSAHandleDoesNotExist)?;
786			let display_name_handle = handle_from_state.0;
787			let display_name_str = core::str::from_utf8(&display_name_handle)
788				.map_err(|_| Error::<T>::InvalidHandleEncoding)?;
789
790			let (base_handle_str, suffix_num) =
791				split_display_name(display_name_str).ok_or(Error::<T>::InvalidHandle)?;
792
793			// Convert base handle into a canonical base
794			let (_, canonical_base) =
795				Self::get_canonical_string_vec_from_base_handle(&base_handle_str);
796
797			// Remove handle from storage but not from CanonicalBaseHandleToSuffixIndex because retired handles can't be reused
798			MSAIdToDisplayName::<T>::remove(msa_id);
799			CanonicalBaseHandleAndSuffixToMSAId::<T>::remove(canonical_base, suffix_num);
800
801			Ok(display_name_str.as_bytes().to_vec())
802		}
803
804		/// Checks whether the supplied handle passes all the checks performed by a
805		/// claim_handle call.
806		/// # Returns
807		/// * true if it is valid or false if invalid
808		pub fn validate_handle(handle: Vec<u8>) -> bool {
809			match Self::validate_base_handle(handle) {
810				Ok(base_handle_str) => {
811					// Convert base handle into a canonical base
812					let (canonical_handle_str, _) =
813						Self::get_canonical_string_vec_from_base_handle(&base_handle_str);
814
815					Self::validate_canonical_handle(&canonical_handle_str).is_ok()
816				},
817				_ => false,
818			}
819		}
820
821		/// Converts a base handle to a canonical base.
822		fn get_canonical_string_vec_from_base_handle(
823			base_handle_str: &str,
824		) -> (String, CanonicalBase) {
825			let canonical_handle_str = convert_to_canonical(base_handle_str);
826			let canonical_handle_vec = canonical_handle_str.as_bytes().to_vec();
827			let canonical_base: CanonicalBase = canonical_handle_vec.try_into().unwrap_or_default();
828			(canonical_handle_str, canonical_base)
829		}
830	}
831}