使用 Embark 開發投票 DApp

買賣虛擬貨幣
前面我們基於Embark Demo[1] 介紹了 Embark 框架,今天使用 Embark 來實實在在開發一個 DApp:從零開發開發一個投票DApp。之前我們也使用Truffle 開發過投票DApp[2],大家可以自行對比兩個框架的優劣。透過本文可以學習到:1.使用 Embark 建立專案2.利用 EmbarkJS 與合約互動3.Embark 如果部署合約到主網(利用Infura節點)
本文使用的 Embark 版本是 5.2.3建立Embark專案> embark new embark-election會在當前目錄下生成一個 embark-election 目錄,並建立好了相應的專案框架檔案:如: app/、contracts/、config/、embark.json等。我們需要在對應的目錄中,新增相應的實現。編寫合約
在contracts/中新增合約Election.sol:pragma solidity ^0.6.0;contract Election {    // Model a Candidate    struct Candidate {        uint id;
        string name;        uint voteCount;    }    mapping(address => bool) public voters;    mapping(uint => Candidate) public candidates;    // Store Candidates Count
    uint public candidatesCount;    // voted event    event votedEvent (        uint indexed _candidateId    );    constructor () public {
        addCandidate("Tiny 熊");        addCandidate("LearnBlockChain.cn");    }    function addCandidate (string memory _name) private {        candidatesCount ++;        candidates[candidatesCount] = Candidate(candidatesCount, _name, 0);
    }    function vote (uint _candidateId) public {        // require that they haven't voted before        require(!voters[msg.sender]);        // require a valid candidate        require(_candidateId > 0 && _candidateId <= candidatesCount);
        // record that voter has voted        voters[msg.sender] = true;        // update candidate vote Count        candidates[_candidateId].voteCount ++;        // trigger voted event        emit votedEvent(_candidateId);
    }}之前有使用過Truffle開發過投票DApp[3],合約的程式碼完全一樣,就不在解釋。Embark 合約編譯部署Embark 合約部署的配置在 config/contracts.js, 在 deploy 欄位加入 Election 合約:    deploy: {
      Election: {      }    }現在執行 embark run , Embark 會自動編譯及部署Election.sol到 config/blockchain.js 配置的 development 網路。embark run 等價 embark run development 。blockchain.js 中 development 網路是使用 ganache-cli 啟動的網路,其配置如下:
  development: {    client: 'ganache-cli',    clientConfig: {      miningMode: 'dev'     }  }
embark啟動後,我們可以在 COCKPIT 或 DashBoard 看到Election.sol合約的部署日誌,大概類似下面:deploying Election with 351122 gas at the price of 1 Wei, estimated cost: 351122 Wei (txHash: 0x9da4dfb951149...d5c306dcabf300a4)Election deployed at 0x10C257c76Cd3Dc35cA2618c6137658bFD1fFCcfA using 346374 gas (txHash: 0x9da4dfb951149ea4...d5c306dcabf300a4)finished deploying contracts編寫前端程式碼Embark Artifacts
Embark提供了一個 EmbarkJS的JavaScript庫,來幫助開發者和合約進行互動。在使用web3.js 時,和合約互動需要知道合約的ABI及地址來建立JS環境中對應的合約物件,一般程式碼是這樣的:// 需要ABI及地址建立物件var myContract = new web3.eth.Contract([...ABI...], '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe');Embark 在編譯部署後,每個合約會生成一個對應的構件Artifact(可以在embarkArtifacts/contracts/ 目錄下找的這些檔案),我們可以直接使用 Artifact 生成的合約物件呼叫合約。一個構件通常會包含:合約的ABI、部署地址、啟動程式碼及其他的配置資料。
檢視一下Election.sol 對應的構件Election.js 程式碼就更容易理解:import EmbarkJS from '../embarkjs';let ElectionJSONConfig = {"contract_name":"Election","address":"0x10C257c76Cd3Dc35cA2618c6137658bFD1fFCcfA","code":"...", ... ,"abiDefinition":[...]};let Election = new EmbarkJS.Blockchain.Contract(ElectionJSONConfig);export default Election;Election.js 最後一行匯出了一個與合約同名的JavaScript 物件,下面看看怎麼使用這個物件。
修改前端index.html在使用embark new embark-election建立專案時, 前端目錄app/下生成了一個 index.html:<html>  <head>    <title>Embark</title>    <link rel="stylesheet" href="css/app.css">  
    <script src="js/app.js"></script>  </head>  <body>    <h3>Welcome to Embark!</h3>  </body></html>
這裡有一個地方需要注意一下,第5 6行引入了 css/app.css, js/app.js,而其實app/下並沒有這兩個檔案,這兩個檔案其實是按照 embark.json 配置的規程生成的。embark.json 關於前端的配置如下:  "app": {    "css/app.css": ["app/css/**"],    "js/app.js": ["app/js/index.js"],    "images/": ["app/images/**"],
    "index.html": "app/index.html"  },"css/app.css": ["app/css/**"] 表示所有在app/css/目錄下的檔案會被壓縮到 dist目錄的 css/app.css ,app/js/index.js則會編譯為js/app.js,其他的配置類似。我猜測embark 這樣統一 css 及 js程式碼,可能是為了在IPFS之類的去中心化儲存上訪問起來更方便,在IPFS上傳整個目錄時,只能以相對路徑去訪問資源。歡迎留言和我交流。接下來修改前端部分的程式碼,主要是在index.html的body加入一個table顯示候選人,以及加入一個投票框,程式碼如下(節選):<table class="table">
  <thead>    <tr>      <th scope="col">#</th>      <th scope="col">候選人</th>      <th scope="col">得票數</th>    </tr>
  </thead>  <tbody id="candidatesResults">  </tbody></table><div class="form-group"><label for="candidatesSelect">選擇候選人</label>
<select class="form-control" id="candidatesSelect"></select></div>前端,我們使用了 bootstrap css ,把檔案複製到app/css目錄下,接下來,看看關鍵的一步:前端如何與與合約互動。使用 Artifacts與合約互動EmbarkJS 連線 Web3
建立專案時生成的app/js/index.js 生成了如下程式碼:import EmbarkJS from 'Embark/EmbarkJS';EmbarkJS.onReady((err) => {  // You can execute contract calls after the connection});這段程式碼裡,EmbarkJS為我們準備了一個onReady回撥函式,這是因為EmbarkJS會自動幫我們完成與web3節點的連線與初始化,當這些就緒後(呼叫onReady),前端就可以和鏈進行互動了。
大家也許會好奇EmbarkJS怎麼知道我們需要連線那個節點呢?其實在config/contracts.js 有一個 dappConnection 配置項:dappConnection: [  "$EMBARK",  "$WEB3",  // 使用瀏覽器注入的web3, 如MetaMask等  "ws://localhost:8546",  "http://localhost:8545"
],$EMBARK : 是Embark在DApp和節點之前實現的一個代理,使用$EMBARK有幾個好處:1.可以在config/blockchain.js 配置於DApp互動的賬號 accounts。2.可以更友好的的看到交易記錄。EmbarkJS 會從上到下,依次嘗試 dappConnection提供的連線,如果有一個可以連線上,就會停止嘗試。獲取合約資料渲染介面
當 EmbarkJS 環境準備 onReady後,就可以使用構件Election.js獲取合約資料,如獲取呼叫合約獲取候選人數量:import EmbarkJS from 'Embark/EmbarkJS';import Election from '../../embarkArtifacts/contracts/Election.js';EmbarkJS.onReady((err) => {    Election.methods.candidatesCount().call().then(count => console.log(" candidatesCount: " + count);    );
});程式碼中直接使用構件匯出的Election物件,呼叫合約方法 Election.methods.candidatesCount().call(), 呼叫合約方法與web3.js 一致。瞭解瞭如何與合約互動,接下來渲染介面就簡單了,我們把程式碼整理下,分別定義3個函式: App.getAccount()、App.render()、App.onVote() 來獲取當前賬號(需要用來判斷哪些賬號投過票)、介面渲染、處理點選投標。EmbarkJS.onReady((err) => {  App.getAccount();  App.render();
  App.onVote();});App.getAccount() 的實現如下:import "./jquery.min.js"var App = {  account: null,
  getAccount: function() {    web3.eth.getCoinbase(function(err, account) {      if (err === null) {        App.account = account;        console.log(account);        $("#accountAddress").html("Your Account: " + account);
      }    })  },  }在程式碼中,我們直接使用了web3物件,就是因為EmbarkJS幫我們進行了web3的初始化。另外,我們引入jquery.min.js 來進行UI介面的渲染。App.render() 的實現(主幹)如下:
render: function () {    Election.methods.candidatesCount().call().then(      candidatesCount =>       {        var candidatesResults = $("#candidatesResults");        var candidatesSelect = $('#candidatesSelect');
        for (var i = 1; i <= candidatesCount; i++) {          Election.methods.candidates(i).call().then(function(candidate) {            var id = candidate[0];            var name = candidate[1];            var voteCount = candidate[2];            // Render candidate Result
            var candidateTemplate = "<tr><th>" + id + "</th><td>" + name + "</td><td>" + voteCount + "</td></tr>";            candidatesResults.append(candidateTemplate);            // Render candidate ballot option            var candidateOption = "<option value='" + id + "' >" + name + "</ option>";            candidatesSelect.append(candidateOption);          });
        }      });  }App.onVote() 的實現(主幹)如下:  onVote: function() {    $("#vote").click(function(e){
      var candidateId = $('#candidatesSelect').val();      Election.methods.vote(candidateId).send()      .then(function(result) {        App.render();      }).catch(function(err) {        console.error(err);
      });    });  }部署

使用 embark run 時,會為我們啟動一個Geth 或 ganache-cli 的本地網路部署合約,以及在8000埠上啟用一個本地伺服器來部署前端應用,我們在瀏覽器輸入http://localhost:8000/ 就可以看到DApp介面,如圖:

當我們的DApp 在測試環境透過後,就可以部署到以太坊的主網。

利用Infura部署到主網

要部署到主網,需要在blockchain.js 中新增一個主網網路,這裡以測試網Ropsten網路為例:

ropsten: {
    endpoint: "https://ropsten.infura.io/v3/d3fe47c...4f",
    accounts: [
      {
        mnemonic: " 你的助記詞 ",
        hdpath: "m/44'/60'/0'/0/",
        numAddresses: "1"
      }
    ]
  }

如果我們沒有自己的主網節點,可以使用 endpoint 來指向以個外部節點,最常用的就是Infura[4]。

新增好配置之後,使用build命令來構建主網釋出版本:

embark build ropsten  # 最後是網路引數

所有的檔案在生成在dist目錄下,把他們部署到線上伺服器就完成了部署。也可以使用embark upload ropsten 上傳到IPFS。

References

[1] Embark Demo: https://learnblockchain.cn/article/566
[2] Truffle 開發過投票DApp: https://learnblockchain.cn/2019/04/10/election-dapp
[3] Truffle開發過投票DApp: https://learnblockchain.cn/2019/04/10/election-dapp
[4] Infura: https://infura.io/

免責聲明:

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

推荐阅读

;