OpenGuild
Published on

Code Breakdown @ FRAME Pallet Template

Language: Vietnamese

Cấp độ: Intermediate

Code Breakdown: Template for FRAME Pallet

Lý thuyết có vẻ sẽ khó giúp bạn hình dung được ứng dụng của Pallet trong Substrate, nên tốt nhất chúng ta sẽ tiếp cận trực tiếp từ code nhé. Khi clone repo substrate-node-template về bạn sẽ thấy có sẵn một folder là pallet/template chứa code mẫu cho pallet. Trong đó có 2 file quan trọng mà bạn cần chú ý là lib.rsweights.rs. Mục đích của lib.rs chứa các logic và cấu hình chính của pallet trong khi đó weights.rs định nghĩa thông tin về Weight của pallet. Bạn có thể tìm hiểu thêm về Weight tại đây: Khái niệm về Transaction Fee và Weight

Bài viết liên quan

Phân tích code mẫu cho Pallet

#![cfg_attr(not(feature = "std"), no_std)]

/// Edit this file to define custom logic or remove it if it is not needed.
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://docs.substrate.io/reference/frame-pallets/>
pub use pallet::*;

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
pub use weights::*;

#[frame_support::pallet]
pub mod pallet {
	use super::*;
	use frame_support::pallet_prelude::*;
	use frame_system::pallet_prelude::*;

	#[pallet::pallet]
	pub struct Pallet<T>(_);

	#[pallet::config]
	pub trait Config: frame_system::Config {
		/// Because this pallet emits events, it depends on the runtime's definition of an event.
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
		/// Type representing the weight of this pallet
		type WeightInfo: WeightInfo;
	}

	#[pallet::storage]
	#[pallet::getter(fn something)]
	pub type Something<T> = StorageValue<_, u32>;

	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T: Config> {
		SomethingStored { something: u32, who: T::AccountId },
	}

	#[pallet::error]
	pub enum Error<T> {
		NoneValue,
		StorageOverflow,
	}

	#[pallet::call]
	impl<T: Config> Pallet<T> {
		#[pallet::call_index(0)]
		#[pallet::weight(T::WeightInfo::do_something())]
		pub fn do_something(origin: OriginFor<T>, something: u32) -> DispatchResult {
			let who = ensure_signed(origin)?;
			<Something<T>>::put(something);
			Self::deposit_event(Event::SomethingStored { something, who });
			Ok(())
		}

		#[pallet::call_index(1)]
		#[pallet::weight(T::WeightInfo::cause_error())]
		pub fn cause_error(origin: OriginFor<T>) -> DispatchResult {
			let _who = ensure_signed(origin)?;
			match <Something<T>>::get() {
				None => return Err(Error::<T>::NoneValue.into()),
				Some(old) => {
					let new = old.checked_add(1).ok_or(Error::<T>::StorageOverflow)?;
					<Something<T>>::put(new);
					Ok(())
				},
			}
		}
	}
}

Ở đây chúng ta sẽ chú ý đến các khai báo macros sau, các thông tin được mình tham khảo từ đây frame_support::pallet documentation.

#![cfg_attr(not(feature = "std"), no_std)]

Khai báo rằng Pallet sẽ không hỗ trợ standard library của Rust bởi vì Pallet được tích hợp vào Runtime của Substrate và Runtime sẽ được compile sang WASM. Nhưng vì môi trường của WASM không hỗ trợ standard library nên Pallet cũng vậy.

  • pallet::pallet: Macro này là bất buộc khi bạn bắt đầu viết bất kỳ pallet nào, nó cho phép bạn khai báo các thông tin của Pallet. Việc đặt tên cho struct là Pallet không bắt nhưng là best practice vì nó sẽ giúp cho pallet của bạn đồng nhất với các pallet khác trong hệ sinh thái.
#[pallet::pallet]
pub struct Pallet<T>(_);
  • pallet:config: Macro khai báo các cấu hình và parameters yêu cầu từ Runtime khi pallet được sử dụng ở phía Runtime
#[pallet::config]
pub trait Config: frame_system::Config {
	/// Bởi vì pallet này sẽ bắn Event nên định nghĩa của Event sẽ cần được khai báo từ phía Runtime
	type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
	/// Type đại diện cho weight của pallet này
	type WeightInfo: WeightInfo;
}
  • pallet:storage: Khai báo cho loại dữ liệu được lưu trữ trong onchain storage. Ví dụ trong Rust bạn có HashMap có thể hiểu là đại diện cho cấu trúc dữ liệu Map thì chúng ta sẽ có StorageMap để đại diện cho Map trong ngữ cảnh của pallet. Chúng ta sẽ cố một bài viết để bàn về onchain storage và cách sử dụng của một số cấu trúc dữ liệu sau nhé.
#[pallet::storage]
#[pallet::getter(fn something)]
pub type Something<T> = StorageValue<_, u32>;
  • pallet::event: Định nghĩa cho các sự kiện có thể được gọi từ Pallet. Nếu bạn đã từng có kinh nghiệm làm việc với các hệ thống Event Driven Design hay biết qua về khái niệm như Finite State Machine từ trước thì hẳn bạn cũng không còn xa lạ gì với cách thiết kế event này. Mình nhớ trong Solidity cũng có cách thiết kế tương tự.
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
	SomethingStored { something: u32, who: T::AccountId },
}
  • pallet::error: Khai báo các loại lỗi mà pallet này có thể trả về
#[pallet::error]
pub enum Error<T> {
	NoneValue,
	StorageOverflow,
}

Và macro quan trọng cuối cùng bên trong code mẫu là pallet::call, nơi khai báo các hàm thức extrinsic để thay đổi trạng thái của blockchain.

Extrinsic là một từ chuyển ngành khi phát triển trên Polkadot, có nghĩa là các lệnh gọi từ phía bên ngoài blockchain. Bạn có thể hiểu Extrinsic tương tự như Transaction

Ví dụ mẫu code dưới đây khai báo 2 hàm thức là do_somethingcause_error. Các hàm thức có thể được thực thi cho phép người dùng tương tác với các pallets và thực hiện việc thay đổi trạng thái của blockchain. Chuẩn code của các hàm thức phải theo đúng cú pháp:

Hàm thức phải được khai báo weight với pallet::weight và phải trả về DispatchResult. Tìm hiểu thêm về weight tại đây Khái niệm về Transaction Fee và Weight

#[pallet::call]
impl<T: Config> Pallet<T> {
	#[pallet::call_index(0)]
	#[pallet::weight(T::WeightInfo::do_something())]
	pub fn do_something(origin: OriginFor<T>, something: u32) -> DispatchResult {
		// Chắc chắn rằng transaction gồm extrinsic call được gọi đã được sign
		// Hàm này sẽ trả về lỗi nếu extrinsic chưa được sign
		let who = ensure_signed(origin)?;
		<Something<T>>::put(something);
		Self::deposit_event(Event::SomethingStored { something, who });
		Ok(())
	}

	#[pallet::call_index(1)]
	#[pallet::weight(T::WeightInfo::cause_error())]
	pub fn cause_error(origin: OriginFor<T>) -> DispatchResult {
		// Chắc chắn rằng transaction gồm extrinsic call được gọi đã được sign
		// Hàm này sẽ trả về lỗi nếu extrinsic chưa được sign
		let _who = ensure_signed(origin)?;
		match <Something<T>>::get() {
			None => return Err(Error::<T>::NoneValue.into()),
			Some(old) => {
				let new = old.checked_add(1).ok_or(Error::<T>::StorageOverflow)?;
				<Something<T>>::put(new);
				Ok(())
			},
		}
	}
}

Đáng lưu ý là việc dùng ensure_signed ở ngay đầu hàm thức để kiểm tra origin khi thực thi extrinsic là một mẫu hình phổ biến trong Substrate gọi là Verify first, write last. Mẫu hình này cũng được sử dụng nhiều khi thực thi các lệnh write vào cơ sở dữ liệu. Chúng ta sẽ xử lý các điều kiện cần trước và trả lõi, sau đó sẽ xử lý đến các lệnh write vào onchain storage ở cuối.

Tương tác của Runtime với Pallet

Phía runtime, bạn có thể để ý thấy một macro được gọi là construct_runtime!, macro này sẽ parse và tim kiếm các macro pallet::* được sử dụng.

construct_runtime!(
	pub struct Runtime {
		System: frame_system,
		Timestamp: pallet_timestamp,
		Aura: pallet_aura,
		Grandpa: pallet_grandpa,
		Balances: pallet_balances,
		TransactionPayment: pallet_transaction_payment,
		Sudo: pallet_sudo,
		// Khai báo pallet mẫu được sử dụng vào Runtime
		TemplateModule: pallet_template,
	}
);

Về khái báo Type của Pallet ở phía Runtime, chúng ta có mẫu code sau:

/// Configure the pallet-template in pallets/template.
impl pallet_template::Config for Runtime {
	type RuntimeEvent = RuntimeEvent;
	type WeightInfo = pallet_template::weights::SubstrateWeight<Runtime>;
}

Kết luận

Tổng quan chung thì kiến trúc và thông tin cơ bản của Pallet là như vậy. Hi vọng sau bài viết này, bạn sẽ có một cái nhìn cụ thể hơn về Pallet và cách mà Pallet được sử dụng trong Runtime của Subtrate Node. Khi tham khảo thêm về các Pallet khác, bạn cũng dựa trên kiến trúc mẫu ở phía trên và phân tích ra logic chính của mã nguồn.