SETP6,开发一个存证模块

简介

  • 参考文档:https://substrate.dev/docs/en/tutorials/build-a-dapp/
  • ssh -L 9944:127.0.0.1:9944 user@ip 可以将远程端口通过 ssh 隧道映射到本地这样就比较容易测试,是非常好的方法。

开发

下载3.0.0版本的Substrate-node-template 代码

  • git clone -b v3.0.0 --depth 1 https://github.com/substrate-developer-hub/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 ']
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> + IsType<::Event>;
    }

    // 定义一个结构体类型,来承载整个功能模块,使用 pallet::pallet 这个宏进行定义
    #[pallet::pallet]
    // 表示这个模块依赖的存储单元,一级存储单元依赖的 trait
    #[pallet::generate_store(pub (super) trait Store)]
    pub struct Pallet(_);

    // 通过 storage 宏来定义存储类型,用来存储存证
    #[pallet::storage]
    // 这里定义的getter方法可以通过前段接口进行调用 my_proofs 方法来查询连上的状态,也就是说没必要单独写一个读取的接口。
    #[pallet::getter(fn my_proofs)]
    pub type Proofs = StorageMap<
        _,
        Blake2_128Concat,
        Vec, // 存证的哈希值
        (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 {
        ClaimCreated(T::AccountId, Vec),
        ClaimRevoked(T::AccountId, Vec),
    }

    // 通过 error 宏定义一个错误信息
    #[pallet::error]
    pub enum Error {
        // 定义一个错误信息,存证已经存在
        ProofAlreadyExist,
        ClaimNotExist,
        NotClaimOwner,
    }

    // 定义一个 hooks ,如果有初始化区块的信息可以放到这里面,如果没有这个也必须要加上否则会报错
    #[pallet::hooks]
    impl Hooks> for Pallet {}

    // 构建可调用函数,通过 call 这个宏
    #[pallet::call]
    impl Pallet {
        // 为了测试方便起见,weight 给入 0 值,实际应用中需要根据实际情况进行预估。
        #[pallet::weight(0)]
        pub fn create_claim(
            // 这个参数表示交易的发送方
            origin: OriginFor,
            // 表示存证的哈希值,对应 38行。
            claim: Vec,
        ) -> DispatchResultWithPostInfo { // 返回值是一个Result类型的别名它会包含一些weight的信息,这是通过use引入进来的
            // TODO:: 写入创建存证的逻辑。
            // 验证签名信息是否合法
            let sender = ensure_signed(origin)?;
            // 判断存证信息是否存在
            ensure!(!Proofs::::contains_key(&claim), Error::::ProofAlreadyExist);
            // 插入存证
            Proofs::::insert(
                &claim,
                (sender.clone(), frame_system::Pallet::::block_number()),
            );

            // 发送事件
            Self::deposit_event(Event::ClaimCreated(sender, claim));
            // 返回结果信息,并进行类型转换。
            Ok(().into())
        }

        // 建立一个吊销存证的方法
        #[pallet::weight(0)]
        pub fn revoke_claim(
            origin: OriginFor,
            claim: Vec,
        ) -> DispatchResultWithPostInfo {
            // 先验证 origin
            let sender = ensure_signed(origin)?;
            let (owner, _) = Proofs::::get(&claim).ok_or(Error::::ClaimNotExist)?;
            // 判断发送者和存证所有者是否是同一个人
            ensure!(owner == sender, Error::::NotClaimOwner);
            // 删除存证
            Proofs::::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},
        // 将自定义的POE模块加入runtime中。
        PoeModule: pallet_poe::{Module, Call, Storage, Event},
    }
);

测试

  • 编译并启动执行 cargo run -- --dev --tmp 然后通过 http://substrate.cancanyou.com/#/extrinsics 进行相关测试,一切功能正常。

结束

  • 感谢阅读

你可能感兴趣的:(SETP6,开发一个存证模块)