美文网首页DApp开发者教程Dapp开发
全栈投票Dapp教程—第二部分

全栈投票Dapp教程—第二部分

作者: rajs20222007 | 来源:发表于2018-08-25 11:53 被阅读18次

    本教程翻译自Mahesh Murthy的教程.
    文章链接如下:

    1. https://medium.com/@mvmurthy/full-stack-hello-world-voting-ethereum-dapp-tutorial-part-1-40d2d0d807c2
    2. https://medium.com/@mvmurthy/full-stack-hello-world-voting-ethereum-dapp-tutorial-part-2-30b3d335aa1f
    3. https://medium.com/@mvmurthy/full-stack-hello-world-voting-ethereum-dapp-tutorial-part-3-331c2712c9df

    教程的所有代码可以在这里看到.

    在本教程的第1部分中,我们使用ganache在开发环境中构建了一个简单的投票程序. 现在, 让我们将这个程序部署在真正的区块链上. 以太坊有一些公共测试链和一个主链。

    1. 测试网: 有一些测试区块链, 比如 Ropsten, Rinkeby, Kovan. 将他们看作一个QA服务或者接近正式环境服务器, 它们仅用来测试. 所有这些网络上的以太都是假的.
    2. 主网(也叫 Homestead): 这是真正所有人都在用的交易区块链. 这个网络上的所有区块都是有价值的.

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

    1. 安装geth - 下载区块链和在本地机器运行以太坊节点的客户端软件.
    2. 安装truffle - 以太坊Dapp库, 用来编译和部署合约.
    3. 对我们的投票App进行小的修改来使之运用truffle.
    4. 编译和部署合约到Rinkeby测试网.
    5. 通过truffle命令和网页与我们的合约交互.

    0x1 安装geth和同步区块链

    我在MacOS和Ubuntu上安装并测试了所有内容. 安装非常简单:

    On Mac:

    brew tap ethereum/ethereum
    brew install ethereum
    

    On Ubuntu:

    sudo apt-get install software-properties-common
    sudo add-apt-repository -y ppa:ethereum/ethereum
    sudo apt-get update
    sudo apt-get install ethereum
    

    在这里可以看到在各种平台上如何安装geth: https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum

    安装geth后, 在命令行中运行以下命令:

    geth --rinkeby --syncmode "fast" --rpc --rpcapi db,eth,net,web3,personal --cache=1024  --rpcport 8545 --rpcaddr 127.0.0.1 --rpccorsdomain "*"
    

    这将启动本地以太坊节点, 连接到其他对等节点并开始下载区块链. 下载区块链所需的时间取决于各种因素, 比如网速, 内存, 硬盘类型等. 在一台具有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…]
    

    0x2 安装Truffle

    使用npm安装truffle. 教程中使用的truffle版本是3.1.1.

    npm install -g truffle
    

    由于系统设置不同, 在你的电脑上可能需要<code>sudo</code>命令.

    0x3 设置投票合约

    首先设置truffle项目:

    mkdir voting
    cd voting
    npm install -g webpack
    truffle unbox webpack
    

    truffle会创建运行dapp所需的必要文件和目录. Truffle还创建了一个示例应用程序来帮助您入门(我们不会在本教程中使用). 因此可以随意删除contracts目录中的ConvertLib.sol和MetaCoin.sol文件。

    理解migrations文件夹中的内容非常重要. 这些migration文件用于将合约部署到区块链. (如果你还记得, 在上一篇文章中,我们使用VotingContract.new将合约部署到区块链). 第一个 1_initial_migration.js文件将名为Migrations的合约部署到区块链, 用于存储已部署的最新合同. 每次运行migration时, truffle都会查询区块链中已部署的最后一个合约, 然后部署尚未部署的合约. 然后, 更新Migrations合约中的last_completed_migration字段, 以指示已部署的最新合同. 你可以将其视为名为Migration的数据库表, 其中包含名为last_completed_migration的列, 该列始终保持最新. 您可以在truffle文档页面上查看更多详细内容.

    现在我们稍稍修改一下上一个教程中写的代码, 下面会列出一些修改的注释.

    首先,将Voting.sol从上一个教程复制到contract目录(此文件没有修改).

    pragma solidity ^0.4.18;
    // 指定代码编译器版本
    
    contract Voting {
      /* 下面的Map等效于字典或散列。
      映射的key是候选人名称(bytes32类型), 值用于存储投票计数(无符号整数)
      */
      
      mapping (bytes32 => uint8) public votesReceived;
      
      /* Solidity目前还不允许您在构造函数中传递一个字符串数组.
      我们将使用bytes32数组来存储候选人列表
      */
      
      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;
      }
    }
    
    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});
    };
    /* 部署方法第一个参数是合约的路径, 然后是构造函数的参数. 在我们的例子中, 只有一个参数: 一系列候选人名单. 第三个参数是一个字典, 我们指定部署代码所需的gas. Gas值取决于合同的大小.
    */
    

    你可以将gas值设置为truffle.js中的全局变量. 继续添加如下所示的gas选项, 以便将来如果忘记在特定的migration文件中设置, 它将默认使用全局值.

    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'
    
    /*
     * 当你编译和部署投票合约时,
     * truffle 在构建目录下的一个json文件中存储abi和部署地址.
     * 我们会使用这些信息来初始化投票类. 然后再创建一个投票合约的实例.
     * 与上一篇文章中的 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")
        // 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 - 使用你的回退策略 (本地节点 / 托管节点 + in-dapp id 管理 / 失败)
        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行外, 其他与上一篇文章的相同.

    <!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>
    

    0x4 在Rinkeby测试网上部署合约

    在我们部署合约前, 我们需要一个账户和一些以太币. 在我们使用ganache时, 创建了10个测试帐户并预装了100个以太币. 但是对于测试网和主网, 我们必须创建帐户并自己添加一些以太币.

    在终端中, 执行以下操作:

    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」的帐户(在你的Demo中,你会拥有不同的地址), 余额为0.

    你可以在https://faucet.rinkeby.io/获取一个Rinkeby上的测试以太币. 再使用web3.eth.getBalance以确保你已经有以太币. 你也可以在rinkeby.etherscan.io上输入你的地址来查看账户余额. 如果你在web3.eth.getBalance上获取余额为0, 但是在rinkeby.etherscan.io上看到非零余额, 这代表本地同步尚未完成. 只需要等待本地区块链同步完成即可.

    现在你有一些以太币, 就可以继续编译并将合约部署到区块链. 如果运行顺利, 下面是你运行命令和输出的结果.

    *不要忘记先解锁账户

    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...
    

    在我的电脑上, 差不多70-80秒部署完成.

    0x5 与投票合约交互

    如果你已经部署合约成功, 现在就可以通过truffle命令行来获取投票数量了.

    truffle console
    truffle(default)> Voting.deployed().then(function(contractInstance) {contractInstance.voteForCandidate('Rama').then(function(v) {console.log(v)})})
    // 几秒后, 你会看到像下面内容的接收到的交易:
    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] }
    

    如果做到了这里, 就代表你已经成功啦, 你的合约已经生效并运行正常! 现在启动服务吧.

    npm run dev
    

    你可以在localhost:8080看到投票页面, 并能够投票和查看所有候选人的投票数. 由于我们正在处理真正的区块链, 因此每次对区块链的写入(voteForCandidate)都需要几秒钟(矿工必须将您的交易包含在一个区块中, 并将区块包含在区块链中).

    网页内容

    如果你看到了上面的图片内容, 代表你已经在测试网上创建了一个完整的以太坊程序. 恭喜你!

    现在你的所有交易都是公开的, 你可以在https://rinkeby.etherscan.io/来查看. 只要输入你的账号地址, 你就会看到你所有的交易.

    相关文章

      网友评论

        本文标题:全栈投票Dapp教程—第二部分

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