這個(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ì)看到下面圖片的輸出:
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è)投票智能合約,它具有:
初始化候選人數(shù)組的構(gòu)造函數(shù)
投票方法(增加投票數(shù)量)
返回候選人收到的總票數(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)證每位候選人收到多少票状共。
以下是我們完成的:
- 可以通過安裝node,npm 和 ganache 來設(shè)置開發(fā)環(huán)境
- 編寫了一份簡單的投票合約谁帕,編譯并部署了合約到區(qū)塊鏈
- 通過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í)的資料:
- 以太坊白皮書 Ethereum white paper
- Solidity語言 Solidity Language
- 遇到問題 Ethereum Stack Exchange
- 很有用的社區(qū) https://gitter.im/ethereum/web3.js and https://gitter.im/ethereum/solidity
- 現(xiàn)在的 Gas 價(jià)格: http://ethgasstation.info/
接下來姐扮,最后一部分我們將加入更復(fù)雜的Token功能。