使用Substrate开发区块链存证dApp
前面文章介绍了在Substrate上开发智能合约,包括使用原生的ink!语言开发ERC20智能合约,以及将以太坊的Solidity智能合约跑在Substrate链上,在本文将进一步学习在Substrate链上开发一个自定义的区块链存证dApp。
*jasonruan 2020.08.05* # 1 前言 前面文章介绍了在`Substrate`上开发智能合约,包括使用原生的`ink!`语言开发`ERC20`智能合约,以及将以太坊的`Solidity`智能合约跑在`Substrate`链上,在本文将进一步学习在`Substrate`链上开发一个自定义的区块链存证`dApp`。 本文内容参考:https://substrate.dev/docs/en/tutorials/build-a-dapp/ # 2 前置准备 ## 2.1 rust安装 `Substrate`是由`rust`语言开发,首先需要安装`rust`环境。 ### 2.1.1 软件安装 - Rust的安装比较简单,执行如下一条命令即可,该命令将自动完成软件包的下载、安装、环境变量设置: ```bash $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` - 安装成功后,会显示如下日志 ```bash stable installed - rustc 1.45.1 (c367798cf 2020-07-26) Rust is installed now. Great! ``` - 安装完成后,在`~/.cargo/bin`目录可以看到相关命令行工具 ``` .cargo/bin/ ├── cargo ├── cargo-clippy ├── cargo-fmt ├── cargo-miri ├── clippy-driver ├── rls ├── rustc ├── rustdoc ├── rustfmt ├── rust-gdb ├── rust-lldb └── rustup ``` ### 2.1.2 环变设置 - 执行下面命令,即将`export PATH="$HOME/.cargo/bin:$PATH"`,追加到`~/.bashrc`中 ```bash $ cat ~/.cargo/env >> ~/.bashrc ``` - 执行下面命令,使得添加的环境变量生效 ```bash $ . ~/.bashrc ``` - 可执行如下命令查看安装版本 ```bash $ rustc --version rustc 1.45.1 (c367798cf 2020-07-26) ``` ### 2.1.3 配套安装 #### (1) Racer安装 > `Racer`是一个由`Rust`爱好者提供的`Rust`自动补全和语法分析工具,被用来提供基本的补全功能和自定义跳转功能。本身完全由`Rust`写成,补全功能比较完善。 - **安装命令** ```bash $ cargo install racer ...... Finished release [optimized] target(s) in 2m 44s Installing /home/jason/.cargo/bin/racer Installed package `racer v2.1.36` (executable `racer`) ``` > 若安装报错:`error[E0554]: #![feature] may not be used on the stable release channel` > > 请先执行下面命令,切换到`nightly`版本后,再进行安装: > > ```bash > $ rustup install nightly > $ rustup default nightly > $ rustc --version > rustc 1.47.0-nightly (6c8927b0c 2020-07-26) > ``` - **查看版本** ```bash $ racer -V racer 2.1.36 ``` #### (2) 源码下载 > 为了对`Rust`标准库进行补全,`Racer`需要获取`Rust`源码路径。通过`rustup`获取源码的好处是`rustup update`可以随时获取最新代码 - **获取源码** ```bash $ rustup component add rust-src info: downloading component 'rust-src' info: installing component 'rust-src' ``` - **更新源码** ```bash $ rustup update info: checking for self-updates stable-x86_64-unknown-linux-gnu unchanged - rustc 1.45.2 (d3fb005a3 2020-07-31) nightly-x86_64-unknown-linux-gnu unchanged - rustc 1.47.0-nightly (6c8927b0c 2020-07-26) info: cleaning up downloads & tmp directories ``` - 环变设置 > 在`.bashrc`中添加以下内容: ```bash export RUST_SRC_PATH="$(rustc --print sysroot)/lib/rustlib/src/rust/src" ``` ## 2.2 yarn安装 `Substrate`前端模板工程(`front-end-template`)是使用`yarn`进行包管理的,在此我们进行安装。 安装步骤参考:https://classic.yarnpkg.com/en/docs/install/#centos-stable ```bash $ curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo [yarn] name=Yarn Repository baseurl=https://dl.yarnpkg.com/rpm/ enabled=1 gpgcheck=1 gpgkey=https://dl.yarnpkg.com/rpm/pubkey.gpg $ curl --silent --location https://rpm.nodesource.com/setup_12.x | sudo bash - $ sudo yum install yarn $ yarn --version 1.22.4 ``` # 3 存证dApp后端节点开发 存证`dApp`后端节点是基于`node-template`来开发,它是一个基于`FRAME`的`Substrate`后端节点,可以在其基础上,进行修改以便快速搭建属于自己的`Substrate`网络。 ## 3.1 node-template安装 - **版本** `v2.0.0-rc5` - **下载** ```bash [Jason@RUAN:~/Blockchain]$ git clone git@github.com:substrate-developer-hub/substrate-node-template.git [Jason@RUAN:~/Blockchain/substrate-node-template] (master)$ git checkout -b v2.0.0-rc5 v2.0.0-rc5 切换到一个新分支 'v2.0.0-rc5' [Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ ``` - **编译** > 安装依赖,避免后续编译错误: > > ```bash > [Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ rustup target add wasm32-unknown-unknown --toolchain nightly > [Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ yum install -y llvm-devel clang-devel > ``` ```bash [Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ cargo build --release ``` - **编译错误及处理** > - **编译错误1** > > - **错误描述** > > ```bash > Finished release [optimized] target(s) in 2m 51s > Running `/root/Yikuai/substrate-node-template/target/release/wbuild-runner/node-template-runtime3424067592371620269/target/x86_64-unknown-linux-gnu/release/wasm-build-runner-impl` > Rust WASM toolchain not installed, please install it! > warning: build failed, waiting for other jobs to finish... > error: build failed > ``` > > - **解决办法** > > ``` > $ rustup target add wasm32-unknown-unknown --toolchain nightly > info: downloading component 'rust-std' for 'wasm32-unknown-unknown' > info: installing component 'rust-std' for 'wasm32-unknown-unknown' > info: Defaulting to 500.0 MiB unpack ram > ``` > > - **编译错误2** > > - **错误描述** > > ```bash > warning: couldn't execute `llvm-config --prefix` (error: No such file or directory (os error 2)) > warning: set the LLVM_CONFIG_PATH environment variable to the full path to a valid `llvm-config` executable (including the executable itself) > > error: failed to run custom build command for `librocksdb-sys v6.7.4` > ``` > > - **解决办法** > > ```bash > $ yum install -y llvm-devel > ``` > > - **编译错误3** > > - **错误描述** > > ```bash > Compiling librocksdb-sys v6.7.4 > error: failed to run custom build command for `librocksdb-sys v6.7.4` > > Caused by: > process didn't exit successfully: `/root/Yikuai/substrate-node-template/target/release/build/librocksdb-sys-1bb53efdfd682ab6/build-script-build` (exit code: 101) > --- stdout > cargo:rerun-if-changed=build.rs > > --- stderr > thread 'main' panicked at 'Unable to find libclang: "couldn\'t find any valid shared libraries matching: [\'libclang.so\', \'libclang-*.so\', \'libclang.so.*\', \'libclang-*.so.*\'], set the `LIBCLANG_PATH` environment variable to a path where one of these files can be found (invalid: [])"', /root/.cargo/registry/src/github.com-1ecc6299db9ec823/bindgen-0.53.3/src/lib.rs:1956:31 > ``` > > - **解决办法** > > ```bash > $ yum install -y clang-devel > ``` ## 3.2 存证pallet开发 > `Substrate`运行时由`FRAME` `pallets`组成。这些`pallets`可以被认为是定义你的区块链能够做什么的一个个独立的逻辑单元。 > > `Substrate`已经提供了许多预置`pallets`,用于基于`FRAME`的运行时。如下图所示: > > ![image.png](https://img.learnblockchain.cn/attachments/2020/08/ZmNAOzLJ5f2a802b59214.png) > > 例如,`FRAME`中包含一个`balances`的`pallet`,这个`pallet`通过管理系统中所有账户余额来控制你的区块链系统中的基础货币。如果你想向你的区块链系统中添加智能合约功能,你只需要包含**合约`pallet`**即可。 本节我们就是要开发一个存证`pallet`,并将其添加到我们自定义的区块链中。 ### 3.2.1 创建poe pallet工程目录 > pos => Proof Of Existence ```bash [Jason@RUAN:~/Blockchain/substrate-node-template/pallets] (v2.0.0-rc5)$ cargo new --lib poe [Jason@RUAN:~/Blockchain/substrate-node-template/pallets/poe] (v2.0.0-rc5)$ tree . ├── Cargo.toml └── src └── lib.rs ``` ### 3.2.2 代码框架 在新生成`lib.rs`文件中,填写以下代码框架,这也是从宏观角度来讲,`Substrate` `pallet`可以拆分成的6个部分: ```rust // 1. Imports use frame_support::{decl_module, decl_storage, decl_event, decl_error, dispatch}; use frame_system::{self as system, ensure_signed}; // 2. Pallet Configuration pub trait Trait: system::Trait { /* --snip-- */ } // 3. Pallet Storage Items decl_storage! { /* --snip-- */ } // 4. Pallet Events decl_event! { /* --snip-- */ } // 5. Pallet Errors decl_error! { /* --snip-- */ } // 6. Callable Pallet Functions decl_module! { /* --snip-- */ } ``` ### 3.2.3 添加依赖 #### (1)完善引用 ```rust #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ decl_module, decl_storage, decl_event, decl_error, ensure, StorageMap }; use frame_system::{self as system, ensure_signed}; use sp_std::vec::Vec; ``` #### (2)完善`Cargo.toml`文件 将`pallets/template/Cargo.toml`拷贝至`pallets/poe`目录,并增加以下内容: ```toml # 增加段 [dependencies.sp-std] git = 'https://github.com/paritytech/substrate.git' default-features = false tag = 'v2.0.0-rc5' version = '2.0.0-rc5' [features] default = ['std'] std = [ 'codec/std', 'frame-support/std', 'frame-system/std', 'sp-std/std', # <-- 增加行 ] ``` ### 3.2.4 配置pallet > 每一个`pallet`都有一个配置`trait` ```rust // 2. Pallet Configuration pub trait Trait: system::Trait { /// The overarching event type. type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>; } ``` ### 3.2.5 定义事件 > 事件:可以展示`pallet`成功被调用的时间和信息。 ```rust // 4. Pallet Events decl_event! { pub enum Event<T> where AccountId = <T as system::Trait>::AccountId { /// Event emitted when a proof has been claimed. ClaimCreated(AccountId, Vec<u8>), /// Event emitted when a claim is revoked by the owner. ClaimRevoked(AccountId, Vec<u8>), } } ``` 我们的存证`palet`,包含了以下事件: - `ClaimCreated`:存证创建 - `ClaimRevoked`:存证撤销 事件可以包含一些附加数据,例如: - `AccountId`:谁触发了事件 - `Vec<u8>`:存储或撤销的存证数据 ### 3.2.6 定义错误 > 错误:可以展示`pallet`调用失败的时间,及失败原因。 ```rust // 5. Pallet Errors decl_error! { pub enum Error for Module<T: Trait> { /// This proof has already been claimed ProofAlreadyClaimed, /// The proof does not exist, so it cannot be revoked NoSuchProof, /// The proof is claimed by another account, so caller can't revoke it NotProofOwner, } } ``` ### 3.2.7 定义存储 要添加一个新的存证到我们的区块链上,就是要将其存储到我们的`pallet`的存储里面。在这里创建我们的存储结构。 ```rust // 3. Pallet Storage Items decl_storage! { trait Store for Module<T: Trait> as TemplateModule { /// The storage item for our proofs. /// It maps a proof to the user who made the claim and when they made it. Proofs: map hasher(blake2_128_concat) Vec<u8> => (T::AccountId, T::BlockNumber); } } ``` ### 3.2.8 实现接口 ```rust // 6. Callable Pallet Functions decl_module! { /// The module declaration. pub struct Module<T: Trait> for enum Call where origin: T::Origin { // Initializing errors // this includes information about your errors in the node's metadata. // it is needed only if you are using errors in your pallet type Error = Error<T>; // A default function for depositing events fn deposit_event() = default; /// Allow a user to claim ownership of an unclaimed proof #[weight = 10_000] fn create_claim(origin, proof: Vec<u8>) { // Verify that the incoming transaction is signed and store who the // caller of this function is. let sender = ensure_signed(origin)?; // Verify that the specified proof has not been claimed yet or error with the message ensure!(!Proofs::<T>::contains_key(&proof), Error::<T>::ProofAlreadyClaimed); // Call the `system` pallet to get the current block number let current_block = <system::Module<T>>::block_number(); // Store the proof with the sender and the current block number Proofs::<T>::insert(&proof, (&sender, current_block)); // Emit an event that the claim was created Self::deposit_event(RawEvent::ClaimCreated(sender, proof)); } /// Allow the owner to revoke their claim #[weight = 10_000] fn revoke_claim(origin, proof: Vec<u8>) { // Determine who is calling the function let sender = ensure_signed(origin)?; // Verify that the specified proof has been claimed ensure!(Proofs::<T>::contains_key(&proof), Error::<T>::NoSuchProof); // Get owner of the claim let (owner, _) = Proofs::<T>::get(&proof); // Verify that sender of the current call is the claim owner ensure!(sender == owner, Error::<T>::NotProofOwner); // Remove claim from storage Proofs::<T>::remove(&proof); // Emit an event that the claim was erased Self::deposit_event(RawEvent::ClaimRevoked(sender, proof)); } } } ``` ### 3.2.9 完善runtime配置 - 修改`runtime/Cargo.toml` ```toml # 增加段 [dependencies.poe] default-features = false package = 'pallet-poe' path = '../pallets/poe' version = '2.0.0-rc5' [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` ```rust // 增加代码块 impl poe::Trait for Runtime { type Event = Event; } 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}, 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>}, TemplateModule: template::{Module, Call, Storage, Event<T>}, PoeModule: poe::{Module, Call, Storage, Event<T>}, // <-- 增加代码行 } ); ``` ## 3.3 node-template节点编译 完成存证`pallet`的开发后,需要重新编译节点。 ```bash [Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ cargo build --release Compiling node-template-runtime v2.0.0-rc5 (/root/Blockchain/substrate-node-template/runtime) Compiling pallet-poe v2.0.0-rc5 (/root/Blockchain/substrate-node-template/pallets/poe) Compiling node-template v2.0.0-rc5 (/root/Blockchain/substrate-node-template/node) Finished release [optimized] target(s) in 12m 18s ``` ## 3.4 node-template节点启动 ```bash [Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ ./target/release/node-template purge-chain --dev Are you sure to remove "/root/.local/share/node-template/chains/dev/db"? [y/N]: y "/root/.local/share/node-template/chains/dev/db" removed. [Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ ./target/release/node-template --dev --ws-external --rpc-external --rpc-cors=all 2020-08-04 22:23:44 Substrate Node 2020-08-04 22:23:44 ️ version 2.0.0-rc5-8f769db-x86_64-linux-gnu 2020-08-04 22:23:44 ️ by Substrate DevHub <https://github.com/substrate-developer-hub>, 2017-2020 2020-08-04 22:23:44 Chain specification: Development 2020-08-04 22:23:44 Node name: gray-island-3707 2020-08-04 22:23:44 Role: AUTHORITY 2020-08-04 22:23:44 Database: RocksDb at /root/.local/share/node-template/chains/dev/db 2020-08-04 22:23:44 Native runtime: node-template-1 (node-template-1.tx1.au1) 2020-08-04 22:23:44 Initializing Genesis block/state (state: 0x5ea9…1904, header-hash: 0x6dac…f18d) 2020-08-04 22:23:44 Loading GRANDPA authority set from genesis on what appears to be first startup. 2020-08-04 22:23:44 ⏱ Loaded block-time = 6000 milliseconds from genesis on first-launch 2020-08-04 22:23:44 Highest known block at #0 2020-08-04 22:23:44 Using default protocol ID "sup" because none is configured in the chain specs 2020-08-04 22:23:44 Local node identity is: 12D3KooWBSKitzNNzfSszWXRggcMe44bv6WfyKy9kyM2DwjcjJNr (legacy representation: QmX77kaM8ydN99qjyRTRznRqkHahzi5jX286MnQTqUp3UR) 2020-08-04 22:23:44 〽 Prometheus server started at 127.0.0.1:9615 2020-08-04 22:23:48 Starting consensus session on top of parent 0x6dac7f7bfbd9cbc4e91be19069d230c9b044ef6080d781e6717a9c99e442f18d 2020-08-04 22:23:48 Prepared block for proposing at 1 [hash: 0xd129eea95a079183db3dd87947194add22643588a1fec10e778b93a867d0f161; parent_hash: 0x6dac…f18d; extrinsics (1): [0xc502…67b6]] 2020-08-04 22:23:48 Pre-sealed block for proposal at 1. Hash now 0x2305ab1c8aee785cb991c993e849b79d7231ad8206b0e7e9b75ef17c3ee90b64, previously 0xd129eea95a079183db3dd87947194add22643588a1fec10e778b93a867d0f161. 2020-08-04 22:23:48 Imported #1 (0x2305…0b64) 2020-08-04 22:23:49 Idle (0 peers), best: #1 (0x2305…0b64), finalized #0 (0x6dac…f18d), ⬇ 0 ⬆ 0 2020-08-04 22:23:54 Starting consensus session on top of parent 0x2305ab1c8aee785cb991c993e849b79d7231ad8206b0e7e9b75ef17c3ee90b64 2020-08-04 22:23:54 Prepared block for proposing at 2 [hash: 0xf68d2a78c2da715618cfeb4488db17ac925386f06f0b7afa0946f006bef4a770; parent_hash: 0x2305…0b64; extrinsics (1): [0xef36…63d4]] 2020-08-04 22:23:54 Pre-sealed block for proposal at 2. Hash now 0x2acd73ad04ea2e9a5cbd4e3eb4273c56e7a55eb2a3e15d45aed422a682bc5a72, previously 0xf68d2a78c2da715618cfeb4488db17ac925386f06f0b7afa0946f006bef4a770. 2020-08-04 22:23:54 Imported #2 (0x2acd…5a72) 2020-08-04 22:23:54 Idle (0 peers), best: #2 (0x2acd…5a72), finalized #0 (0x6dac…f18d), ⬇ 0 ⬆ 0 2020-08-04 22:23:59 Idle (0 peers), best: #2 (0x2acd…5a72), finalized #0 (0x6dac…f18d), ⬇ 0 ⬆ 0 2020-08-04 22:24:00 Starting consensus session on top of parent 0x2acd73ad04ea2e9a5cbd4e3eb4273c56e7a55eb2a3e15d45aed422a682bc5a72 2020-08-04 22:24:00 Prepared block for proposing at 3 [hash: 0x3a954db6a3e82636161ce4cbc60e5d12617caafad52912512a5bd3a77c2d5eca; parent_hash: 0x2acd…5a72; extrinsics (1): [0x0cd5…444d]] 2020-08-04 22:24:00 Pre-sealed block for proposal at 3. Hash now 0x7e2616b9f3d0b18caf611bf3c4109609c1fa5edb4391e813d15f2f0e957903ba, previously 0x3a954db6a3e82636161ce4cbc60e5d12617caafad52912512a5bd3a77c2d5eca. 2020-08-04 22:24:00 Imported #3 (0x7e26…03ba) 2020-08-04 22:24:04 Idle (0 peers), best: #3 (0x7e26…03ba), finalized #1 (0x2305…0b64), ⬇ 0 ⬆ 0 ``` # 4 存证dApp前端界面开发 存证`dApp`前端界面是基于`front-end-template`开发,它是`Substrate`前端应用开发模板,可以通过其连接`Substrate`后端节点。 ## 4.1 front-end-template安装 - **版本** `v2.0.0-rc5` - **下载** ```bash [Jason@RUAN:~/Blockchain]$ git clone git@github.com:substrate-developer-hub/substrate-front-end-template.git [Jason@RUAN:~/Blockchain/substrate-front-end-template] (master)$ git checkout -b v2.0.0-rc5 v2.0.0-rc5 切换到一个新分支 'v2.0.0-rc5' [Jason@RUAN:~/Blockchain/substrate-front-end-template] (v2.0.0-rc5)$ ``` - **安装** > 安装依赖,避免后续编译错误: > > ```bash > $ yum instally -y libusbx-devel libusb-devel libudev-devel > ``` ```bash [Jason@RUAN:~/Blockchain/substrate-front-end-template] (v2.0.0-rc5)$ yarn install yarn install v1.22.4 ``` - **安装错误及处理** > - **安装错误1** > > - **错误描述** > > ```bash > Package libusb-1.0 was not found in the pkg-config search path. > Perhaps you should add the directory containing `libusb-1.0.pc' > to the PKG_CONFIG_PATH environment variable > No package 'libusb-1.0' found > ``` > > - **解决办法** > > ```bash > $ yum install libusbx-devel libusb-devel -y > ``` > > - **安装错误2** > > - **错误描述** > > ```bash > libudev.h:没有那个文件或目录 > ``` > > - **解决办法** > > ```bash > $ yum install libudev-devel > ``` - **启动命令** ```bash [Jason@RUAN:~/Blockchain/substrate-front-end-template] (v2.0.0-rc5)$ yarn start Compiled successfully! You can now view substrate-front-end-template in the browser. Local: http://localhost:8000/ On Your Network: http://172.29.0.4:8000/ Note that the development build is not optimized. To create a production build, use yarn build. ``` ## 4.2 存证React组件开发 ### 4.2.1 实现存证组件 在`substrate-front-end-template/src`目录下创建`PoeModule.js`,代码如下: ```react React and Semantic UI elements. import React, { useState, useEffect } from 'react'; import { Form, Input, Grid, Message } from 'semantic-ui-react'; // Pre-built Substrate front-end utilities for connecting to a node // and making a transaction. import { useSubstrate } from './substrate-lib'; import { TxButton } from './substrate-lib/components'; // Polkadot-JS utilities for hashing data. import { blake2AsHex } from '@polkadot/util-crypto'; // Our main Proof Of Existence Component which is exported. export function Main (props) { // Establish an API to talk to our Substrate node. const { api } = useSubstrate(); // Get the 'selected user' from the `AccountSelector` component. const { accountPair } = props; // React hooks for all the state variables we track. // Learn more at: https://reactjs.org/docs/hooks-intro.html const [status, setStatus] = useState(''); const [digest, setDigest] = useState(''); const [owner, setOwner] = useState(''); const [block, setBlock] = useState(0); // Our `FileReader()` which is accessible from our functions below. let fileReader; // Takes our file, and creates a digest using the Blake2 256 hash function. const bufferToDigest = () => { // Turns the file content to a hexadecimal representation. const content = Array.from(new Uint8Array(fileReader.result)) .map((b) => b.toString(16).padStart(2, '0')) .join(''); const hash = blake2AsHex(content, 256); setDigest(hash); }; // Callback function for when a new file is selected. const handleFileChosen = (file) => { fileReader = new FileReader(); fileReader.onloadend = bufferToDigest; fileReader.readAsArrayBuffer(file); }; // React hook to update the 'Owner' and 'Block Number' information for a file. useEffect(() => { let unsubscribe; // Polkadot-JS API query to the `proofs` storage item in our pallet. // This is a subscription, so it will always get the latest value, // even if it changes. api.query.poeModule .proofs(digest, (result) => { // Our storage item returns a tuple, which is represented as an array. setOwner(result[0].toString()); setBlock(result[1].toNumber()); }) .then((unsub) => { unsubscribe = unsub; }); return () => unsubscribe && unsubscribe(); // This tells the React hook to update whenever the file digest changes // (when a new file is chosen), or when the storage subscription says the // value of the storage item has updated. }, [digest, api.query.poeModule]); // We can say a file digest is claimed if the stored block number is not 0. function isClaimed () { return block !== 0; } // The actual UI elements which are returned from our component. return ( <Grid.Column> <h1>Proof Of Existence</h1> {/* Show warning or success message if the file is or is not claimed. */} <Form success={!!digest && !isClaimed()} warning={isClaimed()}> <Form.Field> {/* File selector with a callback to `handleFileChosen`. */} <Input type='file' id='file' label='Your File' onChange={(e) => handleFileChosen(e.target.files[0])} /> {/* Show this message if the file is available to be claimed */} <Message success header='File Digest Unclaimed' content={digest} /> {/* Show this message if the file is already claimed. */} <Message warning header='File Digest Claimed' list={[digest, `Owner: ${owner}`, `Block: ${block}`]} /> </Form.Field> {/* Buttons for interacting with the component. */} <Form.Field> {/* Button to create a claim. Only active if a file is selected, and not already claimed. Updates the `status`. */} <TxButton accountPair={accountPair} label={'Create Claim'} setStatus={setStatus} type='SIGNED-TX' disabled={isClaimed() || !digest} attrs={{ palletRpc: 'poeModule', callable: 'createClaim', inputParams: [digest], paramFields: [true] }} /> {/* Button to revoke a claim. Only active if a file is selected, and is already claimed. Updates the `status`. */} <TxButton accountPair={accountPair} label='Revoke Claim' setStatus={setStatus} type='SIGNED-TX' disabled={!isClaimed() || owner !== accountPair.address} attrs={{ palletRpc: 'poeModule', callable: 'revokeClaim', inputParams: [digest], paramFields: [true] }} /> </Form.Field> {/* Status message about the transaction. */} <div style={{ overflowWrap: 'break-word' }}>{status}</div> </Form> </Grid.Column> ); } export default function PoeModule (props) { const { api } = useSubstrate(); return (api.query.poeModule && api.query.poeModule.proofs ? <Main {...props} /> : null); } ``` ### 4.2.2 添加存证组件 在`substrate-front-end-template/src/App.js`中添加存证组件,代码如下: ```react import PoeModule from './PoeModule'; return ( <div ref={contextRef}> <Sticky context={contextRef}> <AccountSelector setAccountAddress={setAccountAddress} /> </Sticky> <Container> <Grid stackable columns='equal'> ...... <Grid.Row> <PoeModule accountPair={accountPair} /> </Grid.Row> </Grid> <DeveloperConsole /> </Container> </div> ); ``` ### 4.2.3 修改连接配置 配置文件:`substrate-front-end-template/src/config/development.json` ```json "PROVIDER_SOCKET": "ws://119.28.233.229:9944" ``` ## 4.3 front-end-template启动 ```bash $ yarn start Compiled successfully! You can now view substrate-front-end-template in the browser. Local: http://localhost:8000/ On Your Network: http://172.29.0.6:8000/ Note that the development build is not optimized. To create a production build, use yarn build. ``` ## 4.4 存证界面展示 ![image.png](https://img.learnblockchain.cn/attachments/2020/08/KFx2ZcLQ5f2a806979512.png) # 5 存证dApp使用展示 ## 5.1 提交存证 - 选择文件 ![image.png](https://img.learnblockchain.cn/attachments/2020/08/M2ED9jYm5f2a809f98868.png) - 提交存证 - 存证入块 ![image.png](https://img.learnblockchain.cn/attachments/2020/08/QSeKYv1Z5f2a80b0a2754.png) - 存证入块确认 ![image.png](https://img.learnblockchain.cn/attachments/2020/08/MQpuWd7Z5f2a80c627bd7.png) ## 5.2 撤销存证 > 撤销存证的按钮,只对创建对应存证的用户可见,例如`Alice`创建的存证,在切换到`Bob`账号后,撤销存证的按钮会灰掉: > > ![image.png](https://img.learnblockchain.cn/attachments/2020/08/Cz2buDD55f2a80e56f5e6.png) ![image.png](https://img.learnblockchain.cn/attachments/2020/08/0FMREBt85f2a810a4aeaf.png) ## 5.3 事件查看 可以查看到提交存证和撤销存证接口调用后触发的事件。 ![image.png](https://img.learnblockchain.cn/attachments/2020/08/ok7vwlku5f2a8120ae956.png) # 6 参考资料 https://substrate.dev/docs/en/tutorials/build-a-dapp/
jasonruan 2020.08.05
1 前言
前面文章介绍了在Substrate
上开发智能合约,包括使用原生的ink!
语言开发ERC20
智能合约,以及将以太坊的Solidity
智能合约跑在Substrate
链上,在本文将进一步学习在Substrate
链上开发一个自定义的区块链存证dApp
。
本文内容参考:https://substrate.dev/docs/en/tutorials/build-a-dapp/
2 前置准备
2.1 rust安装
Substrate
是由rust
语言开发,首先需要安装rust
环境。
2.1.1 软件安装
- Rust的安装比较简单,执行如下一条命令即可,该命令将自动完成软件包的下载、安装、环境变量设置:
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
- 安装成功后,会显示如下日志
stable installed - rustc 1.45.1 (c367798cf 2020-07-26)
Rust is installed now. Great!
- 安装完成后,在
~/.cargo/bin
目录可以看到相关命令行工具
.cargo/bin/
├── cargo
├── cargo-clippy
├── cargo-fmt
├── cargo-miri
├── clippy-driver
├── rls
├── rustc
├── rustdoc
├── rustfmt
├── rust-gdb
├── rust-lldb
└── rustup
2.1.2 环变设置
- 执行下面命令,即将
export PATH="$HOME/.cargo/bin:$PATH"
,追加到~/.bashrc
中
$ cat ~/.cargo/env >> ~/.bashrc
- 执行下面命令,使得添加的环境变量生效
$ . ~/.bashrc
- 可执行如下命令查看安装版本
$ rustc --version
rustc 1.45.1 (c367798cf 2020-07-26)
2.1.3 配套安装
(1) Racer安装
Racer
是一个由Rust
爱好者提供的Rust
自动补全和语法分析工具,被用来提供基本的补全功能和自定义跳转功能。本身完全由Rust
写成,补全功能比较完善。
- 安装命令
$ cargo install racer
......
Finished release [optimized] target(s) in 2m 44s
Installing /home/jason/.cargo/bin/racer
Installed package `racer v2.1.36` (executable `racer`)
若安装报错:
error[E0554]: #![feature] may not be used on the stable release channel
请先执行下面命令,切换到
nightly
版本后,再进行安装:$ rustup install nightly $ rustup default nightly $ rustc --version rustc 1.47.0-nightly (6c8927b0c 2020-07-26)
- 查看版本
$ racer -V
racer 2.1.36
(2) 源码下载
为了对
Rust
标准库进行补全,Racer
需要获取Rust
源码路径。通过rustup
获取源码的好处是rustup update
可以随时获取最新代码
- 获取源码
$ rustup component add rust-src
info: downloading component 'rust-src'
info: installing component 'rust-src'
- 更新源码
$ rustup update
info: checking for self-updates
stable-x86_64-unknown-linux-gnu unchanged - rustc 1.45.2 (d3fb005a3 2020-07-31)
nightly-x86_64-unknown-linux-gnu unchanged - rustc 1.47.0-nightly (6c8927b0c 2020-07-26)
info: cleaning up downloads & tmp directories
- 环变设置
在
.bashrc
中添加以下内容:
export RUST_SRC_PATH="$(rustc --print sysroot)/lib/rustlib/src/rust/src"
2.2 yarn安装
Substrate
前端模板工程(front-end-template
)是使用yarn
进行包管理的,在此我们进行安装。
安装步骤参考:https://classic.yarnpkg.com/en/docs/install/#centos-stable
$ curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
[yarn]
name=Yarn Repository
baseurl=https://dl.yarnpkg.com/rpm/
enabled=1
gpgcheck=1
gpgkey=https://dl.yarnpkg.com/rpm/pubkey.gpg
$ curl --silent --location https://rpm.nodesource.com/setup_12.x | sudo bash -
$ sudo yum install yarn
$ yarn --version
1.22.4
3 存证dApp后端节点开发
存证dApp
后端节点是基于node-template
来开发,它是一个基于FRAME
的Substrate
后端节点,可以在其基础上,进行修改以便快速搭建属于自己的Substrate
网络。
3.1 node-template安装
-
版本
v2.0.0-rc5
-
下载
[Jason@RUAN:~/Blockchain]$ git clone git@github.com:substrate-developer-hub/substrate-node-template.git
[Jason@RUAN:~/Blockchain/substrate-node-template] (master)$ git checkout -b v2.0.0-rc5 v2.0.0-rc5
切换到一个新分支 'v2.0.0-rc5'
[Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$
- 编译
安装依赖,避免后续编译错误:
[Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ rustup target add wasm32-unknown-unknown --toolchain nightly [Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ yum install -y llvm-devel clang-devel
[Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ cargo build --release
- 编译错误及处理
编译错误1
- 错误描述
Finished release [optimized] target(s) in 2m 51s Running `/root/Yikuai/substrate-node-template/target/release/wbuild-runner/node-template-runtime3424067592371620269/target/x86_64-unknown-linux-gnu/release/wasm-build-runner-impl` Rust WASM toolchain not installed, please install it! warning: build failed, waiting for other jobs to finish... error: build failed
- 解决办法
$ rustup target add wasm32-unknown-unknown --toolchain nightly info: downloading component 'rust-std' for 'wasm32-unknown-unknown' info: installing component 'rust-std' for 'wasm32-unknown-unknown' info: Defaulting to 500.0 MiB unpack ram
编译错误2
- 错误描述
warning: couldn't execute `llvm-config --prefix` (error: No such file or directory (os error 2)) warning: set the LLVM_CONFIG_PATH environment variable to the full path to a valid `llvm-config` executable (including the executable itself) error: failed to run custom build command for `librocksdb-sys v6.7.4`
- 解决办法
$ yum install -y llvm-devel
编译错误3
- 错误描述
Compiling librocksdb-sys v6.7.4 error: failed to run custom build command for `librocksdb-sys v6.7.4` Caused by: process didn't exit successfully: `/root/Yikuai/substrate-node-template/target/release/build/librocksdb-sys-1bb53efdfd682ab6/build-script-build` (exit code: 101) --- stdout cargo:rerun-if-changed=build.rs --- stderr thread 'main' panicked at 'Unable to find libclang: "couldn\'t find any valid shared libraries matching: [\'libclang.so\', \'libclang-*.so\', \'libclang.so.*\', \'libclang-*.so.*\'], set the `LIBCLANG_PATH` environment variable to a path where one of these files can be found (invalid: [])"', /root/.cargo/registry/src/github.com-1ecc6299db9ec823/bindgen-0.53.3/src/lib.rs:1956:31
- 解决办法
$ yum install -y clang-devel
3.2 存证pallet开发
Substrate
运行时由FRAME
pallets
组成。这些pallets
可以被认为是定义你的区块链能够做什么的一个个独立的逻辑单元。
Substrate
已经提供了许多预置pallets
,用于基于FRAME
的运行时。如下图所示:例如,
FRAME
中包含一个balances
的pallet
,这个pallet
通过管理系统中所有账户余额来控制你的区块链系统中的基础货币。如果你想向你的区块链系统中添加智能合约功能,你只需要包含合约pallet
即可。
本节我们就是要开发一个存证pallet
,并将其添加到我们自定义的区块链中。
3.2.1 创建poe pallet工程目录
pos => Proof Of Existence
[Jason@RUAN:~/Blockchain/substrate-node-template/pallets] (v2.0.0-rc5)$ cargo new --lib poe
[Jason@RUAN:~/Blockchain/substrate-node-template/pallets/poe] (v2.0.0-rc5)$ tree
.
├── Cargo.toml
└── src
└── lib.rs
3.2.2 代码框架
在新生成lib.rs
文件中,填写以下代码框架,这也是从宏观角度来讲,Substrate
pallet
可以拆分成的6个部分:
// 1. Imports
use frame_support::{decl_module, decl_storage, decl_event, decl_error, dispatch};
use frame_system::{self as system, ensure_signed};
// 2. Pallet Configuration
pub trait Trait: system::Trait { /* --snip-- */ }
// 3. Pallet Storage Items
decl_storage! { /* --snip-- */ }
// 4. Pallet Events
decl_event! { /* --snip-- */ }
// 5. Pallet Errors
decl_error! { /* --snip-- */ }
// 6. Callable Pallet Functions
decl_module! { /* --snip-- */ }
3.2.3 添加依赖
(1)完善引用
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::{
decl_module, decl_storage, decl_event, decl_error, ensure, StorageMap
};
use frame_system::{self as system, ensure_signed};
use sp_std::vec::Vec;
(2)完善Cargo.toml
文件
将pallets/template/Cargo.toml
拷贝至pallets/poe
目录,并增加以下内容:
# 增加段
[dependencies.sp-std]
git = 'https://github.com/paritytech/substrate.git'
default-features = false
tag = 'v2.0.0-rc5'
version = '2.0.0-rc5'
[features]
default = ['std']
std = [
'codec/std',
'frame-support/std',
'frame-system/std',
'sp-std/std', # <-- 增加行
]
3.2.4 配置pallet
每一个
pallet
都有一个配置trait
// 2. Pallet Configuration
pub trait Trait: system::Trait {
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
3.2.5 定义事件
事件:可以展示
pallet
成功被调用的时间和信息。
// 4. Pallet Events
decl_event! {
pub enum Event<T> where AccountId = <T as system::Trait>::AccountId {
/// Event emitted when a proof has been claimed.
ClaimCreated(AccountId, Vec<u8>),
/// Event emitted when a claim is revoked by the owner.
ClaimRevoked(AccountId, Vec<u8>),
}
}
我们的存证palet
,包含了以下事件:
ClaimCreated
:存证创建ClaimRevoked
:存证撤销
事件可以包含一些附加数据,例如:
AccountId
:谁触发了事件Vec<u8>
:存储或撤销的存证数据
3.2.6 定义错误
错误:可以展示
pallet
调用失败的时间,及失败原因。
// 5. Pallet Errors
decl_error! {
pub enum Error for Module<T: Trait> {
/// This proof has already been claimed
ProofAlreadyClaimed,
/// The proof does not exist, so it cannot be revoked
NoSuchProof,
/// The proof is claimed by another account, so caller can't revoke it
NotProofOwner,
}
}
3.2.7 定义存储
要添加一个新的存证到我们的区块链上,就是要将其存储到我们的pallet
的存储里面。在这里创建我们的存储结构。
// 3. Pallet Storage Items
decl_storage! {
trait Store for Module<T: Trait> as TemplateModule {
/// The storage item for our proofs.
/// It maps a proof to the user who made the claim and when they made it.
Proofs: map hasher(blake2_128_concat) Vec<u8> => (T::AccountId, T::BlockNumber);
}
}
3.2.8 实现接口
// 6. Callable Pallet Functions
decl_module! {
/// The module declaration.
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
// Initializing errors
// this includes information about your errors in the node's metadata.
// it is needed only if you are using errors in your pallet
type Error = Error<T>;
// A default function for depositing events
fn deposit_event() = default;
/// Allow a user to claim ownership of an unclaimed proof
#[weight = 10_000]
fn create_claim(origin, proof: Vec<u8>) {
// Verify that the incoming transaction is signed and store who the
// caller of this function is.
let sender = ensure_signed(origin)?;
// Verify that the specified proof has not been claimed yet or error with the message
ensure!(!Proofs::<T>::contains_key(&proof), Error::<T>::ProofAlreadyClaimed);
// Call the `system` pallet to get the current block number
let current_block = <system::Module<T>>::block_number();
// Store the proof with the sender and the current block number
Proofs::<T>::insert(&proof, (&sender, current_block));
// Emit an event that the claim was created
Self::deposit_event(RawEvent::ClaimCreated(sender, proof));
}
/// Allow the owner to revoke their claim
#[weight = 10_000]
fn revoke_claim(origin, proof: Vec<u8>) {
// Determine who is calling the function
let sender = ensure_signed(origin)?;
// Verify that the specified proof has been claimed
ensure!(Proofs::<T>::contains_key(&proof), Error::<T>::NoSuchProof);
// Get owner of the claim
let (owner, _) = Proofs::<T>::get(&proof);
// Verify that sender of the current call is the claim owner
ensure!(sender == owner, Error::<T>::NotProofOwner);
// Remove claim from storage
Proofs::<T>::remove(&proof);
// Emit an event that the claim was erased
Self::deposit_event(RawEvent::ClaimRevoked(sender, proof));
}
}
}
3.2.9 完善runtime配置
- 修改
runtime/Cargo.toml
# 增加段
[dependencies.poe]
default-features = false
package = 'pallet-poe'
path = '../pallets/poe'
version = '2.0.0-rc5'
[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;
}
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},
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>},
TemplateModule: template::{Module, Call, Storage, Event<T>},
PoeModule: poe::{Module, Call, Storage, Event<T>}, // <-- 增加代码行
}
);
3.3 node-template节点编译
完成存证pallet
的开发后,需要重新编译节点。
[Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ cargo build --release
Compiling node-template-runtime v2.0.0-rc5 (/root/Blockchain/substrate-node-template/runtime)
Compiling pallet-poe v2.0.0-rc5 (/root/Blockchain/substrate-node-template/pallets/poe)
Compiling node-template v2.0.0-rc5 (/root/Blockchain/substrate-node-template/node)
Finished release [optimized] target(s) in 12m 18s
3.4 node-template节点启动
[Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ ./target/release/node-template purge-chain --dev
Are you sure to remove "/root/.local/share/node-template/chains/dev/db"? [y/N]: y
"/root/.local/share/node-template/chains/dev/db" removed.
[Jason@RUAN:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ ./target/release/node-template --dev --ws-external --rpc-external --rpc-cors=all
2020-08-04 22:23:44 Substrate Node
2020-08-04 22:23:44 ️ version 2.0.0-rc5-8f769db-x86_64-linux-gnu
2020-08-04 22:23:44 ️ by Substrate DevHub <https://github.com/substrate-developer-hub>, 2017-2020
2020-08-04 22:23:44 Chain specification: Development
2020-08-04 22:23:44 Node name: gray-island-3707
2020-08-04 22:23:44 Role: AUTHORITY
2020-08-04 22:23:44 Database: RocksDb at /root/.local/share/node-template/chains/dev/db
2020-08-04 22:23:44 Native runtime: node-template-1 (node-template-1.tx1.au1)
2020-08-04 22:23:44 Initializing Genesis block/state (state: 0x5ea9…1904, header-hash: 0x6dac…f18d)
2020-08-04 22:23:44 Loading GRANDPA authority set from genesis on what appears to be first startup.
2020-08-04 22:23:44 ⏱ Loaded block-time = 6000 milliseconds from genesis on first-launch
2020-08-04 22:23:44 Highest known block at #0
2020-08-04 22:23:44 Using default protocol ID "sup" because none is configured in the chain specs
2020-08-04 22:23:44 Local node identity is: 12D3KooWBSKitzNNzfSszWXRggcMe44bv6WfyKy9kyM2DwjcjJNr (legacy representation: QmX77kaM8ydN99qjyRTRznRqkHahzi5jX286MnQTqUp3UR)
2020-08-04 22:23:44 〽 Prometheus server started at 127.0.0.1:9615
2020-08-04 22:23:48 Starting consensus session on top of parent 0x6dac7f7bfbd9cbc4e91be19069d230c9b044ef6080d781e6717a9c99e442f18d
2020-08-04 22:23:48 Prepared block for proposing at 1 [hash: 0xd129eea95a079183db3dd87947194add22643588a1fec10e778b93a867d0f161; parent_hash: 0x6dac…f18d; extrinsics (1): [0xc502…67b6]]
2020-08-04 22:23:48 Pre-sealed block for proposal at 1. Hash now 0x2305ab1c8aee785cb991c993e849b79d7231ad8206b0e7e9b75ef17c3ee90b64, previously 0xd129eea95a079183db3dd87947194add22643588a1fec10e778b93a867d0f161.
2020-08-04 22:23:48 Imported #1 (0x2305…0b64)
2020-08-04 22:23:49 Idle (0 peers), best: #1 (0x2305…0b64), finalized #0 (0x6dac…f18d), ⬇ 0 ⬆ 0
2020-08-04 22:23:54 Starting consensus session on top of parent 0x2305ab1c8aee785cb991c993e849b79d7231ad8206b0e7e9b75ef17c3ee90b64
2020-08-04 22:23:54 Prepared block for proposing at 2 [hash: 0xf68d2a78c2da715618cfeb4488db17ac925386f06f0b7afa0946f006bef4a770; parent_hash: 0x2305…0b64; extrinsics (1): [0xef36…63d4]]
2020-08-04 22:23:54 Pre-sealed block for proposal at 2. Hash now 0x2acd73ad04ea2e9a5cbd4e3eb4273c56e7a55eb2a3e15d45aed422a682bc5a72, previously 0xf68d2a78c2da715618cfeb4488db17ac925386f06f0b7afa0946f006bef4a770.
2020-08-04 22:23:54 Imported #2 (0x2acd…5a72)
2020-08-04 22:23:54 Idle (0 peers), best: #2 (0x2acd…5a72), finalized #0 (0x6dac…f18d), ⬇ 0 ⬆ 0
2020-08-04 22:23:59 Idle (0 peers), best: #2 (0x2acd…5a72), finalized #0 (0x6dac…f18d), ⬇ 0 ⬆ 0
2020-08-04 22:24:00 Starting consensus session on top of parent 0x2acd73ad04ea2e9a5cbd4e3eb4273c56e7a55eb2a3e15d45aed422a682bc5a72
2020-08-04 22:24:00 Prepared block for proposing at 3 [hash: 0x3a954db6a3e82636161ce4cbc60e5d12617caafad52912512a5bd3a77c2d5eca; parent_hash: 0x2acd…5a72; extrinsics (1): [0x0cd5…444d]]
2020-08-04 22:24:00 Pre-sealed block for proposal at 3. Hash now 0x7e2616b9f3d0b18caf611bf3c4109609c1fa5edb4391e813d15f2f0e957903ba, previously 0x3a954db6a3e82636161ce4cbc60e5d12617caafad52912512a5bd3a77c2d5eca.
2020-08-04 22:24:00 Imported #3 (0x7e26…03ba)
2020-08-04 22:24:04 Idle (0 peers), best: #3 (0x7e26…03ba), finalized #1 (0x2305…0b64), ⬇ 0 ⬆ 0
4 存证dApp前端界面开发
存证dApp
前端界面是基于front-end-template
开发,它是Substrate
前端应用开发模板,可以通过其连接Substrate
后端节点。
4.1 front-end-template安装
-
版本
v2.0.0-rc5
-
下载
[Jason@RUAN:~/Blockchain]$ git clone git@github.com:substrate-developer-hub/substrate-front-end-template.git
[Jason@RUAN:~/Blockchain/substrate-front-end-template] (master)$ git checkout -b v2.0.0-rc5 v2.0.0-rc5
切换到一个新分支 'v2.0.0-rc5'
[Jason@RUAN:~/Blockchain/substrate-front-end-template] (v2.0.0-rc5)$
- 安装
安装依赖,避免后续编译错误:
$ yum instally -y libusbx-devel libusb-devel libudev-devel
[Jason@RUAN:~/Blockchain/substrate-front-end-template] (v2.0.0-rc5)$ yarn install
yarn install v1.22.4
- 安装错误及处理
安装错误1
- 错误描述
Package libusb-1.0 was not found in the pkg-config search path. Perhaps you should add the directory containing `libusb-1.0.pc' to the PKG_CONFIG_PATH environment variable No package 'libusb-1.0' found
- 解决办法
$ yum install libusbx-devel libusb-devel -y
安装错误2
- 错误描述
libudev.h:没有那个文件或目录
- 解决办法
$ yum install libudev-devel
- 启动命令
[Jason@RUAN:~/Blockchain/substrate-front-end-template] (v2.0.0-rc5)$ yarn start
Compiled successfully!
You can now view substrate-front-end-template in the browser.
Local: http://localhost:8000/
On Your Network: http://172.29.0.4:8000/
Note that the development build is not optimized.
To create a production build, use yarn build.
4.2 存证React组件开发
4.2.1 实现存证组件
在substrate-front-end-template/src
目录下创建PoeModule.js
,代码如下:
React and Semantic UI elements.
import React, { useState, useEffect } from 'react';
import { Form, Input, Grid, Message } from 'semantic-ui-react';
// Pre-built Substrate front-end utilities for connecting to a node
// and making a transaction.
import { useSubstrate } from './substrate-lib';
import { TxButton } from './substrate-lib/components';
// Polkadot-JS utilities for hashing data.
import { blake2AsHex } from '@polkadot/util-crypto';
// Our main Proof Of Existence Component which is exported.
export function Main (props) {
// Establish an API to talk to our Substrate node.
const { api } = useSubstrate();
// Get the 'selected user' from the `AccountSelector` component.
const { accountPair } = props;
// React hooks for all the state variables we track.
// Learn more at: https://reactjs.org/docs/hooks-intro.html
const [status, setStatus] = useState('');
const [digest, setDigest] = useState('');
const [owner, setOwner] = useState('');
const [block, setBlock] = useState(0);
// Our `FileReader()` which is accessible from our functions below.
let fileReader;
// Takes our file, and creates a digest using the Blake2 256 hash function.
const bufferToDigest = () => {
// Turns the file content to a hexadecimal representation.
const content = Array.from(new Uint8Array(fileReader.result))
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
const hash = blake2AsHex(content, 256);
setDigest(hash);
};
// Callback function for when a new file is selected.
const handleFileChosen = (file) => {
fileReader = new FileReader();
fileReader.onloadend = bufferToDigest;
fileReader.readAsArrayBuffer(file);
};
// React hook to update the 'Owner' and 'Block Number' information for a file.
useEffect(() => {
let unsubscribe;
// Polkadot-JS API query to the `proofs` storage item in our pallet.
// This is a subscription, so it will always get the latest value,
// even if it changes.
api.query.poeModule
.proofs(digest, (result) => {
// Our storage item returns a tuple, which is represented as an array.
setOwner(result[0].toString());
setBlock(result[1].toNumber());
})
.then((unsub) => {
unsubscribe = unsub;
});
return () => unsubscribe && unsubscribe();
// This tells the React hook to update whenever the file digest changes
// (when a new file is chosen), or when the storage subscription says the
// value of the storage item has updated.
}, [digest, api.query.poeModule]);
// We can say a file digest is claimed if the stored block number is not 0.
function isClaimed () {
return block !== 0;
}
// The actual UI elements which are returned from our component.
return (
<Grid.Column>
<h1>Proof Of Existence</h1>
{/* Show warning or success message if the file is or is not claimed. */}
<Form success={!!digest && !isClaimed()} warning={isClaimed()}>
<Form.Field>
{/* File selector with a callback to `handleFileChosen`. */}
<Input
type='file'
id='file'
label='Your File'
onChange={(e) => handleFileChosen(e.target.files[0])}
/>
{/* Show this message if the file is available to be claimed */}
<Message success header='File Digest Unclaimed' content={digest} />
{/* Show this message if the file is already claimed. */}
<Message
warning
header='File Digest Claimed'
list={[digest, `Owner: ${owner}`, `Block: ${block}`]}
/>
</Form.Field>
{/* Buttons for interacting with the component. */}
<Form.Field>
{/* Button to create a claim. Only active if a file is selected,
and not already claimed. Updates the `status`. */}
<TxButton
accountPair={accountPair}
label={'Create Claim'}
setStatus={setStatus}
type='SIGNED-TX'
disabled={isClaimed() || !digest}
attrs={{
palletRpc: 'poeModule',
callable: 'createClaim',
inputParams: [digest],
paramFields: [true]
}}
/>
{/* Button to revoke a claim. Only active if a file is selected,
and is already claimed. Updates the `status`. */}
<TxButton
accountPair={accountPair}
label='Revoke Claim'
setStatus={setStatus}
type='SIGNED-TX'
disabled={!isClaimed() || owner !== accountPair.address}
attrs={{
palletRpc: 'poeModule',
callable: 'revokeClaim',
inputParams: [digest],
paramFields: [true]
}}
/>
</Form.Field>
{/* Status message about the transaction. */}
<div style={{ overflowWrap: 'break-word' }}>{status}</div>
</Form>
</Grid.Column>
);
}
export default function PoeModule (props) {
const { api } = useSubstrate();
return (api.query.poeModule && api.query.poeModule.proofs
? <Main {...props} /> : null);
}
4.2.2 添加存证组件
在substrate-front-end-template/src/App.js
中添加存证组件,代码如下:
import PoeModule from './PoeModule';
return (
<div ref={contextRef}>
<Sticky context={contextRef}>
<AccountSelector setAccountAddress={setAccountAddress} />
</Sticky>
<Container>
<Grid stackable columns='equal'>
......
<Grid.Row>
<PoeModule accountPair={accountPair} />
</Grid.Row>
</Grid>
<DeveloperConsole />
</Container>
</div>
);
4.2.3 修改连接配置
配置文件:substrate-front-end-template/src/config/development.json
"PROVIDER_SOCKET": "ws://119.28.233.229:9944"
4.3 front-end-template启动
$ yarn start
Compiled successfully!
You can now view substrate-front-end-template in the browser.
Local: http://localhost:8000/
On Your Network: http://172.29.0.6:8000/
Note that the development build is not optimized.
To create a production build, use yarn build.
4.4 存证界面展示
5 存证dApp使用展示
5.1 提交存证
- 选择文件
-
提交存证
- 存证入块
- 存证入块确认
5.2 撤销存证
撤销存证的按钮,只对创建对应存证的用户可见,例如
Alice
创建的存证,在切换到Bob
账号后,撤销存证的按钮会灰掉:
5.3 事件查看
可以查看到提交存证和撤销存证接口调用后触发的事件。
6 参考资料
https://substrate.dev/docs/en/tutorials/build-a-dapp/
区块链技术网。
- 发表于 2020-08-05 17:51
- 阅读 ( 2801 )
- 学分 ( 119 )
- 分类:Polkadot
评论