以太坊DAPP开发实例: 全栈投票系统(第2章)

在第1章(全栈投票系统),我们用ganache在我们的开发环境建立了一个简单的投票程序。现在,让我们把这个应用在真正的blockchain。以太坊有几个公共测试网络和一个主网。

1. 测试网络:如ropsten blockchains,Rinkeby,Kovan。它们仅用于测试目的。在这些网络上使用的以太币都是假的。

2. 主网(Homestead):这是整个世界真实交易的链。你在这个网络上使用的以太币有真正的价值。

在本教程中,我们将完成以下内容:

1. 安装geth-用于下载以太坊区块链和在本地运行以太坊节点的客户端。
2. 安装Truffle 用于编译和部署智能合约的以太坊DAPP开发框架
3. 我们要在上一章的基础上做一些小改动, 以便之前的投票系统能够运行在truffle框架上
4. 编译并部署合约到Rinkeby测试网络

5. 通过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 "*"

这将启动以太坊节点,连接到其他节点开始下载区块。下载时间取决于多种因素,如您的网速,计算机的内存,硬盘等,cache根据你的机器内存调整, 我的机器是8GB内存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,使用的truffle版本是 3.1.1.

npm install -g truffle
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目录的内容很重要。这些migrations文件用于将合约部署到区块链。 (如果您还记得,在上一篇文章中,我们使用了VotingContract.new将合约部署到区块链,现在我们不需要这样做)。第一个文件:1_initial_migration.js将名为Migrations的合同部署到区块链中,并用于存储您已部署的最新合约。每次运行 migration时,truffle都会查询区块链以获取已部署的最后一个合约,然后部署尚未部署的任何合约。然后更新Migrations合约中的last_completed_migration字段,以指示部署的最新合约。您可以简单地将其视为名为 migration的数据库表,其中名为last_completed_migration的列始终保持最新状态。你可以在 truffle文件页面找到更多细节。

现在让我们用我们在前一个教程中编写的所有代码更新该项目,并在下面进行一些更改。

首先,将上一教程中的Voting.sol复制到contracts目录(这个文件没有变化)。

pragma solidity ^0.4.18;
// We have to specify what version of compiler this code will compile with

contract Voting {
  /* mapping field below is equivalent to an associative array or hash.
  The key of the mapping is candidate name stored as type bytes32 and value is
  an unsigned integer to store the vote count
  */
  
  mapping (bytes32 => uint8) public votesReceived;
  
  /* Solidity doesn't let you pass in an array of strings in the constructor (yet).
  We will use an array of bytes32 instead to store the list of candidates
  */
  
  bytes32[] public candidateList;

  /* This is the constructor which will be called once when you
  deploy the contract to the blockchain. When we deploy the contract,
  we will pass an array of candidates who will be contesting in the election
  */
  function Voting(bytes32[] candidateNames) public {
    candidateList = candidateNames;
  }

  // This function returns the total votes a candidate has received so far
  function totalVotesFor(bytes32 candidate) view public returns (uint8) {
    require(validCandidate(candidate));
    return votesReceived[candidate];
  }

  // This function increments the vote count for the specified candidate. This
  // is equivalent to casting a vote
  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;
  }
}

Voting.sol hosted with ❤ by GitHub

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});
};
/* As you can see above, the deployer expects the first argument to   be the name of the contract followed by constructor arguments. In our case, there is only one argument which is an array of
candidates. The third argument is a hash where we specify the gas required to deploy our code. The gas amount varies depending on the size of your contract.
*/


您还可以将gas值设置为truffle.js中的全局设置。像下面那样添加gas选项,以便将来如果您在特定migration文件忘记设置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 libraries we need.
import { default as Web3} from 'web3';
import { default as contract } from 'truffle-contract'

/*
 * When you compile and deploy your Voting contract,
 * truffle stores the abi and deployed address in a json
 * file in the build directory. We will use this information
 * to setup a Voting abstraction. We will use this abstraction
 * later to create an instance of the Voting contract.
 * Compare this against the index.js from our previous tutorial to see the difference
 * 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() 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.js hosted with ❤ by GitHub

将app/index.html的内容替换为以下内容。 即使这个文件与上一章几乎相同,除了包含的js文件是第41行的app.js。




  Hello World DApp
  
  


  

A Simple Hello World Voting Application

Candidate Votes
Rama
Nick
Jose
Vote

index.html hosted with ❤ by GitHub

4.将合约部署到Rinkeby测试网络

在我们可以部署合约之前,我们需要一个帐户和一些以太币。当我们使用ganache时,它创建了10个测试账户并预分配100个虚假以太币。 但是对于测试网络和主网,我们必须创建帐户并自己添加一些ETH。

在您的命令行终端中,执行以下操作:

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)
// Replace 'verystrongpassword' with a good strong password.
// The account is locked by default, make sure to unlock it before using the account for deploying and interacting with the blockchain.

在之前的文章中,我们启动了一个node控制台并初始化了web3对象。当我们执行truffle控制台时,所有这些都是自动完成的,并且我们得到一个可以使用的web3对象。我们现在有一个地址为0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1的账户(您的账户中会是不同的地址),余额为0。

您可以通过这个网站为Rinkeby网络分配一些测试以太币:https://faucet.rinkeby.io/。再次尝试web3.eth.getBalance以确保您拥有以太币。您也可以在https://rinkeby.etherscan.io/上输入您的地址以查看您的帐户余额。如果您可以在https://rinkeby.etherscan.io/上看到非零余额,但是如果web3.eth.getBalance仍显示0,则表示您的同步尚未完成。您只需等待本地区块链同步并追上即可。


现在您已经有了一些以太币,请继续编译并将合约部署到区块链。如果一切顺利,下面是运行的命令和输出。

注意: 在部署合同之前,不要忘记解锁账户(默认使用 web3.eth.accounts[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)})})
// After a few seconds, you should see a transaction receipt like this:
receipt:
{ 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)将需要几秒钟(矿工必须将您的交易包括在区块中并将区块包含在区块链中)。

以太坊DAPP开发实例: 全栈投票系统(第2章)_第1张图片

如果你看到这个页面并且能够投票,你可以在公共测试网络上建立一个完整的以太坊应用程序,恭喜!

由于您所有的交易都是公开的,您可以在这里查看它们:http://rinkeby.etherscan.io/。 只需输入您的帐户地址,它会显示您所有的交易与时间戳。

希望你能够跟得上我并让程序工作。 你可以在这里找到github仓库中的所有文件。 如果遇到让应用程序工作的问题,请随时通过twitter @zastrinlab来戳我。

[本教程的第3部分现已发布!]

如果你想要一个更具挑战性的项目,我创建了一个在Ethereum和IPFS上构建去中心化eBay的课程。

一如既往地感谢Raine Rupert Revere的所有反馈,并感谢Craig Skipsey找到所有的错误!

你可能感兴趣的:(以太坊DAPP开发实例: 全栈投票系统(第2章))