[教程] 使用 Embark 開發投票 DApp
- 2020 年 3 月 26 日
- 筆記
前面我們基於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/