美文网首页
以太坊投票Dapp教程

以太坊投票Dapp教程

作者: youclavier | 来源:发表于2018-05-04 18:39 被阅读0次

    这个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功能。

    相关文章

      网友评论

          本文标题:以太坊投票Dapp教程

          本文链接:https://www.haomeiwen.com/subject/dfurrftx.html