简单投票DAPP
接下来我们要开始动手真正的做一个DApp,尽管它是很简单的一个投票应用,但会包含完整的工作流程和交互页面。构建这个应用的主要步骤如下:
基础知识了解开发环境配置编写合约并在线测试部署合约网页交互
Dapp(Decentralized application,去中心化应用):运行在分布式网络上,参与者的信息被安全保护(也可能是匿名的),通过网络节点不同人,进行去中心化操作的应用。
Nodejs:基于 Chrome V8 引擎的 JavaScript 运行环境,通过JavaScript语言开发web服务端的东西。有非阻塞,事件驱动I/O等特性。传统的服务器(比如Apache)每次一个新用户连到你的网站上,你的服务器就得开一个连接,非常占资源。而nodejs就可以解决这个问题,非常适合分布式设备上运行数据密集型应用。
ganache-cli:模拟区块链,相对geth更加方便的测试平台。具有一键创建一个区块链,快速打包确认交易,直接提供多个测试账户等特点。
Web3:以太坊区块链交互的javascript库,应用和节点进行通信的中间层。Web3.js有0.2x
Solc: 合约代码的一个编译器
新建一个simple_voting_dapp,并打开它
[小提示:如果你的ubuntu不能适应屏幕,视觉上不舒服,可以输入 sudo apt-get install -y open-vm*]
Nodejs安装
安装node.js sudo apt-get install nodejs
查看nodejs版本 node -v
下载npm sudo apt install npm
查看版本 npm -v
将nodejs更新到最新版本(不然只能下载web3 1.X版本)
Sudo npm install n -g
Sudo n stable
Ganache-cli安装 npm install ganache-cli
查看版本 npm list ganache-cli
Solc安装,智能合约的版本是什么就下载什么版本
Npm install solc@0.4.22
查看版本 npm list solc
Web3.js下载。需现下载git,不然又只能下载web3 的1.x版本
Sudo apt-get install git
Npm install web3@^0.20.0
好的,现在我们的环境就此配置完成!!!
1.Solidity合约
我们会写一个叫做Voting的合约,这个合约并不复杂,当然有很多地方可以改进,但是我们主要是熟悉开发的流程,如果同学们想要更加完善的合约代码,请自行努力吧。
这个合约有以下内容:
一个构造函数,用来初始化一些候选者。
一个用来投票的方法(对投票数加1)。voteForCandidate()
一个返回候选者所获得的总票数的方法。totalVotesFo()
一个用来检测是否为有效的投票地址:validCandidat()
简单投票DAPP
接下来我们要开始动手真正的做一个DApp,尽管它是很简单的一个投票应用,但会包含完整的工作流程和交互页面。构建这个应用的主要步骤如下:
基础知识了解开发环境配置编写合约并在线测试部署合约网页交互
Dapp(Decentralized application,去中心化应用):运行在分布式网络上,参与者的信息被安全保护(也可能是匿名的),通过网络节点不同人,进行去中心化操作的应用。
Nodejs:基于 Chrome V8 引擎的 JavaScript 运行环境,通过JavaScript语言开发web服务端的东西。有非阻塞,事件驱动I/O等特性。传统的服务器(比如Apache)每次一个新用户连到你的网站上,你的服务器就得开一个连接,非常占资源。而nodejs就可以解决这个问题,非常适合分布式设备上运行数据密集型应用。
ganache-cli:模拟区块链,相对geth更加方便的测试平台。具有一键创建一个区块链,快速打包确认交易,直接提供多个测试账户等特点。
Web3:以太坊区块链交互的javascript库,应用和节点进行通信的中间层。Web3.js有0.2x
Solc: 合约代码的一个编译器
新建一个simple_voting_dapp,并打开它
[小提示:如果你的ubuntu不能适应屏幕,视觉上不舒服,可以输入 sudo apt-get install -y open-vm*]
Nodejs安装
安装node.js sudo apt-get install nodejs
查看nodejs版本 node -v
下载npm sudo apt install npm
查看版本 npm -v
将nodejs更新到最新版本(不然只能下载web3 1.X版本)
Sudo npm install n -g
Sudo n stable
Ganache-cli安装 npm install ganache-cli
查看版本 npm list ganache-cli
Solc安装,智能合约的版本是什么就下载什么版本
Npm install solc@0.4.22
查看版本 npm list solc
Web3.js下载。需现下载git,不然又只能下载web3 的1.x版本
Sudo apt-get install git
Npm install web3@^0.20.0
好的,现在我们的环境就此配置完成!!!
1.Solidity合约
我们会写一个叫做Voting的合约,这个合约并不复杂,当然有很多地方可以改进,但是我们主要是熟悉开发的流程,如果同学们想要更加完善的合约代码,请自行努力吧。
这个合约有以下内容:
一个构造函数,用来初始化一些候选者。
一个用来投票的方法(对投票数加1)。voteForCandidate()
一个返回候选者所获得的总票数的方法。totalVotesFo()
一个用来检测是否为有效的投票地址:validCandidat()
1.pragma solidity ^0.4.22;
2.contract Voting {
3. mapping (bytes32 => uint8) public votesReceived;
4.bytes32[] public candidateList;
5.constructor(bytes32[] candidateNames) public {
6. candidateList = candidateNames; }
7.function totalVotesFor(bytes32 candidate) view public returns (uint8) {
8.require(validCandidate(candidate));
9. return votesReceived[candidate]; }
10.function voteForCandidate(bytes32 candidate) public {
11. require(validCandidate(candidate));
12.votesReceived[candidate] += 1; }
13.function validCandidate(bytes32 candidate) view public returns (bool) {
14. for(uint i = 0; i < candidateList.length; i++) {
15. if (candidateList[i] == candidate) {
16. return true; } }
17. return false;
}
}
1.指定代码将会哪个版本的编译器进行编译
3.mapping 相当于一个关联数组或者是字典,是一个键值对。mapping
votesReceived 的键是候选者的名字,类型为 bytes32
4.在solidity没有可直接获得候选者的key,只能单独管理一个候选者数组candidateNames
12.注意votesReceived[key]有一个默认值为0所有我们不需要初始化
2.接下来我们用solc的集成开发工具Remix进行部署和测试,成功后再部署到私有链中
Remix网址:http://remix.ethereum.org/
进入旧版本(旧版本比较直观)
进入执行阶段,构造函数deploy时输入构造函数的参数:
["0xd4967590eb024589dfb6b9e48a576eb49ebc19d764b0d1d67dc21975e7258e97",
"0x0000000000000000000000000000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000002",
"0x0000000000000000000000000000000000000000000000000000000000000003",
"0x065e0be95fb43db528a20ba65c0e575e33cd4a9e1ca089dba4efff24596e8553"]
点击deploy初始化后就会生成相应的候选者和投票的函数
从现在开始我们的代码就正式的调试成功完成了。下面我们就开始在终端进行编译和部署到区块链中吧。
在进行部署之前我们需先在一个终端窗口打开ganache-cli,确保模拟区块链的存在
node_modules/.bin/ganache-cli
再打开一个终端进行部署
首先,在终端中运行 node 进入 node 控制台
node
看引入web3是否成功
初始化 web3 对象
var Web3=require('web3')
var web3=new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
看是否连接区块链
web3.isConnected()
向区块链查询获取所有的账户
web3.eth.accounts
将我们写好的合约代码放入我们最最开始所创建的simple_voting_dapp里,进行编译
从 Voting.sol 中加载代码并绑定到一个 string 类型的变量
var solc=require('solc')
var sourceCode =fs.readFileSync('vot123.sol').toString()
成功地编译好合约给 compiledCode 对象
var compiledCode=solc.compile(sourceCode)
查看compiledCode你会发现俩个重要的字段abi和bytecode
compiledCode.contracts[':Voting'].bytecode
这就是 Voting.sol 编译好后的字节码。也是要部署到区块链上的代码。
compiledCode.contracts[':Voting'].interface
这是一个合约的接口或者说模板(叫做 abi 定义),它告诉了用户在这个合约里有哪些方法。
现在将合约部署到区块链上。为此,你必须先通过传入 abi 定义来创建一个合约对象 VotingContract。然后用这个对象在链上部署并初始化合约。
将abi转换为json类型,因为web3.eth.contract()方法里是接受json类型
var abi=compiledCode.contracts[':Voting'].interface
var abi=JSON.parse(compiledCode.contracts[':Voting'].interface);
编译好的字节码,部署在区块链上的代码
var byteCode=compiledCode.contracts[':Voting'].bytecode
创建合约对象
var VotingContract= web3.eth.contract(abi)
VotingContract.new 将合约部署到区块链
var contractInstance=VotingContract.new(['小刘','老贺','老彭'],{data: byteCode, from: web3.eth.accounts[0], gas: 4700000})
我们已经部署了合约,并有了一个合约实例(变量 contractInstance),我们可以用这个实例与合约进行交互。
在区块链上有上千个合约。那么,如何识别你的合约已经上链了呢?答案是找到已部署合约的地址:ContractInstance.address. 当你需要跟合约进行交互时,就需要这个部署地址和我们之前谈到的 abi 定义.
至此,大部分的工作都已完成,我们还需要做的事情就是创建一个简单的 html,里面有候选者姓名并调用投票命令(我们已经在Remix里试过)。你可以在右侧找到 html 代码和 js 代码。将它们放到 chapter1 目录,并在浏览器中打开 index.html。
!DOCTYPE html>
<html>
<head>
<title>投票</title>
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
</head>
<body class="container">
<h1 align="center">简单投票DAPP</h1>
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>候选者</th>
<th>票数</th>
</tr>
</thead>
<tbody>
<tr>
<td>小刘</td>
<td id="candidate-1"></td>
</tr>
<tr>
<td>老贺</td>
<td id="candidate-2"></td>
</tr>
<tr>
<td>老彭</td>
<td id="candidate-3"></td>
</tr>
</tbody>
</table>
</div>
<input type="text" id="candidate" />
<a href="#" οnclick="voteForCandidate()" class="btn btn-primary">投票</a>
<div id="msg"></div>
</body>
<script src="https://cdn.jsdelivr.net/gh/ethereum/web3.js/dist/web3.min.js"></script>
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<script src="./index.js"></script>
</html>
1.<head>中用 link 形式引入 bootstrap 的 css 类型库,以下 container、table-responsive 等 class 均来自 bootstrap
2. <a>超链接形式的按键 btn,href=“#”为跳转至本页,即不跳转;onclick 指向js中方法
3. <script>引入了web3,js的在线文档。
Index.js
var web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8545'));
var abi = JSON.parse('[{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"validCandidate","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"voteForCandidate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]');
var contractAddr = '0x149a4feb23f9705f40ea7a23e2e0d48eb079da4b';
var contractInstance = new web3.eth.Contract(abi,contractAddr);
var candidates = {'小刘':'candidate-1','老贺':'candidate-2','老彭':'candidate-3'};
var account;
web3.eth.getAccounts().then(function(accounts){
account = accounts[0];
});
function voteForCandidate(){
let candidateName = $("#candidate").val();
let candidateNameHex = web3.utils.asciiToHex(candidateName);
contractInstance.methods.voteForCandidate(candidateNameHex).send({from:account}).then(function(receipt){
$("#msg").html("已投给: "+ candidateName + "<br>交易哈希: " + receipt.transactionHash + "<br>投票人地址: " + account);
contractInstance.methods.totalVotesFor(candidateNameHex).call(function(err,res){
$('#'+candidates[candidateName]).html(res.toString());
});
});
}
$(document).ready(function(){
var candidateList = Object.keys(candidates);
for (let i = 0; i < candidateList.length; i++){
let name = candidateList[i];
let nameHex = web3.utils.asciiToHex(name);
let count = contractInstance.methods.totalVotesFor(nameHex).call(function(err,res){
$("#"+candidates[name]).html(res.toString());
});
}
})
红色标注:
黄色标注:
为此我们的开发流程就全部完成了,让我们看一看界面,欣赏一下自己的成果吧!!
1.初始的候选者票数都为0
2.投票
再来让我们看看区块链都记录了哪些东西吧!!!