开发以太坊去中心化投票应用程序指南

在整个加密货币市场的市值超过7000亿美元之后,加密货币市场在过去几个月太疯狂了,但这只是一个开始。随着区块链系统的不断发展和扩展,进入这一新领域并利用这项技术的一个好方法是使用去中心化应用程序,也称为dApps。

CryptoKitties以其使以太坊区块链拥挤而闻名,是dApp的一个很好的例子,它将可养殖和可收藏的概念与区块链相结合。这个耸人听闻的游戏只是一个创造性的例子,几乎有无限的机会。 虽然看似非常复杂,但已经开发出某些框架和工具来抽象你与区块链和智能合约的交互。在这篇博文中,我将通过一种方式在以太坊上创建一个去中心化的投票应用程序。我将简要介绍以太坊,但你可能应该对它有所了解,以便充分利用本指南。另外,我希望你知道Javascript。

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

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

  • 从本质上讲,区块链是一个共享的,可编程的,加密安全的,可信赖的分类账本,没有任何一个用户可以控制,任何人都可以查询。- Klaus Schwab

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

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

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

以太坊的核心组成部分

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

  • 智能合约充当后端逻辑和存储。合约是用Solidity一种智能合约语言编写的,是一个代码和数据的集合,驻留在以太坊区块链的特定地址。它与面向对象编程中的类非常相似,它包含函数和状态变量。智能合约以及区块链是所有权力下放应用程序的基础。像Blockchain一样,它们是不可变的和分布式的,这意味着如果它们已经在以太坊网络上,升级它们将是一种痛苦。幸运的是,这里有一些方法可以做到这一点。
  • 以太坊虚拟机(EVM)处理整个以太坊网络的内部状态和计算。将EVM视为这种大规模去中心化计算机,其中包含能够执行代码,更改数据和相互交互的addresses
  • Web3.js是一个Javascript API,允许你与区块链进行交互,包括进行交易和调用智能合约。此API抽象了与以太坊客户端的通信,允许开发人员专注于他们的应用程序的内容。你必须在浏览器中嵌入一个web3实例才能执行此操作。

我们将使用的其他工具

  • Truffle是以太坊的流行测试开发框架。它包括开发区块链,编译和迁移脚本,用于将合约部署到区块链,合约测试等。它使开发更容易!
  • Truffle Contracts是Web3 Javascript API之上的抽象,允许你轻松连接智能合约并与之互动。
  • Metamask将以太坊带入你的浏览器。它是一个浏览器扩展,提供链接到你的以太坊地址的安全web3实例,允许你使用去中心化应用程序。我们不会在本教程中使用Metamask,但它是人们在生产中与DApp交互的一种方式。相反,我们将在开发期间注入我们自己的web3实例。有关更多信息,请查看此链接

开始吧!

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

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

1.首先,让我们在全局范围内安装Truffle。

1
npm install -g truffle

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

1
2
3
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扩展名。与本指南的其余部分一样,我将在代码中提供注释解释它正在做什么,然后我将在指出某些警告和逻辑的同时解释大体思路。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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的文件,告诉它在迁移时包含你的投票智能合约。

1
2
3
4
5
var Voting = artifacts.require("Voting")

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

要开始开发以太坊区块链,请转到命令行并运行:

1
truffle develop

由于Solidity是一种编译语言,我们必须首先将其编译为字节码,以便EVM执行。

1
compile

你现在应该在目录中看到一个文件夹build/。此文件夹包含构建文件,这对Truffle的内部工作至关重要,因此请勿修改它们!

接下来,我们必须迁移合约。migrations是一个truffle脚本,可帮助你在开发时更改应用程序合约的状态。请记住,你的合约已部署到区块链上的某个地址,因此无论何时进行更改,你的合约都将位于不同的地址。 迁移可帮助你执行此操作,还可帮助你移动数据。

1
migrate

恭喜!你的智能合约现在永远在区块链上。好吧,还不是真的…… 因为truffle develop会在每次停止时刷新。

如果你想拥有一个持久的区块链,可以考虑一下由Truffle开发的Ganache。如果你使用的是Ganache,则无需调用truffle develop。相反,你将运行truffle compiletruffle migrate。要了解在没有Truffle的情况下部署合约需要什么,请查看此博客文章

一旦我们将智能合约部署到区块链,我们将不得不在应用程序启动时在浏览器上使用Javascript设置web3.0实例。因此,下一段代码将放在js/app.js的底部。请注意,我们使用的是web3.0版本0.20.1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!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的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// 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/上看到它!

1
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的开发。

汇智网原创翻译,转载请标明出处。这里是原文