简介
开发
下载3.0.0版本的Substrate-node-template 代码
建立存证模块
- 终端下进入项目目录的 pallets 文件夹后输入如下命令:
cd pallets
cargo new --lib poe
- 为了简单,COPY template/Cargo.toml 到 poe/Cargo.toml 上做简单修改。
name = 'pallet-poe'
(可选) 添加 main.rs 的依赖关系
- 我一般会这样做,这样会方便某些 IDE 更好的预分析代码结构:
- 修改 node/Cargo.toml
[dependencies]
...
# 添加进去,加入存证的依赖关系,方便IDE找到程序入口
pallet-poe = { path ='../pallets/poe' , version='3.0.0' }
...
修改 pallets/poe/Cargo.toml
[package]
authors = ['Substrate DevHub <https://github.com/substrate-developer-hub>']
description = 'FRAME pallet template for defining custom runtime logic.'
edition = '2018'
homepage = 'https://substrate.dev'
license = 'Unlicense'
name = 'pallet-poe'
repository = 'https://github.com/substrate-developer-hub/substrate-node-template/'
version = '3.0.0'
[package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu']
# alias "parity-scale-code" to "codec"
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = '2.0.0'
[dependencies]
frame-support = { default-features = false, version = '3.0.0' }
frame-system = { default-features = false, version = '3.0.0' }
#需要注意引入 sp-std 模块,这里面定义了一些基础类型,比如这里用到的 vec::Vec
sp-std = { default-features = false, version = '3.0.0' }
[dev-dependencies]
serde = { version = "1.0.119" }
sp-core = { default-features = false, version = '3.0.0' }
sp-io = { default-features = false, version = '3.0.0' }
sp-runtime = { default-features = false, version = '3.0.0' }
[features]
default = ['std']
std = [
'codec/std',
'frame-support/std',
'frame-system/std',
]
修改 pallets/poe/src/lib.rs
#![cfg_attr(not(feature = "std"), no_std)]
pub use pallet::*;
// 首先通过 frame_support::pallet 宏创建 pallet
#[frame_support::pallet]
pub mod pallet {
// 引入需要的包
use frame_support::{
dispatch::DispatchResultWithPostInfo,
pallet_prelude::*,
};
// 比较粗暴的引用 frame_system 所有宏函数,和系统类型信息
use frame_system::pallet_prelude::*;
use sp_std::vec::Vec;
//创建配置接口,通过 config 宏完成
//继承自系统模块的 Config 接口,只有一个
#[pallet::config]
pub trait Config: frame_system::Config {
// 只有一个关联类型就是 Event,并且约束
// 可以从本模块的Event 类型进行转换,并且它的类型是一个系统模块的Event 类型。
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
}
// 定义一个结构体类型,来承载整个功能模块,使用 pallet::pallet 这个宏进行定义
#[pallet::pallet]
// 表示这个模块依赖的存储单元,一级存储单元依赖的 trait
#[pallet::generate_store(pub (super) trait Store)]
pub struct Pallet<T>(_);
// 通过 storage 宏来定义存储类型,用来存储存证
#[pallet::storage]
// 这里定义的getter方法可以通过前段接口进行调用 my_proofs 方法来查询连上的状态,也就是说没必要单独写一个读取的接口。
#[pallet::getter(fn my_proofs)]
pub type Proofs<T: Config> = StorageMap<
_,
Blake2_128Concat,
Vec<u8>, // 存证的哈希值
(T::AccountId, T::BlockNumber) // 值时两个元素的tuple,第一个是AccountId, 第二个存储区块高度。
>;
// 通过 Event 定义一个时间存储类型,这是一个枚举。
#[pallet::event]
// 生成一个 转换后的 metadata 方便前段接收
#[pallet::metadata(T::AccountId = "AccountId")]
// 生成一个帮助性的方法,方便这个方法进行触发。
#[pallet::generate_deposit(pub (super) fn deposit_event)]
pub enum Event<T: Config> {
ClaimCreated(T::AccountId, Vec<u8>),
ClaimRevoked(T::AccountId, Vec<u8>),
}
// 通过 error 宏定义一个错误信息
#[pallet::error]
pub enum Error<T> {
// 定义一个错误信息,存证已经存在
ProofAlreadyExist,
ClaimNotExist,
NotClaimOwner,
}
// 定义一个 hooks ,如果有初始化区块的信息可以放到这里面,如果没有这个也必须要加上否则会报错
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
// 构建可调用函数,通过 call 这个宏
#[pallet::call]
impl<T: Config> Pallet<T> {
// 为了测试方便起见,weight 给入 0 值,实际应用中需要根据实际情况进行预估。
#[pallet::weight(0)]
pub fn create_claim(
// 这个参数表示交易的发送方
origin: OriginFor<T>,
// 表示存证的哈希值,对应 38行。
claim: Vec<u8>,
) -> DispatchResultWithPostInfo { // 返回值是一个Result类型的别名它会包含一些weight的信息,这是通过use引入进来的
// TODO:: 写入创建存证的逻辑。
// 验证签名信息是否合法
let sender = ensure_signed(origin)?;
// 判断存证信息是否存在
ensure!(!Proofs::<T>::contains_key(&claim), Error::<T>::ProofAlreadyExist);
// 插入存证
Proofs::<T>::insert(
&claim,
(sender.clone(), frame_system::Pallet::<T>::block_number()),
);
// 发送事件
Self::deposit_event(Event::ClaimCreated(sender, claim));
// 返回结果信息,并进行类型转换。
Ok(().into())
}
// 建立一个吊销存证的方法
#[pallet::weight(0)]
pub fn revoke_claim(
origin: OriginFor<T>,
claim: Vec<u8>,
) -> DispatchResultWithPostInfo {
// 先验证 origin
let sender = ensure_signed(origin)?;
let (owner, _) = Proofs::<T>::get(&claim).ok_or(Error::<T>::ClaimNotExist)?;
// 判断发送者和存证所有者是否是同一个人
ensure!(owner == sender, Error::<T>::NotClaimOwner);
// 删除存证
Proofs::<T>::remove(&claim);
// 发送存证Revoked事件
Self::deposit_event(Event::ClaimRevoked(sender, claim));
// 返回函数成功结果
Ok(().into())
}
}
}
配置 runtime/Cargo.toml
[dependencies]
#... 此处省略
# local dependencies
pallet-template = { path = '../pallets/template', default-features = false, version = '3.0.0' }
# 引入我们定义的 poe 模块
pallet-poe = { path = '../pallets/poe', default-features = false, version = '3.0.0' }
#... 此处省略
std = [
#... 此处省略
'pallet-template/std',
'pallet-poe/std', # 引入POE的 STD
#... 此处省略
]
修改 runtime/src/lib.rs
---- 省略 ----
/// Configure the template pallet in pallets/template.
impl pallet_template::Config for Runtime {
type Event = Event;
}
/// 定义我们的配置模块接口
impl pallet_poe::Config for Runtime {
type Event = Event;
}
---- 省略 ----
// Create the runtime by composing the FRAME pallets that were previously configured.
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
--- 省略 ---
// Include the custom logic from the template pallet in the runtime.
TemplateModule: pallet_template::{Module, Call, Storage, Event<T>},
// 将自定义的POE模块加入runtime中。
PoeModule: pallet_poe::{Module, Call, Storage, Event<T>},
}
);
测试
结束