Substrate区块链应用开发-存证模块的功能开发
链上存证介绍
最小化substrate代码开发
来源substrate官方维护的最小框架
substrate-node-template 是substrate的最小化框架,最小实现,麻雀虽小,五脏俱全,适合新手入门和理解substrate
https://github.com/substrate-developer-hub/substrate-node-template
关注pallets和runtime
1. pallets template
存证模板
2.runtime
应用实现
pallets poe 模块编写
poe : proof of existence
建立poe模块
1.复制pallets/template 为 pallets/poe
2.修改poe的Cargo.toml配置文件
修改[package]的description和name
描述和名字可自定义
3.修改poe的lib.rs文件
认识lib.rs构成
lib.rs:模板引入依赖、主程序、存储单元(宏)、事件(宏)、异常处理(宏)、可调用函数(宏)
4. decl_storage 定义存储单元
目标:先定义一个存储单元,用于存储存在归属信息
代码实现:
(1) 定义一个存储项 Proofs,给它一个default get散户,称之为 proofs
(2) 给Proofs设置类型为map,map的key是Vec,即存证hash值,由于无法得知使用哪些hash函数,所以使用变长类型u8
(3)存证归属信息需要归属到一个人身上,以及它在哪个时间点被存储。这里定义一个tuple,给定两个参数,一个是用户信息(AccountId),一个是区块链时间(BlockNumber)
(4)由于Vec是由用户输入,属于非安全的,这里得使用blake2_128_concat
(5)一个简单存储单元编码完成,可以使用cargo check或者cargo build来检查语法是否有错误
(6)使用了Vec,需要引入依赖
use sp_std::prelude::*; // 使用了Vec
5. decl_module 创建存证
注意:Proofs存储项是在decl_storage存储单元中定义的
(1)我们需要创建一个存证,创建存证需要有两个关键参数:交易发送方origin,和存证hash值claim,由于存证使用的hash函数是未知的,也需要和上面decl_storage定义对应,这里需要使用变长Vec来保存存证
(2)创建存证之前,我们需要“Verify First,Write Last”的原则,先对存证内容进行检查,检查两个方面:
①:交易发送方是不是一个签名的用户;
②:存证是否被别人创建过,创建过就得抛出异常(由decl_error处理)
(3)通过ensuer_signed来验证存证拥有人sender就是交易发送方origin
(4)通过Proofs::::contains_key传参claim的引用去获取存证,不存在就抛出异常Error::::ProofAlreadyExist,该异常ProofAlreadyExit需要由decl_error处理
(5)如果通过检查校验,开始插入操作:insert是一个map的key-value插入操作,这里的key-value是一个tuple,这个tuple的第一个元素是AccountId;第二个元素是当前交易所处的区块,使用系统模块提供的block_number工具方法获取;
(6)插入完毕,触发一个event时间来通知客户端,RawEvent由宏生成(为啥使用该宏?)
// 创建存证,创建存证需要有两个关键参数:交易发送方origin,存证hash值claim,由于存证hash函数未知,也和decl_storage定义对应,这里使用变长Vec<u8>
#[weight = 0]
pub fn create_claim(origin,claim:Vec<u8>)->dispatch::DispatchResult{
// 做必要检查,检查内容: 1,交易发送方是不是一个签名的用户 2,存证是否被别人创建过,创建过就抛出错误
// 首先去创建签名交易,通过ensure_signed这样的system提供的版本方法来校验
let sender = ensure_signed(origin)?; // 存证拥有人是交易发送方,只有拥有人才可以调用存证,sender即当前交易发送方
// 如果存在存证,返回错误 ProofAlreadyExist
// ps:ensure!宏是确保表达式中的结果为true,这里取反操作
ensure!(!Proofs::<T>::contains_key(&claim),Error::<T>::ProofAlreadyExist); // 这里用到一个错误 ProofAlreadyExist,该错误需要在decl_error声明
// 做insert操作,insert是key-value方式。这里的key-value是一个tuple
// 这个tuple的第一个元素是AccountId;第二个是当前交易所处的区块,使用系统模块提供的block_number工具方法获取
Proofs::<T>::insert(&claim,(sender.clone(),system::Module::<T>::block_number())); // 插入操作
// 触发一个event来通知客户端,RawEvent由宏生成; sender:存在拥有人;claim:存在hash值 通过event通知客户端
Self::deposit_event(RawEvent::ClaimCreated(sender,claim)); // ClaimCreated事件,需要decl_event处理
// 返回ok
Ok(())
}
6.decl_error 异常处理
将decl_module中定义的存证异常ProofAlreadyExist,需要在decl_error中声明
// 异常处理
// The pallet's errors
decl_error! {
pub enum Error for Module<T: Trait> {
ProofAlreadyExist, // 存在异常,即存证已经存在
}
}
7. decl_event 事件处理
将decl_module中定义的ClaimCreated事件在decl_evnet中声明处理
// 事件
// The pallet's events
decl_event!(
pub enum Event<T> where AccountId = <T as system::Trait>::AccountId {
ClaimCreated(AccountId,Vec<u8>), // 用户AccountId,存证内容 Vec<u8>
}
);
8. 撤销存证
#[weight = 0]
pub fn revoke_claim(origin,claim: Vec<u8>) -> dispatch::DispatchResult{
let sender = ensure_signed(origin)?; // 交易发送方式已签名的, 存证拥有人是交易发送方,只有拥有人才可以吊销存证
// 判断存储单元里面是存在这样一个存证;如果不存在,抛出错误,错误我们叫ClaimNotExist
ensure!(Proofs::<T>::contains_key(&claim),Error::<T>::ClaimNotExist);
// 获取这样的存证 owner: accountId block_number
let (owner,_block_number) = Proofs::<T>::get(&claim); // 通过get api获取这样的一个存证
ensure!(owner == sender,Error::<T>::NotClaimOwner); // 确保交易发送方是我们的存证人,如果不是,返回Error,这个Error我们叫NotClaimOwner
// 以上校验完成之后,我们就可以删除我们的存证
// 存储向上调用remove函数进行删除
Proofs::<T>::remove(&claim);
// 触发一个事件,返回存证人和hash
Self::deposit_event(RawEvent::ClaimRevoked(sender,claim));
// 返回
Ok(())
}
9. 将第8步中用到的异常处理和事件进行声明
// 事件
// The pallet's events
decl_event!(
pub enum Event<T> where AccountId = <T as system::Trait>::AccountId {
ClaimCreated(AccountId,Vec<u8>), // 用户AccountId,存证内容 Vec<u8>
ClaimRevoked(AccountId,Vec<u8>),
}
);
// 异常处理
// The pallet's errors
decl_error! {
pub enum Error for Module<T: Trait> {
ProofAlreadyExist, // 存证已经存在
ClaimNotExist,
NotClaimOwner,
}
}
在runtime中引入poe模块
在runtime/Cargo.toml 中添加poe模块
# 引入poe依赖
[dependencies.poe]
default-features = false
package = 'pallet-poe'
path = '../pallets/poe'
version = '2.0.0-rc2'
[features]
default = ['std']
std = [
'aura/std',
'balances/std',
'codec/std',
'frame-executive/std',
'frame-support/std',
'grandpa/std',
'randomness-collective-flip/std',
'serde',
'sp-api/std',
'sp-block-builder/std',
'sp-consensus-aura/std',
'sp-core/std',
'sp-inherents/std',
'sp-io/std',
'sp-offchain/std',
'sp-runtime/std',
'sp-session/std',
'sp-std/std',
'sp-transaction-pool/std',
'sp-version/std',
'sudo/std',
'system/std',
'timestamp/std',
'transaction-payment/std',
'template/std',
'poe/std', # 增加标签
]
在runtime/src/lib.rs中引入配置接口
实现配置接口
/// 对配置接口进行实现
impl poe::Trait for Runtime{
type Event = Event;
}
引入poe接口对应的操作信息
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: system::{Module, Call, Config, Storage, Event<T>},
RandomnessCollectiveFlip: randomness_collective_flip::{Module, Call, Storage},
Timestamp: timestamp::{Module, Call, Storage, Inherent},
Aura: aura::{Module, Config<T>, Inherent(Timestamp)},
Grandpa: grandpa::{Module, Call, Storage, Config, Event},
Balances: balances::{Module, Call, Storage, Config<T>, Event<T>},
TransactionPayment: transaction_payment::{Module, Storage},
Sudo: sudo::{Module, Call, Config<T>, Storage, Event<T>},
// Used for the module template in `./template.rs`
TemplateModule: template::{Module, Call, Storage, Event<T>},
// 引入poe对应模块的操作信息 Module: 模块 Call:调用函数 Storage:存储项 Event<T>:事件 Error不需要额外引入
PoeModule: poe::{Module, Call, Storage, Event<T>},
}
);