欢迎来到创意信息服务平台--起兴网!
当前位置: 首页 文章资讯 IT开发/营销 开发/APP 软件开发 DAPP系统开发|如何在以太坊搭建DAPP(技术说明)

DAPP系统开发|如何在以太坊搭建DAPP(技术说明)

发布时间:2022-08-25 10:03:43

如何在以太坊上搭建一个Dapp?对于开发人员来说,最好的学习办法就是亲自动手做一个小项目。所以,接下来将会以一个投票程序为例,带着你在以太坊平台上搭建一个 dapp并且通过借助这样一个例子介绍 Dapp的编译、部署及交互过程。

 

这个程序的功能很简单,只是设定一组候选项,让所有人都可以给这些候选项投票,以及显示每个候选项收到的总票数。

 

微信截图_20220820184017.png 

事先说明,因为所有 dapp框架都会隐藏掉一些底层细节,对初学者来说,贸然使用框架可能会形成对系统认识上的障碍,所以本文不会介绍如何借助框架搭建 dapp。这样等将来需要甄选框架时,你也能清楚地看到框架到底帮你做了什么。

 

首先,准备开发环境学习在开发环境中的合约编写、编译和部署流程通过 node.js控制台与区块链上的合约交互通过一个简单的网页与合约交互,在页面上提供投票功能并显示候选项及相应的票数。

 

整个程序的开发都是在一台干净的 ubuntu 16.04 xenial上完成的。除此之外,我还在一台 macos上重复了一遍搭建和测试过程

 

准备开发环境

 

web开发的说法,真实区块链(live blockchain)相当于生产环境,我们自然不应该在生产环境上做开发,因此本文用了一个名为 ganache的内存区块链(相当于区块链模拟器)。本教程的第二篇文章才会跟真正的区块链交互。

 

下面是在 linux操作系统上安装 ganacheweb3js,以及启动测试区块链的步骤。在 macos上可以用同样的命令。windows系统可以参照这里的命令。

 

注意:ganache-cli会创建 10个自动参与交易的测试账号,每个账号里都预存了 100个以太币(当然,只能用于测试),区块链DAPP项目153开发,DAPP系统开发o56o模式1oo3源码,DAPP钱包系统搭建技术

 

简单的投票合约

 

接下来我们要用 Solidity编程语言编写合约。如果你熟悉面向对象编程,就会觉得这个学起来很轻松。

 

我们要编写一个名为 Voting的合约(相当于 OOP语言中的类)。这个合约中会有个构造器,负责初始化一个包含候选项的数组;还会有两个方法,一个用于返回指定候选项的总票数,另一个给候选项的得票数加一。

 

注意:在将合约部署到区块链上时,构造器会执行,并且只会执行这一次。在做 web应用时,每次重新部署都会覆盖掉原来的代码,但部署到区块链上的代码是不可变的。也就是说,即便你更新了合约,又重新部署了一次,之前的合约仍然会原封不动地留在区块链上,并且其中存储的数据也不会受到丝毫影响,新部署的代码会创建一个全新的合约实例。


下面是带有注释的投票合约代码:

 

pragma solidity ^0.4.18;

 

// 必须指明编译这段代码的编译器版本

 

contract Voting {

 

/* 下面这个 mapping域相当于一个关联数组或哈希。

 

mapping的键是候选项的名字,类型为 bytes32

 

值的类型是无符号整型,用于存储得票数。

 

*/

 

mapping (bytes32 => uint8) public votesReceived;

 

/* Solidity(还)不允许给构造器传入字符串数组。

 

所以我们用 bytes32数组存储候选项

 

*/

 

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 voteForCandidate(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;

 

}

 

}

 

 

部署区块链

将上面的代码保存到 Voting.sol文件中,放在 hello_world_voting目录下。接下来我们要编译这段代码,并将它部署到ganache区块链上。

在编译 Solidity代码之前,需要先安装 npm模块 solc。我们会在 node控制台中用这个库编译合约。

首先,在终端中运行 node命令进入 node控制台,初始化 solcweb3对象。下面是需要在 node控制台中输入的代码:

 

mahesh@projectblockchain:~/hello_world_voting$ node

 

> Web3 = require('web3')

 

> web3 = new Web3(new Web3.providers.HttpProvider

 

为了确保 web3对象初始化成功,可以跟区块链通讯,我们可以查询一下区块链上的所有账号。

 

为了编译合约,需要先加载文件 Voting.sol中的代码,并将其赋值给一个字符串变量,然后再编译这个字符串。

 

> code = fs.readFileSync('Voting.sol').toString()

 

> solc = require('solc')

 

> compiledCode = solc.compile(code)

 

代码编译成功后,可以在 node终端中输入 compiledCode命令查看 contract对象,有两个域非常重要,一定要搞明白:compiledCode.contracts[:Voting].bytecode: 这是 Voting.sol中的代码编译而成的字节码,也是要部署到区块链上的代码。compiledCode.contracts[:Voting].interface: 这是合约的接口或者说模板(称为 abi),告诉合约的用户有哪些方法可用。将来不管什么时候要跟合约交互,都需要这个 abi定义。这里有关于 ABI的详细介绍。

 

部署合约

 

先创建一个在区块链中部署和初始化合约的合约对象(即下面的 VotingContract)。

 

> abiDefinition = JSON.parse(compiledCode.contracts[':Voting'].interface)

 

> VotingContract = web3.eth.contract(abiDefinition)

 

> byteCode = compiledCode.contracts[':Voting'].bytecode

 

> deployedContract = VotingContract.new(['Rama','Nick','Jose'],{data: byteCode, from: web3.eth.accounts[0], gas:

 

4700000})

 

> deployedContract.address

 

> contractInstance = VotingContract.at(deployedContract.address)

 

上面代码中的 VotingContract.new 将合约部署到区块链上。它的第一个参数是包含候选项的数组,一看就能明白。第二个参数中各数据项的含义分别为:data: 这是已编译好要部署到区块链上的字节码。from: 区块链必须追踪是谁部署的合约。在这个例子中,我们只是调用了 web3.eth.accounts,然后将返回结果的第一个账号作为这个合约的所有者(即将合约部署到区块链上的账号)。

 

记住,web3.eth.accounts返回的是 ganche在启动测试区块链时创建的 10个测试账号组成的数组。然而在真实的区块链中,不能随便指定一个账号。那必须是你拥有的账号,并且在交易之前要解锁那个账号。在创建账号时,系统会要求你提供一个口令,这个口令就是用来证明你对账号的所有权的。为了用起来方便,Ganache默认把 10个账号全解锁了。

 

gas: 跟区块链交互是要花钱的。为了把你的代码放到区块链上,是需要让矿机干活的,这笔钱就是给那些付出计算力的矿机的。

 

你必须明确愿意为此支付多少钱,即给gas’一个值。购买燃料的以太币是从你的 from账号中出的。燃料的价格是由网络设定的。合约部署好之后,我们就可以跟合约的实例(即上面的变量 contractInstance)交互了。区块链上有成百上千个合约,怎么确定哪个是你的呢?答案是用 deployedContract.address。在你必须跟合约交互时,需要这个部署地址和之前说过的那个 abi定义。

 

nodejs控制台中与合约交互

 

> 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'

 

node控制台中运行上面的命令,应该可以看到票数的增长。每次投票给候选项,都会得到一个交易 id,比如上面的‘0xdedc7ae544c3dde74ab5a0b07422c5a51b5240603d31074f5b75c0ebc786bf53’。这个 id是交易已经发生的证据,将来随时可以用这个 id访问这笔交易。交易是不可变的,而不可变性正是以太坊这样的区块链的一个显著优点。

 

连接区块链并且可以投票的网页

 

现在基本上算是完工了,只剩下一件事情。接下来我们要创建一个简单的 html文件,让它显示候选项的名称、票数,还有投票控件,以便调用放在 js文件中的投票命令。下面是 html文件和 js文件中的代码。把它们存到相应的文件中,放在 hello_world_voting目录下,然后在浏览器中打开 index.html

 

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>

 

<h1>A Simple Hello World Voting Application</h1>

 

<div>

 

<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);

 

// 在你的 node控制台中执行 contractInstance.address以获取合约的部署地址,并将下面的地址换成你自己的部署地址

 

contractInstance = VotingContract.at('0x2a9c1d265d06d47e8f7b00ffa987c9185aecf672');

 

candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"}

 

function voteForCandidate() {

 

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);

 

}

 

});

 

我们之前说过,跟合约交互需要 abi和地址。上面的 index.js中有使用它们跟合约交互的代码。

 

微信截图_20220825094828.png

 

如果在文本框中输入候选项的名称,点击投票按钮后能见到票数的增长,说明你已经成功地创建了自己的第一个 dapp。简单回顾一下整个过程:搭建开发环境;编译合约,部署到区块链上;在 node控制台中跟合约交互;通过网页跟合约交互。