Dapp开发智能合约优秀文章

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

2018-03-19  本文已影响152人  rectinajh

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

一个典型的中心化应用的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

包含以下内容:

有两点需要特别指出:

编译器要求

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

开始部署:

传入合约的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

页面的主要功能如下:

整个项目地址:

https://github.com/maheshmurthy/ethereum_voting_dapp.git
上一篇下一篇

猜你喜欢

热点阅读