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

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

一个典型的中心化应用的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 世界里每次部署代码都会覆盖旧代码不同,在区块链上部署的合约是不可改变的,也就是说,如果你更新 合约并再次部署,旧的合约仍然会在区块链上存在,并且合约的状态数据也依然存在。新的部署将会创建合约的一 个新的实例。

编译器要求

pragma solidity ^0.4.18;

合约声明

contract Voting{}

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

字典类型:mapping

mapping (bytes32 => uint8) public votesReceived;

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

image

bytes32[] public candidateList;

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

function voteForCandidate(bytes32 candidate) public { 

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

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

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

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)查询获取所有的账户:

~$ 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()方法 对合约代码进行编译:

> code = fs.readFileSync('Voting.sol').toString()> solc = require('solc')> compiledCode = solc.compile(code)

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

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

开始部署:

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

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

> 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的得票数:

> contractInstance.totalVotesFor.call('Rama')

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

> contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]})

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

> 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 。

整个项目地址:

https://github.com/maheshmurthy/ethereum_voting_dapp.git

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏申龙斌的程序人生

交易Transaction【区块链生存训练】

日常生活中,我们每天都会与他人进行各种交易,对于“交易”这个概念感觉再熟悉不过了。比如:今天我去吃凉皮,支付给商家5元钱,非常简单吧,通常的交易记录可以是这样的...

3277
来自专栏极客编程

java工程师用spring boot和web3j构建以太坊区块链应用

区块链最近IT世界的流行语之一。这项有关数字加密货币的技术,并与比特币一起构成了这个热门的流行趋势。它是去中心化的,不可变的分块数据结构,这是可以安全连接和使用...

2001
来自专栏蜉蝣禅修之道

以太坊DApp开发初探

关于“以太猫”的流行,相信不少人都有所耳闻,甚至入手养过几只。从游戏性来说,其本质就是一个简单的收集交换类游戏,然鹅,是区块链赋予了它魅力,让用户每一只猫永远不...

1.2K15
来自专栏极客编程

如何实现以太坊支付

在这篇文章中,我将实现一个简单但完整的以太坊支付通道。支付通道使用密码签名,以安全、即时、无交易费用重复地传送Ether。

3372
来自专栏区块链大本营

干货 | 以太坊工具集合,解决你的入门困难

2023
来自专栏区块链入门

【区块链实践案例】基于以太坊区块链的电子存证应用

传统的电子存证简单来说就是将源信息经过加密存储在一个具有公信力的独立第三方处,并绑定时间戳、创建人等信息用来证明在某个时间点存在这样的信息。举例来说,对于原创作...

3142
来自专栏醒者呆

【精解】EOS智能合约演练

EOS,智能合约,abi,wasm,cleos,eosiocpp,开发调试,钱包,账户,签名权限 热身 本文旨在针对EOS智能合约进行一个完整的实操演练,...

7676
来自专栏智能计算时代

Ethereum - 以太坊项目

以太坊项目进一步扩展了区块链网络的能力,从交易延伸为智能合约(Smart Contract)。 其官网首页为 ethereum.org。 简介 根据以太坊官方的...

3137
来自专栏区块链

从"什么是区块链"到一个小时内构建区块链

区块链是记录的数字账本, 以称作区块的块状数据排列. 这些区块随后通过称为哈希函数的加密验证相互链接.这些区块连在一起形成一个连续的链 - 区块链.

50110
来自专栏网络

虾说区块链-52-《精通比特币》笔记七

一直在说区块链是一系列技术结合后的新的技术架构,那么这里分别介绍下这些相关技术,也涉及到一些扩展开去的相关内容。 ? 区块链-《精通比特币》笔记七: 《精通比特...

2098

扫码关注云+社区

领取腾讯云代金券