创建一个PoE的dapp
这一章我们创建一个PoE的dapp。
名词解释:
- PoE: “Proof Of Existence” “存在证明”
- dapp: Decentralized Application,云中心化的app
开始之前你应该已经完成了章节1中的内容。
安装最新稳定版Node.js:
curl -sL https://deb.nodesource.com/setup_13.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v
Node.js目前最新版是13.x。
安装Yarn,配置仓库:
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
安装Yarn:
sudo apt update && sudo apt install yarn
下载前端模板代码:
# Clone the code from github
git clone -b v2.0.0-alpha.5 https://github.com/substrate-developer-hub/substrate-front-end-template
通yarn命令安装:
# Install the dependencies
cd substrate-front-end-template
yarn install
- Proof of Existence : 通过文件hash的方式证明文件存在,具体可参考Wikipedia。
我们的PoE Pallet暴露两个可以调用的函数:
- create_claim : 允许用户通过上传文件hash来声明文件的存在。
- revoke_claim: 允许文件拥有者撤销其所有权。
Substrate的runtime由一系列的Pallets组成,你可以开发自己的Pallet,和已经存在的Pallets组合起来,形成你的runtime。
接下来我们学习如何创建你自己到的Pallet到你的区块链中。
这里要用到第一章的substrate-node-template工程,看看substrate-node-template的工程结构:
substrate-node-template
|
+-- runtime
|
+-- pallets
| |
| +-- template
| |
| +-- Cargo.toml <-- One change in this file
| |
| +-- src
| |
| +-- lib.rs <-- Most changes in this file
| |
| +-- mock.rs
| |
| +-- tests.rs
|
+-- scripts
|
+-- node
|
+-- ...
在pallets/template/Cargo.toml最后加入以下内容:
[dependencies.sp-std]
default-features = false
version = '2.0.0-alpha.5'
在pallets/template/Cargo.toml中修改以下内容:
[features]
default = ['std']
std = [
'codec/std',
'frame-support/std',
'safe-mix/std',
'system/std',
'sp-std/std', <-- 添加行
]
接下来清空lib.rs文件,我们要在lib.rs中编写的代码主要分以下几大块:
// 1. Imports
use frame_support::{decl_module, decl_storage, decl_event, dispatch::DispatchResult};
use system::ensure_signed;
// 2. Pallet Configuration
pub trait Trait: system::Trait { /* --snip-- */ }
// 3. Pallet Events
decl_event! { /* --snip-- */ }
// 4. Pallet Errors
decl_error! { /* --snip-- */ }
// 5. Pallet Storage Items
decl_storage! { /* --snip-- */ }
// 6. Callable Pallet Functions
decl_module! { /* --snip-- */ }
以下是完整内容,请务必仔细阅读每一行,最好是自己亲手写一遍,并理解每行代码的意思。
#![cfg_attr(not(featue = "std"), no_std)]
use frame_support:: {
decl_module, decl_storage, decl_event, decl_error, ensure, StorageMap
};
use system::ensure_signed;
use sp_std::vec::Vec;
pub trait Trait: system::Trait {
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
decl_event! {
pub enum Event<T> where AccountId = <T as system::Trait>::AccountId {
ClaimCreated(AccountId, Vec<u8>),
ClaimRevoked(AccountId, Vec<u8>),
}
}
decl_error! {
pub enum Error for Module<T: Trait> {
ProofAlreadyClaimed,
NoSuchProof,
NotProofOwner,
}
}
decl_storage! {
trait Store for Module<T: Trait> as TemplateModule {
Proofs: map hasher(blake2_128_concat) Vec<u8> => (T::AccountId, T::BlockNumber);
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
fn deposit_event() = default;
fn create_clain(origin, proof: Vec<u8>) {
let sender = ensure_signed(origin)?;
ensure!(!Proofs::<T>::contains_key(&proof), Error::<T>::ProofAlreadyClaimed);
let current_block = <system::Module<T>>::block_number();
Proofs::<T>::insert(&proof, (sender.clone(), current_block));
Self::deposit_event(RawEvent::ClaimCreated(sender, proof));
}
fn revoke_claim(origin, proof: Vec<u8>) {
let sender = ensure_signed(origin)?;
ensure!(Proofs::<T>::contains_key(&proof), Error::<T>::NoSuchProof);
let (owner, _) = Proofs::<T>::get(&proof);
ensure!(sender == owner, Error::<T>::NotProofOwner);
Proofs::<T>::remove(&proof);
Self::deposit_event(RawEvent::ClaimRevoked(sender, proof));
}
}
}
重新编译:
cargo build --release
运行,运行之前清除链数据:
# Purge chain to clean up your old chain state
# You will be prompted to type `y`
./target/release/node-template purge-chain --dev
# Re-run your node in "development" mode
./target/release/node-template --dev