需求
需求:为 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> { ... }
}
- 要开发存证模块,先要定义链上储存存证的结构,我们修改模板的第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,//查询方式
>;
- 我们使用了 BoundedVec 要在注释8 增加依赖,BoundedVec 是一个有边界的数组可以限制 item 的长度范围BoundedVec in frame_support::storage::bounded_vec - Rust (paritytech.github.io)
use frame_support::{pallet_prelude::*, storage::bounded_vec::BoundedVec};
- 因为需要使用 event 向外通知方法执行情况,需要增加 event ,所以修改注释3 config 部分如下代码,
#[pallet::config]
pub trait Config: frame_system::Config {
type Event: From<Event<Self>>
+ IsType<<Self as frame_system::Config>::Event>;
}
- 在注释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>),
}
- 在注释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