在本教程的第一篇文章中,我们用 ganache 配置了开发环境,并在其中搭建了一个简单的投票程序。接下来我们要把这个程序放到真正的区块链上。以太坊有几个公开的测试链和一个主链。
Testnet: Ropsten、Rinkeby、Kovan 都是测试链。你可以把它们当作 QA 或临时服务器,只用于测试。在这些网络上不需要使用真正的以太币。
Mainnet (又叫 Homestead):所有的真实交易都发生在这个区块链上。在这个网络中必须使用真正的以太币。
这个教程要完成如下几项任务:
安装 geth — 用来下载区块链的客户端软件,在你的机器上运行以太坊节点的也是它。
安装以太坊 Dapp 框架 Truffle(http://truffleframework.com/),用于编译和部署我们的合约。
对之前的投票程序做简单的修改,使之可以使用 truffle。
编译合约并将其部署到 Rinkeby 测试网络上。
通过 truffle 控制台与合约交互,然后实现网页与合约的交互。
1.安装 geth,同步区块链
我在 MacOS 和 Ubuntu 上都做了安装和测试。安装很简单
在 Mac 上:
mahesh@projectblockchain:~$ brew tap ethereum/ethereum
mahesh@projectblockchain:~$ brew install ethereum
复制代码
在 Ubuntu 上:
mahesh@projectblockchain:~$ sudo apt-get install software-properties-common
mahesh@projectblockchain:~$ sudo add-apt-repository -y ppa:ethereum/ethereum
mahesh@projectblockchain:~$ sudo apt-get update
mahesh@projectblockchain:~$ sudo apt-get install ethereum
复制代码
后面这个链接里有各种平台上的安装指南: https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum
安装好 geth 之后,在命令行控制台中运行下面这条命令:
mahesh@projectblockchain:~$ geth --rinkeby --syncmode "fast" --rpc --rpcapi db,eth,net,web3,personal --cache=1024 --rpcport 8545 --rpcaddr 127.0.0.1 --rpccorsdomain "*"
复制代码
这条命令会启动以太坊节点,连接网络中的对等节点并开始下载区块链。下载时长取决于多个因素,比如网络连接速度,电脑上的 RAM,硬盘类型等。我在一台 8GB RAM 和 50Mbps 连接的机器上用了大概 30~45 分钟。
在运行 geth 的控制台里,你会看到下面这样的输出。找到粗体的区块号。当区块链完全同步时,区块链应该接近这个页面的区块号: https://rinkeby.etherscan.io/
I0130 22:18:15.116332 core/blockchain.go:1064] imported 32 blocks, 49 txs ( 6.256 Mg) in 185.716ms (33.688 Mg/s). #445097 [e1199364… / bce20913…]
I0130 22:18:20.267142 core/blockchain.go:1064] imported 1 blocks, 1 txs ( 0.239 Mg) in 11.379ms (20.963 Mg/s). #445097 [b4d77c46…]
I0130 22:18:21.059414 core/blockchain.go:1064] imported 1 blocks, 0 txs ( 0.000 Mg) in 7.807ms ( 0.000 Mg/s). #445098 [f990e694…]
I0130 22:18:34.367485 core/blockchain.go:1064] imported 1 blocks, 0 txs ( 0.000 Mg) in 4.599ms ( 0.000 Mg/s). #445099 [86b4f29a…]
I0130 22:18:42.953523 core/blockchain.go:1064] imported 1 blocks, 2 txs ( 0.294 Mg) in 9.149ms (32.136 Mg/s). #445100 [3572f223…]
2.安装 Truffle 框架
用 npm 安装 truffle,本教程中用的是 3.1.1 版。
注意:根据系统的配置,你可能需要在这个命令的开头加上sudo
。
3. 设置投票合约
首先要创建 truffle 项目:
mahesh@projectblockchain:~$ mkdir voting
mahesh@projectblockchain:~$ cd voting
mahesh@projectblockchain:~/voting$ npm install -g webpack
mahesh@projectblockchain:~/voting$ truffle unbox webpack
mahesh@projectblockchain:~/voting$ ls
README.md contracts node_modules test webpack.config.js truffle.js
app migrations package.json
mahesh@projectblockchain:~/voting$ ls app/
index.html javascripts stylesheets
mahesh@projectblockchain:~/voting$ ls contracts/
ConvertLib.sol MetaCoin.sol Migrations.sol
mahesh@projectblockchain:~/voting$ ls migrations/
1_initial_migration.js 2_deploy_contracts.js
复制代码
如上所示,truffle 会创建运行全栈 dapp 所必需的文件和目录。Truffle 还创建了一个样本程序,可以作为基础进行修改调整。我们不用它,所以可以删掉 contracts 目录下的 ConvertLib.sol 和 MetaCoin.sol 文件。
一定要搞明白 migrations 目录中的内容。这些迁移文件是用来把合约部署到区块链上的。(如果你还记得,我们在上一篇文章中是用 VotingContract.new 把合约部署到区块链上的,这次不用那么做了)。第一个迁移文件 1_initial_migration.js 会将一个名为 Migrations 的合约部署到区块链上,用来存放你部署的最新合约。每次运行 migration,truffle 都会查询区块链,获取部署过的最新合约,然后将还没部署的合约部署上去。然后更新 Migrations 合约中的 last_completed_migration 域,以指明部署好的最新合约。你可以把它看做一个名为 Migration 的数据库表,有一个永远保持最新状态的 last_completed_migration 域。详细信息请参考truffle文档。
接下来我们要把上篇文章中写的代码改一下。
首先将 Voting.sol 复制到 contracts 目录下(这个文件不用改)。
mahesh@projectblockchain:~/voting$ ls contracts/
Migrations.sol Voting.sol
复制代码
然后打开 migrations 目录下的 2_deploy_contracts.js 文件,换成下面的代码:
var Voting = artifacts.require("./Voting.sol");
module.exports = function(deployer) {
deployer.deploy(Voting, ['Rama', 'Nick', 'Jose'], {gas: 6700000});
};
/* 如上所示,部署器的第一个参数是合约的名称,第二个参数是要传给构造器的参数。就这个例子而言,构造器只有一个参数,即包含候选项的数组。第三个参数是哈希表,指定为部署代码提供的燃料。燃料多少取决于合约的大小。
*/
复制代码
燃料值也可以在 truffle.js 中设定。打开 truffle.js,像下面的代码这样设定全局参数 gas。这样即便将来忘了在哪个迁移文件中设定 gas,它也会用这个全局参数作为燃料的默认值。
require('babel-register')
module.exports = {
networks: {
development: {
host: 'localhost',
port: 8545,
network_id: '*',
gas: 470000
}
}
}
复制代码
将 app/javascripts/app.js 中的代码换成下面的代码。
// 引入页面的CSS。Webpack知道该怎么做。
import "../stylesheets/app.css";
// 引入我们需要的库
import { default as Web3} from 'web3';
import { default as contract } from 'truffle-contract'
/*
* 在编译和部署Voting合约时,truffle会将abi和部署地址
* 存在build目录下的json文件中。我们会用这个信息设置
* Voting抽象。之后会用这个抽象创建Voting合约的实例。
* 可以跟我们之前那篇文章中的index.js文件比较一下有什么差异
* https://gist.github.com/maheshmurthy/f6e96d6b3fff4cd4fa7f892de8a1a1b4#file-index-js
*/
import voting_artifacts from '../../build/contracts/Voting.json'
var Voting = contract(voting_artifacts);
let candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "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()返回合约的实例。在Truffle中
* 每次都会返回一个promise,所以有交易调用的地方
* 都要用then()
*/
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")
// 用Mist/MetaMask的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 中的代码。这里的代码跟上一篇文章中的代码几乎一样,只是第 41 行引入的 js 文件是 app.js。
<!DOCTYPE html>
<html>
<head>
<title>Hello World DApp</title>
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' 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 测试网络
在部署合约之前,我们还需要一个账号和一些以太币。在用 ganache 时,它会创建 10 个测试账号,每个都有 100 个测试币。但用测试网络和主网时,我们只能自己创建账号,自己存以太币。
在命令行终端中执行下面这些命令:
mahesh@projectblockchain:~/voting$ truffle console
truffle(default)> web3.personal.newAccount('verystrongpassword')
'0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1'
truffle(default)> web3.eth.getBalance('0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1')
{ [String: '0'] s: 1, e: 0, c: [ 0 ] }
truffle(default)> web3.personal.unlockAccount('0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1', 'verystrongpassword', 15000)
// 将'verystrongpassword'换成真正的好密码。
// 账户默认是锁着的,在用于部署和跟区块链交互之前,要确认一下已经解锁了。
复制代码
在上一篇文章中,我们启动 node 控制台初始化了一个 web3 对象。而 truffle 控制台会帮我们做好所有事情,所以我们会得到一个可用的 web3 对象。一个地址为‘0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1’ 的账号(当然,你会有自己的地址),初始余额为 0。
你可以在 faucet(https://faucet.rinkeby.io/) 申请一些 Rinkeby 网络的测试币。再试着运行一下web3.eth.getBalance
,确保你得到了自己申请的以太币。如果你在 rinkeby.etherscan.io 上看到的余额不是 0,而web3.eth.getBalance
返回的结果依然是 0,这说明你的本地区块链还没完成同步,你只需要等着它完成同步。
有了以太币,你可以继续了,编译合约,把它部署到区块链上。下面是你要运行的命令,以及,如果一切顺利的话,应有的输出。
再次提醒:别忘了在部署合约前解锁你的账号。
mahesh@projectblockchain:~/voting\$ truffle migrate
Compiling Migrations.sol…
Compiling Voting.sol…
Writing artifacts to ./build/contracts
Running migration: 1_initial_migration.js
Deploying Migrations…
Migrations: 0x3cee101c94f8a06d549334372181bc5a7b3a8bee
Saving successful migration to network…
Saving artifacts…
Running migration: 2_deploy_contracts.js
Deploying Voting…
Voting: 0xd24a32f0ee12f5e9d233a2ebab5a53d4d4986203
Saving successful migration to network…
Saving artifacts…
mahesh@projectblockchain:~/voting$
在我的机器上,部署这个合约大概用了 70–80 秒。
5. 与投票合约交互
如果可以成功部署合约,现在应该可以通过 truffle 控制台进行投票和获取票数信息了。
mahesh@projectblockchain:~/voting$ truffle console
truffle(default)> Voting.deployed().then(function(contractInstance) {contractInstance.voteForCandidate('Rama').then(function(v) {console.log(v)})})
// 几秒钟之后,应该可以见到下面这样的交易收据:
{ blockHash: '0x7229f668db0ac335cdd0c4c86e0394a35dd471a1095b8fafb52ebd7671433156',
blockNumber: 469628,
contractAddress: null,
....
....
truffle(default)> Voting.deployed().then(function(contractInstance) {contractInstance.totalVotesFor.call('Rama').then(function(v) {console.log(v)})})
{ [String: '1'] s: 1, e: 0, c: [ 1] }
复制代码
如果你可以这样做,说明合约已经部署成功,开始发挥作用了!继续前进,启动服务器:
mahesh@projectblockchain:~/voting$ npm run dev
复制代码
你应该可以在 localhost:8080 上看到投票页面,能在这个页面上投票,见到所有候选项的票数。既然这次是真正的区块链,每次写入(voteForCandidate
)都要花几秒钟(矿机必须把你的交易放到区块中,并将这个区块添加到区块链上)。
如果能看到这个页面,并且可以投票,说明你刚在公共测试链上搭建了自己的第一个完整的以太坊应用程序,恭喜你!
既然所有交易都是公开的,所以你可以在这里看到它们:https://rinkeby.etherscan.io/。 只要输入你的账号地址,就能看到你的所有交易。
希望你按照教程成功完成了这个应用。所有代码都放在了 github 的这个代码库里。
评论 1 条评论