使用Substrate開發區塊鏈存證dApp

買賣虛擬貨幣
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└── rustup2.1.2 環變設定執行下面命令,即將export PATH="$HOME/.cargo/bin:$PATH",追加到~/.bashrc中$ cat ~/.cargo/env >> ~/.bashrc執行下面命令,使得新增的環境變數生效
$ . ~/.bashrc可執行如下命令檢視安裝版本$ rustc --versionrustc 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 -Vracer 2.1.36(2) 原始碼下載為了對Rust標準庫進行補全,Racer需要獲取Rust原始碼路徑。透過rustup獲取原始碼的好處是rustup update可以隨時獲取最新程式碼
· 獲取原始碼$ rustup component add rust-srcinfo: 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 Repositorybaseurl=https://dl.yarnpkg.com/rpm/enabled=1gpgcheck=1gpgkey=https://dl.yarnpkg.com/rpm/pubkey.gpg$ curl --silent --location https://rpm.nodesource.com/setup_12.x | sudo bash -
$ sudo yum install yarn$ yarn --version1.22.43 存證dApp後端節點開發存證dApp後端節點是基於node-template來開發,它是一個基於FRAME的Substrate後端節點,可以在其基礎上,進行修改以便快速搭建屬於自己的Substrate網路。3.1 node-template安裝
版本v2.0.0-rc5下載[admin@chaindaily:~/Blockchain]$ git clone admin@chaindaily:substrate-developer-hub/substrate-node-template.git[admin@chaindaily:~/Blockchain/substrate-node-template] (master)$ git checkout -b v2.0.0-rc5 v2.0.0-rc5切換到一個新分支 'v2.0.0-rc5'
[admin@chaindaily:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$編譯安裝依賴,避免後續編譯錯誤:[admin@chaindaily:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ rustup target add wasm32-unknown-unknown --toolchain nightly[admin@chaindaily:~/Blockchain/substrate-node-template] (v2.0.0-rc5)$ yum install -y llvm-devel clang-devel[admin@chaindaily:~/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 nightlyinfo: 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.4error: 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-devel3.2 存證pallet開發Substrate執行時由FRAME pallets組成。這些pallets可以被認為是定義你的區塊鏈能夠做什麼的一個個獨立的邏輯單元。

Substrate已經提供了許多預置pallets,用於基於FRAME的執行時。如下圖所示:

例如,FRAME中包含一個balances的pallet,這個pallet透過管理系統中所有賬戶餘額來控制你的區塊鏈系統中的基礎貨幣。如果你想向你的區塊鏈系統中新增智慧合約功能,你只需要包含**合約pallet**即可。

本節我們就是要開發一個存證pallet,並將其新增到我們自定義的區塊鏈中。

3.2.1 建立poe pallet工程目錄

pos => Proof Of Existence

[admin@chaindaily:~/Blockchain/substrate-node-template/pallets] (v2.0.0-rc5)$ cargo new --lib poe
[admin@chaindaily:~/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的開發後,需要重新編譯節點。

[admin@chaindaily:~/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節點啟動

[admin@chaindaily:~/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.

[admin@chaindaily:~/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 0x2305ab1c8aee785cb991c993e849b7

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;