前面文章介绍了在Substrate上开发智能合约,包括使用原生的ink!语言开发ERC20智能合约,以及将以太坊的Solidity智能合约跑在Substrate链上,在本文将进一步学习在Substrate链上开发一个自定义的区块链存证dApp。
jasonruan 2020.08.05
前面文章介绍了在Substrate上开发智能合约,包括使用原生的ink!语言开发ERC20智能合约,以及将以太坊的Solidity智能合约跑在Substrate链上,在本文将进一步学习在Substrate链上开发一个自定义的区块链存证dApp。
本文内容参考:https://substrate.dev/docs/en/tutorials/build-a-dapp/
Substrate是由rust语言开发,首先需要安装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
export PATH="$HOME/.cargo/bin:$PATH",追加到~/.bashrc中$ cat ~/.cargo/env >> ~/.bashrc
$ . ~/.bashrc
$ rustc --version
rustc 1.45.1 (c367798cf 2020-07-26)
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
为了对
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"
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
存证dApp后端节点是基于node-template来开发,它是一个基于FRAME的Substrate后端节点,可以在其基础上,进行修改以便快速搭建属于自己的Substrate网络。
版本
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
Substrate运行时由FRAMEpallets组成。这些pallets可以被认为是定义你的区块链能够做什么的一个个独立的逻辑单元。
Substrate已经提供了许多预置pallets,用于基于FRAME的运行时。如下图所示:
例如,
FRAME中包含一个balances的pallet,这个pallet通过管理系统中所有账户余额来控制你的区块链系统中的基础货币。如果你想向你的区块链系统中添加智能合约功能,你只需要包含合约pallet即可。
本节我们就是要开发一个存证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
在新生成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-- */ }
#![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;
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',                      # <-- 增加行
]
每一个
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>;
} 
事件:可以展示
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>:存储或撤销的存证数据错误:可以展示
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,
    }
}
要添加一个新的存证到我们的区块链上,就是要将其存储到我们的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);                                                                                          
    }   
}  
// 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));
        }
    }
}
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>},                                         // <-- 增加代码行
        }                                                                                                                                                                         
); 
完成存证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
[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
存证dApp前端界面是基于front-end-template开发,它是Substrate前端应用开发模板,可以通过其连接Substrate后端节点。
版本
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.
在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);
}
在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>   
  );         
配置文件:substrate-front-end-template/src/config/development.json
    "PROVIDER_SOCKET": "ws://119.28.233.229:9944"
$ 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.


提交存证


撤销存证的按钮,只对创建对应存证的用户可见,例如
Alice创建的存证,在切换到Bob账号后,撤销存证的按钮会灰掉:

可以查看到提交存证和撤销存证接口调用后触发的事件。

如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!