以太坊投票Dapp教程

這個(gè)Dapp教程會(huì)分為三部分。從簡單的投票功能開始矿筝,慢慢熟悉Dapp的開發(fā)環(huán)境與生態(tài)圈秽誊,后面再慢慢加入ERC20 Token等功能。
Dapp: Decentralized Application (去中心化應(yīng)用)

Part 1


以下開發(fā)的系統(tǒng)環(huán)境基于MacOS

如果還沒安裝Homebrew纫雁,請(qǐng)按照 https://brew.sh/ 上的說明安裝。 Homebrew是包管理器倾哺,可以幫助我們安裝開發(fā)所需的所有其他軟件轧邪。安裝好Homebrew之后再按照下面程序安裝其他包.

brew update
brew install nodejs
node --version
v9.11.1
npm --version
6.0.0
mkdir -p eth_vote_dapp/ch1
cd eth_vote_dapp/ch1
npm install ganache-cli web3@0.20.1 solc
node_modules/.bin/ganache-cli

以上所有步驟都成功后,會(huì)看到下面圖片的輸出:


啟動(dòng)Ganache

Ganache 默認(rèn)創(chuàng)建10個(gè)帳戶羞海,并給每個(gè)賬戶加載100個(gè)以太幣忌愚,以便測(cè)試。如果你不知道賬戶扣猫,可以把它想象成一個(gè)有錢的銀行賬戶(以太幣是以太坊生態(tài)系統(tǒng)的貨幣)菜循。你需要此帳戶來支付交易和發(fā)送/接收以太幣,我們會(huì)在后續(xù)完成創(chuàng)建帳戶的過程。

你也可以在這里下載安裝GUI版本的 Ganache

2.1 Solidity 智能合約

ganache已經(jīng)安裝并成功運(yùn)行了癌幕,我們來編寫第一個(gè)智能合約衙耕。

我們將編寫一個(gè)投票智能合約,它具有:

  1. 初始化候選人數(shù)組的構(gòu)造函數(shù)

  2. 投票方法(增加投票數(shù)量)

  3. 返回候選人收到的總票數(shù)的方法

當(dāng)智能合約部署到區(qū)塊鏈時(shí)勺远,構(gòu)造函數(shù)只會(huì)調(diào)用一次橙喘。與傳統(tǒng)的web開發(fā),每次部署代碼覆蓋舊代碼不同胶逢,區(qū)塊鏈中部署的代碼是不可更改的厅瞎。如果要更新智能合約并再次部署,則舊的合約仍會(huì)在區(qū)塊鏈中并且存儲(chǔ)在其中的所有數(shù)據(jù)都不會(huì)變初坠。而新的部署將會(huì)創(chuàng)建全新的合約和簸。

2.2 合約代碼

Mapping相當(dāng)于關(guān)聯(lián)數(shù)組或哈希。key是類型為bytes32的候選人名字碟刺,value類型為uint8的存儲(chǔ)投票計(jì)數(shù)

votesReceived[key]默認(rèn)值為0锁保,因此可以直接增加而不必初始化為0。

每個(gè)函數(shù)都有一個(gè)可見性說明符, public 表示該功能可以從合約外部調(diào)用半沽。如果不想讓其他人調(diào)用一個(gè)函數(shù)爽柒,可以將它設(shè)為私有。如果不指定可見性者填,編譯器會(huì)有警告浩村。這是編譯器最近在增強(qiáng)的功能,解決人們忘記標(biāo)記其私有函數(shù)的問題占哟,從而導(dǎo)致私有函數(shù)暴露給公眾使用心墅。你可以在 這里 看到所有可用的可見性說明符。

view 是一個(gè)modifier重挑,用于告訴編譯器該函數(shù)是只讀的(調(diào)用該函數(shù)嗓化,區(qū)塊鏈的狀態(tài)不會(huì)被更新)棠涮。所有可用的 modifier 都可以在 這里 看到谬哀。

pragma solidity ^0.4.18; 

contract Voting {

  mapping (bytes32 => uint8) public votesReceived;
  bytes32[] public candidateList;

  function Voting(bytes32[] candidateNames) public {
    candidateList = candidateNames;
  }

  function totalVotesFor(bytes32 candidate) view public returns (uint8) {
    require(validCandidate(candidate));
    return votesReceived[candidate];
  }

  function voteForCandidate(bytes32 candidate) public {
    require(validCandidate(candidate));
    votesReceived[candidate]  += 1;
  }

  function validCandidate(bytes32 candidate) view public returns (bool) {
    for(uint i = 0; i < candidateList.length; i++) {
      if (candidateList[i] == candidate) {
        return true;
      }
    }
    return false;
   }
}

2.3 編譯

我們用之前安裝的solc庫來編譯代碼。web3js是一個(gè)庫严肪,可以通過RPC與區(qū)塊鏈進(jìn)行交互史煎。我們會(huì)在節(jié)點(diǎn)控制臺(tái)中使用該庫來部署合約并與區(qū)塊鏈進(jìn)行交互。

編譯合約

首先驳糯,在終端運(yùn)行 node 命令進(jìn)入 node 控制臺(tái)并初始化web3對(duì)象并查詢區(qū)塊鏈以列出所有帳戶篇梭。
輸入 node

確保你有在另一個(gè)終端窗口中運(yùn)行g(shù)anache

要編譯合約,把 Voting.sol 中的代碼加載到字符串變量中酝枢,并按 圖2.3 所示進(jìn)行編譯恬偷。

node console
> Web3 = require('web3')
> web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))
> web3.eth.accounts
['0xa887e6af1d593f3febb9ec1c3851f412b1100eea',
  '0x26231321227df2d23d1692a2096f094857caf690',
  '0x1129492a919505178e755f4eb694c76317e30a76',
  '0x49b94491aea9100b046da47705c0a2a233145b62',
  '0x73dfeab2ed0c77a7aa90f4a4e329486c2d0ca461',
  '0x698dce912dfbdc66c1aefb137187eed86358dbf5',
  '0x3321e2b5c253864a30655e90f0966484a33a2afc',
  '0xff76305a85ee7c703061706d326cd54ceca30e4b',
  '0x6b706a56d3f9117c45d26ee82f366e0ecdf95976',
  '0x5c82cfcdacb2d2ecd0feb8d9eb5ddc5426b7e38e']

> code = fs.readFileSync('Voting.sol').toString()
> solc = require('solc')
> compiledCode = solc.compile(code)

當(dāng)成功編譯代碼并打印 compiled Code 對(duì)象(只需在 node 控制臺(tái)中輸入 compiled Code 以查看內(nèi)容)時(shí),要注意到兩個(gè)重要的字段帘睦,這些字段對(duì)于理解很重要:

compiledCode.contracts [':Voting'].bytecode:這是 Voting.sol 源代碼編譯時(shí)得到的字節(jié)碼袍患,這是將被部署到區(qū)塊鏈的代碼坦康。
compiledCode.contracts [':Voting'].interface:這是合同的接口或模板(稱為abi定義),它告訴合約用戶合約中有哪些方法可用诡延。無論何時(shí)需要與任何合約交互滞欠,都需要此 abi 定義。你可以在這里閱讀關(guān)于 ABI 的更多細(xì)節(jié)肆良。

2.4 部署合約

現(xiàn)在要把合約部署到區(qū)塊鏈筛璧。

首先通過傳遞 abi 來創(chuàng)建合約對(duì)象 VotingContract,然后使用此對(duì)象在區(qū)塊鏈上部署和啟動(dòng)合約惹恃。
VotingContract.new 將合約部署到區(qū)塊鏈中夭谤。

在 node console 執(zhí)行:
> abi = JSON.parse(compiledCode.contracts[':Voting'].interface)
> VotingContract = web3.eth.contract(abi)
> byteCode = compiledCode.contracts[':Voting'].bytecode
> deployedContract = VotingContract.new(['James', 'Norah', 'Jones'],{data: byteCode, from: web3.eth.accounts[0], gas: 4700000})
> deployedContract.address
'0xab906387b47d004e7153e7e04dbf2f6a0b9a20be' <- 你的地址會(huì)不一樣
> contractInstance = VotingContract.at(deployedContract.address)

第一個(gè)參數(shù)是參與投票的候選人。
第二個(gè)參數(shù):

data:這是部署到區(qū)塊鏈的 bytecode
from:區(qū)塊鏈必須跟蹤部署合約的擁有者巫糙。我們調(diào)用 web3.eth.accounts[0] 獲得第一個(gè)帳戶作為此合約的擁有者沮翔。web3.eth.accounts 會(huì)在我們啟動(dòng)測(cè)試區(qū)塊鏈(ganache)時(shí)返回一個(gè)由10個(gè)測(cè)試帳戶組成的數(shù)組。在實(shí)際區(qū)塊鏈中曲秉,不能只使用任何帳戶采蚀,必須要擁有該賬戶并在交易前解鎖。在創(chuàng)建帳戶時(shí)會(huì)要求你輸入密碼承二,這就是用來證明你擁有該帳戶的所有權(quán)榆鼠,我們將在下一部分中講解這個(gè)。為了方便亥鸠,Ganache默認(rèn)解鎖所有10個(gè)帳戶妆够。
gas:與區(qū)塊鏈交互需要花錢。這些資金交給礦工负蚊,讓他們來把所有代碼都寫進(jìn)區(qū)塊鏈中神妹。你必須指定愿意支付多少錢才能將代碼寫進(jìn)區(qū)塊鏈中,并通過設(shè)置 gas 的值來實(shí)現(xiàn)這一點(diǎn)家妆。

我們現(xiàn)在已經(jīng)部署了合約鸵荠,并且有一個(gè)可以用來與合約進(jìn)行交互的合約實(shí)例(variable contractInstance)。

區(qū)塊鏈上面成千上萬的合約伤极,如何在區(qū)塊鏈中識(shí)別我們的合約蛹找?

答案是 deployedContract.address。當(dāng)我們要和合約進(jìn)行交互時(shí)哨坪,需要我們先前的部署地址和 abi庸疾。

2.5 終端交互

為候選人投票并查看投票計(jì)數(shù)

繼續(xù)在 node 控制臺(tái)中調(diào)用 voteForCandidate 方法和 totalVotesFor 并查看結(jié)果。

In your node console:
> contractInstance.totalVotesFor.call('James')
{ [String: '0'] s: 1, e: 0, c: [ 0 ] }
> contractInstance.voteForCandidate('James', {from: web3.eth.accounts[0]})
'0x1b34ce61d464729900c0033bbe24842c6f8af37f4f96e8a96a308ef568240bf6'
> contractInstance.voteForCandidate('James', {from: web3.eth.accounts[0]})
'0x1925596dcba6d5673ec5713a66d998d67ff5cdac7eb3a00d24030f8525dd15a6'
> contractInstance.voteForCandidate('James', {from: web3.eth.accounts[0]})
'0x30a4bbe11d47df650dcd7378ad103d7027068dda1c92ad72413a8d11fae4c113'
> contractInstance.totalVotesFor.call('James').toLocaleString()
'3'

每次為候選人投票時(shí)当编,都會(huì)得到一個(gè)交易ID:例如:'0x1b34ce61d464729900c0033bbe24842c6f8af37f4f96e8a96a308ef568240bf6' 届慈。 此交易ID證明此交易已發(fā)生,可以在將來隨時(shí)回顧此交易,這個(gè)交易是不可變的金顿。

這種不可改變性是以太坊等區(qū)塊鏈的一大優(yōu)點(diǎn)词渤。

2.6 Web前端

現(xiàn)在大部分工作已經(jīng)完成,剩下要做的就是創(chuàng)建一個(gè)簡單 html 文件串绩,并在 js 文件中調(diào)用投票命令缺虐,
把它們放在 ch1 目錄中,然后在瀏覽器中打開index.html礁凡。

index.html

<!DOCTYPE html>
<html>
<head>
 <title>DApp</title>
 <link href='https://fonts.googleapis.com/css?family=Open Sans:400,700' rel='stylesheet' type='text/css'>
 <link  rel='stylesheet' type='text/css'>
</head>
<body class="container">
 <h1>Voting Application</h1>
 <div class="table-responsive">
  <table class="table table-bordered">
   <thead>
    <tr>
     <th>Candidate</th>
     <th>Votes</th>
    </tr>
   </thead>
   <tbody>
    <tr>
     <td>James</td>
     <td id="candidate-1"></td>
    </tr>
    <tr>
     <td>Norah</td>
     <td id="candidate-2"></td>
    </tr>
    <tr>
     <td>Jones</td>
     <td id="candidate-3"></td>
    </tr>
   </tbody>
  </table>
 </div>
 <input type="text" id="candidate" />
 <a href="#" onclick="voteForCandidate()" class="btn btn-primary">Vote</a>
</body>
<script src="https://cdn.rawgit.com/ethereum/web3.js/develop/dist/web3.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="./index.js"></script>
</html>

index.js

web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
abi = JSON.parse('[{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"validCandidate","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"x","type":"bytes32"}],"name":"bytes32ToString","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"voteForCandidate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"contractOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"type":"constructor"}]')
VotingContract = web3.eth.contract(abi);
contractInstance = VotingContract.at('0xab906387b47d004e7153e7e04dbf2f6a0b9a20be');
candidates = {"James": "candidate-1", "Norah": "candidate-2", "Jones": "candidate-3"}

function voteForCandidate(candidate) {
 candidateName = $("#candidate").val();
 try {
  contractInstance.voteForCandidate(candidateName, {from: web3.eth.accounts[0]}, function() {
   let div_id = candidates[candidateName];
   $("#"+div_id).html(contractInstance.totalVotesFor.call(candidateName).toString());
  });
 } catch (err) {
 }
}

$(document).ready(function() {
 candidateNames = Object.keys(candidates);
 for (var i = 0; i < candidateNames.length; i++) {
  let name = candidateNames[i];
  let val = contractInstance.totalVotesFor.call(name).toString()
  $("#"+candidates[name]).html(val);
 }
});

第4行中把合約地址替換為你自己的合約地址高氮。

如果一切設(shè)置正確,你應(yīng)該能夠在文本框中輸入候選人名字并進(jìn)行投票顷牌,并且投票計(jì)數(shù)會(huì)增加剪芍。

2.5 總結(jié)

如果你可以看到頁面并為候選人投票并查看投票計(jì)數(shù),那你已經(jīng)成功創(chuàng)建了第一份合約窟蓝,恭喜罪裹!所有的選票保存在區(qū)塊鏈中,并且是不可變的运挫。任何人都可以獨(dú)立驗(yàn)證每位候選人收到多少票状共。

以下是我們完成的:

  1. 可以通過安裝node,npm 和 ganache 來設(shè)置開發(fā)環(huán)境
  2. 編寫了一份簡單的投票合約谁帕,編譯并部署了合約到區(qū)塊鏈
  3. 通過nodejs控制臺(tái)與網(wǎng)頁進(jìn)行交互峡继,然后通過網(wǎng)頁進(jìn)行交互

我們會(huì)在第二部分把該合約部署到 Ropsten testnet 的公共鏈中,還有學(xué)習(xí)如何使用 Truffle Framework 構(gòu)建合約并管理dapp匈挖。

Part2 部署智能合約到Ropsten 公共鏈


1.1 安裝 Geth

Geth 是一個(gè)Go編程語言編寫的以太坊客戶端碾牌,用于下載區(qū)塊鏈并連接到以太坊網(wǎng)絡(luò)。

現(xiàn)在可以把之前運(yùn)行 Ganache 停掉

MacOS 的安裝說明儡循,其他平臺(tái)的說明可以在這里找到:
https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum

brew tap ethereum/ethereum
brew install ethereum
geth --testnet --syncmode fast --rpc --rpcapi db,eth,net,web3,personal --cache=1024 --rpcport 8545 --rpcaddr 127.0.0.1 --rpccorsdomain "*"
Geth 參數(shù)解釋
--testnet:告訴 Geth 開始連接到測(cè)試網(wǎng)絡(luò)舶吗。我們連接的網(wǎng)絡(luò)名稱叫做 Ropsten。

--syncmode fast:在前面择膝,我們講解過誓琼,當(dāng)下載區(qū)塊鏈軟件并啟動(dòng)時(shí),必須將整個(gè)區(qū)塊鏈副本下載到計(jì)算機(jī)上调榄。你可以下載整個(gè)區(qū)塊鏈并執(zhí)行每個(gè)區(qū)塊內(nèi)的每一筆交易踊赠,這樣就可以在本地獲得區(qū)塊鏈的全部歷史記錄,這樣非常耗時(shí)每庆。但是,還有其他模式今穿,只需下載交易回執(zhí)缤灵,而不是執(zhí)行每個(gè)交易。我們不需要此測(cè)試區(qū)塊鏈的歷史記錄,因此我們將使用快速模式同步區(qū)塊鏈腮出。

--rpc --rpcapi db,eth,net,web3,personal:告訴 geth 通過 RPC 接受請(qǐng)求帖鸦,并啟用這些API。

--rpcport 8545 --rpcaddr 127.0.0.1 --rpccorsdomain"*":這是要使用 web3js庫與區(qū)塊鏈服務(wù)器 geth 進(jìn)行通信的主機(jī)和端口胚嘲。

上面那行 geth 命令會(huì)啟動(dòng)以太坊節(jié)點(diǎn)作儿,連接到其他對(duì)等節(jié)點(diǎn)并開始下載區(qū)塊鏈。下載區(qū)塊鏈所需的時(shí)間取決于許多因素馋劈,例如網(wǎng)速攻锰,內(nèi)存,硬盤類型等妓雾。在擁有8GB RAM娶吞,SSD硬盤100Mbps連接的計(jì)算機(jī)上用了90-120分鐘。如果在快速模式下同步 Ropsten械姻,需要6 - 7 GB的硬盤空間妒蛇。

在你正在運(yùn)行的控制臺(tái)中,你會(huì)看到類似下面的輸出楷拳。

INFO [05-04|01:13:26] Imported new state entries               count=798  elapsed=4.785ms   processed=37925184 pending=918   retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:13:32] Imported new state entries               count=797  elapsed=4.914ms   processed=37925981 pending=1157  retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:13:38] Imported new state entries               count=789  elapsed=5.169ms   processed=37926770 pending=893   retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:13:44] Imported new state entries               count=776  elapsed=6.202ms   processed=37927546 pending=601   retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:13:45] Imported new block headers               count=1    elapsed=5.202ms   number=3161881 hash=8114b2…8f82fe ignored=0
INFO [05-04|01:13:47] Imported new state entries               count=513  elapsed=3.461ms   processed=37928059 pending=774   retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:13:52] Imported new state entries               count=793  elapsed=4.537ms   processed=37928852 pending=1647  retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:13:57] Imported new state entries               count=799  elapsed=5.473ms   processed=37929651 pending=1265  retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:14:03] Imported new state entries               count=816  elapsed=5.058ms   processed=37930467 pending=886   retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:14:08] Imported new state entries               count=824  elapsed=4.234ms   processed=37931291 pending=1895  retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:14:16] Imported new state entries               count=1420 elapsed=11.231ms  processed=37932711 pending=600   retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:14:20] Imported new block headers               count=1    elapsed=5.423ms   number=3161882 hash=af6cd1…8cf9ce ignored=0
INFO [05-04|01:14:22] Imported new state entries               count=881  elapsed=4.790ms   processed=37933592 pending=1512  retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:14:29] Imported new state entries               count=1169 elapsed=8.459ms   processed=37934761 pending=711   retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:14:31] Imported new block headers               count=1    elapsed=5.508ms   number=3161883 hash=12a410…ee6cf5 ignored=0
INFO [05-04|01:14:33] Imported new state entries               count=592  elapsed=4.565ms   processed=37935353 pending=749   retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:14:36] Imported new state entries               count=599  elapsed=3.192ms   processed=37935952 pending=1796  retry=0   duplicate=25899 unexpected=45507
INFO [05-04|01:14:42] Imported new state entries               count=884  elapsed=5.504ms   processed=37936836 pending=1442  retry=0   duplicate=25899 unexpected=45507

在區(qū)塊鏈正在同步的同時(shí)绣夺,去Etherscan的 Ropsten explorer,看看目前開采的最新區(qū)塊欢揖。

在輸出中乐导,查找塊標(biāo)題條目的“number”字段的值(以粗體顯示),這是已經(jīng)同步到你的計(jì)算機(jī)的總塊數(shù)浸颓。

1.2 測(cè)試區(qū)塊鏈網(wǎng)絡(luò)

我們安裝了geth并啟動(dòng)節(jié)點(diǎn)在Ropsten testnet上下載區(qū)塊鏈物臂。那么什么是 Ropsten, 什么是測(cè)試網(wǎng)絡(luò)产上?

在開發(fā)任何軟件時(shí)棵磷,首先在電腦上進(jìn)行本地開發(fā)。然后晋涣,你可以部署到QA或測(cè)試環(huán)境仪媒,以確保代碼按預(yù)期工作,并允許其他人與應(yīng)用程序進(jìn)行交互谢鹊。一旦修復(fù)了所有錯(cuò)誤并確保代碼運(yùn)行良好算吩,就可以部署到生產(chǎn)環(huán)境,以便客戶可以擁有更好的體驗(yàn)佃扼。

以太坊中的QA / Staging環(huán)境稱為Testnet偎巢。以太坊基金會(huì)支持兩個(gè)測(cè)試網(wǎng),Ropsten和Rinkeby兼耀。兩個(gè)網(wǎng)絡(luò)之間的唯一區(qū)別是Ropsten使用Proof of Work算法压昼,而Rinkeby使用權(quán)限證明求冷。還有另外一個(gè)流行的測(cè)試網(wǎng)叫做Kovan,它是由少數(shù)以太坊公司開發(fā)的窍霞,需要Parity來使用這個(gè)網(wǎng)絡(luò)匠题。

2.1 設(shè)置

現(xiàn)在來安裝 truffle,它會(huì)讓構(gòu)建和管理dapp非常簡單但金。

你可以使用npm來安裝truffle韭山。
npm install -g truffle

創(chuàng)建投票項(xiàng)目
初始化 truffle 項(xiàng)目時(shí),它會(huì)創(chuàng)建運(yùn)行完整 dapp 所需的文件和目錄冷溃。先刪除該項(xiàng)目的合約目錄中的ConvertLib.sol和MetaCoin.sol文件钱磅。

mkdir -p eth_vote_dapp/ch2
cd eth_vote_dapp/ch2
npm install -g webpack
truffle unbox webpack
ls
README.md       contracts       node_modules      test          webpack.config.js   truffle.js
app          migrations       package.json
ls app/
index.html javascripts stylesheets
ls contracts/
ConvertLib.sol MetaCoin.sol Migrations.sol
ls migrations/
1_initial_migration.js 2_deploy_contracts.js
rm contracts/ConvertLib.sol contracts/MetaCoin.sol

查找名為 truffle.js 文件并進(jìn)行以下更改:

將端口從7545更改為8545 - 這樣可以連接到默認(rèn)情況下在端口8545上運(yùn)行的ganache
添加"gas:4700000"作為端口配置后的設(shè)置,以便在部署時(shí)不會(huì)遇到gas問題秃诵。

2.2 Migration

遷移的概念

理解遷移目錄的內(nèi)容很重要续搀。這些遷移文件用于將合約部署到區(qū)塊鏈。

第一次遷移 1_initial_migration.js 將名為Migrations的合約部署到區(qū)塊鏈中菠净,并用于存儲(chǔ)你已部署的最新合約禁舷。每次運(yùn)行遷移時(shí),truffle 都會(huì)查詢區(qū)塊鏈以獲取已部署的最后一個(gè)合約毅往,然后部署尚未發(fā)布的任何合約牵咙。然后更新Migrations合約中的last_completed_migration字段,以指示部署的最新合約攀唯。你可以簡單地將其視為名為遷移的數(shù)據(jù)庫表洁桌,其中名為last_completed_migration的列始終保持最新狀態(tài)。

更新遷移文件
更新2_deploy_contracts.js的內(nèi)容侯嘀,如下所示

var Voting = artifacts.require("./Voting.sol");
module.exports = function(deployer) {
 deployer.deploy(Voting, ['James', 'Norah', 'Jones'], {gas: 290000});
};

deployer第一個(gè)參數(shù)是合約的名字另凌,后面是構(gòu)造器參數(shù)。 第二個(gè)參數(shù)是一組候選人戒幔。第三個(gè)參數(shù)是一個(gè)哈希吠谢,我們指定部署代碼所需的Gas。 Gas數(shù)量取決于合約的規(guī)模诗茎,對(duì)于投票合約工坊,290000就足夠了。

更新truffle.js配置文件敢订,如下所示:

require('babel-register')

module.exports = {
 networks: {
  development: {
   host: 'localhost',
   port: 8545,
   network_id: '*',
   gas: 470000
  }
 }
}

2.3 合約代碼

我們已經(jīng)編寫好了合約代碼王污,并且我們沒有需要使用Truffle進(jìn)行更改。 把 Voting.sol 文件從ch1復(fù)制到contracts目錄中

cp ../ch1/Voting.sol contracts/
ls contracts/
Migrations.sol Voting.sol

將app / index.html的內(nèi)容替換為以下內(nèi)容楚午。

<!DOCTYPE html>
<html>
<head>
 <title>DApp</title>
 <link  rel='stylesheet' type='text/css'>
 <link  rel='stylesheet' type='text/css'>
</head>
<body class="container">
 <h1>Voting Application</h1>
 <div id="address"></div>
 <div class="table-responsive">
  <table class="table table-bordered">
   <thead>
    <tr>
     <th>Candidate</th>
     <th>Votes</th>
    </tr>
   </thead>
   <tbody>
    <tr>
     <td>James</td>
     <td id="candidate-1"></td>
    </tr>
    <tr>
     <td>Norah</td>
     <td id="candidate-2"></td>
    </tr>
    <tr>
     <td>Jones</td>
     <td id="candidate-3"></td>
    </tr>
   </tbody>
  </table>
  <div id="msg"></div>
 </div>
 <input type="text" id="candidate" />
 <a href="#" onclick="voteForCandidate()" class="btn btn-primary">Vote</a>
</body>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="app.js"></script>
</html>

2.4 JS代碼

將javascript文件的內(nèi)容復(fù)制到app / javascripts / app.js昭齐。

當(dāng)編譯和部署合約時(shí),truffle將abi和部署的地址存儲(chǔ)在構(gòu)建目錄中的json文件中醒叁。我們將使用這些信息來設(shè)置投票對(duì)象司浪。稍后將使用此對(duì)象來創(chuàng)建表決合約的一個(gè)實(shí)例泊业。

Voting.deployed() 返回合約的一個(gè)實(shí)例把沼。 在Truffle中的每一次調(diào)用都會(huì)返回一個(gè)promise啊易,這就是為什么我們?cè)诿看斡惺聞?wù)調(diào)用的地方都使用then()。

// Import the page's CSS. Webpack will know what to do with it.
import "../stylesheets/app.css";

// Import libraries we need.
import { default as Web3} from 'web3';
import { default as contract } from 'truffle-contract'

import voting_artifacts from '../../build/contracts/Voting.json'

var Voting = contract(voting_artifacts);

let candidates = {"James": "candidate-1", "Norah": "candidate-2", "Jones": "candidate-3"}

window.voteForCandidate = function(candidate) {
 let candidateName = $("#candidate").val();
 try {
  $("#msg").html("Vote has been submitted. The vote count will increment as soon as the vote is recorded on the blockchain. Please wait.")
  $("#candidate").val("");

  Voting.deployed().then(function(contractInstance) {
   contractInstance.voteForCandidate(candidateName, {gas: 140000, from: web3.eth.accounts[0]}).then(function() {
    let div_id = candidates[candidateName];
    return contractInstance.totalVotesFor.call(candidateName).then(function(v) {
     $("#" + div_id).html(v.toString());
     $("#msg").html("");
    });
   });
  });
 } catch (err) {
  console.log(err);
 }
}

$( document ).ready(function() {
 if (typeof web3 !== 'undefined') {
  console.warn("Using web3 detected from external source like Metamask")
  // Use Mist/MetaMask's provider
  window.web3 = new Web3(web3.currentProvider);
 } else {
  console.warn("No web3 detected. Falling back to http://localhost:8545. You should remove this fallback when you deploy live, as it's inherently insecure. Consider switching to Metamask for development. More info here: http://truffleframework.com/tutorials/truffle-and-metamask");
  // fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
  window.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
 }

 Voting.setProvider(web3.currentProvider);
 let candidateNames = Object.keys(candidates);
 for (var i = 0; i < candidateNames.length; i++) {
  let name = candidateNames[i];
  Voting.deployed().then(function(contractInstance) {
   contractInstance.totalVotesFor.call(name).then(function(v) {
    $("#" + candidates[name]).html(v.toString());
   });
  })
 }
});

2.5 創(chuàng)建賬號(hào)

在部署合約之前饮睬,我們需要一個(gè)帳戶和一些以太幣租谈。當(dāng)我們使用 ganache 時(shí),它創(chuàng)建了10個(gè)測(cè)試賬戶并預(yù)裝了100個(gè)以太幣捆愁。但是對(duì)于 testnet 和 mainnet割去,我們必須創(chuàng)建帳戶并自己添加一些以太幣。
truffle console

truffle(default)> web3.personal.newAccount('password')
'0x807a59ca6e531225f86dc5f5abfd42f779290325'
truffle(default)> web3.eth.getBalance('0x807a59ca6e531225f86dc5f5abfd42f779290325')
{ [String: '0'] s: 1, e: 0, c: [ 0 ] }
truffle(default)> web3.personal.unlockAccount('0x807a59ca6e531225f86dc5f5abfd42f779290325', 'password', 15000)

我們現(xiàn)在有一個(gè)地址為'0x807a59ca6e531225f86dc5f5abfd42f779290325'的賬戶(你的賬戶會(huì)有不同的地址)昼丑,余額為0呻逆。

您可以通過 https://faucet.bitfwd.xyz/ 獲取一些Ropsten測(cè)試以太幣。再次嘗試web3.eth.getBalance以確保你擁有以太幣菩帝。如果看到余額為0咖城,那是因?yàn)檫€沒有完全同步區(qū)塊鏈。你只需等待完全同步然后就可以使用了呼奢。

2.6 部署

現(xiàn)在你已經(jīng)有了一些以太幣宜雀,可以繼續(xù)編譯并將合約部署到區(qū)塊鏈。 如果一切順利握础,就會(huì)和下面顯示的一樣辐董。

truffle compile
Compiling Migrations.sol...
Compiling Voting.sol...
Writing artifacts to ./build/contracts

truffle migrate
Running migration: 1_initial_migration.js
Deploying Migrations...
Migrations: 0xf7f69bcfbebe09fbb26c0192f1fdea98182efc5e
Saving successful migration to network...
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying Voting...
Voting: 0xd24a32f0ee12f5e9d233a2ebab5a53d4d4986203
Saving successful migration to network...
Saving artifacts...

可能會(huì)出現(xiàn)的問題和解決方案

如果由于Gas數(shù)量不足而導(dǎo)致部署失敗,請(qǐng)嘗試將migrations / 2_deploy_contracts.js中的Gas數(shù)量提高到500000禀综。例如:deployer.deploy(Voting简烘,['James','Norah'定枷,'Jones']孤澎,{gas:500000});

2.7 Web前端交互

如果部署順利,你可以通過控制臺(tái)和網(wǎng)頁與合約進(jìn)行交互依鸥。

truffle console
truffle(default)> Voting.deployed().then(function(contractInstance) {contractInstance.voteForCandidate('James').then(function(v) {console.log(v)})})

{ blockHash: '0x5a2efc17847f8404c2bc66b094e2bda768002425f87e43f36618eb4b510389f4',
blockNumber: 469628,
contractAddress: null,
....
....
truffle(default)> Voting.deployed().then(function(contractInstance) {contractInstance.totalVotesFor.call('James').then(function(v) {console.log(v)})})

執(zhí)行下面這個(gè)命令會(huì)在localhost:8080 這個(gè)地址打開應(yīng)用的前端網(wǎng)站
npm run dev

2.8 總結(jié)

成功構(gòu)建你的第一個(gè)Dapp亥至。恭喜!
這里有一些有趣的資源贱迟,可以作為進(jìn)一步學(xué)習(xí)的資料:

接下來姐扮,最后一部分我們將加入更復(fù)雜的Token功能。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末衣吠,一起剝皮案震驚了整個(gè)濱河市茶敏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缚俏,老刑警劉巖惊搏,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贮乳,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡恬惯,警方通過查閱死者的電腦和手機(jī)向拆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酪耳,“玉大人浓恳,你說我怎么就攤上這事⊥氚担” “怎么了颈将?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長言疗。 經(jīng)常有香客問我晴圾,道長,這世上最難降的妖魔是什么噪奄? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任死姚,我火速辦了婚禮,結(jié)果婚禮上梗醇,老公的妹妹穿的比我還像新娘知允。我一直安慰自己,他們只是感情好叙谨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布温鸽。 她就那樣靜靜地躺著,像睡著了一般手负。 火紅的嫁衣襯著肌膚如雪涤垫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天竟终,我揣著相機(jī)與錄音蝠猬,去河邊找鬼。 笑死统捶,一個(gè)胖子當(dāng)著我的面吹牛榆芦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播喘鸟,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼匆绣,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了什黑?” 一聲冷哼從身側(cè)響起崎淳,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎愕把,沒想到半個(gè)月后拣凹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體森爽,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年嚣镜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爬迟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡祈惶,死狀恐怖雕旨,靈堂內(nèi)的尸體忽然破棺而出扮匠,到底是詐尸還是另有隱情捧请,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布棒搜,位于F島的核電站疹蛉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏力麸。R本人自食惡果不足惜可款,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望克蚂。 院中可真熱鬧闺鲸,春花似錦、人聲如沸埃叭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赤屋。三九已至立镶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間类早,已是汗流浹背媚媒。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涩僻,地道東北人缭召。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像逆日,于是被迫代替她去往敵國和親嵌巷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353