在Part 1中,我在ganache blockchain的開發環境上創建了一個簡單的投票Dapp,本節我將把Dapp部署到真正的blockchain上。Ethereum有多個公共的test blockchain,一條main blockchain。
Test Blockchain
有很多測試,開發用的以太坊區塊鏈可供我們選擇,例如Ropsten, Rinkeby, Kovan。可以理解為測試網絡,網絡中使用的Ether都是假的。Main Blockchain
主網絡只有一個,抄幣時交易的Ether,錢包應用中的Ether都使用的主網。
本篇文章覆蓋的內容有:
安裝geth ,一個客戶端應用,用戶同步區塊鏈數據,并且可以在本地運行Ethereum節點。
安裝Ethereum Dapp開發框架Truffle,方面我們進行合約的開發,編譯和打包。
優化Voting合約。
在Rinkeby測試網絡中編譯和部署Voting合約。
使用truffle console調用合約,并在網頁中使用。
1. 安裝geth和同步blockchain
brew tap ethereum/ethereum
brew install ethereum
如果你使用Windows或者Ubuntu,可以在這里查閱安裝方法。
成功安裝geth后,運行下面的代碼:
geth --rinkeby --syncmode "fast" --rpc --rpcapi db,eth,net,web3,personal --cache=1024 --rpcport 8545 --rpcaddr 127.0.0.1 --rpccorsdomain "*"
執行后,geth會連接測試網絡中的節點,并同步blockchain信息到本地,整個過程會持續一段時間,大概40分鐘左右。
在console里你可以看到下面的信息,即同步blockchain的進度:
2. 安裝Truffle Framework
使用npm安裝:
npm install -g truffle
3. 使用Truffle創建Voting的contract
第一步,使用truffle的命令,創建一個Dapp工程:
mkdir voting
cd voting
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
Truffle為我們創建好了必要的路徑和文件,同時初始化了一個默認的Dapp。這里我們不需要這個初始化工程,可以把ConvertLib.sol
和MetaCoin.sol
刪掉。
這里有個migrations
的路徑,它的作用可以和數據庫表結構的更新結合理解,migration的過程就是將最新的合約內容部署到區塊鏈中。
pragma solidity ^0.4.18;
// 指定solidity版本
contract Voting {
// mapping 方法生成一個字典,key為bytes32類型,value為uint8類型
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;
}
}
在contracts
路徑下,新建一個文件Voting.sol
,并將上面的合約代碼復制進去(并沒有改動)。
> ls contracts/
Migrations.sol Voting.sol
接下來將下面的代碼復制到2_deploy_contracts.js
文件中:
var Voting = artifacts.require("./Voting.sol");
module.exports = function(deployer) {
deployer.deploy(Voting, ['Sam', 'Leslie', 'Jetty', 'Arkila', 'Piu'], {gas: 6700000});
};
你也可以在truffle.js
文件中設置gas費用:
require('babel-register')
module.exports = {
networks: {
development: {
host: 'localhost',
port: 8545,
network_id: '*',
gas: 470000
}
}
}
把app/javascripts/app.js
中的內容替換成如下:
// Import the page's CSS. Webpack will know what to do with it.
import "../stylesheets/app.css";
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 = {"Sam": "candidate-1", "Leslie": "candidate-2", "Jetty": "candidate-3", "Arkila": "candidate-4", "Piu": "candidate-5"}
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() returns an instance of the contract. Every call
* in Truffle returns a promise which is why we have used then()
* everywhere we have a transaction call
*/
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());
});
})
}
});
同時替換app/index.html
里面的內容:
<!DOCTYPE html>
<html>
<head>
<title>Hello World DApp</title>
<link rel='stylesheet' type='text/css'>
<link rel='stylesheet' type='text/css'>
</head>
<body class="container">
<h1>A Simple Hello World 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>Rama</td>
<td id="candidate-1"></td>
</tr>
<tr>
<td>Nick</td>
<td id="candidate-2"></td>
</tr>
<tr>
<td>Jose</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://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="app.js"></script>
</html>
4. 部署到Rinkeby網絡
在部署之前,你需要給賬戶里充電ether。從創建賬戶開始:
> ruffle console
truffle(development)> web3.personal.newAccount('verystrongpassword')
'0xb4831d00165b2faa495e209e58be1549ab94d27d'
truffle(development)> web3.eth.getBalance('0xb4831d00165b2faa495e209e58be1549ab94d27d')
{ [String: '0'] s: 1, e: 0, c: [ 0 ] }
truffle(development)> web3.personal.unlockAccount('0xb4831d00165b2faa495e209e58be1549ab94d27d', 'verystrongpassword', 15000)
// 記得把verystrongpassword替換成一個比較安全的密碼
在上一章節中,我在node console
中初始化了一個web3 object。在truffle console
里直接使用就可以了。我們現在有了一個錢包地址,但是balance是0。
你可以在Rinkeby網絡中獲取一些測試用的ether:https://faucet.rinkeby.io/
按照網站里的步驟執行完成后,你可以在rinkeby.etherscan.io 中查看balance,如果這個時候你執行web3.eth.getBalance
看到余額任然 為0,這表示geth
沒有同步完測試網絡的所有區塊。
本地同步區塊完成后,你的賬戶上應該有了一定的ether,下面開始部署:
note: 在migrage之前,一定要unlock你的賬戶,不然會出現認證問題
? voting truffle migrate
Compiling ./contracts/Migrations.sol...
Compiling ./contracts/Voting.sol...
Writing artifacts to ./build/contracts
Using network 'development'.
Running migration: 1_initial_migration.js
Deploying Migrations...
... 0x5687c59779ec6cf1432974327b6fd8cd31e4d1f038d6abc1e1452599b2f3c56c
Migrations: 0x03b11bdc336a395899675a1545de58e8ccd8f6b5
Saving successful migration to network...
... 0x1f09b4895d20598450e030114830f742f6f013e35bde8cf8f179b2dbb240065c
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying Voting...
... 0x0c60551172065b2d92054845073a7c4c68d49234d0fa85e0c3aa4f3d6699307f
Voting: 0x2bedf107cf41ae7fd526d884f3df69ebf5e7e2d2
Saving successful migration to network...
... 0x17141ba56a0d7c4d1f8768f8c760a6a40f8183c11722e1acff830741c881c4ad
Saving artifacts...
? voting
migrate過程大概需要一分鐘。
5. 使用voting contract
? voting npm run dev
> truffle-init-webpack@0.0.2 dev /Users/sam/Documents/workspace/blockchain/voting
> webpack-dev-server
Project is running at http://localhost:8080/
webpack output is served from /
Hash: 91cb5e9cc32c8b8b3ee0
Version: webpack 2.7.0
Time: 2033ms
Asset Size Chunks Chunk Names
app.js 1.67 MB 0 [emitted] [big] main
index.html 1.24 kB [emitted]
...
webpack: Compiled successfully.
啟動本地服務,在瀏覽器中打開localhost:8080,就可以開始投票了:
如果你能看到上面的界面,說明你已經成功在公共測試網絡上完成了一個Ethereum application的開發和部署,恭喜你。。。
你可以登錄https://rinkeby.etherscan.io/ ,使用賬戶地址查看所有的transaction。