随遇而安的DAPP开发实践教程(一)搭建一个可用的开发环境

1、必备的基础知识

在进入实践之前,我们需要至少了解一点有关区块链(blockchain),以太坊(Ethereum),以及智能合约(smart contract)的基础知识,这些内容足以承载到一本厚重的辞典当中,因此显然不是本教程期望关注的重点内容。本文(以及后继的教程文章)假设读者已经具备了相关方面的基础知识,并且有一定的程序开发经验(无论什么编程语言,因为我们即将面对全新的一种语言)。

本教程主要选择Truffle作为智能合约和DAPP(即分布式应用程序)的开发框架,并且力图通过循序渐进的Demo开发和深入解读源码两条路线并行的方式,从零开始完成全部学习。须知,实践才是检验真理的唯一标准,直面问题,少言多做;否则就算是再老牌的开发者,也只会面对未知的领域不知所措而已。

Truffle的官网网址:http://truffleframework.com

一篇相当不错的概述性文章:http://truffleframework.com/tutorials/ethereum-overview

以及它的中文翻译版本:http://ethfans.org/posts/ethereum-overview

2、Windows下的开发准备工作

在本文(以及后继的数篇文章里)中我们只考虑Windows下的智能合约编写和DAPP开发流程,而不涉及其他教程中常用到的Linux系统。毕竟笔者所接触到的国内开发中群体以Windows用户为大多数。在使用Truffle之前,首先需要下载并安装NodeJS:

https://nodejs.org/en/

NodeJS是一个JavaScript的运行时环境(Runtime),可以帮助开发者在非浏览器环境中实现基于JavaScript脚本语言的应用开发。事实上,Truffle也是构建于JS语言之上的。我们通过NodeJS自带的Package管理工具npm就可以快速完成Truffle库(以及其它可能的依赖库)的下载和自动配置。因此,在安装NodeJS的过程中务必确认选中npm package manager,以及Add to PATH选项(确保NodeJS的执行文件可以在任何目录下被找到)。之后我们就可以在Windows系统控制台(cmd或者powershell)中完成NodeJS的指令调用了。

通过Windows的“运行”窗口执行cmd.exe,打开控制台界面,我们可以随意地测试一下NodeJS的用法了:

打印Hello World这种做法很俗套,不过至少证明我们已经安装了NodeJS并且具备了随手编译和运行JS代码的能力。按Ctrl+C两次后退出。同样,我们可以把一些JS代码保存到.js文件中,并且通过类似

# node.exe filename.js

这样的方式去运行它。本处的#号代指控制台界面下的前缀路径名称,之后不再赘述。

不过马上就开始把玩JS代码并不是我们的首要目标,我们现在需要通过npm去安装Truffle了。在控制台下执行下面的语句:

# npm.cmd install -g truffle

我们会看到一个简陋的安装页面,告诉用户当前安装的进度,以及系统为了安装Truffle和它的依赖库付出了怎样的努力。这里的install参数表示安装某个JS库,-g表示它是一个全局可用的库。安装后的库文件通常可以在C:\Users\\AppData\Roaming\npm中找到,如果现在到这个目录去一探究竟,我们会发现Truffle已经为用户准备好了指令文件truffle.cmd,以后我们会非常频繁地用到它。

安装就是这么简单!现在我们还是在控制台界面下,试验一下Truffle是否已经可用了:

系统给出了当前安装的Truffle库的版本号,以及智能合约语言Solidity的版本号。现在是时候找一个合适的Demo作为参考,来践行第一次的开发之旅了。

不过在此之前我们还有一步工作要做。须知:智能合约是运行在以太坊区块链之上的,而以太坊公链上的所有写入操作都需要消耗以太(ETH),也就是用户手中的数字资产。对于资历尚浅的我们来说,这绝对是花不起的学费。因此,我们有必要选择一个合适的测试用区块链,或者直接建立一个私有的区块链,用于DAPP的编写和测试。在本文中我们选择后者。

在其他教程文档中,经常用到一个私有链构建工具TestRPC,它在npm中的包名称为ethereumjs-testrpc,可以直接安装和使用。

不过这个库随着时间的发展已经过时了,本文更期望使用它的后继版本,即Ganache。它也是Truffle官方所推荐的开发和测试用工具,包含了完善和清晰的用户界面。该工具可以在下面的地址下载(Windows用户选择Ganache.appx,双击安装):

https://github.com/trufflesuite/ganache/releases

它的运行界面如图,系统已经自动分配了多位虚拟用户,并且给每个人分配了100ETH作为初始资产。在后继的开发过程中,我们可以随时使用这里的虚拟用户来进行合约的测试。

3、试一试现成的案例

我们首先下载Truffle官方提供的最简单的智能合约案例MetaCoin,在控制台下依次执行:

# mkdir MetaCoin

# cd MetaCoin

# truffle.cmd unbox metacoin

得到的结果如下:

此时我们新建的MetaCoin工程目录中已经包含了多个预生成的目录和文件,包括contracts(合约代码目录),migrations(链上发布相关文件目录),test(测试用的文件目录),以及truffle.js(核心配置文件)。

Windows用户在这里有一个特别需要注意的地方,由于.js文件属于可以直接执行的文件类型(通过环境变量PATHEXT设置),因此当我们试图在工程目录中调用truffle指令时,如果没有附带.cmd扩展名,系统会自动选择当前目录中的truffle.js作为执行文件,因而产生看起来无法理喻的错误提示(例如下图):

因此就像本文之前的书写方式一样,严格地输入truffle.cmd来执行各种指令是最好的选择;或者我们也可以保留当前目录中的truffle-config.js文件来配置工程,或者修改PATHEXT环境变量并去除.js扩展名字样。注意Powershell用户通常并不会受到此类困扰。

现在我们直接选择编译智能合约,即

# truffle.cmd compile

当前工程目录中同时生成了新的build子目录,用来保存编译的结果。

虽然还不知道自己到底做了什么(后面会逐步进行讲解),不过下一步我们需要将智能合约发布到私有链上了!还记得之前的Ganache吗?启动它,并确认当前的私有链服务器网络配置(本处为127.0.0.1:7545)。

编辑truffle.js文件,给它添加如下的内容(显而易见,服务器地址和端口号必须和Ganache中的设置一致):

然后在控制台下执行发布智能合约的指令,即

# truffle.cmd migrate

再看Ganache的主界面,我们的第一位虚拟用户已经为这个智能合约的发布付出了代价,幸好“他”看起来对此不以为然……

发布合约的信息同样可以在交易记录中查询到:

不求甚解的日子已经快要看到尽头了,我们马上就执行一下合约代码的测试。测试相关的文件同样保存在工程的test子目录下,通过下面的控制台指令来执行基于JS的测试代码:

# truffle.cmd test .\test\metacoin.js

到目前为止的一切看起来都像是黑客的魔法一样……不过我们至少可以发现,发布合约和调用合约前后的用户地址,以及合约地址都是相同的,这与我们测试的期望应当也是一致的。

4、源码是最好的老师

现在是时候阅读一下MetaCoin这个“最简单例子”的源代码了。我们目前还不了解智能合约语言到底是怎样的一种语法格式,只知道它的名字是Solidity,而源码文件的扩展名统一使用.sol标识。幸运的是,Solidity的语法非常类似于JavaScript,可读性高,所以我们大可以一边前进一边学习相关的语法。在MetaCoin工程的contracts目录下,有三个预先生成好的合约代码文件,分别是:

ConvertLib.sol:演示如何实现一个可以被引用的库,用来实现各种交易无关的辅助功能。

MetaCoin.sol:本工程的主要功能实现,一个非常原始的代币(token)合约。

Migrations.sol:和链上发布有关的合约代码。

我们首先看ConverLib.sol的内容,使用文本编辑器打开它。第一行是有关版本兼容性的提示,它提示编译器当前脚本所兼容的Solidity最低版本:

pragma solidity ^0.4.4;

然后是接口的主体,如果有编程经验的话,我们可以很容易地理解:这里定义了一个library,名为ConvertLib。

library ConvertLib{

……

}

它的内部实现了一个名为convert()的函数,参数包括amount和conversionRate,均为uint(无符号整数)类型。这个函数的返回值同样是uint类型,这里返回的结果就是amount和conversionRate的乘积:

function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount)

{

return amount * conversionRate;

}

函数还有两个修饰符public和pure。前者很容易理解,表示函数可以被公开调用(包括Solidity内部和外部);与之相对的修饰符还有internal(只能在合约内部被调用),private(只能在声明范围内调用,无法被继承),以及external(被外部接口调用)。不加public或者internal修饰符的函数通常默认为internal;但是如果它属于contract接口中定义的,那么默认就是public。

另一个修饰符叫做pure,不过它和其它编程语言的一些特性,例如纯虚函数,并没有任何关系。这里的pure表示这个函数的实现是完全基于它的输入参数和局部变量的,不牵涉到任何成员变量(因此与交易无关),它的概念类似于C语言中的static(静态)函数。

除此之外,还有一个非常常见的修饰符叫做view,或者constant。顾名思义,它与C语言中的const(常量)函数是相类似的,被view修饰的函数只会读取当前接口的成员变量,而不会改变它的数值,因此也就不会触发任何交易。

短短的一个ConvertLib.sol文件就可以让我们积累如此丰厚的Solidity语言知识。那么下一步我们打开MetaCoin.sol代码文件。

在pragma行之后,这里有一行专门执行了外部库的导入:

import "./ConvertLib.sol";

它导入了同一目录下的ConverLib.sol,并且必然会随后调用ConvertLib.convert()函数。注意这里通常必须有“./”或者“../”来指示导入库的相对路径,否则总是会被视为绝对路径。

这次的接口被定义为contract,即智能合约接口:

contract MetaCoin {

……

}

合约接口中增加了一个成员变量定义:

mapping (address => uint) balances;

它表示一个映射类型的变量balances,将地址数据映射到uint数据。事实上,我们很容易想像:这样的变量可以用来记录用户的代币(token)数据,即每个用户的地址与他所持有的代币总量的映射关系表。

这之后的一行定义了一个事件:

event Transfer(address indexed _from, address indexed _to, uint256 _value);

在交易函数中我们可以适时触发事件,在交易日志中存储相关的信息,进而在前端代码中进行事件的监听。这里的indexed是参数的修饰符,之后可以根据参数对事件进行索引和搜索;不过同一个事件中的indexed参数不能超过三个。

之后的四个函数,分别是:

构造函数MetaCoin():为每一个调用合约的用户分配初始代币,这个函数只会在发布合约时被执行一次。

函数sendCoin():向指定用户地址发送代币,同时减持发送者的代币总量。

函数getBalanceInEth()和getBalance():获取指定用户所持代币总量,前者通过ConvertLib导入库提供了一种换算机制。

函数体中还有一些预置的全局变量,包括:

tx.origin:表示整个交易的原始发起者地址,address类型。

msg.sender:表示当前交易函数调用的发起者地址,address类型。

更多的全局变量说明,请参考下面的地址:

https://solidity.readthedocs.io/en/develop/units-and-global-variables.html

也许对于语言本身还有很多不了解的地方,但是这些已经足够我们理解MetaCoin合约的整体实现流程了。值得一提的是,MetaCoin只是一个初学者的实践案例,它所示现的代币机制(其实只是发送和查询)非常不完整;如果要深入下去,我们后面必然需要了解代币的标准接口,例如ERC20和ERC223等。

本文并不想再深入研究Migrations.sol的内容了,简单来说,每个基于Truffle框架的合约都必须实现一个Migrations合约接口,从而与migrations目录下的脚本配合实现整个发布流程的初始化工作。而有关后者的讲解,会放在之后的教程当中。

那么在这篇冗长的入门教程的最后,我们快速地浏览一下test/metacoin.js脚本的实现,以便了解我们之前的测试代码究竟做了什么。

前几行代码中获取了MetaCoin智能合约接口,并且定义了智能合约对象:

var MetaCoin = artifacts.require("./MetaCoin.sol");

contract('MetaCoin', function(accounts) {

……

});

在之后的测试代码中,通过三个不同的测试来分别验证MetaCoin的代币初始化过程是否正确,调用基于导入库ConverLib函数的getBalanceInEth()方法执行是否正确,以及发送代币给其它用户的交易函数是否正确。每个测试阶段的实现代码是类似的,列举和注释如下:

return MetaCoin.deployed().then(function(instance) { //建立MetaCoin实例

return instance.getBalance.call(accounts[0]); //调用合约的getBalance()函数

}).then(function(balance) { //后继的用户过程

……

});

这三段测试程序的主要目的是验证智能合约函数的可用性,我们不妨在.js文件中再增加两段,打印一下两个参与交易的用户的地址和他们所持的代币总量(尤其是在经过了sendCoin()的代币交易测试之后):

it("First user after test", function() {

return MetaCoin.deployed().then(function(instance) {

return instance.getBalance.call(accounts[0]);

}).then(function(balance) {

console.log("Account 0 (" + accounts[0] + ") has token " + balance.valueOf());

});

});

it("Second user after test", function() {

return MetaCoin.deployed().then(function(instance) {

return instance.getBalance.call(accounts[1]);

}).then(function(balance) {

console.log("Account 1 (" + accounts[1] + ") has token " + balance.valueOf());

});

});

保存并在控制台下重新执行:

# truffle.cmd test .\test\metacoin.js

通过阅读之前的第三段测试代码可以知道,它通过下面的代码执行了代币的交易操作:

var account_one = accounts[0];

var account_two = accounts[1];

var amount = 10;

……

meta.sendCoin(account_two, amount, );

也就是说,第一位用户accounts[0]将自己初始化时得到的10000个代币(保存在balances中)取出10个发送给第二位用户accounts[1]。而我们新增的两个测试段正确打印了两个用户的地址(可以从之前Ganache的界面截图中看到相同的地址)和他们所持的新的代币数。无论如何,我们已经开始走进DAPP开发的大门了!

教程内容参考网址:http://truffleframework.com/docs/

欢迎随便转载。

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180215G0R5G300?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券