在初步了解了以太坊基本概念之后,我们可以着手开始进行开发,此次我们将通过web3.js封装的以太坊接口来与区块链进行交互。应用整体结构如图所示:
获得一个全节点相当耗时,本次实战将使用ganache软件模拟区块链节点,以便快速开发并测试应用。
为了便于开发和测试,ganache默认会自动创建10个账户,每个账户有100个以太币。
上图所示,模拟节点的监听地址和端口为127.0.0.1:7545, 在使用web3.js时,我们将传入此地址告诉web3.js应当连接哪一个节点,接下来我们将使用第一个账号(0x627306090abaB3A6e1400e9345bC60c78a8BEf57)创建交易、发送/接受以太币。
投票合约设计
我们使用Solidity语言来编写合约,合约的属性用来保存合约的状态,合约中的方法为调用提供接口
投票合约包含以下内容:
* 构造函数Voting,用来初始化候选人名单
* 投票方法vote, 为指定的候选人投票数加1
*查询方法totalVotesFor(),查询指定候选人的得票数
有几点是区别以往
1. 合约的状态是持久化到区块链上,对合约状态的修改需要消耗费用
2. 只有部署合约到区块链上时,才会调用构造函数
3. 合约不可更改,如果想更新合约需要再次部署,但旧的合约仍会在区块链上,合约的状态也依然存在。
投票合约开发
合约代码保存在 voting.sol文件中
编译器要求
pragma solidity ^0.4.18
合约声明
contract Voting{}
contract 关键字用来声明一个合约
字典类型:mapping
mapping(bytes32 => uint8) public votesReceived;
votesReceived是一个键值对,其Key为候选者名字,类型为byte32字符串,值为每个候选者的得票数,类型为uint8
投票方法vote为指定的候选人投票。
function vote(byte32 candidate) public {
require(validCandidate(candidate));
votesReceived[candidate] += 1;
}
require 类似断言,只有条件为真时,合约才会继续执行。
validCandidate()方法判断指定的候选人是否是有效的候选人
function validCandidate(byte32 candidate) view public returns (bool) {
for(uint i = 0; i< candidateList.length; i++) {
if(candidateList[i] == candidate) {
return true;
}
}
}
完整代码如下:
pragma solidity ^0.4.18;
contract Voting {
mapping (bytes32 => uint8) public votesReceived;
bytes32[] public candidateList;
function Voting(bytes32[] candidateNames) public {
candidateList = candidateNames; }
function totalVotesFor(bytes32 candidate) view public returns (uint8) {
require(validCandidate(candidate));
return votesReceived[candidate];
}
function vote(bytes32 candidate) public {
require(validCandidate(candidate));
votesReceived[candidate] += 1;
}
function validCandidate(bytes32 candidate) view public returns (bool) {
for(uint i = 0; i < candidateList.length; i++) {
if (candidateList[i] == candidate) {
return true;
}
}
return false;
}
}
投票合约编译
启动node控制台,初始化web3对象,并向模拟的区块链节点(http://127.0.0.1:7545)查询所有账户
> Web3 = require('web3')
> web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
> web3.eth.accounts
[ '0x627306090abab3a6e1400e9345bc60c78a8bef57', '0xf17f52151ebef6c7334fad080c5704d77216b732', '0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef', '0x821aea9a577a9b44299b9c15c88cf3087f3b5544', '0x0d1d4e623d10f9fba5db95830f7d3839406c6af2', '0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e', '0x2191ef87e392377ec08e7c08eb105ef5448eced5', '0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5', '0x6330a553fc93768f612722bb8c2ec78ac90b3bbc', '0x5aeda56215b167893e80b4fe645ba6d5bab767de' ]
编译合约,首先载入voting.sol的内容,之后使用solc编译器对合约代码进行编译
code = fs.readFileSync('voting.sol')
solc = require('solc')
compiledCode = solc.compile(code)
其编译后的代码有两个重要字段
bytecode: 投票合约编译后的字节码,将要被部署到区块链上
interface: 投票合约接口
投票合约部署
在node控制台下执行以下命令
abi = JSON.parse(compiledCode.contracts[':Voting'].interface)
contract = web3.eth.contract(abi)
byteCode = compiledCode.contracts[':Voting'].bytecode
deployedContract = contract.new(['zhangsan','lisi','wama'],{ data: byteCode,from:web3.eth.accounts[0], gas:4700000})
> deployedContract.address
'0x8cdaf0cd259887258bc13a92c0a6da92698644c0'
contractInstance = contract.at(deployedContract.address)
contract对象的new()方法将投票合约部署到区块链上,至此我们已经成功部署了投票合约,并且获得一个实例contractInstance,现在可以通过该实例与合约进行交互
合约交互
通过totalVotesFor方法查看候选人的票数
contractInstance.totalVotesFor.call('zhangsan')
{ [String: '0'] s: 1, e: 0, c: [ 0 ] }
vote()投票给候选人,下面的代码给zhangsan 投了二次票
> contractInstance.vote('zhangsan',{from:web3.eth.accounts[0]})
'0x7fd48761ba8f1fb83239ad6c5da1eff16cbec5259f6c60d28c5464523ac17f6f'
>contractInstance.vote('zhangsan',{from:web3.eth.accounts[0]})
'0xcd479e1b7d492dc608d584178adce1181e0d251974f70a1cac3f36c00a9eb27b'
再次通过totalVotesFor查看zhangsan的得票数
> contractInstance.totalVotesFor.call('zhangsan').toString()
'2'
每进行一次投票就产生一次交易,voteForCandidate()将返回交易的凭证,任何时候都可以通过交易凭证查看交易
总结
当前为止我们已经接触到的相关知识点
1. nodejs 和 ganache 作为开发环境开发编译部署合约
2. 开发了简单的投票合约,并编译部署到了区块链节点
3. 使用nodejs控制台与合约进行交互
4. 所有数据保存到了区块链,不可修改
5. 可独立验证每个候选人获得了多少投票
后续将使用Truffle框架构建此投票应用。