SETP6,开发一个存证模块

简介

开发

下载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>},
    }
);

测试

结束

  • 感谢阅读

推荐阅读更多精彩内容