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

本教程翻譯自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  rel='stylesheet' type='text/css'>
  <link  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/來查看. 只要輸入你的賬號地址, 你就會看到你所有的交易.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容