以太坊开发:如何在Windows下开发一个简易Dapp

来源:互联网 发布:c语言初学者用什么编程软件 编辑:程序博客网 时间:2024/05/16 06:32
这是我粗浅地翻译了国外的一篇以太坊开发的文章,加入了一些个人的理解。原文是在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 installweb3@0.20.0,表示安装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 withcontract 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,输入以下命令:(我不懂js,下面的解释仅供大家参考)
//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看到我们的交易和以太币的变动情况。