疑難排解 Substrate是一個快速發展中的項目，即如果出現重大變更時下面的指令也有機會出現問顯。如果你遇到任何問題，請隨時與我們聯繫

這篇文章將會一步一步指引你所需要的步驟重複Gavid Wood在2018年 Web3 Submmit上的演示，展示怎樣在30分鐘內建立Substrate區塊鏈運行時函式庫。

教程將會使用Mac OS X系統，其它系統可能需要特別設定一下

必須要有的程序

在開始前確保你經已安裝最新 node 及 npm，然後需要確保你能夠運行 Substrate，這意味著你需要安裝Rust和其它相關程序。

只需要一個簡單的指令就能安裝Substrate (可能會需要一點時間，先去喝杯茶吧

curl https://getsubstrate.io -sSf | bash

你需要在工作中的資料夾建立二個Gavin在演示中展示的倉庫(repository) :

Substrate Node Template

Substrate UI

在你的電腦下載這個腳本並且運行以下指令

注意 可能需要你重新打開终端才能執行以上腳本和Substrate指令

substrate-node-new substrate-node-template <author-name> substrate-ui-new substrate

以上指令會建立二個資料夾分別為 substrate-node-template 和 substrate-ui 加上相關倉庫(repository)。

當然你可以利用以上指令更改項目的名稱，但是為了清晰起見，我們都會跟隨以上資料夾名稱。

第1步： 發佈區塊鏈

如果以上設定正確，現在你可以啟動 substrate (dev chain) 開發鏈。在 substrate-node-template 資料夾，執行

./target/release/substrate-node-template --dev

清除鏈數據庫 如果開始或運行節點時發生任何錯誤，你可能需要清除電腦上鏈的檔案。你可以執行 cargo run -- purge-chain --dev 或 手動移除整個鏈的資料夾: rm -rf ~/Library/Application\ Support/Substrate/chains/dev/

如果運作正常，你應該看到區塊開始產生。

與區塊鏈互動前，你需要啟動 Substrate UI，進入 substrate-ui 資料夾之後再執行：

npm run dev

最後打開瀏覽器前往 http://localhost:8000 你應該能夠使用這個新鏈!

第2步: 增加Alice到網絡

在substrate系統裹，Alice是預先設定好的帳戶，使你節省時間。如果你正使用最新版本 substrate-node-template ，Alice可能已經增加到你網絡，如果沒有，你可以利用以下方法增加她。

打開終端，之後利用已經安裝好的 substrate/subkey 套件，你可以檢索預設帳號的種子(Seed):

subkey restore Alice > Seed > 0x416c696365202020202020202020202020202020202020202020202020202020 is account: > SS58: 5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDtZ Hex: 0xd172a74cda4c865912c32ba0a80a57ae69abae410e5ccb59dee84e2f4432db4f

之後在Substrate UI，前往到 Wallet 部分再利用Alice的 種子seed 和 名字name 增加 。

如果運作正常，你會見到Alice有大量 units 已經預先在她的帳戶。現在你可以前往 Send Funds 部份並從 Alice 發送資金到 Default ，利用 Alice 發送一 些 units 到 Default 並等看到綠色的剔號及顯示了 Default 的餘額，說明已經成功紀錄到區塊鏈上。

第3步: 建立一個新運行時模組(new runtime module)

現在是時候建立屬於自己的模組

打開 substrate-node-template 資料夾並創建一個檔案。

./runtime/src/demo.rs

這裹就是放置運行時模組(runtime module)的位置，希望內裹註解能樣你理解代碼的運作。

首先需要在這個檔案最頂匯入幾個函式庫:

// Encoding library use parity_codec::Encode; // Enables access to the runtime storage use srml_support::{StorageValue, dispatch::Result}; // Enables us to do hashing use runtime_primitives::traits::Hash; // Enables access to account balances and interacting with signed messages use {balances, system::{self, ensure_signed}};

所有模組需要公開配置特徵 (trait) ，在這種情況下，由於我們將會利用從Balances模組特徵繼承了的特徵和函數。

pub trait Trait: balances::Trait {}

這個例子我們將會創造一個簡單的擲硬幣遊戲，用戶需要支付入場費進入遊戲然後再“擲硬幣”。當他們嬴了，他們會得到罐內的錢，但如果他們輸了，他們什麼也拿不到。不論結果是怎樣，他們已付的費用都會放在罐內等待下一位用戶試圖嬴取。

在建立這個遊戲前需要新增模組聲明(module declaration)，這些都是我們需要編寫的功能，下面的巨集將會負責参數的集结。

這個遊戲有二個功能:

1: 玩這個遊戲

2: 設定付款

decl_module! { pub struct Module<T: Trait> for enum Call where origin: T::Origin { fn play(origin) -> Result { // Logic for playing the game } fn set_payment(_origin, value: T::Balance) -> Result { // Logic for setting the game payment } } }

建立了模組結構後，可以正式編寫這二個功能。首先編寫玩這個遊戲的邏輯:

fn play(origin) -> Result { // Ensure we have a signed message, and derive the sender's account id from the signature let sender = ensure_signed(origin)?; // Here we grab the payment, and put it into a local variable. // We are able to use Self::payment() because we defined it in our decl_storage! macro below // If there is no payment, exit with an error message let payment = Self::payment().ok_or("Must have payment amount set")?; // First, we decrease the balance of the sender by the payment amount using the balances module <balances::Module<T>>::decrease_free_balance(&sender, payment)?; // Then we flip a coin by generating a random seed // We pass the seed with our sender's account id into a hash algorithm // Then we check if the first byte of the hash is less than 128 if (<system::Module<T>>::random_seed(), &sender) .using_encoded(<T as system::Trait>::Hashing::hash) .using_encoded(|e| e[0] < 128) { // If the sender wins the coin flip, we increase the sender's balance by the pot amount // `::take()` will also remove the pot amount from storage, which by default will give it a value of 0 <balances::Module<T>>::increase_free_balance_creating(&sender, <Pot<T>>::take()); } // No matter the outcome, we will add the original sender's payment back into the pot <Pot<T>>::mutate(|pot| *pot += payment); Ok(()) }

下一步編寫遊戲初始化時支付的邏輯:

fn set_payment(_origin, value: T::Balance) -> Result { //If the payment has not been set... if Self::payment().is_none() { // ... we will set it to the value we passed in. <Payment<T>>::put(value); // We will also put that initial value into the pot for someone to win <Pot<T>>::put(value); } Ok(()) }

之後利用 decl_storage! 巨集新增儲存聲明(storage declaration)，我們可以定義將會儲存在鏈上指定的數據模組。在這裹可以了解更多巨集。

decl_storage! { trait Store for Module<T: Trait> as Demo { Payment get(payment): Option<T::Balance>; Pot get(pot): T::Balance; } }

那麼容易就完成建立新運行時模組(runtime modules)，你可以利用這裹完整的版本來檢查你寫的。

第4步: 整合新的模組到運行時(runtime)

實際使用新模組之前我們需要告訴給運行時(runtime)知道，需要修改 ./runtime/src/lib.rs 檔案:

首先，我們需要定義將會使用新的模組時：

... extern crate substrate_consensus_aura_primitives as consensus_aura; mod demo; // Add this line

下一步，需要編寫Trait的配置，我們可以在其他特徵編寫完成後做:

... impl sudo::Trait for Runtime { /// The uniquitous event type. type Event = Event; type Proposal = Call; } impl demo::Trait for Runtime {} // Add this line

最後，我們放新模組到運行時結構巨集 construct_runtime! :

construct_runtime!( pub enum Runtime with Log(InternalLog: DigestItem<Hash, Ed25519AuthorityId>) where Block = Block, NodeBlock = opaque::Block, InherentData = BasicInherentData { System: system::{default, Log(ChangesTrieRoot)}, Timestamp: timestamp::{Module, Call, Storage, Config<T>, Inherent}, Consensus: consensus::{Module, Call, Storage, Config<T>, Log(AuthoritiesChange), Inherent}, Aura: aura::{Module}, Indices: indices, Balances: balances, Sudo: sudo, Demo: demo::{Module, Call, Storage}, // Add this line } );

你可以在這裹找到這個檔案的完整版本。

第5步: 升級鏈

現在我們已經創建了新運行時模組，是時候升級我們的區塊鏈。

升級之前我們首先需要編譯我們的新運行時，前往 substrate-node-template 並執行:

./build.sh

如果執行成功，它會更新接下來的檔案:

./runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm

你可以返回Substrate UI的 Runtime Upgrade 位置，選擇這個檔案再按下 upgrade 。

如果沒有問題，在Substrate UI頁面最頂部的 Runtime 你會看到更新了的名字。

第6步: 與新的模組互動

最後可以試玩一下剛剛創造的遊戲，首先需要透過打開瀏覽器。

在Substrate UI頁，按下F12打開開發者控制台，之後我們需要利用Javascript匯入函式庫。

在玩這個遊戲前，我們需要先從帳號預設 set_payment ，這樣會代Alice進行信息簽名執行函數預設罐的數值。

post({sender: "5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDtZ", call: calls.demo.setPayment(1000)}).tie(console.log)

當函數執行完成，你應該會見到 {finalized: "..."} 顯示數據已經寫到鏈上，我們可透過讀取罐的餘額。

runtime.demo.pot.then(console.log)

應該會返回 Number {1000}

第7步: 更新Substrate UI

現在我們看到運作正常， 是時候加點其它功能，建立一個樣用戶玩遊戲的界面，開始之前需要修改 substrate-ui 庫。

打開 ./src/app.jsx 檔案，搜尋 readyRender() 功能你會看到所有生成界面的組件。

例如: 此段代碼控制我們剛剛用到的升級運行時的界面:

<Divider hidden /> <Segment style={{margin: '1em'}} padded> <Header as='h2'> <Icon name='search' /> <Header.Content> Runtime Upgrade <Header.Subheader>Upgrade the runtime using the Sudo module</Header.Subheader> </Header.Content> </Header> <div style={{paddingBottom: '1em'}}></div> <FileUploadBond bond={this.runtime} content='Select Runtime' /> <TransactButton content="Upgrade" icon='warning' tx={{ sender: runtime.sudo.key, call: calls.sudo.sudo(calls.consensus.setCode(this.runtime)) }} /> </Segment>

我們可以利用這個模板作為參考應該如何添加遊戲界面。

在 </Segment> 之後，貼上下面的代碼：

... </Segment> <Divider hidden /> <Segment style={{margin: '1em'}} padded> <Header as='h2'> <Icon name='game' /> <Header.Content> Play the game <Header.Subheader>Play the game here!</Header.Subheader> </Header.Content> </Header> <div style={{paddingBottom: '1em'}}> <div style={{fontSize: 'small'}}>player</div> <SignerBond bond={this.player}/> <If condition={this.player.ready()} then={<span> <Label>Balance <Label.Detail> <Pretty value={runtime.balances.balance(this.player)}/> </Label.Detail> </Label> </span>}/> </div> <TransactButton content="Play" icon='game' tx={{ sender: this.player, call: calls.demo.play() }} /> <Label>Pot Balance <Label.Detail> <Pretty value={runtime.demo.pot}/> </Label.Detail> </Label> </Segment>

除了更新文字之外，你可以看到我們正在讀取一個新 this.player bond，表示那個用戶正在玩遊戲。

利用以下代碼，可以獲得用戶餘額等詳細資料：

runtime.balances.balance(this.player)

之後再替用戶提交交易。

tx={{ sender: this.player, call: calls.demo.play() }}

另外留意我們能夠動態顯示罐內的現有結餘等內容，就好像之前我們透過開發者控制台檢索:

<Label>Pot Balance <Label.Detail> <Pretty value={runtime.demo.pot}/> </Label.Detail> </Label>

餘下一件需要做的事就是在同一個檔案最頂上的 constructor() 函數裹新增 player Bond :

... this.runtime = new Bond; this.player = new Bond; // Add this line

當你儲存並且重新整理這一頁，你應該會見到新界面! 現在你可以利用 Default 用戶試玩這個遊戲:

現在你看到玩家輸了，他1,000 units 被加到罐及1 unit 交易費用將會從他的結餘扣除。

當我們再玩幾次之後，用戶終於嬴了，而罐將會重設到一開始的數量給下一位玩家：

最後筆記

雖然玩這個遊戲賺不了錢，但是希望你知道利用Substrate開發區塊鏈是有多容易。

總結一下你學會了:

透過一個指令在你電腦下載及安裝 substrate

建立新的 substrate-node-template 和 substrate-ui 使你可以馬上開始開發

編寫屬於你的區塊鏈運行(runtime)

透過 substrate-ui 實時升級你的運行時(runtime)不用分叉

更新 substrae-ui 反映新運行時(runtime)特點及功能

Substrate是一項快速發展的技術，我們期待收到你的意見、回答你的問題並了解你更多的新開發想法。

歡迎聯絡我們詳情在這裹。

最後謝謝中國Polkadot社區 @0xThreeBody 和 @yuelipeng 幫助核對!