以太坊投票Dapp教程

这个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

Ganache 默认创建10个帐户,并给每个账户加载100个以太币,以便测试。如果你不知道账户,可以把它想象成一个有钱的银行账户(以太币是以太坊生态系统的货币)。你需要此帐户来支付交易和发送/接收以太币,我们会在后续完成创建帐户的过程。

你也可以在这里下载安装GUI版本的 Ganache

2.1 Solidity 智能合约

ganache已经安装并成功运行了,我们来编写第一个智能合约。

我们将编写一个投票智能合约,它具有:

  1. 初始化候选人数组的构造函数

  2. 投票方法(增加投票数量)

  3. 返回候选人收到的总票数的方法

当智能合约部署到区块链时,构造函数只会调用一次。与传统的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 总结

如果你可以看到页面并为候选人投票并查看投票计数,那你已经成功创建了第一份合约,恭喜!所有的选票保存在区块链中,并且是不可变的。任何人都可以独立验证每位候选人收到多少票。

以下是我们完成的:

  1. 可以通过安装node,npm 和 ganache 来设置开发环境
  2. 编写了一份简单的投票合约,编译并部署了合约到区块链
  3. 通过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。恭喜!
这里有一些有趣的资源,可以作为进一步学习的资料:

接下来,最后一部分我们将加入更复杂的Token功能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342