全棧投票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在開發(fā)環(huán)境中構(gòu)建了一個(gè)簡單的投票程序. 現(xiàn)在, 讓我們將這個(gè)程序部署在真正的區(qū)塊鏈上. 以太坊有一些公共測試鏈和一個(gè)主鏈。

  1. 測試網(wǎng): 有一些測試區(qū)塊鏈, 比如 Ropsten, Rinkeby, Kovan. 將他們看作一個(gè)QA服務(wù)或者接近正式環(huán)境服務(wù)器, 它們僅用來測試. 所有這些網(wǎng)絡(luò)上的以太都是假的.
  2. 主網(wǎng)(也叫 Homestead): 這是真正所有人都在用的交易區(qū)塊鏈. 這個(gè)網(wǎng)絡(luò)上的所有區(qū)塊都是有價(jià)值的.

本教程中, 我們要完成以下內(nèi)容:

  1. 安裝geth - 下載區(qū)塊鏈和在本地機(jī)器運(yùn)行以太坊節(jié)點(diǎn)的客戶端軟件.
  2. 安裝truffle - 以太坊Dapp庫, 用來編譯和部署合約.
  3. 對我們的投票App進(jìn)行小的修改來使之運(yùn)用truffle.
  4. 編譯和部署合約到Rinkeby測試網(wǎng).
  5. 通過truffle命令和網(wǎng)頁與我們的合約交互.

0x1 安裝geth和同步區(qū)塊鏈

我在MacOS和Ubuntu上安裝并測試了所有內(nèi)容. 安裝非常簡單:

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

在這里可以看到在各種平臺(tái)上如何安裝geth: https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum

安裝geth后, 在命令行中運(yùn)行以下命令:

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

這將啟動(dòng)本地以太坊節(jié)點(diǎn), 連接到其他對等節(jié)點(diǎn)并開始下載區(qū)塊鏈. 下載區(qū)塊鏈所需的時(shí)間取決于各種因素, 比如網(wǎng)速, 內(nèi)存, 硬盤類型等. 在一臺(tái)具有8GB內(nèi)存和50Mbps帶寬的機(jī)器上花了30-45分鐘.

在運(yùn)行g(shù)eth的終端中, 可以看到如下所示的輸出: 以粗體顯示的區(qū)塊編號(hào). 當(dāng)區(qū)塊鏈完全同步時(shí), 區(qū)塊編號(hào)將接近此頁面上的區(qū)塊編號(hào): 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

由于系統(tǒng)設(shè)置不同, 在你的電腦上可能需要<code>sudo</code>命令.

0x3 設(shè)置投票合約

首先設(shè)置truffle項(xiàng)目:

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

truffle會(huì)創(chuàng)建運(yùn)行dapp所需的必要文件和目錄. Truffle還創(chuàng)建了一個(gè)示例應(yīng)用程序來幫助您入門(我們不會(huì)在本教程中使用). 因此可以隨意刪除contracts目錄中的ConvertLib.sol和MetaCoin.sol文件。

理解migrations文件夾中的內(nèi)容非常重要. 這些migration文件用于將合約部署到區(qū)塊鏈. (如果你還記得, 在上一篇文章中,我們使用VotingContract.new將合約部署到區(qū)塊鏈). 第一個(gè) 1_initial_migration.js文件將名為Migrations的合約部署到區(qū)塊鏈, 用于存儲(chǔ)已部署的最新合同. 每次運(yùn)行migration時(shí), truffle都會(huì)查詢區(qū)塊鏈中已部署的最后一個(gè)合約, 然后部署尚未部署的合約. 然后, 更新Migrations合約中的last_completed_migration字段, 以指示已部署的最新合同. 你可以將其視為名為Migration的數(shù)據(jù)庫表, 其中包含名為last_completed_migration的列, 該列始終保持最新. 您可以在truffle文檔頁面上查看更多詳細(xì)內(nèi)容.

現(xiàn)在我們稍稍修改一下上一個(gè)教程中寫的代碼, 下面會(huì)列出一些修改的注釋.

首先,將Voting.sol從上一個(gè)教程復(fù)制到contract目錄(此文件沒有修改).

pragma solidity ^0.4.18;
// 指定代碼編譯器版本

contract Voting {
  /* 下面的Map等效于字典或散列。
  映射的key是候選人名稱(bytes32類型), 值用于存儲(chǔ)投票計(jì)數(shù)(無符號(hào)整數(shù))
  */
  
  mapping (bytes32 => uint8) public votesReceived;
  
  /* Solidity目前還不允許您在構(gòu)造函數(shù)中傳遞一個(gè)字符串?dāng)?shù)組.
  我們將使用bytes32數(shù)組來存儲(chǔ)候選人列表
  */
  
  bytes32[] public candidateList;

  /* 下面是僅會(huì)被調(diào)用一次的構(gòu)造方法.
  當(dāng)部署合約時(shí), 傳入一組等待投票的候選人名單
  */
  function Voting(bytes32[] candidateNames) public {
    candidateList = candidateNames;
  }

  // 此函數(shù)返回候選人到目前為止收到的總票數(shù)
  function totalVotesFor(bytes32 candidate) view public returns (uint8) {
    require(validCandidate(candidate));
    return votesReceived[candidate];
  }

  // 此函數(shù)會(huì)增加指定候選項(xiàng)的投票計(jì)數(shù)
  // 相當(dāng)于一次投票
  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

然后, 用下面的內(nèi)容替換 migrations 目錄下 2_deploy_contracts.js 的內(nèi)容.

var Voting = artifacts.require("./Voting.sol");
module.exports = function(deployer) {
  deployer.deploy(Voting, ['Rama', 'Nick', 'Jose'], {gas: 6700000});
};
/* 部署方法第一個(gè)參數(shù)是合約的路徑, 然后是構(gòu)造函數(shù)的參數(shù). 在我們的例子中, 只有一個(gè)參數(shù): 一系列候選人名單. 第三個(gè)參數(shù)是一個(gè)字典, 我們指定部署代碼所需的gas. Gas值取決于合同的大小.
*/

你可以將gas值設(shè)置為truffle.js中的全局變量. 繼續(xù)添加如下所示的gas選項(xiàng), 以便將來如果忘記在特定的migration文件中設(shè)置, 它將默認(rèn)使用全局值.

require('babel-register')
module.exports = {
  networks: {
    development: {
      host: 'localhost',
      port: 8545,
      network_id: '*',
      gas: 470000
    }
  }
}

用下面的內(nèi)容替換 app/javascripts/app.js 的內(nèi)容.

// 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'

/*
 * 當(dāng)你編譯和部署投票合約時(shí),
 * truffle 在構(gòu)建目錄下的一個(gè)json文件中存儲(chǔ)abi和部署地址.
 * 我們會(huì)使用這些信息來初始化投票類. 然后再創(chuàng)建一個(gè)投票合約的實(shí)例.
 * 與上一篇文章中的 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() 返回了一個(gè)合約的實(shí)例.
     * Truffle中的每一次調(diào)用都會(huì)返回一個(gè)promise, 
     * 因此我們在有交易調(diào)用的地方使用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 - 使用你的回退策略 (本地節(jié)點(diǎn) / 托管節(jié)點(diǎn) + 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 的內(nèi)容. 除了第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測試網(wǎng)上部署合約

在我們部署合約前, 我們需要一個(gè)賬戶和一些以太幣. 在我們使用ganache時(shí), 創(chuàng)建了10個(gè)測試帳戶并預(yù)裝了100個(gè)以太幣. 但是對于測試網(wǎng)和主網(wǎng), 我們必須創(chuàng)建帳戶并自己添加一些以太幣.

在終端中, 執(zhí)行以下操作:

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)
// 用一個(gè)強(qiáng)密碼替換 'verystrongpassword'.
// 新建的賬戶是默認(rèn)鎖定的, 要確認(rèn)在部署合約和與區(qū)塊鏈交互時(shí)你的賬戶已經(jīng)解鎖.

在上一篇文章中, 我們啟動(dòng)了一個(gè)node命令行并初始化web3對象. 當(dāng)我們使用truffle命令行時(shí), 這些都已經(jīng)默認(rèn)完成了, 我們得到了一個(gè)可以使用的web3對象. 我們現(xiàn)在有一個(gè)地址為「0x95a94979d86d9c32d1d2ab5ace2dcc8d1b446fa1」的帳戶(在你的Demo中,你會(huì)擁有不同的地址), 余額為0.

你可以在https://faucet.rinkeby.io/獲取一個(gè)Rinkeby上的測試以太幣. 再使用web3.eth.getBalance以確保你已經(jīng)有以太幣. 你也可以在rinkeby.etherscan.io上輸入你的地址來查看賬戶余額. 如果你在web3.eth.getBalance上獲取余額為0, 但是在rinkeby.etherscan.io上看到非零余額, 這代表本地同步尚未完成. 只需要等待本地區(qū)塊鏈同步完成即可.

現(xiàn)在你有一些以太幣, 就可以繼續(xù)編譯并將合約部署到區(qū)塊鏈. 如果運(yùn)行順利, 下面是你運(yùn)行命令和輸出的結(jié)果.

*不要忘記先解鎖賬戶

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 與投票合約交互

如果你已經(jīng)部署合約成功, 現(xiàn)在就可以通過truffle命令行來獲取投票數(shù)量了.

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

如果做到了這里, 就代表你已經(jīng)成功啦, 你的合約已經(jīng)生效并運(yùn)行正常! 現(xiàn)在啟動(dòng)服務(wù)吧.

npm run dev

你可以在localhost:8080看到投票頁面, 并能夠投票和查看所有候選人的投票數(shù). 由于我們正在處理真正的區(qū)塊鏈, 因此每次對區(qū)塊鏈的寫入(voteForCandidate)都需要幾秒鐘(礦工必須將您的交易包含在一個(gè)區(qū)塊中, 并將區(qū)塊包含在區(qū)塊鏈中).

網(wǎng)頁內(nèi)容

如果你看到了上面的圖片內(nèi)容, 代表你已經(jīng)在測試網(wǎng)上創(chuàng)建了一個(gè)完整的以太坊程序. 恭喜你!

現(xiàn)在你的所有交易都是公開的, 你可以在https://rinkeby.etherscan.io/來查看. 只要輸入你的賬號(hào)地址, 你就會(huì)看到你所有的交易.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容