以太坊怎么实现中心化投票DApp与智能合约

发布时间:2022-01-15 14:42:53 作者:iii
来源:亿速云 阅读:178

这篇文章主要介绍“以太坊怎么实现中心化投票DApp与智能合约”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“以太坊怎么实现中心化投票DApp与智能合约”文章能帮助大家解决问题。

为什么要开发去中心化投票应用?

从本质上讲,利用区块链技术的去中心化应用程序允许你在没有可信赖的第三方的情况下执行与今天相同的操作(如转移资金)。最好的dApp具有特定的真实世界的用例,以便利用区块链的独特特征。

即使投票应用对大家来说可能不是一个伟大的应用程序,但是我选择使用它作为本指南,这是因为区块链解决的主要问题:透明度,安全性,可访问性,可信任,是困扰当前民主选举的主要问题。

由于区块链是去中心化的交易(投票)的永久记录,因此每次投票都可以无可辩驳地追溯到它发生的时间和地点,而不会泄露选民的身份。此外,过去的投票也不能被改变,而现在也不能被黑客攻击,因为每个交易都是由网络中的每个节点验证的。任何外部或内部攻击者必须控制51%的节点才能改变记录。

即使攻击者能够在伪造用户输入真实身份证投票时也能实现这一点,但端到端投票系统可以让选民验证他们的投票是否在系统中正确输入,这使得系统极其安全。

以太坊的核心组成部分

我希望你读本指南的其余部分前,了解了区块链和以太坊。这里有一个很棒的指南,写了我想让你知道的核心组件的简要概述。

我们将使用的其他工具

开始吧!

为简单起见,我们实际上不会构建我之前描述的完整投票系统。为了便于说明,它只是一个单页应用程序,用户可以输入他们的ID并为候选人投票。还将有一个按钮,计算并显示每个候选人的投票数。

这样,我们将能够专注于在应用程序中创建智能合约并与之交互的过程。整个应用程序的源代码将在此存储库中,你需要安装Node.js和npm。

1.首先,让我们在全局范围内安装Truffle。
npm install -g truffle

要使用Truffle命令,必须在现有项目中运行它们。

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.目录结构

你的目录结构应包括以下内容:

1.写下你的智能合约

设置和介绍完,让我们开始写代码吧!首先,我们将编写我们的智能合约,这是用Solidity编写的(其他语言不那么受欢迎)。这可能看起来很不爽,但事实并非如此。

对于任何应用程序,你希望智能合约尽可能简单,甚至是非常简单。请记住,你必须为你所做的每笔计算/交易付费,而你的智能合约将永远存在于区块链中。所以,你真的希望它能够完美地运作——也就是说,它越复杂,就越容易犯错误。

我们的合约将包括:

这里没有列出更多类型,但有些类型稍微复杂一些。这五个包含了智能合约通常使用的大部分结构。这里将更深入地解释这些类型。

作为参考,这是智能合约的代码。请注意,此文件应该被称为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以便为候选人投票。

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>
          <h2 class="text-center">Ethereum Voting Dapp</h2>
          <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/aK3qNeh25eJKGWxsqtnX/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属性为某个候选人投票。

交易完成后,我们将解决退回的承诺并显示Voted已经完成投票的消息。

App.findNumOfVotes()

最后一个函数将找到每个候选人的投票数并显示它们。我们将通过候选人并调用两个智能合约函数,getCandidatetotalVotes。我们将解决这些承诺并为该特定候选人创建HTML元素。

现在,启动应用程序,你将在`http://localhost:8080/上看到它!

npm run dev

资源

我知道,这很多......当你慢慢开发这个应用程序并真正了解正在发生的事情时,你可能会暂时打开这篇文章。但那是在学习!请使用以太网,truffle以及我在下面提供的所有文档补充本指南。我试图点击本文中的许多关键点,但这只是一个简短的概述,这些资源将有很大帮助。

关于“以太坊怎么实现中心化投票DApp与智能合约”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注亿速云行业资讯频道,小编每天都会为大家更新不同的知识点。

推荐阅读:
  1. 如何用智能合约开发以太坊DApp应用程序
  2. 以太坊智能合约开发DApp的方法是什么

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

以太坊 dapp

上一篇:matplot代码配置化后如何修改Excel调整图表

下一篇:springboot整合quartz定时任务框架的方法是什么

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》