体验开发一个DApp的流程-简单投票DApp

开发准备

主要学习开发一个DApp的流程,合约的设计与逻辑不是重点,所以对于投票合约内容做了简化。

基本知识

DApp中的D指的是decentralization,DApp的意思就是去中心化的应用。

ganache是相对于geth更加方便的一个区块链测试平台,项目中使用ganache。

总体流程

  1. 我们首先安装一个叫做 ganache 的模拟区块链,能够让我们的程序在开发环境中运行。
  2. 写一个合约并部署到 ganache 上。
  3. 然后我们会通过命令行和网页与 ganache 进行交互。

环境要求

要求我们预先安装 nodejs 和 npm,再用npm安装 ganache-cli、web3和solc。

在项目文件夹下:

npm install web3@0.20.1 solc ganache-cli

安装后检查:

正式开始

启动ganache:

准备合约

合约代码如下:

pragma solidity >=0.4.22;

contract Voting{
    bytes32[] public candidateList; //候选人数组
    mapping(bytes32 => uint) public votesReceived;  
    constructor(bytes32[] candidateListName) public{
        candidateList = candidateListName;
    }
    
    //检测是否为有效的投票地址
    function validateCandidate(bytes32 candidateName) internal view returns(bool){
        for(uint i = 0; i < candidateList.length; i++){
            if (candidateName == candidateList[i])
                return true;
        }
        return false;
    }

    //投票函数
    function vote(bytes32 candidateName) public {
        require(validateCandidate(candidateName));
        votesReceived[candidateName] += 1;
    }

    //返回某个候选人的得票
    function totalVotesFor(bytes32 candidateName) public view returns(uint){
        require(validateCandidate(candidateName));
        return votesReceived[candidateName];
    }

}

将此sol文件放到项目目录下。

启动node控制台连接ganache测试环境:

ganache反馈:

编译合约

接下来引入solc编译:

var solc = require('solcjs')

读取sol源文件:

var sourceCode = fs.readFileSync('simple_ballot.sol')

开始编译:

var compiledCode = solc.compile(sourceCode)

注意:编译出来的不是字节码!,是一个很大的js对象,其中有各种合约的信息,我们需要的就是字节码,bytecode和abi

这两个就是我们部署合约所需要的。

获得abi和字节码

整体compile.js:

var solc = require('solc');
var fs = require('fs');

//合约源代码位置
var solPath = 'Voting.sol';
//合约名字 
var contractName = 'Voting';

//读取
var contractSource = fs.readFileSync(solPath, 'utf-8'); 

// 编译得到结果,在转为json
var contractObj = solc.compile(contractSource).contracts[':' + contractName];

//得到abi和字节码
var abi = JSON.parse(contractObj.interface);  
var bytecode = contractObj.bytecode;

部署合约

var VotingContract = web3.eth.contract(abi)
var deployTxObj = {data:bytecode, from:web3.eth.accounts[0], gas:3000000}
var contractInstance = VotingContract.new(['Alice', 'Bob', 'Cary'], deployTxObj)

注意:构造函数有参数。

部署后在ganache上可以收到消息:

这也是ganache的方便之处,它会自动挖矿把交易部署到区块链上,可以看做是一个虚拟的区块链。

调用合约

contractInstance.vote('Bob',{from:web3.eth.accounts[0]}, (err,res)=>console.log(res))

ganache中的显示:

contractInstance.totalVotesFor('Bob').toString(10)

view类型不需要发起交易:

前端页面

前端没啥好说的,直接放代码:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
	<title>投票DApp</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>简单区块链投票系统</h1>
	<div class="table-responsive">
		<table class="table table-bordered">
			<!-- 表头 -->
			<thead>
				<tr>
					<th>候选人</th>
					<th>得票数</th>
				</tr>
			</thead>
			<!-- 内容 -->
			<tbody>
				<tr>
					<td>Alice</td>
					<td id="candidate-1"></td>
				</tr>
				<tr>
					<td>Bob</td>
					<td id="candidate-2"></td>
				</tr>
				<tr>
					<td>Cary</td>
					<td id="candidate-3"></td>					
				</tr>
			</tbody>
		</table>

		
	</div>
	<input type="text" id="candidate" />
	<a href="#" onclick="voteForCandidate()" class="btn btn-primary">投票</a>
</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.1/jquery.min.js"></script>
<script src="./vote.js"></script>
</html>

需要指出的是:前端的投票人的显示也应该用ejs模板引擎刷出来,这里为了核心功能就写死了。

需要注意的是:引用的web3的版本是1.2.11版本的,所以语法上与1.0以下的有很多差别。

vote.js的内容:

var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'));

var abi = JSON.parse('[{"constant":true,"inputs":[{"name":"candidateName","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"candidateName","type":"bytes32"}],"name":"vote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"candidateListName","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]');
var contractAddress = "0xd87425dced6bfc9c172ddda58dd367cc07eb628e";
var contractInstance = new web3.eth.Contract(abi, contractAddress);

//候选人和其ID对应关系
var candidates = {"Alice": "candidate-1", "Bob": "candidate-2", "Cary": "candidate-3"};
//候选人名数组
var candidateNames;  
//渲染初始页面
$(document).ready(function(){
	//获得所有的候选人名字keys
	candidateNames = Object.keys(candidates);
	//遍历去查询(call)当前的票数
	for (let i = 0; i < candidateNames.length; i++) {
		let name = candidateNames[i];
		contractInstance.methods.totalVotesFor(web3.utils.toHex(name)).call((err,res)=>{
			if (err) console.log(err);
			else{
				//渲染到屏幕上
				$("#" + candidates[name]).html(res.toString());
			}
		});
	}	
})

//开始投票
function voteForCandidate(){
	//获取当前输入的内容
	let testName = $("#candidate").val();

	//判断输入
	if (!isVaildName(testName)) {
		alert("输入有效姓名!");
		return;
	}
	//调用合约函数-投票
	contractInstance.methods.vote(web3.utils.toHex(testName)).send({from:'0xaA67ad1AF6D2fd4eeaFE7d7B5Ac205a769757376'},(err,res)=>{
		if (err) console.log(err);
		else{
			//!!!注意这里不能直接加1显示,而是必须要到区块链中查询
			contractInstance.methods.totalVotesFor(web3.utils.toHex(testName)).call((err,res)=>{
				if (err) console.log(err);
				else{
					//渲染到屏幕上
					$("#" + candidates[testName]).html(res.toString());
				}
			});
		}
	})
}


//检查输入
function isVaildName(name){
	for (let i = 0; i < candidateNames.length; i++) {
		if (name == candidateNames[i]) {
			return true;
		}
	}
	return false;
}

几点说明:

  1. 不需要require web3,因为html中的外链web3就引入模块了
  2. new web3.eth.Contract(abi, contractAddress);是1.0以上版本的写法,直接一步到位,注意Contract的C是大写的
  3. 还有1.0合约方法的调用也不相同,具体可细看API

测试:

后端node

只是简单的搭建一个node服务器,所以非常简单。

var http = require('http');
var fs = require('fs');
var url = require('url');
 
 
// 创建服务器
http.createServer( function (request, response) {  
   // 解析请求,包括文件名
   var pathname = url.parse(request.url).pathname;
   
   // 输出请求的文件名
   console.log("Request for " + pathname + " received.");
   
   // 从文件系统中读取请求的文件内容
   fs.readFile(pathname.substr(1), function (err, data) {
      if (err) {
         console.log(err);
         // HTTP 状态码: 404 : NOT FOUND
         // Content Type: text/html
         response.writeHead(404, {'Content-Type': 'text/html'});
      }else{             
         // HTTP 状态码: 200 : OK
         // Content Type: text/html
         response.writeHead(200, {'Content-Type': 'text/html'});    
         
         // 响应文件内容
         response.write(data.toString());        
      }
      //  发送响应数据
      response.end();
   });   
}).listen(8080);
 
// 控制台会输出以下信息
console.log('Server running at http://127.0.0.1:8080/');

最后的效果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xxx_undefined

谢谢请博主吃糖

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值