这是我粗浅地翻译了国外的一篇以太坊开发的文章,加入了一些个人的理解。原文是在linux下开发的,但经过我尝试,Windows下也能实现。
原文地址:https://medium.com/@mvmurthy/full-stack-hello-world-voting-ethereum-dapp-tutorial-part-1-40d2d0d807c2 需要翻墙
一、环境配置
安装nodejs,npm,git,web3,solc
nodejs:官网下载最新版本https://nodejs.org/en/download/current/
npm:在Windows下安装nodejs会自带npm
git:官网下载即可https://git-scm.com/downloads
web3:直接采用npm install web3在Windows下会报错,需要配置大量环境,不建议使用。如果确实需要,可以参考下面这篇文章。
https://medium.com/@PrateeshNanada/steps-to-install-testrpc-in-windows-10-96989a6cd594
建议用npm install[email protected],表示安装0.20.0的版本。0.20.0是我写这篇文章时候的最新稳定版本。或者在https://www.npmjs.com/package/web3查询当前的稳定版本。将0.20.0替换即可。
solc:直接npm install solc即可
二、用Solidity编写智能合约并使用solc编译
我们这里写一个简单的投票智能合约,可以通过Dapp对给定的候选人投票并计算每个候选人获得的票数。这个合约有四个方法,分别是构造方法,查询票数,投票,和判断是否是候选人,逻辑非常简单,甚至不需要懂得Solidity语法都可以看得懂。
-
pragma solidity ^0.4.11; -
// We have to specify what version of compiler this code will compile with -
contract Voting { -
/* mapping field below is equivalent to an associative array or hash. -
The key of the mapping is candidate name stored as type bytes32 and value is -
an unsigned integer to store the vote count -
*/ -
mapping (bytes32 => uint8) public votesReceived; -
/* Solidity doesn't let you pass in an array of strings in the constructor (yet). -
We will use an array of bytes32 instead to store the list of candidates -
*/ -
bytes32[] public candidateList; -
/* This is the constructor which will be called once when you -
deploy the contract to the blockchain. When we deploy the contract, -
we will pass an array of candidates who will be contesting in the election -
*/ -
function Voting(bytes32[] candidateNames) { -
candidateList = candidateNames; -
} -
// This function returns the total votes a candidate has received so far -
function totalVotesFor(bytes32 candidate) returns (uint8) { -
if (validCandidate(candidate) == false) throw; -
return votesReceived[candidate]; -
} -
// This function increments the vote count for the specified candidate. This -
// is equivalent to casting a vote -
function voteForCandidate(bytes32 candidate) { -
if (validCandidate(candidate) == false) throw; -
votesReceived[candidate] += 1; -
} -
function validCandidate(bytes32 candidate) returns (bool) { -
for(uint i = 0; i < candidateList.length; i++) { -
if (candidateList[i] == candidate) { -
return true; -
} -
} -
return false; -
} -
}
进入node,然后输入以下语句:
> Web3 = require('web3')
> web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
这样我们就初始化了一个web3对象,并可以利用这个web3对象来和区块链进行交互。比如,可以通过这个web3对象来查询它所连接到的区块链的账户信息:
> web3.eth.accounts
那么我们如何编译呢?很简单,使用我们之前安装的solc:
> solc = require('solc')
>code = fs.readFileSync('Voting.sol').toString()
>compiledCode = solc.compile(code)
这几条命令执行完了之后我们就把编译好了的代码存到了compiledCode当中。
三、部署智能合约
testrpc安装语句: npm install ethereumjs-testrpc web3
在部署之前首先我们要先安装并打开testRPC,可以理解为一个测试链,会自动生成十个账户,并且每个账户中都会初始有100个以太币(当然是假的以太币)。
另外开启一个控制台,进入node,输入以下命令:
//abiDefinition中保存的是该智能合约的界面信息,JSON.parse() 方法解析一个JSON字符串,构造由字符串描述的JavaScript值或对象。也就是说,把JSON字符串解析为JavaScript值。
> abiDefinition = JSON.parse(compiledCode.contracts[':Voting'].interface)
//这句代码的作用相当于初始化一个只有外壳和界面的投票合约对象
> VotingContract = web3.eth.contract(abiDefinition)
//将这些字节码存到byteCode这个变量中去
> byteCode = compiledCode.contracts[':Voting'].bytecode
//VotingContract.new就将这个合约部署到了以太链上去了,这里VotingContract本身就有abiDefinition,这里把byteCode作为data传入,因此就把其界面和比特码同时利用上了。
> deployedContract = VotingContract.new(['Rama','Nick','Jose'],{data: byteCode, from: web3.eth.accounts[0], gas: 4700000})
//到上一句,实际上智能合约就部署好了,这里我们使用这个命令来获取这个合约的地址
> deployedContract.address
这里其实只是相当于作了一个名称的简化
> contractInstance = VotingContract.at(deployedContract.address)
四、在控制台中与智能合约进行交互
可以参考下面的代码,理解起来比较简单,不作过多解释。
> contractInstance.totalVotesFor.call('Rama')
{ [String: '0'] s: 1, e: 0, c: [ 0 ] }
> contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]})
'0xdedc7ae544c3dde74ab5a0b07422c5a51b5240603d31074f5b75c0ebc786bf53'
> contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]})
'0x02c054d238038d68b65d55770fabfca592a5cf6590229ab91bbe7cd72da46de9'
> contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]})
'0x3da069a09577514f2baaa11bc3015a16edf26aad28dffbcd126bde2e71f2b76f'
> contractInstance.totalVotesFor.call('Rama').toLocaleString()
'3'
五、利用网页与智能合约进行交互
在上一步的交互中我们是在nodejs中进行投票和查询的,现在我们就要把这些命令写到js中,并写一个简单的html文件,通过网页来与智能合约进行交互。html和js文件见下。把这两个文件放到与Voting.sol同级别的目录下。
index.html
-
<!DOCTYPE html> -
<html> -
<head> -
<title>Hello World DApp</title> -
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,700' rel='stylesheet' type='text/css'> -
<link href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'> -
</head> -
<body class="container"> -
<h1>SSC VOTING APPLICATION</h1> -
<div class="table-responsive"> -
<table class="table table-bordered"> -
<thead> -
<tr> -
<th>Candidate</th> -
<th>Votes</th> -
</tr> -
</thead> -
<tbody> -
<tr> -
<td>Rama</td> -
<td id="candidate-1"></td> -
</tr> -
<tr> -
<td>Nick</td> -
<td id="candidate-2"></td> -
</tr> -
<tr> -
<td>Jose</td> -
<td id="candidate-3"></td> -
</tr> -
</tbody> -
</table> -
</div> -
<input type="text" id="candidate" /> -
<a href="#" onclick="voteForCandidate()" class="btn btn-primary">Vote</a> -
</body> -
<script src="https://cdn.rawgit.com/ethereum/web3.js/develop/dist/web3.js"></script> -
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script> -
<script src="./index.js"></script> -
</html>
index.js
-
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); -
abi = JSON.parse('[{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"validCandidate","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"x","type":"bytes32"}],"name":"bytes32ToString","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"voteForCandidate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"contractOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"type":"constructor"}]') -
VotingContract = web3.eth.contract(abi); -
//在你的控制台中, 执行contractInstance.address,并将获得的地址替换下面这个0x413a...地址 -
contractInstance = VotingContract.at('0x4131a0f92d36932d3ec3b7a0581546f2e662ad0b'); -
candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"} -
function voteForCandidate(candidate) { -
candidateName = $("#candidate").val(); -
contractInstance.voteForCandidate(candidateName, {from: web3.eth.accounts[0]}, function() { -
let div_id = candidates[candidateName]; -
$("#" + div_id).html(contractInstance.totalVotesFor.call(candidateName).toString()); -
}); -
} -
$(document).ready(function() { -
candidateNames = Object.keys(candidates); -
for (var i = 0; i < candidateNames.length; i++) { -
let name = candidateNames[i]; -
let val = contractInstance.totalVotesFor.call(name).toString() -
$("#" + candidates[name]).html(val); -
} -
});
在index.js中把合约的地址替换之后打开index.html即可在网页上进行我们的投票操作,同时可以通过MetaMask看到我们的交易和以太币的变动情况。