【Substrate 第一行代码】 编写存证 pallet

151 阅读4分钟

需求

需求:为 node-template 链增加一个保存 hash 到链上,从链上读取的 hash 的功能。

之后可以在此基础上扩展其他功能,在拓展功能的过程了解更多 substrate 内容。

创建目录

一开始直接利用 node-template 的 pallets/template 来创建,复制 template 更改名 poe 到 pallets 目录下。

删除 poe/src 下的 benchmarking.rs mock.rs tests.rs 现在涉及不到的内容。

进入 poe/Cargo.toml 文件, 此文件为 pallet 的描述文件。

[package]
name = "pallet-poe"  #pallet 的名字
description = 描述
authors = 作者
repository = "https://github.com/substrate-developer-hub/substrate-node-template/"

编写 pallet 代码

删除 pallets/poe/src/lib.rs 中的所有代码,复制之前的模板代码

// 1. Imports and Dependenciespub
use pallet::*;
#[frame_support::pallet]
pub mod pallet {
// 8.依赖
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
    // 2. Declaration of the Pallet type    
    // This is a placeholder to implement traits and methods.    
    #[pallet::pallet]    
    #[pallet::generate_store(pub(super) trait Store)]   
    pub struct Pallet<T>(_);
    // 3. Runtime Configuration Trait    
    // All types and constants go here.    
    // Use #[pallet::constant] and #[pallet::extra_constants]   
    // to pass in values to metadata.    
    #[pallet::config]    
    pub trait Config: frame_system::Config { ... }
    // 4. Runtime Storage    
    // Use to declare storage items.    
    #[pallet::storage]    
    #[pallet::getter(fn something)]    
    pub MyStorage<T: Config> = StorageValue<_, u32>;
    // 5. Runtime Events    
    // Can stringify event types to metadata.    
    #[pallet::event]    
    #[pallet::generate_deposit(pub(super) fn deposit_event)]    
    pub enum Event<T: Config> { ... }
    // 6. Hooks    
    // Define some logic that should be executed    
    // regularly in some context, for e.g. on_initialize.    
    #[pallet::hooks]    
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { ... }
    // 7. Extrinsics    
    // Functions that are callable from outside the runtime.    
    #[pallet::call]    
    impl<T:Config> Pallet<T> { ... }
}
  1. 要开发存证模块,先要定义链上储存存证的结构,我们修改模板的第4个注释定义链上储存结构。 存证使用 StorageMap ,具体定义可以在 rust docs StorageMap in frame_support::storage - Rust (paritytech.github.io)
	////定义存证单元 可选择get函数
	#[pallet::storage]
	#[pallet::getter(fn proofs)]
	pub(super) type Proofs<T:Config> = StorageMap<
		_,
		Blake2_128Concat,//key加密方式
		BoundedVec<u8,u32>,//key
		(T::AccountId,T::BlockNumber),//value
		OptionQuery,//查询方式
	>;
  1. 我们使用了 BoundedVec 要在注释8 增加依赖,BoundedVec 是一个有边界的数组可以限制 item 的长度范围BoundedVec in frame_support::storage::bounded_vec - Rust (paritytech.github.io)
use frame_support::{pallet_prelude::*, storage::bounded_vec::BoundedVec};
  1. 因为需要使用 event 向外通知方法执行情况,需要增加 event ,所以修改注释3 config 部分如下代码,
  #[pallet::config]  
  pub trait Config: frame_system::Config {        
          type Event: From<Event<Self>>         
          + IsType<<Self as frame_system::Config>::Event>;    
  }

  1. 在注释5 定义事件
	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T: Config> {
        /// Event emitted when a proof has been claimed. [who, claim]
        ClaimCreated(T::AccountId, BoundedVec<u8, u32>),
        /// Event emitted when a claim is revoked by the owner. [who, claim]
        ClaimRevoked(T::AccountId, BoundedVec<u8, u32>),

	ClaimTransaction(T::AccountId, T::AccountId, BoundedVec<u8, u32>),
	}
  1. 在注释7 编写存证逻辑
#[pallet::call]
	impl<T: Config> Pallet<T> {
		#[pallet::weight(1_000)]
		pub fn create_claim(
			origin:OriginFor<T>,
			proof:BoundedVec<u8,u32>,
		) -> DispatchResult {
			// 检查是否签名
			let sender = ensure_signed(origin)?;
			//检查 proof 是否已经被 claimed, 是的话 报错 ProofAlreadyClaimed
			ensure!(!Proofs::<T>::contains_key(&proof),Error::<T>::ProofAlreadyClaimed);
			// 获得当前的块号
			let current_block = <frame_system::Pallet<T>>::block_number();
		// 向 StorageMap 中插入
			Proofs::<T>::insert(&proof, (&sender, current_block));
		// 发送 ClaimCreated 通知
			Self::deposit_event(Event::ClaimCreated(sender, proof));
			Ok(())
		}
		#[pallet::weight(1_000)]
		pub fn revole_claim(
			origin:OriginFor<T>,
			proof:BoundedVec<u8,u32>
		)->DispatchResult{
			let sender = ensure_signed(origin)?;
			ensure!(Proofs::<T>::contains_key(&proof), Error::<T>::NoSuchProof);
			// StorageMap 获得 value
			let (owner, _) = Proofs::<T>::get(&proof).expect("All proofs must have an owner!");
		
			ensure!(sender == owner, Error::<T>::NotProofOwner);

			Proofs::<T>::remove(&proof);

			Self::deposit_event(Event::ClaimRevoked(sender, proof));
			Ok(())
		}
		#[pallet::weight(1_000)]
		pub fn transaction_claim(
			origin:OriginFor<T>,
			proof:BoundedVec<u8,u32>,
			to_address:T::AccountId
		)->DispatchResult{
			let sender = ensure_signed(origin)?;
			ensure!(Proofs::<T>::contains_key(&proof), Error::<T>::NoSuchProof);
			// StorageMap 获得 value
			let (owner, _) = Proofs::<T>::get(&proof).expect("All proofs must have an owner!");
		
			ensure!(sender == owner, Error::<T>::NotProofOwner);
			// 获得当前的块号
			let current_block = <frame_system::Pallet<T>>::block_number();
			Proofs::<T>::remove(&proof);
		// 向 StorageMap 中插入
			Proofs::<T>::insert(&proof, (&to_address, current_block)); 
			
			Self::deposit_event(Event::ClaimTransaction(sender, to_address,proof));
			Ok(())
		}

	}

将pallet添加到runtime

刚才编写了 pallet 相当于是 java 定义好了类,下面要使用类。分成两步添加 pallet 的依赖使用 pallet

 修改Cargo.toml

进入 runtime/Cargo.toml 中

...
[dependencies]
...
# Local Dependencies
pallet-template = { version = "4.0.0-dev", default-features = false, path = "../pallets/template" }
pallet-poe = { version = "4.0.0-dev", default-features = false, path = "../pallets/poe" }
...  

[features]
default = ["std"]
std = [
  ...
	"pallet-template/std",
	"pallet-poe/std", //可以仿照 template 来写
  ...
]

修改runtime/src/lib.rs

runtime/src/lib.rs 中增加,具体位置可以找 template 模块添加的位置。

增加依赖


pub use pallet_poe;
pub use pallet_template;

指定自定义的 pallet 中的 config 的关联类型,将 pallet 附加给 Runtime,可以看之前的前置 rust 语法。

impl pallet_template::Config for Runtime {
	type Event = Event;
}

impl pallet_poe::Config for Runtime {
	type Event = Event;
}
construct_runtime!(
	pub struct Runtime
	where
		Block = Block,
		NodeBlock = opaque::Block,
		UncheckedExtrinsic = UncheckedExtrinsic,
	{
		System: frame_system,
		RandomnessCollectiveFlip: pallet_randomness_collective_flip,
		Timestamp: pallet_timestamp,
		Aura: pallet_aura,
		Grandpa: pallet_grandpa,
		Balances: pallet_balances,
		TransactionPayment: pallet_transaction_payment,
		Sudo: pallet_sudo,
		// Include the custom logic from the pallet-template in the runtime.
		TemplateModule: pallet_template,
		PoeModule: pallet_poe,
	}
);

编译运行

接下来,我们可以进行编译运行我们的链了。回到substrate-node-template目录,运行如下命令编译:

cargo build

运行如下命令启动节点:

./target/debug/node-template --tmp --dev

注意 node-template 单节点启动要加 dev 参数不然不会出块

调试

我们使用 polkadotjs链接本地节点调试

1、在浏览器中输入https://polkadot.js.org/apps;
2、点击左上角会展开;
3、在展开的菜单中点击DEVELOPMENT;
4、点击Local Node;
5、点击switch
1,选择Developer->Extrinsics->Submission;
2,然后使用Alice账户,选择poePallet
3,可以看到我们定义在方法。
4,点击右下角的提交即完成了存在的创建。

总结

这样就实现了基本的 pallet 操作,这个过程中使用了 StorageMap 链上储存结构,还有其他的储存结果可以根据实际需求在官方文档中查询sc_service - Rust (paritytech.github.io)

#[pallet::weight(1_000)] 每个方法执行所需要的权重现在还是用的默认值,之后也会介绍如何使用合理的 weight