这个Dapp教程会分为三部分。从简单的投票功能开始,慢慢熟悉Dapp的开发环境与生态圈,后面再慢慢加入ERC20 Token等功能。
Dapp: Decentralized Application (去中心化应用)
Part 1
以下开发的系统环境基于MacOS
如果还没安装Homebrew,请按照 https://brew.sh/ 上的说明安装。 Homebrew是包管理器,可以帮助我们安装开发所需的所有其他软件。安装好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
以上所有步骤都成功后,会看到下面图片的输出:
Ganache 默认创建10个帐户,并给每个账户加载100个以太币,以便测试。如果你不知道账户,可以把它想象成一个有钱的银行账户(以太币是以太坊生态系统的货币)。你需要此帐户来支付交易和发送/接收以太币,我们会在后续完成创建帐户的过程。
你也可以在这里下载安装GUI版本的 Ganache
2.1 Solidity 智能合约
ganache已经安装并成功运行了,我们来编写第一个智能合约。
我们将编写一个投票智能合约,它具有:
初始化候选人数组的构造函数
投票方法(增加投票数量)
返回候选人收到的总票数的方法
当智能合约部署到区块链时,构造函数只会调用一次。与传统的web开发,每次部署代码覆盖旧代码不同,区块链中部署的代码是不可更改的。如果要更新智能合约并再次部署,则旧的合约仍会在区块链中并且存储在其中的所有数据都不会变。而新的部署将会创建全新的合约。
2.2 合约代码
Mapping相当于关联数组或哈希。key是类型为bytes32的候选人名字,value类型为uint8的存储投票计数
votesReceived[key]默认值为0,因此可以直接增加而不必初始化为0。
每个函数都有一个可见性说明符, public 表示该功能可以从合约外部调用。如果不想让其他人调用一个函数,可以将它设为私有。如果不指定可见性,编译器会有警告。这是编译器最近在增强的功能,解决人们忘记标记其私有函数的问题,从而导致私有函数暴露给公众使用。你可以在 这里 看到所有可用的可见性说明符。
view 是一个modifier,用于告诉编译器该函数是只读的(调用该函数,区块链的状态不会被更新)。所有可用的 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是一个库,可以通过RPC与区块链进行交互。我们会在节点控制台中使用该库来部署合约并与区块链进行交互。
编译合约
首先,在终端运行 node 命令进入 node 控制台并初始化web3对象并查询区块链以列出所有帐户。
输入 node
确保你有在另一个终端窗口中运行ganache
要编译合约,把 Voting.sol 中的代码加载到字符串变量中,并按 图2.3 所示进行编译。
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)
当成功编译代码并打印 compiled Code 对象(只需在 node 控制台中输入 compiled Code 以查看内容)时,要注意到两个重要的字段,这些字段对于理解很重要:
compiledCode.contracts [':Voting'].bytecode:这是 Voting.sol 源代码编译时得到的字节码,这是将被部署到区块链的代码。
compiledCode.contracts [':Voting'].interface:这是合同的接口或模板(称为abi定义),它告诉合约用户合约中有哪些方法可用。无论何时需要与任何合约交互,都需要此 abi 定义。你可以在这里阅读关于 ABI 的更多细节。
2.4 部署合约
现在要把合约部署到区块链。
首先通过传递 abi 来创建合约对象 VotingContract,然后使用此对象在区块链上部署和启动合约。
VotingContract.new 将合约部署到区块链中。
在 node console 执行:
> 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' <- 你的地址会不一样
> contractInstance = VotingContract.at(deployedContract.address)
第一个参数是参与投票的候选人。
第二个参数:
data:这是部署到区块链的 bytecode
from:区块链必须跟踪部署合约的拥有者。我们调用 web3.eth.accounts[0] 获得第一个帐户作为此合约的拥有者。web3.eth.accounts 会在我们启动测试区块链(ganache)时返回一个由10个测试帐户组成的数组。在实际区块链中,不能只使用任何帐户,必须要拥有该账户并在交易前解锁。在创建帐户时会要求你输入密码,这就是用来证明你拥有该帐户的所有权,我们将在下一部分中讲解这个。为了方便,Ganache默认解锁所有10个帐户。
gas:与区块链交互需要花钱。这些资金交给矿工,让他们来把所有代码都写进区块链中。你必须指定愿意支付多少钱才能将代码写进区块链中,并通过设置 gas 的值来实现这一点。
我们现在已经部署了合约,并且有一个可以用来与合约进行交互的合约实例(variable contractInstance)。
区块链上面成千上万的合约,如何在区块链中识别我们的合约?
答案是 deployedContract.address。当我们要和合约进行交互时,需要我们先前的部署地址和 abi。
2.5 终端交互
为候选人投票并查看投票计数
继续在 node 控制台中调用 voteForCandidate 方法和 totalVotesFor 并查看结果。
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'
每次为候选人投票时,都会得到一个交易ID:例如:'0x1b34ce61d464729900c0033bbe24842c6f8af37f4f96e8a96a308ef568240bf6' 。 此交易ID证明此交易已发生,可以在将来随时回顾此交易,这个交易是不可变的。
这种不可改变性是以太坊等区块链的一大优点。
2.6 Web前端
现在大部分工作已经完成,剩下要做的就是创建一个简单 html 文件,并在 js 文件中调用投票命令,
把它们放在 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 href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' 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行中把合约地址替换为你自己的合约地址。
如果一切设置正确,你应该能够在文本框中输入候选人名字并进行投票,并且投票计数会增加。
2.5 总结
如果你可以看到页面并为候选人投票并查看投票计数,那你已经成功创建了第一份合约,恭喜!所有的选票保存在区块链中,并且是不可变的。任何人都可以独立验证每位候选人收到多少票。
以下是我们完成的:
- 可以通过安装node,npm 和 ganache 来设置开发环境
- 编写了一份简单的投票合约,编译并部署了合约到区块链
- 通过nodejs控制台与网页进行交互,然后通过网页进行交互
我们会在第二部分把该合约部署到 Ropsten testnet 的公共链中,还有学习如何使用 Truffle Framework 构建合约并管理dapp。
Part2 部署智能合约到Ropsten 公共链
1.1 安装 Geth
Geth 是一个Go编程语言编写的以太坊客户端,用于下载区块链并连接到以太坊网络。
现在可以把之前运行 Ganache 停掉
MacOS 的安装说明,其他平台的说明可以在这里找到:
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 参数解释
--testnet:告诉 Geth 开始连接到测试网络。我们连接的网络名称叫做 Ropsten。
--syncmode fast:在前面,我们讲解过,当下载区块链软件并启动时,必须将整个区块链副本下载到计算机上。你可以下载整个区块链并执行每个区块内的每一笔交易,这样就可以在本地获得区块链的全部历史记录,这样非常耗时。但是,还有其他模式,只需下载交易回执,而不是执行每个交易。我们不需要此测试区块链的历史记录,因此我们将使用快速模式同步区块链。
--rpc --rpcapi db,eth,net,web3,personal:告诉 geth 通过 RPC 接受请求,并启用这些API。
--rpcport 8545 --rpcaddr 127.0.0.1 --rpccorsdomain"*":这是要使用 web3js库与区块链服务器 geth 进行通信的主机和端口。
上面那行 geth 命令会启动以太坊节点,连接到其他对等节点并开始下载区块链。下载区块链所需的时间取决于许多因素,例如网速,内存,硬盘类型等。在拥有8GB RAM,SSD硬盘100Mbps连接的计算机上用了90-120分钟。如果在快速模式下同步 Ropsten,需要6 - 7 GB的硬盘空间。
在你正在运行的控制台中,你会看到类似下面的输出。
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
在区块链正在同步的同时,去Etherscan的 Ropsten explorer,看看目前开采的最新区块。
在输出中,查找块标题条目的“number”字段的值(以粗体显示),这是已经同步到你的计算机的总块数。
1.2 测试区块链网络
我们安装了geth并启动节点在Ropsten testnet上下载区块链。那么什么是 Ropsten, 什么是测试网络?
在开发任何软件时,首先在电脑上进行本地开发。然后,你可以部署到QA或测试环境,以确保代码按预期工作,并允许其他人与应用程序进行交互。一旦修复了所有错误并确保代码运行良好,就可以部署到生产环境,以便客户可以拥有更好的体验。
以太坊中的QA / Staging环境称为Testnet。以太坊基金会支持两个测试网,Ropsten和Rinkeby。两个网络之间的唯一区别是Ropsten使用Proof of Work算法,而Rinkeby使用权限证明。还有另外一个流行的测试网叫做Kovan,它是由少数以太坊公司开发的,需要Parity来使用这个网络。
2.1 设置
现在来安装 truffle,它会让构建和管理dapp非常简单。
你可以使用npm来安装truffle。
npm install -g truffle
创建投票项目
初始化 truffle 项目时,它会创建运行完整 dapp 所需的文件和目录。先删除该项目的合约目录中的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 文件并进行以下更改:
将端口从7545更改为8545 - 这样可以连接到默认情况下在端口8545上运行的ganache
添加"gas:4700000"作为端口配置后的设置,以便在部署时不会遇到gas问题。
2.2 Migration
迁移的概念
理解迁移目录的内容很重要。这些迁移文件用于将合约部署到区块链。
第一次迁移 1_initial_migration.js 将名为Migrations的合约部署到区块链中,并用于存储你已部署的最新合约。每次运行迁移时,truffle 都会查询区块链以获取已部署的最后一个合约,然后部署尚未发布的任何合约。然后更新Migrations合约中的last_completed_migration字段,以指示部署的最新合约。你可以简单地将其视为名为迁移的数据库表,其中名为last_completed_migration的列始终保持最新状态。
更新迁移文件
更新2_deploy_contracts.js的内容,如下所示
var Voting = artifacts.require("./Voting.sol");
module.exports = function(deployer) {
deployer.deploy(Voting, ['James', 'Norah', 'Jones'], {gas: 290000});
};
deployer第一个参数是合约的名字,后面是构造器参数。 第二个参数是一组候选人。第三个参数是一个哈希,我们指定部署代码所需的Gas。 Gas数量取决于合约的规模,对于投票合约,290000就足够了。
更新truffle.js配置文件,如下所示:
require('babel-register')
module.exports = {
networks: {
development: {
host: 'localhost',
port: 8545,
network_id: '*',
gas: 470000
}
}
}
2.3 合约代码
我们已经编写好了合约代码,并且我们没有需要使用Truffle进行更改。 把 Voting.sol 文件从ch1复制到contracts目录中
cp ../ch1/Voting.sol contracts/
ls contracts/
Migrations.sol Voting.sol
将app / 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 href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' 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文件的内容复制到app / javascripts / app.js。
当编译和部署合约时,truffle将abi和部署的地址存储在构建目录中的json文件中。我们将使用这些信息来设置投票对象。稍后将使用此对象来创建表决合约的一个实例。
Voting.deployed() 返回合约的一个实例。 在Truffle中的每一次调用都会返回一个promise,这就是为什么我们在每次有事务调用的地方都使用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 创建账号
在部署合约之前,我们需要一个帐户和一些以太币。当我们使用 ganache 时,它创建了10个测试账户并预装了100个以太币。但是对于 testnet 和 mainnet,我们必须创建帐户并自己添加一些以太币。
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)
我们现在有一个地址为'0x807a59ca6e531225f86dc5f5abfd42f779290325'的账户(你的账户会有不同的地址),余额为0。
您可以通过 https://faucet.bitfwd.xyz/ 获取一些Ropsten测试以太币。再次尝试web3.eth.getBalance以确保你拥有以太币。如果看到余额为0,那是因为还没有完全同步区块链。你只需等待完全同步然后就可以使用了。
2.6 部署
现在你已经有了一些以太币,可以继续编译并将合约部署到区块链。 如果一切顺利,就会和下面显示的一样。
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...
可能会出现的问题和解决方案
如果由于Gas数量不足而导致部署失败,请尝试将migrations / 2_deploy_contracts.js中的Gas数量提高到500000。例如:deployer.deploy(Voting,['James','Norah','Jones'],{gas:500000});
2.7 Web前端交互
如果部署顺利,你可以通过控制台和网页与合约进行交互。
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)})})
执行下面这个命令会在localhost:8080 这个地址打开应用的前端网站
npm run dev
2.8 总结
成功构建你的第一个Dapp。恭喜!
这里有一些有趣的资源,可以作为进一步学习的资料:
- 以太坊白皮书 Ethereum white paper
- Solidity语言 Solidity Language
- 遇到问题 Ethereum Stack Exchange
- 很有用的社区 https://gitter.im/ethereum/web3.js and https://gitter.im/ethereum/solidity
- 现在的 Gas 价格: http://ethgasstation.info/
接下来,最后一部分我们将加入更复杂的Token功能。