以太坊開發去中心化投票DApp與智能合約實例

在整個加密貨幣市場的市值超過7000億美圓以後,加密貨幣市場在過去幾個月太瘋狂了,但這只是一個開始。隨着區塊鏈系統的不斷髮展和擴展,進入這一新領域並利用這項技術的一個好方法是使用去中心化應用程序,也稱爲dApps。php

CryptoKitties以其使以太坊區塊鏈擁擠而聞名,是dApp的一個很好的例子,它將可養殖和可收藏的概念與區塊鏈相結合。這個聳人聽聞的遊戲只是一個創造性的例子,幾乎有無限的機會。 雖然看似很是複雜,但已經開發出某些框架和工具來抽象你與區塊鏈和智能合約的交互。在這篇博文中,我將經過一種方式在以太坊上建立一個去中心化的投票應用程序。我將簡要介紹以太坊,但你可能應該對它有所瞭解,以便充分利用本指南。另外,我但願你知道Javascript。css

爲何要開發去中心化投票應用?

從本質上講,利用區塊鏈技術的去中心化應用程序容許你在沒有可信賴的第三方的狀況下執行與今天相同的操做(如轉移資金)。最好的dApp具備特定的真實世界的用例,以便利用區塊鏈的獨特特徵。html

  • 從本質上講,區塊鏈是一個共享的,可編程的,加密安全的,可信賴的分類帳本,沒有任何一個用戶能夠控制,任何人均可以查詢。- Klaus Schwab

即便投票應用對你們來講可能不是一個偉大的應用程序,可是我選擇使用它做爲本指南,這是由於區塊鏈解決的主要問題:透明度,安全性,可訪問性,可信任,是困擾當前民主選舉的主要問題。前端

因爲區塊鏈是去中心化的交易(投票)的永久記錄,所以每次投票均可以無可辯駁地追溯到它發生的時間和地點,而不會泄露選民的身份。此外,過去的投票也不能被改變,而如今也不能被黑客攻擊,由於每一個交易都是由網絡中的每一個節點驗證的。任何外部或內部攻擊者必須控制51%的節點才能改變記錄。java

即便攻擊者可以在僞造用戶輸入真實身份證投票時也能實現這一點,但端到端投票系統可讓選民驗證他們的投票是否在系統中正確輸入,這使得系統極其安全。node

以太坊的核心組成部分

我但願你讀本指南的其他部分前,瞭解了區塊鏈以太坊。這裏有一個很棒的指南,寫了我想讓你知道的核心組件的簡要概述。python

  • 智能合約充當後端邏輯和存儲。合約是用Solidity一種智能合約語言編寫的,是一個代碼和數據的集合,駐留在以太坊區塊鏈的特定地址。它與面向對象編程中的類很是類似,它包含函數和狀態變量。智能合約以及區塊鏈是全部權力下放應用程序的基礎。像Blockchain同樣,它們是不可變的和分佈式的,這意味着若是它們已經在以太坊網絡上,升級它們將是一種痛苦。幸運的是,這裏有一些方法能夠作到這一點。
  • 以太坊虛擬機(EVM)處理整個以太坊網絡的內部狀態和計算。將EVM視爲這種大規模去中心化計算機,其中包含可以執行代碼,更改數據和相互交互的addresses
  • Web3.js是一個Javascript API,容許你與區塊鏈進行交互,包括進行交易和調用智能合約。此API抽象了與以太坊客戶端的通訊,容許開發人員專一於他們的應用程序的內容。你必須在瀏覽器中嵌入一個web3實例才能執行此操做。

咱們將使用的其餘工具

  • Truffle是以太坊的流行測試開發框架。它包括開發區塊鏈,編譯和遷移腳本,用於將合約部署到區塊鏈,合約測試等。它使開發更容易!
  • Truffle Contracts是Web3 Javascript API之上的抽象,容許你輕鬆鏈接智能合約並與之互動。
  • Metamask將以太坊帶入你的瀏覽器。它是一個瀏覽器擴展,提供連接到你的以太坊地址的安全web3實例,容許你使用去中心化應用程序。咱們不會在本教程中使用Metamask,但它是人們在生產中與DApp交互的一種方式。相反,咱們將在開發期間注入咱們本身的web3實例。有關更多信息,請查看此連接

開始吧!

爲簡單起見,咱們實際上不會構建我以前描述的完整投票系統。爲了便於說明,它只是一個單頁應用程序,用戶能夠輸入他們的ID併爲候選人投票。還將有一個按鈕,計算並顯示每一個候選人的投票數。jquery

這樣,咱們將可以專一於在應用程序中建立智能合約並與之交互的過程。整個應用程序的源代碼將在此存儲庫中,你須要安裝Node.js和npm。android

1.首先,讓咱們在全局範圍內安裝Truffle。

npm install -g truffle

要使用Truffle命令,必須在現有項目中運行它們。webpack

git clone https://github.com/tko22/truffle-webpack-boilerplate
cd truffle-webpack-boilerplate
npm install

這個存儲庫只是一個Truffle Box的框架,它是能夠在一個命令中得到的樣板或示例應用程序 - truffle unbox [box name]。可是,帶有webpack的Truffle box未使用最新版本進行更新,幷包含一個示例應用程序。所以,我建立了這個repo

2.目錄結構

你的目錄結構應包括如下內容:

  • contracts/:包括全部合約的文件夾。不要刪除Migrations.sol
  • migrations/:包含Migration files的文件夾,可幫助你將智能合約部署到區塊鏈中。
  • src/:保存應用程序的HTML/CSS和Javascript文件。
  • truffle.js:truffle配置文件。
  • build/:在編譯合約以前,你不會看到此文件夾。此文件夾包含構建文件,所以不要修改任何這些文件!構建文件描述了合約的功能和體系結構,並提供了有關如何與區塊鏈中的智能合約進行交互的Truffle Contracts和web3信息。

1.寫下你的智能合約

設置和介紹完,讓咱們開始寫代碼吧!首先,咱們將編寫咱們的智能合約,這是用Solidity編寫的(其餘語言不那麼受歡迎)。這可能看起來很不爽,但事實並不是如此。

對於任何應用程序,你但願智能合約儘量簡單,甚至是很是簡單。請記住,你必須爲你所作的每筆計算/交易付費,而你的智能合約將永遠存在於區塊鏈中。因此,你真的但願它可以完美地運做——也就是說,它越複雜,就越容易犯錯誤。

咱們的合約將包括:

  • 狀態變量:包含永久存儲在區塊鏈中的值的變量。咱們將使用狀態變量來保存選民和候選人的名單和數量。
  • 函數:函數是智能合約的可執行文件。它們是咱們要求與區塊鏈進行交互的內容,具備不一樣級別的內部和外部可見性。請記住,不管什麼時候你想要更改變量的值/狀態,都必須進行交易——這要耗費以太幣。你也能夠calls區塊鏈,這不會花費任何以太,由於你所作的更改將被銷燬(當咱們實際進行transactionscall時,在下面會有更多內容)。
  • 事件:每當調用事件時,傳遞給事件的值都將記錄在交易日誌中。這容許Javascript回調函數或已解析的promises查看你想要在交易以後傳回的特定值。這是由於每次進行交易時,都會返回交易日誌。咱們將使用一個事件來記錄新建立的候選者的ID,咱們將顯示該ID。
  • 結構類型 - 這與C結構很是類似。Structs容許你保存多個變量,而且對於具備多個屬性的事物很是棒。Candidates只會有他們的名字和黨派,但你絕對能夠爲他們添加更多屬性。
  • 映射 - 將它們視爲hash映射或字典,它具備鍵值對。咱們將使用兩個映射。

這裏沒有列出更多類型,但有些類型稍微複雜一些。這五個包含了智能合約一般使用的大部分結構。這裏將更深刻地解釋這些類型。

做爲參考,這是智能合約的代碼。請注意,此文件應該被稱爲Voting.sol但我但願Github gist具備style,因此我給它一個.js擴展名。與本指南的其他部分同樣,我將在代碼中提供註釋解釋它正在作什麼,而後我將在指出某些警告和邏輯的同時解釋大致思路。

pragma solidity ^0.4.18;
// written for Solidity version 0.4.18 and above that doesnt break functionality

contract Voting {
    // an event that is called whenever a Candidate is added so the frontend could
    // appropriately display the candidate with the right element id (it is used
    // to vote for the candidate, since it is one of arguments for the function "vote")
    event AddedCandidate(uint candidateID);

    // describes a Voter, which has an id and the ID of the candidate they voted for
    struct Voter {
        bytes32 uid; // bytes32 type are basically strings
        uint candidateIDVote;
    }
    // describes a Candidate
    struct Candidate {
        bytes32 name;
        bytes32 party; 
        // "bool doesExist" is to check if this Struct exists
        // This is so we can keep track of the candidates 
        bool doesExist; 
    }

    // These state variables are used keep track of the number of Candidates/Voters 
    // and used to as a way to index them     
    uint numCandidates; // declares a state variable - number Of Candidates
    uint numVoters;

    
    // Think of these as a hash table, with the key as a uint and value of 
    // the struct Candidate/Voter. These mappings will be used in the majority
    // of our transactions/calls
    // These mappings will hold all the candidates and Voters respectively
    mapping (uint => Candidate) candidates;
    mapping (uint => Voter) voters;
    
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     *  These functions perform transactions, editing the mappings *
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    function addCandidate(bytes32 name, bytes32 party) public {
        // candidateID is the return variable
        uint candidateID = numCandidates++;
        // Create new Candidate Struct with name and saves it to storage.
        candidates[candidateID] = Candidate(name,party,true);
        AddedCandidate(candidateID);
    }

    function vote(bytes32 uid, uint candidateID) public {
        // checks if the struct exists for that candidate
        if (candidates[candidateID].doesExist == true) {
            uint voterID = numVoters++; //voterID is the return variable
            voters[voterID] = Voter(uid,candidateID);
        }
    }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * 
     *  Getter Functions, marked by the key word "view" *
     * * * * * * * * * * * * * * * * * * * * * * * * * */
    

    // finds the total amount of votes for a specific candidate by looping
    // through voters 
    function totalVotes(uint candidateID) view public returns (uint) {
        uint numOfVotes = 0; // we will return this
        for (uint i = 0; i < numVoters; i++) {
            // if the voter votes for this specific candidate, we increment the number
            if (voters[i].candidateIDVote == candidateID) {
                numOfVotes++;
            }
        }
        return numOfVotes; 
    }

    function getNumOfCandidates() public view returns(uint) {
        return numCandidates;
    }

    function getNumOfVoters() public view returns(uint) {
        return numVoters;
    }
    // returns candidate information, including its ID, name, and party
    function getCandidate(uint candidateID) public view returns (uint,bytes32, bytes32) {
        return (candidateID,candidates[candidateID].name,candidates[candidateID].party);
    }
}

基本上,咱們有兩個Structs(包含多個變量的類型),用於描述選民和候選人。使用Structs,咱們能夠爲它們分配多個屬性,例如電子郵件,地址等。

爲了跟蹤選民和候選人,咱們將它們放入單獨的映射中,它們是整數索引的。候選人或選民的索引/密鑰——讓咱們稱之爲ID——是函數訪問它們的惟一方式。

咱們還會跟蹤選民和候選人的數量,這將有助於咱們爲他們編制索引。此外,不要忘記第8行中的事件,該事件將在添加時記錄候選人的ID。咱們的界面將使用此事件,由於咱們須要跟蹤候選人的ID以便爲候選人投票。

  • 1.我知道,與我以前所說的關於使合約變得很是簡單的說法相反,我認爲這個合約與這個應用實際上作的相比有點複雜。可是,我這樣作是爲了讓大家更容易進行編輯並在以後爲此應用程序添加功能(最後更多內容)。若是你想製做一個更簡單的投票應用程序,智能合約能夠在不到15行代碼。
  • 2.請注意,狀態變量numCandidatesnumVoters未聲明爲public。默認狀況下,這些變量具備internal可見性,這意味着它們只能由當前合約或派生合約直接訪問(不用擔憂,咱們不會使用它)。
  • 3.咱們使用32bytes用於字符串而不是使用string類型。咱們的EVM具備32字節的字大小,所以它被optimized以處理32字節的塊中的數據。(當數據不是32字節的塊時,編譯器,例如Solidity,必須作更多的工做並生成更多的字節碼,這實際上會致使更高的自然氣成本。)
  • 4.當用戶投票時,會建立一個新的Voter結構並將其添加到映射中。爲了計算某個候選人的投票數,你必須遍歷全部選民並計算投票數。候選人的行爲相同。所以,這些映射將保留全部候選人和選民的歷史。

2.實例化web3和合約

完成咱們的智能合約後,咱們如今須要運行咱們的測試區塊鏈並將此合約部署到區塊鏈上。咱們還須要一種方法來與它交互,這將經過web3.js完成。

在咱們開始測試區塊鏈以前,咱們必須在/contracts文件夾中建立一個名爲2_deploy_contracts.js的文件,告訴它在遷移時包含你的投票智能合約。

var Voting = artifacts.require("Voting")

module.exports = function(deployer) {
  deployer.deploy(Voting)
}

要開始開發以太坊區塊鏈,請轉到命令行並運行:

truffle develop

因爲Solidity是一種編譯語言,咱們必須首先將其編譯爲字節碼,以便EVM執行。

compile

你如今應該在目錄中看到一個文件夾build/。此文件夾包含構建文件,這對Truffle的內部工做相當重要,所以請勿修改它們!

接下來,咱們必須遷移合約。migrations是一個truffle腳本,可幫助你在開發時更改應用程序合約的狀態。請記住,你的合約已部署到區塊鏈上的某個地址,所以不管什麼時候進行更改,你的合約都將位於不一樣的地址。 遷移可幫助你執行此操做,還可幫助你移動數據。

migrate

恭喜!你的智能合約如今永遠在區塊鏈上。好吧,還不是真的...... 由於truffle develop會在每次中止時刷新。

若是你想擁有一個持久的區塊鏈,能夠考慮一下由Truffle開發的Ganache。若是你使用的是Ganache,則無需調用truffle develop。相反,你將運行truffle compiletruffle migrate。要了解在沒有Truffle的狀況下部署合約須要什麼,請查看此博客文章

一旦咱們將智能合約部署到區塊鏈,咱們將不得不在應用程序啓動時在瀏覽器上使用Javascript設置web3.0實例。所以,下一段代碼將放在js/app.js的底部。請注意,咱們使用的是web3.0版本0.20.1。

// When the page loads, we create a web3 instance and set a provider. We then set up the app
window.addEventListener("load", function() {
  // Is there an injected web3 instance?
  if (typeof web3 !== "undefined") {
    console.warn("Using web3 detected from external source like Metamask")
    // If there is a web3 instance(in Mist/Metamask), then we use its provider to create our web3object
    window.web3 = new Web3(web3.currentProvider)
  } else {
    console.warn("No web3 detected. Falling back to http://localhost:9545. 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:9545"))
  }
  // initializing the App
  window.App.start()
})

若是你不理解這段代碼,你真的沒必要太擔憂。只要知道這將在應用程序啓動時運行,並將檢查瀏覽器中是否已存在web3實例(Metamask)。若是沒有,咱們將建立一個與localhost:9545交互Truffle開發區塊鏈。

若是你正在使用Ganache,你必須將端口更改成7545.一旦建立了一個實例,咱們將調用start函數。

3.添加功能

咱們須要作的最後一件事是爲應用程序編寫接口。這涉及任何Web應用程序的基本要素——HTML,CSS和Javascript(咱們已經編寫了一些用於建立web3實例的Javascript)。首先,讓咱們建立咱們的HTML文件。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <title>Ethereum Voting Dapp</title>

    <!-- Bootstrap -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css" integrity="sha384-Zug+QiDoJOrZ5t4lssLdxGhVrurbmBWopoEl+M6BdEfwnCJZtKxi1KgxUyJq13dy" crossorigin="anonymous">
    
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div>
          <h1 class="text-center">Ethereum Voting Dapp</h1>
          <hr/>
          <br/>
        </div>
      </div>
      <div class="row">
        <div class="col-md-4">
          <p>Add ID and click candidate to vote</p>
          <div class="input-group mb-3">
            <input type="text" class="form-control" id="id-input" placeholder="Enter ID">
          </div>
          <div class="candidate-box"></div>
          <button class="btn btn-primary" onclick="App.vote()">Vote</button>
          <div class="msg"></div>
        </div>
        <div class="col-md-6">
            <button class="btn btn-primary" onclick="App.findNumOfVotes()">Count Votes</button>
            <div id="vote-box"></div>
        </div>
      </div>
    </div>

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/js/bootstrap.min.js" integrity="sha384-a5N7Y/aK3qNeh15eJKGWxsqtnX/wWdSZSKp+81YjTmS15nvnvxKHuzaWwXHDli+4" crossorigin="anonymous"></script>

    <!-- Custom Scripts -->
    <script src="app.js"></script>
  </body>
</html>

這是一個很是簡單的頁面,帶有用戶ID的輸入表單,以及用於投票和計票的按鈕。點擊這些按鈕後,他們將調用投票的特定功能,並找到候選人的投票數。

可是有三個重要的div元素,其中有id:candidate-boxmsgvote-box,它們分別包含每一個候選者的複選框,一條消息和一個投票數。咱們還導入了JQuery,Bootstrap和app.js

如今,咱們只須要與合約互動並實施投票和計算每一個候選人的投票數量的功能。JQuery將控制DOM,當咱們進行交易或調用Blockchain時,咱們將使用Promises。如下是app.js的代碼。

// import CSS. Webpack with deal with it
import "../css/style.css"

// Import libraries we need.
import { default as Web3} from "web3"
import { default as contract } from "truffle-contract"

// get build artifacts from compiled smart contract and create the truffle contract
import votingArtifacts from "../../build/contracts/Voting.json"
var VotingContract = contract(votingArtifacts)

/*
 * This holds all the functions for the app
 */
window.App = {
  // called when web3 is set up
  start: function() { 
    // setting up contract providers and transaction defaults for ALL contract instances
    VotingContract.setProvider(window.web3.currentProvider)
    VotingContract.defaults({from: window.web3.eth.accounts[0],gas:6721975})

    // creates an VotingContract instance that represents default address managed by VotingContract
    VotingContract.deployed().then(function(instance){

      // calls getNumOfCandidates() function in Smart Contract, 
      // this is not a transaction though, since the function is marked with "view" and
      // truffle contract automatically knows this
      instance.getNumOfCandidates().then(function(numOfCandidates){

        // adds candidates to Contract if there aren't any
        if (numOfCandidates == 0){
          // calls addCandidate() function in Smart Contract and adds candidate with name "Candidate1"
          // the return value "result" is just the transaction, which holds the logs,
          // which is an array of trigger events (1 item in this case - "addedCandidate" event)
          // We use this to get the candidateID
          instance.addCandidate("Candidate1","Democratic").then(function(result){ 
            $("#candidate-box").append(`<div class='form-check'><input class='form-check-input' type='checkbox' value='' id=${result.logs[0].args.candidateID}><label class='form-check-label' for=0>Candidate1</label></div>`)
          })
          instance.addCandidate("Candidate2","Republican").then(function(result){
            $("#candidate-box").append(`<div class='form-check'><input class='form-check-input' type='checkbox' value='' id=${result.logs[0].args.candidateID}><label class='form-check-label' for=1>Candidate1</label></div>`)
          })
          // the global variable will take the value of this variable
          numOfCandidates = 2 
        }
        else { // if candidates were already added to the contract we loop through them and display them
          for (var i = 0; i < numOfCandidates; i++ ){
            // gets candidates and displays them
            instance.getCandidate(i).then(function(data){
              $("#candidate-box").append(`<div class="form-check"><input class="form-check-input" type="checkbox" value="" id=${data[0]}><label class="form-check-label" for=${data[0]}>${window.web3.toAscii(data[1])}</label></div>`)
            })
          }
        }
        // sets global variable for number of Candidates
        // displaying and counting the number of Votes depends on this
        window.numOfCandidates = numOfCandidates 
      })
    }).catch(function(err){ 
      console.error("ERROR! " + err.message)
    })
  },

  // Function that is called when user clicks the "vote" button
  vote: function() {
    var uid = $("#id-input").val() //getting user inputted id

    // Application Logic 
    if (uid == ""){
      $("#msg").html("<p>Please enter id.</p>")
      return
    }
    // Checks whether a candidate is chosen or not.
    // if it is, we get the Candidate's ID, which we will use
    // when we call the vote function in Smart Contracts
    if ($("#candidate-box :checkbox:checked").length > 0){ 
      // just takes the first checked box and gets its id
      var candidateID = $("#candidate-box :checkbox:checked")[0].id
    } 
    else {
      // print message if user didn't vote for candidate
      $("#msg").html("<p>Please vote for a candidate.</p>")
      return
    }
    // Actually voting for the Candidate using the Contract and displaying "Voted"
    VotingContract.deployed().then(function(instance){
      instance.vote(uid,parseInt(candidateID)).then(function(result){
        $("#msg").html("<p>Voted</p>")
      })
    }).catch(function(err){ 
      console.error("ERROR! " + err.message)
    })
  },

  // function called when the "Count Votes" button is clicked
  findNumOfVotes: function() {
    VotingContract.deployed().then(function(instance){
      // this is where we will add the candidate vote Info before replacing whatever is in #vote-box
      var box = $("<section></section>") 

      // loop through the number of candidates and display their votes
      for (var i = 0; i < window.numOfCandidates; i++){
        // calls two smart contract functions
        var candidatePromise = instance.getCandidate(i)
        var votesPromise = instance.totalVotes(i)

        // resolves Promises by adding them to the variable box
        Promise.all([candidatePromise,votesPromise]).then(function(data){
          box.append(`<p>${window.web3.toAscii(data[0][1])}: ${data[1]}</p>`)
        }).catch(function(err){ 
          console.error("ERROR! " + err.message)
        })
      }
      $("#vote-box").html(box) // displays the "box" and replaces everything that was in it before
    })
  }
}

// When the page loads, we create a web3 instance and set a provider. We then set up the app
window.addEventListener("load", function() {
  // Is there an injected web3 instance?
  if (typeof web3 !== "undefined") {
    console.warn("Using web3 detected from external source like Metamask")
    // If there is a web3 instance(in Mist/Metamask), then we use its provider to create our web3object
    window.web3 = new Web3(web3.currentProvider)
  } else {
    console.warn("No web3 detected. Falling back to http://localhost:9545. You should remove this fallback when you deploy live, as it's inherently insecure. Consider switching to Metamask for deployment. 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:9545"))
  }
  // initializing the App
  window.App.start()
})

請注意,我在上一步中用於建立web3實例的代碼也在這裏。首先,咱們導入必要的庫和webpack內容,包括web3和Truffle Contracts。咱們將使用Truffle Contracts,它創建在web3之上,與Blockchain進行交互。

要使用它,咱們將獲取在編譯投票智能合約時自動構建的構建文件,並使用它們來建立Truffle Contracts。最後,咱們在全局變量windows中設置函數,用於啓動應用程序,投票給候選人,以及查找投票數。

要實際與區塊鏈交互,咱們必須使用deployed的功能建立松露合約的實例。反過來,這將返回一個承諾,該實例做爲你將用於從智能合約調用函數的返回值。

有兩種方法能夠與這些功能進行交互:交易和調用。交易是一種寫操做,它將被廣播到整個網絡並由礦工處理(所以,成本爲Ether)。若是要更改狀態變量,則必須執行交易,由於它將更改區塊鏈的狀態。

call是一種讀操做,模擬交易但丟棄狀態變化。所以,它不會花費以太。這很是適合調用getter函數(查看咱們以前在智能合約中編寫的四個getter函數)。

要使用Truffle Contracts進行交易,你能夠編寫instance.functionName(param1,param2),將instance做爲deployed函數返回的實例(例如,檢查第36行)。此事務將返回一個以交易數據做爲返回值的promise。所以,若是在智能合約函數中返回一個值,可是使用相同的函數執行交易,則不會返回該值。

這就是爲何咱們有一個事件會記錄你想要寫入要返回的交易數據的任何內容。在第36-37行,咱們進行交易以添加一個候選人即Candidate。當咱們肯定promise時,咱們在結果中有交易數據。

要獲取咱們使用事件AddedCandidate()記錄的候選ID(檢查智能合約以查看它0),咱們必須檢查日誌並檢索它:result.logs[0].args.candidateID

要真正瞭解正在發生的事情,請使用Chrome開發人員工具打印result並查看其result結構。

要進行調用,你將編寫instance.functionName.call(param1,param2)。可是,若是某個函數具備關鍵字view,那麼Truffle Contracts將自動建立一個調用,所以你無需添加.call`。

這就是咱們的getter函數具備view關鍵字的緣由。與進行交易不一樣,返回的調用promise將具備智能合約函數返回的任何返回值。

我如今將簡要解釋這三個函數,但若是你構建了從數據存儲中檢索/更改數據並相應地操做DOM的應用程序,那麼這應該很是熟悉。將Blockchain視爲你的數據庫,將Truffle Contracts視爲從數據庫獲取數據的API。

App.start()

建立web3實例後當即調用此函數。要使Truffle Contracts正常工做,咱們必須將接口設置爲建立的web3實例並設置默認值(例如你正在使用的賬戶以及你要爲交易支付的gas量)。

因爲咱們處於開發模式,咱們可使用任何數量的gas和任何賬戶。在生產過程當中,咱們將採用MetaMask提供的賬戶,並嘗試找出你可使用的最少許的gas,由於它其實是真錢。

設置好全部內容後,咱們如今將顯示每一個候選人的複選框,供用戶投票。爲此,咱們必須建立合約實例並獲取候選人的信息。若是沒有候選人,咱們將建立他們。爲了讓用戶投票給候選人,咱們必須提供該特定候選人的ID。所以,咱們使每一個checkbox元素具備候選ID的id(HTML元素屬性)。另外,咱們將把候選數量添加到全局變量numOfCandidates中,咱們將在App.findNumOfVotes()中使用它。JQuery用於將每一個複選框及其候選名稱附加到.candidate-box

App.vote()

此功能將根據單擊的複選框及其id屬性爲某個候選人投票。

  • 1.咱們將檢查用戶是否輸入了他們的userID,這是他們的身份。若是他們沒有,咱們會顯示一條消息告訴他們須要這樣作。
  • 2.咱們將檢查用戶是否正在爲候選人投票,檢查是否至少有一個被點擊的複選框。若是沒有點擊任何複選框,咱們也會顯示一條消息,告訴他們請投票給候選人。若是單擊其中一個複選框,咱們將獲取該複選框的id屬性,該屬性也是連接候選人的ID,並使用該屬性爲候選人投票。

交易完成後,咱們將解決退回的承諾並顯示Voted已經完成投票的消息。

App.findNumOfVotes()

最後一個函數將找到每一個候選人的投票數並顯示它們。咱們將經過候選人並調用兩個智能合約函數,getCandidatetotalVotes。咱們將解決這些承諾併爲該特定候選人建立HTML元素。

如今,啓動應用程序,你將在`http://localhost:8080/上看到它!

npm run dev

資源

我知道,這不少......當你慢慢開發這個應用程序並真正瞭解正在發生的事情時,你可能會暫時打開這篇文章。但那是在學習!請使用以太網,truffle以及我在下面提供的全部文檔補充本指南。我試圖點擊本文中的許多關鍵點,但這只是一個簡短的概述,這些資源將有很大幫助。

總結

在以太坊上構建應用程序很是相似於調用後端服務的常規應用程序。最難的部分是編寫一份強大而完整的智能合約。我但願本指南能夠幫助你瞭解去中心化應用程序和以太坊的核心知識,並幫助你啓動你對開發它們的興趣。

若是你想建立咱們已經創建的東西,這裏有一些想法。我實際上已經用這樣的方式編寫了智能合約,它能夠很容易地實現我在本指南中提到的一切。

  • 顯示每一個候選人的一方。當咱們運行getCandidate(id)時,咱們已經得到了候選人的聚會。
  • 檢查用戶輸入的ID是否惟一。
  • 詢問並存儲有關用戶的更多信息,例如他們的出生日期和家庭住址。
  • 添加選項以查看具備特定ID的人是否已投票。你將建立一個新表單以輸入ID,而後你能夠在區塊鏈中搜索該特定用戶。
  • 寫一個新的智能合約功能,當即計算兩個候選人的選票。目前,咱們必須爲兩個候選人分別進行兩次調用,要求合約循環遍歷全部用戶兩次。
  • 容許添加新候選人。這意味着添加一個新表單來添加候選人,但也會更改咱們如何在前端顯示和投票候選人。
  • 要求用戶擁有以太坊地址進行投票。我不包括用戶地址的邏輯是由於不但願選民讓以太坊參與這個投票過程。可是,許多DApps將要求用戶擁有以太坊地址。

此外,這裏有一些提示,能夠防止一些錯誤發生:

  • 當發生奇怪的事情時,請多檢查一下你的智能合約函數。我在一個bug上花了幾個小時才發現我在個人一個函數中返回了錯誤的值。
  • 鏈接到開發區塊鏈時,請檢查你的URL和端口是否正確。記住:7545用於truffle開發,9545用於Ganache。這些是默認值,所以若是你沒法鏈接到區塊鏈,你可能已經更改了它們。
  • 我沒有仔細閱讀,由於這個指南已經過久了,我可能會在這個問題上再發一篇文章 - 可是你應該測試你的合約!它會有很大幫助。
  • 若是你不熟悉promises,請了解它們的工做原理以及如何使用它們。Truffle Contracts使用promises,而web3也將支持promises。若是你作錯了,他們可能會搞砸你正在檢索的大量數據。

歡呼致力於去中心化和安全的互聯網 - Web 3.0!

**另外咱們還提供一些加快學習過程和提供問答服務的以太坊教程以下: **

  • web3j教程,主要是針對java和android程序員進行區塊鏈以太坊開發的web3j詳解。 ,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
  • php以太坊,主要是介紹使用php進行智能合約開發交互,進行帳號建立、交易、轉帳、代幣開發以及過濾器和事件等內容。
  • 以太坊教程,主要介紹智能合約與dapp應用開發,適合入門。
  • 以太坊開發
  • python以太坊教程,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
  • C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括帳戶管理、狀態與交易、智能合約開發與交互、過濾器和事件等。
  • php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Php代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
  • EOS入門教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、帳戶與錢包、發行代幣、智能合約開發與部署、使用代碼與智能合約交互等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。

匯智網原創翻譯,轉載請標明出處。這裏是原文

相關文章
相關標籤/搜索