前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一个真实区块链项目的演练:以太坊Voting Dapp

一个真实区块链项目的演练:以太坊Voting Dapp

作者头像
rectinajh
发布2018-05-17 16:37:53
1.3K0
发布2018-05-17 16:37:53
举报

投票作为一个区块链应用,是因为集体决策,尤其是投票机制, 是以太坊的 一个核心的价值主张。另一个原因在于,投票是很多复杂的去中心化应用的基础构件,所以我们选择了投票应用作为学习区块链应用开发的第一个项目。

一个典型的中心化应用的C/S架构:

一个典型web应用的服务端通常由Java,Ruby,Python等等语言实现。前端代码由 HTML/CSS/JavaScript 实现。 然后将整个应用托管在云端,比如 AWS、Google Cloud Platform,阿里云或者放在你租用的一个VPS主机上。

image

在这种架构中,总是存在一个(或一组)中心化的 web 服务器,所有的客户端都需要 与这一(组)服务器进行交互。当一个客户端向服务器发出请求时,服务器处理该请求,与数据库/缓存进行交互, 读/写/更新数据库,然后向客户端返回响应。

基于区块链的去中心化架构:

一个理想的去中心化环境中,每个想要跟DApp交互的人,都需要在他们的计算机或手机上面运行 一个的完整区块链节点,去中心化背后的核心思想,就是不依赖于中心化的服务器。

image

在每个以太坊全节点中,都保存有完整的区块链数据。以太坊不仅将交易数据保存在链上,编译后的合约代码同样也保存在链上。以太坊全节点中,每个节点中还包含一个虚拟机(EVM:Ethereum Virtual Machine)来执行合约代码。

太坊中每笔交易都存储在区块链上。当你部署合约时,一次部署就是一笔交易。当你为候选者投票时,一次投票 又是另一笔交易。

在以太坊的世界里,在数据库层面,区块链的作用就是存储交易数据。使用Solidity语言来编写业务逻辑/应用代码(也就是合约:Contract),然后将合约代码编译为以太坊字节码,并将字节码部署到区块链上。

为了确保网络中的所有节点都有着同一份数据拷贝,并且没有向数据库中写入任何无效数据,以太坊目前使用工作量证明(POW:Proof Of Work)算法来保证网络安全,即通过矿工挖矿(Mining)来达成共识(Consensus),将数据同步到所有节点。

image

网页通过(HTTP上的)远程过程调用(RPC:Remote Procedure Call)与区块链节点进行通信。 web3.js已经封装了以太坊规定的全部 RPC 调用,因此利用它就可以与区块链进行交互。由于获得一个同步的全节点相当耗时,并占用大量磁盘空间。使用ganache软件来模拟区块链节点,以便快速开发并测试应用。

接下来,我们将编写一个投票合约,然后编译合约并将其部署到区块链节点 ganache上。

投票合约的主要接口:

image

投票合约:Voting

包含以下内容:

  • 构造函数,用来初始化候选人名单。
  • 投票方法:Vote(),每次执行就将指定的候选人得票数加 1
  • 得票查询方法totalVotesFor(),执行后将返回指定候选人的得票数

有两点需要特别指出:

  • 合约状态是持久化到区块链上的,因此对合约状态的修改需要消耗以太币。
  • 只有在合约部署到区块链的时候,才会调用构造函数,并且只调用一次。
  • 与 web 世界里每次部署代码都会覆盖旧代码不同,在区块链上部署的合约是不可改变的,也就是说,如果你更新 合约并再次部署,旧的合约仍然会在区块链上存在,并且合约的状态数据也依然存在。新的部署将会创建合约的一 个新的实例。

编译器要求

代码语言:javascript
复制
pragma solidity ^0.4.18;

合约声明

代码语言:javascript
复制
contract Voting{}

contract,关键字用来声明一个合约。

字典类型:mapping

代码语言:javascript
复制
mapping (bytes32 => uint8) public votesReceived;

mapping可以类比于一个关联数组或者是字典,是一个键值对。例如, votesReceived状态的键是候选者的名字,类型为bytes32 —— 32个字节定长字符串。 votesReceived状态中每个键对应的值,是一个单字节无符号整数(uint8),用来存储该候选人的得票数:

image

代码语言:javascript
复制
bytes32[] public candidateList;

在JS中,使用votesReceived.keys,就可以获取所有的候选人姓名。但是在Solidity中,没有这样的方法,所以我们需要单独管理全部候选人的名称 —— candidateList数组。

代码语言:javascript
复制
function voteForCandidate(bytes32 candidate) public { 

 require(validCandidate(candidate));
 votesReceived[candidate] += 1;
}

在voteForCandidate()方法中,请注意 votesReceived[key] 有默认值 0,所以我们没有进行初始化, 而是直接加1。

在合约方法体内的require()语句类似于断言,只有条件为真时,合约才继续执行。validateCandidate() 方法只有在给定的候选人名称在部署合约时传入的候选人名单中时才返回真值,从而避免乱投票的行为:

代码语言:javascript
复制
function validCandidate(bytes32 candidate) view public returns (bool) { 
for(uint i = 0; i < candidateList.length; i++) { 
 if (candidateList[i] == candidate) { return true;
 }
 } return false;
}

方法声明符与修饰符

在Solidity中,可以为函数应用可视性声明符(visibility specifier),例如 public、private。 public意味着可以从合约外调用函数。如果一个方法仅限合约内部调用,可以把它声明为私有(private)。 点击这里 可以查看所有的可视性说明符。

在Solidity中,还可以为函数声明修饰符(modifier),例如view用来告诉编译器,这个函数是只读的,也就是说, 该函数的执行不会改变区块链的状态)。

首先,请确保ganache已经在第一个终端窗口中运行:~$ ganache-cli

开始编译:

然后,在另一个终端中进入repo/chapter1目录,启动node 控制台,然后初始化 web3 对象,并向本地区块 链节点(http://localhost:8545)查询获取所有的账户:

代码语言:javascript
复制
~$ cd ~/repo/chapter1
~/repo/chapter1$ node
> Web3 = require('web3')
> web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
> web3.eth.accounts

要编译合约,首先需要载入 Voting.sol文件的内容,然后使用编译器(solc)的compile()方法 对合约代码进行编译:

代码语言:javascript
复制
> code = fs.readFileSync('Voting.sol').toString()> solc = require('solc')> compiledCode = solc.compile(code)

成功编译合约后可以查看一下编译结果。直接在控制台输入:

代码语言:javascript
复制
> compiledCode
  • compiledCode.contracts[':Voting'].bytecode: 投票合约编译后的字节码,也是要部署到区块链上的代码。
  • compiledCode.contracts[':Voting'].interface: 投票合约的接口,被称为应用二进制接口(ABI:Application Binary Interface

开始部署:

传入合约的abi定义来创建合约对象VotingContract,然后利用该对象完成合约在链上的部署和初始化。

在node控制台执行以下命令:

代码语言:javascript
复制
> abiDefinition = JSON.parse(compiledCode.contracts[':Voting'].interface)
> VotingContract = web3.eth.contract(abiDefinition)
> byteCode = compiledCode.contracts[':Voting'].bytecode
> deployedContract = VotingContract.new(['Rama','Nick','Jose'],{data: byteCode, from: web3.eth.accounts[0], gas: 4700000})
> deployedContract.address'0x0396d2b97871144f75ba9a9c8ae12bf6c019f610' <- 你的部署地址可能和这个不一样
> contractInstance = VotingContract.at(deployedContract.address)

我们已经成功部署了投票合约,并且获得了一个合约实例(变量:contractInstance),现在可以用这个实例 与合约进行交互了。

在区块链上有上千个合约。那么,如何识别你的合约已经上链了呢?

答案是:使用deployedContract.address。 当你需要跟合约进行交互时,就需要这个部署地址和我们之前谈到的abi定义。 因此,请记住这个合约部署地址。

调用合约的totalVotesFor() 方法来查看某个候选人的得票数。例如,下面的代码 查看候选人Rama的得票数:

代码语言:javascript
复制
> contractInstance.totalVotesFor.call('Rama')

调用合约的voteForCandidate()方法投票给某个候选人。下面的代码给Rama投了三次票:

代码语言:javascript
复制
> contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]})

现在我们再次查看Rama的得票数:

代码语言:javascript
复制
> contractInstance.totalVotesFor.call('Rama').toLocaleString()

每执行一次投票,就会产生一次交易,因此voteForCandidate()方法将返回一个交易id,作为交易的凭据。交易id是交易发生的凭据,交易是不可篡改的,因此任何时候可以使用交易id引用或查看交易内容 都会得到同样的结果。

网页交互:让我们创建一个简单的html页面,以便用户可以使用浏览器而不是复杂的命令行来与投票合约交互。

image

页面的主要功能如下:

  • 列出所有的候选人及其得票数 用户在页面中可以输入候选人的名称,然后点击投票按钮,网页中的JS代码将调用投票合约的 voteForCandidate()
  • 和我们nodejs控制台里的流程一样。
  • 打开~/repo/chapter1/index.html来查看页面源代码。 为了聚焦核心业务逻辑,我们在网页中硬编码了候选人姓名。如果你喜欢的话,可以调整代码来动态生成候选人。
  • 页面文件中的JS代码都封装在了一个单独的JS文件中,可以在试验环境编辑器中打开 ~/repo/chapter1/index.js来查看其内容。
  • 为了将页面运行起来,需要根据你的私有试验环境对JS代码进行一下调整: 节点的RPC API地址:
  • 为了将页面运行起来,需要根据你的私有试验环境对JS代码进行一下调整: web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
  • HttpProvier()对象的构造函数参数是web3js库需要链接的以太坊节点RPC API的URL,要调整为 你的私有试验环境中ganache的访问端结点,格式为:
  • http://8545.<你的私有实验环境URL>/
  • 查看试验环境中的嵌入浏览器地址栏来获取你的私有实验环境URL:

image

  • 投票合约地址
  • 当一个合约部署到区块链上时,将获得一个地址,例如0x329f5c190380ebcf640a90d06eb1db2d68503a53。 由于每次部署都会获得一个不同的地址,因此你需要指定它:
  • contractInstance = VotingContract.at('0x329f5c190380ebcf640a90d06eb1db2d68503a53')
  • 如果你在部署合约的时候没有记录这个地址,就重新部署吧。
  • 运行web服务
  • 在第二个终端中输入以下命令来启动一个简单的Web服务器,以便我们可以在试验环境中的嵌入浏览器中访问页面:
  • ~$ cd ~/repo/chapter1 ~/repo/chapter1$ python -m SimpleHTTPServer
  • Python的SimpleHTTPServer模块将启动在8000端口的监听。
  • 现在,在试验环境的嵌入浏览器中点击刷新按钮。如果一切顺利的话,你应该可以看到投票应用的页面了。 当你在文本框中输入候选人姓名,例如Rama,然后点击按钮后,应该会看到候选人Rama的得票数加 1 。

整个项目地址:

代码语言:javascript
复制
https://github.com/maheshmurthy/ethereum_voting_dapp.git
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.03.19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档