以太坊開發(fā)去中心化投票DApp和智能合約

在整個(gè)加密貨幣市場的市值超過7000億美元之后,加密貨幣市場在過去幾個(gè)月太瘋狂了,但這只是一個(gè)開始。隨著區(qū)塊鏈系統(tǒng)的不斷發(fā)展和擴(kuò)展,進(jìn)入這一新領(lǐng)域并利用這項(xiàng)技術(shù)的一個(gè)好方法是使用去中心化應(yīng)用程序,也稱為dApps。

CryptoKitties以其使以太坊區(qū)塊鏈擁擠而聞名,是dApp的一個(gè)很好的例子,它將可養(yǎng)殖和可收藏的概念與區(qū)塊鏈相結(jié)合。這個(gè)聳人聽聞的游戲只是一個(gè)創(chuàng)造性的例子,幾乎有無限的機(jī)會。
雖然看似非常復(fù)雜,但已經(jīng)開發(fā)出某些框架和工具來抽象你與區(qū)塊鏈和智能合約的交互。在這篇博文中,我將通過一種方式在以太坊上創(chuàng)建一個(gè)去中心化的投票應(yīng)用程序。我將簡要介紹以太坊,但你可能應(yīng)該對它有所了解,以便充分利用本指南。另外,我希望你知道Javascript。

為什么要開發(fā)去中心化投票應(yīng)用?

從本質(zhì)上講,利用區(qū)塊鏈技術(shù)的去中心化應(yīng)用程序允許你在沒有可信賴的第三方的情況下執(zhí)行與今天相同的操作(如轉(zhuǎn)移資金)。最好的dApp具有特定的真實(shí)世界的用例,以便利用區(qū)塊鏈的獨(dú)特特征。

  • 從本質(zhì)上講,區(qū)塊鏈?zhǔn)且粋€(gè)共享的,可編程的,加密安全的,可信賴的分類賬本,沒有任何一個(gè)用戶可以控制,任何人都可以查詢。- Klaus Schwab

即使投票應(yīng)用對大家來說可能不是一個(gè)偉大的應(yīng)用程序,但是我選擇使用它作為本指南,這是因?yàn)閰^(qū)塊鏈解決的主要問題:透明度,安全性,可訪問性,可信任,是困擾當(dāng)前民主選舉的主要問題。

由于區(qū)塊鏈?zhǔn)侨ブ行幕慕灰祝ㄍ镀保┑挠谰糜涗洠虼嗣看瓮镀倍伎梢詿o可辯駁地追溯到它發(fā)生的時(shí)間和地點(diǎn),而不會泄露選民的身份。此外,過去的投票也不能被改變,而現(xiàn)在也不能被黑客攻擊,因?yàn)槊總€(gè)交易都是由網(wǎng)絡(luò)中的每個(gè)節(jié)點(diǎn)驗(yàn)證的。任何外部或內(nèi)部攻擊者必須控制51%的節(jié)點(diǎn)才能改變記錄。

即使攻擊者能夠在偽造用戶輸入真實(shí)身份證投票時(shí)也能實(shí)現(xiàn)這一點(diǎn),但端到端投票系統(tǒng)可以讓選民驗(yàn)證他們的投票是否在系統(tǒng)中正確輸入,這使得系統(tǒng)極其安全。

以太坊的核心組成部分

我希望你讀本指南的其余部分前,了解了區(qū)塊鏈以太坊。這里有一個(gè)很棒的指南,寫了我想讓你知道的核心組件的簡要概述。

  • 智能合約充當(dāng)后端邏輯和存儲。合約是用Solidity一種智能合約語言編寫的,是一個(gè)代碼和數(shù)據(jù)的集合,駐留在以太坊區(qū)塊鏈的特定地址。它與面向?qū)ο缶幊讨械念惙浅O嗨疲瘮?shù)和狀態(tài)變量。智能合約以及區(qū)塊鏈?zhǔn)撬袡?quán)力下放應(yīng)用程序的基礎(chǔ)。像Blockchain一樣,它們是不可變的和分布式的,這意味著如果它們已經(jīng)在以太坊網(wǎng)絡(luò)上,升級它們將是一種痛苦。幸運(yùn)的是,這里有一些方法可以做到這一點(diǎn)。
  • 以太坊虛擬機(jī)(EVM)處理整個(gè)以太坊網(wǎng)絡(luò)的內(nèi)部狀態(tài)和計(jì)算。將EVM視為這種大規(guī)模去中心化計(jì)算機(jī),其中包含能夠執(zhí)行代碼,更改數(shù)據(jù)和相互交互的addresses
  • Web3.js是一個(gè)Javascript API,允許你與區(qū)塊鏈進(jìn)行交互,包括進(jìn)行交易和調(diào)用智能合約。此API抽象了與以太坊客戶端的通信,允許開發(fā)人員專注于他們的應(yīng)用程序的內(nèi)容。你必須在瀏覽器中嵌入一個(gè)web3實(shí)例才能執(zhí)行此操作。

我們將使用的其他工具

  • Truffle是以太坊的流行測試開發(fā)框架。它包括開發(fā)區(qū)塊鏈,編譯和遷移腳本,用于將合約部署到區(qū)塊鏈,合約測試等。它使開發(fā)更容易!
  • Truffle Contracts是Web3 Javascript API之上的抽象,允許你輕松連接智能合約并與之互動。
  • Metamask將以太坊帶入你的瀏覽器。它是一個(gè)瀏覽器擴(kuò)展,提供鏈接到你的以太坊地址的安全web3實(shí)例,允許你使用去中心化應(yīng)用程序。我們不會在本教程中使用Metamask,但它是人們在生產(chǎn)中與DApp交互的一種方式。相反,我們將在開發(fā)期間注入我們自己的web3實(shí)例。有關(guān)更多信息,請查看此鏈接

開始吧!

為簡單起見,我們實(shí)際上不會構(gòu)建我之前描述的完整投票系統(tǒng)。為了便于說明,它只是一個(gè)單頁應(yīng)用程序,用戶可以輸入他們的ID并為候選人投票。還將有一個(gè)按鈕,計(jì)算并顯示每個(gè)候選人的投票數(shù)。

這樣,我們將能夠?qū)W⒂谠趹?yīng)用程序中創(chuàng)建智能合約并與之交互的過程。整個(gè)應(yīng)用程序的源代碼將在此存儲庫中,你需要安裝Node.js和npm。

1.首先,讓我們在全局范圍內(nèi)安裝Truffle。

npm install -g truffle

要使用Truffle命令,必須在現(xiàn)有項(xiàng)目中運(yùn)行它們。

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

這個(gè)存儲庫只是一個(gè)Truffle Box的框架,它是可以在一個(gè)命令中獲得的樣板或示例應(yīng)用程序 - truffle unbox [box name]。但是,帶有webpack的Truffle box未使用最新版本進(jìn)行更新,并包含一個(gè)示例應(yīng)用程序。因此,我創(chuàng)建了這個(gè)repo

2.目錄結(jié)構(gòu)

你的目錄結(jié)構(gòu)應(yīng)包括以下內(nèi)容:

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

1.寫下你的智能合約

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

對于任何應(yīng)用程序,你希望智能合約盡可能簡單,甚至是非常簡單。請記住,你必須為你所做的每筆計(jì)算/交易付費(fèi),而你的智能合約將永遠(yuǎn)存在于區(qū)塊鏈中。所以,你真的希望它能夠完美地運(yùn)作——也就是說,它越復(fù)雜,就越容易犯錯誤。

我們的合約將包括:

  • 狀態(tài)變量:包含永久存儲在區(qū)塊鏈中的值的變量。我們將使用狀態(tài)變量來保存選民和候選人的名單和數(shù)量。
  • 函數(shù):函數(shù)是智能合約的可執(zhí)行文件。它們是我們要求與區(qū)塊鏈進(jìn)行交互的內(nèi)容,具有不同級別的內(nèi)部和外部可見性。請記住,無論何時(shí)你想要更改變量的值/狀態(tài),都必須進(jìn)行交易——這要耗費(fèi)以太幣。你也可以calls區(qū)塊鏈,這不會花費(fèi)任何以太,因?yàn)槟闼龅母膶⒈讳N毀(當(dāng)我們實(shí)際進(jìn)行transactionscall時(shí),在下面會有更多內(nèi)容)。
  • 事件:每當(dāng)調(diào)用事件時(shí),傳遞給事件的值都將記錄在交易日志中。這允許Javascript回調(diào)函數(shù)或已解析的promises查看你想要在交易之后傳回的特定值。這是因?yàn)槊看芜M(jìn)行交易時(shí),都會返回交易日志。我們將使用一個(gè)事件來記錄新創(chuàng)建的候選者的ID,我們將顯示該ID。
  • 結(jié)構(gòu)類型 - 這與C結(jié)構(gòu)非常相似。Structs允許你保存多個(gè)變量,并且對于具有多個(gè)屬性的事物非常棒。Candidates只會有他們的名字和黨派,但你絕對可以為他們添加更多屬性。
  • 映射 - 將它們視為hash映射或字典,它具有鍵值對。我們將使用兩個(gè)映射。

這里沒有列出更多類型,但有些類型稍微復(fù)雜一些。這五個(gè)包含了智能合約通常使用的大部分結(jié)構(gòu)。這里將更深入地解釋這些類型。

作為參考,這是智能合約的代碼。請注意,此文件應(yīng)該被稱為Voting.sol但我希望Github gist具有style,所以我給它一個(gè).js擴(kuò)展名。與本指南的其余部分一樣,我將在代碼中提供注釋解釋它正在做什么,然后我將在指出某些警告和邏輯的同時(shí)解釋大體思路。

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);
    }
}

基本上,我們有兩個(gè)Structs(包含多個(gè)變量的類型),用于描述選民和候選人。使用Structs,我們可以為它們分配多個(gè)屬性,例如電子郵件,地址等。

為了跟蹤選民和候選人,我們將它們放入單獨(dú)的映射中,它們是整數(shù)索引的。候選人或選民的索引/密鑰——讓我們稱之為ID——是函數(shù)訪問它們的唯一方式。

我們還會跟蹤選民和候選人的數(shù)量,這將有助于我們?yōu)樗麄兙幹扑饕4送猓灰浀?行中的事件,該事件將在添加時(shí)記錄候選人的ID。我們的界面將使用此事件,因?yàn)槲覀冃枰櫤蜻x人的ID以便為候選人投票。

  • 1.我知道,與我之前所說的關(guān)于使合約變得非常簡單的說法相反,我認(rèn)為這個(gè)合約與這個(gè)應(yīng)用實(shí)際上做的相比有點(diǎn)復(fù)雜。但是,我這樣做是為了讓你們更容易進(jìn)行編輯并在之后為此應(yīng)用程序添加功能(最后更多內(nèi)容)。如果你想制作一個(gè)更簡單的投票應(yīng)用程序,智能合約可以在不到15行代碼。
  • 2.請注意,狀態(tài)變量numCandidatesnumVoters未聲明為public。默認(rèn)情況下,這些變量具有internal可見性,這意味著它們只能由當(dāng)前合約或派生合約直接訪問(不用擔(dān)心,我們不會使用它)。
  • 3.我們使用32bytes用于字符串而不是使用string類型。我們的EVM具有32字節(jié)的字大小,因此它被optimized以處理32字節(jié)的塊中的數(shù)據(jù)。(當(dāng)數(shù)據(jù)不是32字節(jié)的塊時(shí),編譯器,例如Solidity,必須做更多的工作并生成更多的字節(jié)碼,這實(shí)際上會導(dǎo)致更高的天然氣成本。)
  • 4.當(dāng)用戶投票時(shí),會創(chuàng)建一個(gè)新的Voter結(jié)構(gòu)并將其添加到映射中。為了計(jì)算某個(gè)候選人的投票數(shù),你必須遍歷所有選民并計(jì)算投票數(shù)。候選人的行為相同。因此,這些映射將保留所有候選人和選民的歷史。

2.實(shí)例化web3和合約

完成我們的智能合約后,我們現(xiàn)在需要運(yùn)行我們的測試區(qū)塊鏈并將此合約部署到區(qū)塊鏈上。我們還需要一種方法來與它交互,這將通過web3.js完成。

在我們開始測試區(qū)塊鏈之前,我們必須在/contracts文件夾中創(chuàng)建一個(gè)名為2_deploy_contracts.js的文件,告訴它在遷移時(shí)包含你的投票智能合約。

var Voting = artifacts.require("Voting")

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

要開始開發(fā)以太坊區(qū)塊鏈,請轉(zhuǎn)到命令行并運(yùn)行:

truffle develop

由于Solidity是一種編譯語言,我們必須首先將其編譯為字節(jié)碼,以便EVM執(zhí)行。

compile

你現(xiàn)在應(yīng)該在目錄中看到一個(gè)文件夾build/。此文件夾包含構(gòu)建文件,這對Truffle的內(nèi)部工作至關(guān)重要,因此請勿修改它們!

接下來,我們必須遷移合約。migrations是一個(gè)truffle腳本,可幫助你在開發(fā)時(shí)更改應(yīng)用程序合約的狀態(tài)。請記住,你的合約已部署到區(qū)塊鏈上的某個(gè)地址,因此無論何時(shí)進(jìn)行更改,你的合約都將位于不同的地址。 遷移可幫助你執(zhí)行此操作,還可幫助你移動數(shù)據(jù)。

migrate

恭喜!你的智能合約現(xiàn)在永遠(yuǎn)在區(qū)塊鏈上。好吧,還不是真的...... 因?yàn)閠ruffle develop會在每次停止時(shí)刷新。

如果你想擁有一個(gè)持久的區(qū)塊鏈,可以考慮一下由Truffle開發(fā)的Ganache。如果你使用的是Ganache,則無需調(diào)用truffle develop。相反,你將運(yùn)行truffle compiletruffle migrate。要了解在沒有Truffle的情況下部署合約需要什么,請查看此博客文章

一旦我們將智能合約部署到區(qū)塊鏈,我們將不得不在應(yīng)用程序啟動時(shí)在瀏覽器上使用Javascript設(shè)置web3.0實(shí)例。因此,下一段代碼將放在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()
})

如果你不理解這段代碼,你真的不必太擔(dān)心。只要知道這將在應(yīng)用程序啟動時(shí)運(yùn)行,并將檢查瀏覽器中是否已存在web3實(shí)例(Metamask)。如果沒有,我們將創(chuàng)建一個(gè)與localhost:9545交互Truffle開發(fā)區(qū)塊鏈。

如果你正在使用Ganache,你必須將端口更改為7545.一旦創(chuàng)建了一個(gè)實(shí)例,我們將調(diào)用start函數(shù)。

3.添加功能

我們需要做的最后一件事是為應(yīng)用程序編寫接口。這涉及任何Web應(yīng)用程序的基本要素——HTML,CSS和Javascript(我們已經(jīng)編寫了一些用于創(chuàng)建web3實(shí)例的Javascript)。首先,讓我們創(chuàng)建我們的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"  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>

這是一個(gè)非常簡單的頁面,帶有用戶ID的輸入表單,以及用于投票和計(jì)票的按鈕。點(diǎn)擊這些按鈕后,他們將調(diào)用投票的特定功能,并找到候選人的投票數(shù)。

但是有三個(gè)重要的div元素,其中有id:candidate-boxmsgvote-box,它們分別包含每個(gè)候選者的復(fù)選框,一條消息和一個(gè)投票數(shù)。我們還導(dǎo)入了JQuery,Bootstrap和app.js

現(xiàn)在,我們只需要與合約互動并實(shí)施投票和計(jì)算每個(gè)候選人的投票數(shù)量的功能。JQuery將控制DOM,當(dāng)我們進(jìn)行交易或調(diào)用Blockchain時(shí),我們將使用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()
})

請注意,我在上一步中用于創(chuàng)建web3實(shí)例的代碼也在這里。首先,我們導(dǎo)入必要的庫和webpack內(nèi)容,包括web3和Truffle Contracts。我們將使用Truffle Contracts,它建立在web3之上,與Blockchain進(jìn)行交互。

要使用它,我們將獲取在編譯投票智能合約時(shí)自動構(gòu)建的構(gòu)建文件,并使用它們來創(chuàng)建Truffle Contracts。最后,我們在全局變量windows中設(shè)置函數(shù),用于啟動應(yīng)用程序,投票給候選人,以及查找投票數(shù)。

要實(shí)際與區(qū)塊鏈交互,我們必須使用deployed的功能創(chuàng)建松露合約的實(shí)例。反過來,這將返回一個(gè)承諾,該實(shí)例作為你將用于從智能合約調(diào)用函數(shù)的返回值。

有兩種方法可以與這些功能進(jìn)行交互:交易和調(diào)用。交易是一種寫操作,它將被廣播到整個(gè)網(wǎng)絡(luò)并由礦工處理(因此,成本為Ether)。如果要更改狀態(tài)變量,則必須執(zhí)行交易,因?yàn)樗鼘⒏膮^(qū)塊鏈的狀態(tài)。

call是一種讀操作,模擬交易但丟棄狀態(tài)變化。因此,它不會花費(fèi)以太。這非常適合調(diào)用getter函數(shù)(查看我們之前在智能合約中編寫的四個(gè)getter函數(shù))。

要使用Truffle Contracts進(jìn)行交易,你可以編寫instance.functionName(param1,param2),將instance作為deployed函數(shù)返回的實(shí)例(例如,檢查第36行)。此事務(wù)將返回一個(gè)以交易數(shù)據(jù)作為返回值的promise。因此,如果在智能合約函數(shù)中返回一個(gè)值,但是使用相同的函數(shù)執(zhí)行交易,則不會返回該值。

這就是為什么我們有一個(gè)事件會記錄你想要寫入要返回的交易數(shù)據(jù)的任何內(nèi)容。在第36-37行,我們進(jìn)行交易以添加一個(gè)候選人即Candidate。當(dāng)我們確定promise時(shí),我們在結(jié)果中有交易數(shù)據(jù)。

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

要真正了解正在發(fā)生的事情,請使用Chrome開發(fā)人員工具打印result并查看其result結(jié)構(gòu)。

要進(jìn)行調(diào)用,你將編寫instance.functionName.call(param1,param2)。但是,如果某個(gè)函數(shù)具有關(guān)鍵字view,那么Truffle Contracts將自動創(chuàng)建一個(gè)調(diào)用,因此你無需添加.call`。

這就是我們的getter函數(shù)具有view關(guān)鍵字的原因。與進(jìn)行交易不同,返回的調(diào)用promise將具有智能合約函數(shù)返回的任何返回值。

我現(xiàn)在將簡要解釋這三個(gè)函數(shù),但如果你構(gòu)建了從數(shù)據(jù)存儲中檢索/更改數(shù)據(jù)并相應(yīng)地操作DOM的應(yīng)用程序,那么這應(yīng)該非常熟悉。將Blockchain視為你的數(shù)據(jù)庫,將Truffle Contracts視為從數(shù)據(jù)庫獲取數(shù)據(jù)的API。

App.start()

創(chuàng)建web3實(shí)例后立即調(diào)用此函數(shù)。要使Truffle Contracts正常工作,我們必須將接口設(shè)置為創(chuàng)建的web3實(shí)例并設(shè)置默認(rèn)值(例如你正在使用的帳戶以及你要為交易支付的gas量)。

由于我們處于開發(fā)模式,我們可以使用任何數(shù)量的gas和任何帳戶。在生產(chǎn)過程中,我們將采用MetaMask提供的帳戶,并嘗試找出你可以使用的最少量的gas,因?yàn)樗鼘?shí)際上是真錢。

設(shè)置好所有內(nèi)容后,我們現(xiàn)在將顯示每個(gè)候選人的復(fù)選框,供用戶投票。為此,我們必須創(chuàng)建合約實(shí)例并獲取候選人的信息。如果沒有候選人,我們將創(chuàng)建他們。為了讓用戶投票給候選人,我們必須提供該特定候選人的ID。因此,我們使每個(gè)checkbox元素具有候選ID的id(HTML元素屬性)。另外,我們將把候選數(shù)量添加到全局變量numOfCandidates中,我們將在App.findNumOfVotes()中使用它。JQuery用于將每個(gè)復(fù)選框及其候選名稱附加到.candidate-box

App.vote()

此功能將根據(jù)單擊的復(fù)選框及其id屬性為某個(gè)候選人投票。

  • 1.我們將檢查用戶是否輸入了他們的userID,這是他們的身份。如果他們沒有,我們會顯示一條消息告訴他們需要這樣做。
  • 2.我們將檢查用戶是否正在為候選人投票,檢查是否至少有一個(gè)被點(diǎn)擊的復(fù)選框。如果沒有點(diǎn)擊任何復(fù)選框,我們也會顯示一條消息,告訴他們請投票給候選人。如果單擊其中一個(gè)復(fù)選框,我們將獲取該復(fù)選框的id屬性,該屬性也是鏈接候選人的ID,并使用該屬性為候選人投票。

交易完成后,我們將解決退回的承諾并顯示Voted已經(jīng)完成投票的消息。

App.findNumOfVotes()

最后一個(gè)函數(shù)將找到每個(gè)候選人的投票數(shù)并顯示它們。我們將通過候選人并調(diào)用兩個(gè)智能合約函數(shù),getCandidatetotalVotes。我們將解決這些承諾并為該特定候選人創(chuàng)建HTML元素。

現(xiàn)在,啟動應(yīng)用程序,你將在`http://localhost:8080/上看到它!

npm run dev

資源

我知道,這很多......當(dāng)你慢慢開發(fā)這個(gè)應(yīng)用程序并真正了解正在發(fā)生的事情時(shí),你可能會暫時(shí)打開這篇文章。但那是在學(xué)習(xí)!請使用以太網(wǎng),truffle以及我在下面提供的所有文檔補(bǔ)充本指南。我試圖點(diǎn)擊本文中的許多關(guān)鍵點(diǎn),但這只是一個(gè)簡短的概述,這些資源將有很大幫助。

總結(jié)

在以太坊上構(gòu)建應(yīng)用程序非常類似于調(diào)用后端服務(wù)的常規(guī)應(yīng)用程序。最難的部分是編寫一份強(qiáng)大而完整的智能合約。我希望本指南可以幫助你了解去中心化應(yīng)用程序和以太坊的核心知識,并幫助你啟動你對開發(fā)它們的興趣。

如果你想創(chuàng)建我們已經(jīng)建立的東西,這里有一些想法。我實(shí)際上已經(jīng)用這樣的方式編寫了智能合約,它可以很容易地實(shí)現(xiàn)我在本指南中提到的一切。

  • 顯示每個(gè)候選人的一方。當(dāng)我們運(yùn)行g(shù)etCandidate(id)時(shí),我們已經(jīng)獲得了候選人的聚會。
  • 檢查用戶輸入的ID是否唯一。
  • 詢問并存儲有關(guān)用戶的更多信息,例如他們的出生日期和家庭住址。
  • 添加選項(xiàng)以查看具有特定ID的人是否已投票。你將創(chuàng)建一個(gè)新表單以輸入ID,然后你可以在區(qū)塊鏈中搜索該特定用戶。
  • 寫一個(gè)新的智能合約功能,立即計(jì)算兩個(gè)候選人的選票。目前,我們必須為兩個(gè)候選人分別進(jìn)行兩次調(diào)用,要求合約循環(huán)遍歷所有用戶兩次。
  • 允許添加新候選人。這意味著添加一個(gè)新表單來添加候選人,但也會更改我們?nèi)绾卧谇岸孙@示和投票候選人。
  • 要求用戶擁有以太坊地址進(jìn)行投票。我不包括用戶地址的邏輯是因?yàn)椴幌Mx民讓以太坊參與這個(gè)投票過程。但是,許多DApps將要求用戶擁有以太坊地址。

此外,這里有一些提示,可以防止一些錯誤發(fā)生:

  • 當(dāng)發(fā)生奇怪的事情時(shí),請多檢查一下你的智能合約函數(shù)。我在一個(gè)bug上花了幾個(gè)小時(shí)才發(fā)現(xiàn)我在我的一個(gè)函數(shù)中返回了錯誤的值。
  • 連接到開發(fā)區(qū)塊鏈時(shí),請檢查你的URL和端口是否正確。記住:7545用于truffle開發(fā),9545用于Ganache。這些是默認(rèn)值,因此如果你無法連接到區(qū)塊鏈,你可能已經(jīng)更改了它們。
  • 我沒有仔細(xì)閱讀,因?yàn)檫@個(gè)指南已經(jīng)太久了,我可能會在這個(gè)問題上再發(fā)一篇文章 - 但是你應(yīng)該測試你的合約!它會有很大幫助。
  • 如果你不熟悉promises,請了解它們的工作原理以及如何使用它們。Truffle Contracts使用promises,而web3也將支持promises。如果你做錯了,他們可能會搞砸你正在檢索的大量數(shù)據(jù)。

歡呼致力于去中心化和安全的互聯(lián)網(wǎng) - Web 3.0!

**另外我們還提供一些加快學(xué)習(xí)過程和提供問答服務(wù)的以太坊教程如下: **

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

匯智網(wǎng)原創(chuàng)翻譯,轉(zhuǎn)載請標(biāo)明出處。這里是原文

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容