基于区块链构建的去中心化应用(Dapp,Distributed application)引人关注,因为这样的应用并非集中于某个特定的管理者。
下面,我们将生成一个基本的去中心化应用,以此实际了解此类应用的机制。
遵循 Truffle 指南中提供的“以太坊宠物商店”,我将在 Web 上实际构建一个宠物商店去中心化应用。基于 Truffle 开发框架,我使用了一种称为“Solidity”的语言编写智能合约。
项目完工的效果如下图。如果用户点击了心仪宠物所对应的“adopt”按钮,应用将启动 MetaMask 检查所展示的宠物的数量和费用、创建交易并使用 ETH 支付。
我们可以看到,该应用不同于一般的电子商务网站,在购买时不必输入个人信息或信用卡信息。此外,购买数据以交易形式记录在以太坊区块链中,因此不会被篡改。
实现去中心化应用的具体流程如下:
设置开发环境;
使用 Truffle Box 创建 Truffle 项目;
描述智能合约;
编译并模拟部署(Migrating)智能合约;
测试智能合约;
建立附着于智能合约之上的 UI;
在浏览器中使用去中心化应用。
首先准备使用 node 和 npm 的环境。对于 Ubuntu 操作系统,安装 Node.js 8.x 的操作命令为:
$ apt-get update第一步,我们需要安装 Truffle。
$ npm install -g truffleTruffle 是一个以太坊开发框架。对于智能合约开发,Truffle 是一种非常有用的框架,可以高效地实现源代码的编译和部署。
第二步,建立一个名为“pet-shop-tutorial”的文件夹作为工作目录。通常使用命令 truffle init 初始化工作目录,并创建一个空目录。但是在本文给出的教程中,是在一个预先准备好的项目“Truffle Box”手工中创建了这个目录:
$ mkdir pet-shop-tutorial第三步,在“pet-shop-tutorial”目录中建立如下图所列的文件和目录:
实际使用的目录和文件如下:
contracts 目录:包含描述智能合约的 Solidity 源文件;
migrations 目录:模拟部署(migration)系统,用于部署智能合约过程中。
test 目录:目录中为测试文件,使用 JavaScript 和 Solidity 编写;
truffle.js:Truffle 配置文件。
其中,Solidity 是一种描述以太坊智能合约的编程语言。
下面,我将使用 Solidity 编写智能合约。在所创建的 contracts 目录中,建立一个名为“Adoption.sol”的文件,文件内容如下:
下面我依次介绍代码的各个部分:
pragma solidity ^0.4.4;该语句指定了 Solidity 编译器的版本信息。此外,Solidity 与 JavaScript 类似,需在代码行结尾处添加分号“;”。
contract Adoption { ・・・ }这里定义了一个名为 Adoption 的合约,并在其中实现合约。
address[16] public adopters;这句话定义了一个名为 adopters 的状态变量。鉴于 Solidity 是一种静态语言,因此变量必须要定义类型。除了 string、uint 等通用数据类型之外,Solidity 还具有一种特有的数据类型 address。address 中包含账户的地址。
这里,定义了一个名为 adopters 的 address 数组,该变量具有 16 个地址。
此外,在 adopters 变量前指定了 public,即任何人都可以访问合约。
在定义了以上变量之后,开始定义合约的方法。
function adopt(uint petId) public returns (uint) {根据 adopters 数组的长度,将整数类型变量 petId 的值设为 0 到 15(数组的索引值从 0 开始)。
代码中使用 require() 函数设置 petId 的值为 0 到 15。
msg.sender 表示执行函数者(或智能合约)的地址。
这样,语句 adopters [petId] = msg.sender; 将执行函数者的地址添加到 adopters 数组。
返回值在 petId 中。
上面定义的 adopt() 函数返回一个地址,因为 petId 是 adopters 数组的键值。
但是,鉴于每次重加载都需要做 16 次 API 调用,我使用下面定义的 getAdopters() 函数返回整个 adopters 数组:
function getAdopters() public returns (address[16]) {鉴于变量 adopters 已经定义,函数可以仅指定数据类型,并将返回值返回。
至此,我完成了对智能合约的描述。
总结一下,我创建了如下的 Adoption 合约:“共有 16 种宠物。如果用户想领养一只宠物,就将用户地址和该宠物 ID 绑定在一起”。
下面,我们将继续编译智能合约,并模拟部署。
编译将以编程语言编写的源代码转译为机器可直接执行的机器语言。换句话说,本例中就是将 Solidity 语言编写的代码转换为 EVM(以太坊虚拟机,Ethereum Virtual Machine)可执行的字节码。
在包含去中心化应用的目录中,使用终端等方式加载 Truffle Develop:
$ truffle develop然后,在启动的 Truffle 开发控制台上,输入 compile 命令:
$ truffle(develop)> compile如果输出如下,表明编译成功。
Compiling ./contracts/Adoption.sol...尽管其中可能存在一些公开可见的警告,但是继续编译是没有问题的。下面请一直保持 Truffle 开发控制台的运行。
模拟部署(Migration)类似于“移动”,就是将已有系统或类似系统迁移到一个新的平台上。
在本例中,模拟部署文件完成将所创建的 Adoption 合约部署到以太坊区块链网络上。
如果查看 migrations 目录的内容,其中已经存在一个名为“1_initial_migration.js”的 JavaScript 部署文件。该文件将 Migrations.sol 部署到 contracts 目录中,并管理它,这使得一系列的智能合约可以正确地迁移。
下面在 migrations 目录中创建一个名为“2_deploy_contracts.js”的部署文件。在部署文件中写入如下内容:
const Adoption = artifacts.require("Adoption");在前面打开的 Truffle 开发控制台上,运行 migrate 命令:
$ truffle(develop)> migrate如果生成如下输出,表明模拟部署成功完成:
测试智能合约是非常重要的一步。这是因为智能合约中的设计错误和缺陷将与用户的代币(资产)直接相关,可导致对用户利益的严重损害。
智能合约测试主要分为手工测试和自动测试。下面分别介绍这两种测试。
手工测试使用 Ganache 等本地开发环境工具,检查应用的运行情况。这易于理解,因为这些工具实际指向 GUI 中的交易。
本文将跳过对手工测试的介绍。下面介绍自动测试。
在 Truffle 中,可使用 JavaScript 或 Solidity 描述智能合约的自动测试。在本例中,我采用 Solidity 编写。
在所创建的 test 目录中,创建一个名为“TestAdoption.sol”的文件,其中的内容如下:
文件内容略长。我将分解该文件做介绍。
首先,我导入了如下三个合约:
Assert.sol:测试期间的各种检查工作。
DeployedAddresses.sol:获取在测试期间部署的合约的地址。
Adoption.sol:测试智能合约。
创建一个名为“TestAdoption”的合约,并定义变量 adoption。adoption 包含 DeployedAddresses。
在下面给出的 TestAdoption 合约中,我定义了用于测试的函数:
function testUserCanAdoptPet() {该代码测试 Adoption 合约中定义的 adopt() 函数。如果 adopt() 函数功能正常,它将返回与参数具有同一数值的 petId(即返回值)。
此处将值为 8 的 petId 置入 adopt() 函数,并使用 Assert.equal() 函数确保与 petId 返回值匹配。
function testGetAdopterAddressByPetId() {需要测试的是 petId 是否关联了正确的所有者地址。代码测试了宠物 ID 是 8 的所有者的地址是否正确。
顺便提及,变量 this 表示的是当前合约的地址。
function testGetAdopterAddressByPetIdInArray() {最后,检查具有所有地址的数组 adopters 是否被正确返回。
属性 memory 并未保存在合约的“存储”中,这意味着它是一个临时记录的值。
现在可以编写测试。我将使用 Truffle Develop 返回测试文件。
$ truffle(develop)> test如果输出如下,表明测试成功。
目前为止,我们已经完成了智能合约的创建,模拟部署在本地环境的测试区块链中,并测试其是否正常工作。
下面,我们将创建用户界面,在浏览器中实际查看宠物商店。
基本结构已经由 Truffle Box 构建,我们只需在以太坊中添加特性函数。
应用的前端部分位于 src 目录中,我们需要编辑其中的 /src/js/app.js 文件。
下面给出 App 对象的声明,我随后在①到④处添加代码。
下面分别介绍在① ~ ④处添加的源代码。
① web3 实例化if (typeof web3 !== 'undefined') {首先,确保 web3 的实例是“活动”的。如果它是“活动”的,那么使用所创建应用的 web3 对象替换它。如果它并非“活动”的,那么在本地开发环境中创建 web3 对象。
② 合约实例化鉴于我们现在可通过 web3 与“以太坊网络”建立通讯,这时需要实例化所创建的“智能合约”。为实现合约的实例化,我们需要将合约的具体位置以及工作方式告知 web3。
$.getJSON('Adoption.json', function(data) {Truffle 提供了一个有用的软件库,称为“truffle-contract”。该软件库作用于 web3 上,简化了与“智能合约”的联系。例如,truffle-contract 实现模拟部署期间合约信息的同步,无需手工更改部署地址。
Artifact 文件提供了部署地址和 ABI(应用二进制接口,Application Binary Interface)信息。
ABI 表示了合约接口上的信息,即变量、函数、参数等。
在 TruffleContract() 函数中插入 Artifact,并实例化合约。然后设置由 web3 实例化所创建的 App.web3Provider 到合约中。
此外,如果先前已经选定了宠物,那么这时需要调用 markAdopted()。每次智能合约数据发生改变时,都有必要对 UI 进行更新。更新 UI 定义为在③处给出的各种“函数”。
③ UI 更新下面的代码确保宠物状态保持更改,并且 UI 得到了更新。
var adoptionInstance;代码首先在所部署的 Adoption 合约实例上调用 getAdopters() 函数。call() 函数并不更改区块链的状态,它只是读取数据,因此这里无需支付 GAS。
此后,代码检查是否每个 petId 都绑定了一个地址。如果地址存在,就将按钮状态改为“Success”,这样用户不能再次按下按钮。
④ 操作 adopt() 函数var adoptionInstance;在本例中,确认 web3 使用账号无误后,就实际进行交易处理。交易执行通过 adopt() 函数完成,输入参数为一个包含 petId 和账号地址的对象。
之后,使用在③中定义的 markAdopted() 函数,将交易结果在 UI 上以新数据显示。
一旦万事俱备,现在就可以在浏览器中查看上面创建的去中心化应用。
这里需要预先做一些安装工作,因为我们将使用 Chorome 的一个扩展 MetaMask。账号将通过下面给出的“钱包私钥”(Wallet Seed),使用 Truffle Develop 的账号。在执行 Truffle Develop 时,会显示该私钥(它是通用私钥)。
candy maple cake sugar pudding cream honey rich smooth crumble sweet treat如果使用 MetaMask,可以通过菜单项“Lock”访问如下的屏幕。
为了将 MetaMask 连接到 Truffle Develop 创建的区块链,要将左上位置的“Main Network”改为“Custom RPC”,“Truffle Develop”更改为“http://localhost:9545”,并将显示从“Main Network”更改为“Private Network”。
账号由上面给出的私钥生成,其中应该会具有少许的 100ETH,它们来自于合约部署中消费的 GAS 量。
一旦对 MetaMask 做了如上设置,就可以在终端等处输入下面的命令,启动本地 Web 服务器(鉴于已经 bs-config.json 和 package.json 已经创建,还可以使用 lite-server 软件库)。
$ npm run dev这样,在浏览器中就能显示如下的去中心化应用。
一旦点击心仪宠物的“adopt”按钮,交易就通过 MetaMask 发出,使用者可以用 ETH 支付宠物购买。
鉴于本文只是给出一个教程,因此内容主要聚焦于使用 Truffle Box 在以太坊中实现的去中心化应用的一些特性。即便读者并不具备详细的以太坊区块链知识,只要能按教程实际动手操作,就可理解去中心化应用的工作机制。
原文发布时间为:2018-03-26
本文作者:分分钟变大神
本文来源:微信公众号-区块链前哨,如需转载请联系原作者。