前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >以太坊DApp开发初探

以太坊DApp开发初探

原创
作者头像
forrestlin
修改2018-07-17 18:09:45
2.7K0
修改2018-07-17 18:09:45
举报
文章被收录于专栏:蜉蝣禅修之道

关于“以太猫”的流行,相信不少人都有所耳闻,甚至入手养过几只。从游戏性来说,其本质就是一个简单的收集交换类游戏,然鹅,是区块链赋予了它魅力,让用户每一只猫永远不会消失、不被篡改,更重要的是可以炒(滑稽脸),于是今天借此机会一探以太坊应用DApp的开发过程以及开发中遇到的坑。

以太坊DApp介绍

以太坊是一个区块链公有链平台,和比特币类似,以太坊也有其代币--以太币,可在挖矿、交易中获得,然而,说到以太坊和比特币的区别就是其支持智能合约,一个智能合约由代码和数据组成,和其他编程语言中的类类似,一个以太坊分布式应用DApp由众多智能合约组成,每个智能合约都有其独特的地址,可以看做以太坊上的一个账户,可以存取以太币,作用就像一个裁判、中间人。一个简单但不是很恰当的例子就是赌博,我和小明打赌明天会下雨,输的人给赢的人一百块,这种情况我们在现实中一般会以下面两种方法实现:

  • 依靠朋友间的信任。等明天到了,根据下雨与否进行交易。但这种方法一般不可行,因为毕竟是朋友,输的人会自动把昨天的打赌作为玩笑话,而赢的人也碍于面子不好意思要钱,所以交易无法达成。
  • 依靠公证的第三方。OK,我们这次认真点,找一个彼此都认识的朋友小方作为公证,把我和小明的赌注一百块都先存着,等明天到了再给赢的人两百块。这种方法确实比第一种要好,但还是害怕就是第三方拿着两个人的赌注夹带私逃了,这对交易双方的损失更大。

OK,智能合约就是为了解决以上的信任问题而诞生的,由于智能合约存放于区块链,而区块链具有的不可抵赖和不可篡改性,使得智能合约比现实中任意一个机构的公信力都强。其实,区块链去中心化思想最大的优势就是解决了信任问题,而现实中最常见需要解决信任问题的场景莫过于涉及货币交易,从以太坊的众多DApp列表https://www.stateofthedapps.com/ 中看到,大多数都是关于交易、赌博性质的应用,可以说“以太猫”的横空出世刷新了人们对于区块链应用的固有认知。

开发准备

开发以太坊DApp需要安装以下环境或工具,以Mac OS X为例

  • $ brew install node 以太坊DApp其他开发工具都是通过npm安装的,node.js大法好,mac用户可通过homebrew安装。
  • $ npm install ethereumjs-testrpc 以太坊提供的区块链测试环境,所有节点都是虚拟的存在内存中,启动后默认创建10个账户。读者也可以选择安装geth搭建私有链,使用真实节点存储。
  • $ npm install web3 以太坊提供读写区块链数据的JavaScript接口,源码地址:https://github.com/ethereum/web3.js/ ,通过web3.js我们可以访问各个账户、部署智能合约、调用合约方法、发起交易等等。
  • $ npm install truffle 第三方提供的开源以太坊DApp集成工具,源码地址:https://github.com/trufflesuite/truffle ,truffle工具会帮助我们编译、测试、打包和部署DApp项目中的所有合约,类似的还有Meteor(官方推荐工具,但实用下来感觉没有truffle方便,而且文档也较少)。

以下是非必需工具

  • $ npm install truffle-contract 基于web3.js封装的JavaScript与智能合约交互接口,通过链式调用将对合约的各个操作串联在一起,具体API参考源码地址:https://github.com/trufflesuite/truffle-contract
  • $ npm install express node.js社区中基于connect流行的服务器开发框架,本文使用该框架搭建后台服务器,读者可自行选择其他框架。

编程语言

编写一个DApp可以说是包括两部分,合约部分和业务逻辑部分。

智能合约
  • Solidity,类JavaScript,这是以太坊推荐的旗舰语言,也是最流行的智能合约语言,具体用法参考http://solidity.readthedocs.io/en/latest/ ,本文所有合约都使用该语言编写,另外测试、调试Solidity有一个非常好的在线IDE--Remix https://remix.ethereum.org/,由以太坊团队推出的。
  • Serpent,类Python。
  • LLL,类Lisp。
业务逻辑

业务逻辑部分即提供客户端与智能合约交互的接口,相当于目前BS结构中的后台逻辑,因此业务逻辑部分可部署在中心服务器中,而且在以太坊中每个智能合约函数的每一行代码都有固定的gas费用以及延时的,一些简单的逻辑应该交由业务逻辑处理,编写业务逻辑目前提供有以下几种语言:

  • JavaScript,主要是基于Web3.js这个库调用智能合约,本文例子也是使用JavaScript编写的。
  • Go,上述提到的以太坊私链搭建工具geth就是使用Go编写的。
  • Python
  • Java
  • Ruby
  • Haskell
  • Rust

DApp实践

废话不多说,下面我们通过一个DApp例子来窥探一下区块链智能合约的魅力,demo源码地址:https://github.com/Dave1991/QzoneBlockPet

Demo功能介绍

该demo是一个卡片收集类游戏,业务场景为每个用户都拥有一只随机的宠物,用户通过收集卡片作用于宠物身上进行装扮,而卡片的收集来源分三种:

  • 系统定期为随机用户生成卡片
  • 与其他用户交换卡片
  • 在卡片商城中购买卡片

Demo目录结构

我们通过$ truffle init命令创建一个DApp项目,truffle会帮我们组织好一个DApp的目录结构,如下所示,其中app目录为笔者添加的,用于存放业务逻辑代码。

  • app 业务逻辑代码,后面再展开讨论
代码语言:txt
复制
var Migrations = artifacts.require("./Migrations.sol");
var PetCard = artifacts.require("./PetCard.sol");
var UserCenter = artifacts.require("./UserCenter.sol");
module.exports = function(deployer) {
  deployer.deploy(Migrations);
  deployer.deploy(PetCard);
  deployer.deploy(UserCenter);
};
  • build 合约编译生成目录,不要手动修改
  • contracts 合约目录,后面展开讨论
  • migrations truffle部署配置文件,新的合约需要部署需要修改里面的配置文件1_initial_migration.js,该demo包含两个合约,加上truffle部署时需要使用的合约,一共三个合约,代码如下所示,当添加一个合约时需要在该文件中添加合约变量而且需要通过deployer部署到区块链,需要注意的是这里当前目录是contracts目录。
  • test 合约的测试文件,我们可以在该目录中存放各个合约的测试代码,类似于其他编程语言中的单元测试,该文章不展开讨论。
代码语言:txt
复制
module.exports = {
   networks: {
   development: {
   host: "localhost",
   port: 8545,
   network\_id: "\*" // Match any network id
  }
 }
};
  • truffle.js 区块链网络配置文件,在truffle部署合约时会使用该文件定义的地址,目前配的是testrpc默认测试环境,如下所示:

Demo运行方式

  1. 安装上述提到的依赖(包括非必需)
  2. $ testrpc 启动区块链测试环境,可以看到testrpc在内存中为我们创建了10个虚拟账户以及对应的私钥。
    image.png
    image.png
  3. $ truffle compile 编译智能合约,底层调用的是solc编译器,该编译方式是增量的,如果要全量编译,可加上--all参数。
    image.png
    image.png
  4. $ truffle migrate --reset 部署所有智能合约,部署的环境由truffle.js定义,和compile类似,migrate也是增量部署,如果要重新部署所有合约,可加上--reset参数。
    image.png
    image.png
  5. $ cd app
  6. $ npm start 启动服务器
  7. 浏览器访问localhost:8080,目前提供的接口详见INTERFACE.md文件,下面展示其中两个接口。
  8. 生成卡片

接口名称

方法

路由参数

createRandomCard

GET

例子

返回

/createRandomCard

{"cardId":"2","code":"0x616161666","owner":"0x5727b589bca4500e896ffc82e3fedf56cae7017f","value":"52"}

  • 获取用户所有卡片

接口名称

方法

路由参数

getAllCardsForUser

GET

/:address

例子

返回

/getAllCardsForUser/0xc3d9b7ea1e42b04dddf3475b464bb1abd5f8451f

{"cardId":"0","code":"0x616161666","value":"4"}

需要注意的是上面两个方法调用前都需要设置gas(以太坊交易手续费),不过由于demo运行在testrpc中所有账户的balance都是虚拟的,业务逻辑直接从接口调用方账户扣除了gas,对其屏蔽了该过程,但如果正式部署到生产环境我们需要先询问用户是否愿意付该笔gas然后再真正调用合约接口,因此,以太坊的web3.js提供了estimateGas方法来预估合约函数执行所需的gas。

编写智能合约

智能合约使用Solidity语言编写,语法有点类似于JavaScript,文件名以.sol结尾,通常来说一个.sol文件定义一个合约,相当于Java中一个文件定义一个public class。一个合约通常包含两部分,成员变量和成员函数。

进入本demo的contracts目录,可以看见里面包含了以下文件:

  • Migrations.sol:truffle创建目录时创建的合约,用于部署DApp
  • PetCard.sol:本demo核心合约,定义了宠物卡片合约
  • strings.sol:第三方定义的字符串类库,本demo主要使用了其分割字符串的函数
  • UserCenter.sol:用户中心合约,用于注册用户和查询用户

下面展示的是宠物卡片合约的部分代码。

代码语言:txt
复制
pragma solidity ^0.4.17;

contract PetCard {
    struct Card {
        bytes32 code; //卡片代码,决定卡片的功能
        uint256 value;
        address owner;
        bool isSelling;
        uint sellingPrice;
        uint cardId;
    }
    enum ErrorCode {ERROR_NO_ERROR, ERROR_INDEX_OUT_OF_RANGE, ERROR_WRONG_OWNER, ERROR_CARD_IS_SELLING, ERROR_CARD_IS_NOT_SELLING, ERROR_PRICE_NOT_ENOUGH}
    Card[] cards;
    address CEO;
    function PetCard() public payable {
        CEO = msg.sender;
    }

    // 匿名函数,当外部调用找不到时调用该函数
	event FallbackTrigged(bytes data);
	function() public payable {
		FallbackTrigged(msg.data);
	}

    event BuyCardEvent(uint cardId, bool isSuccess, ErrorCode errorCode);
    // 从卡片商城中购买卡片
    function buyCard(uint cardId) public payable {
        address buyer = msg.sender;
        // 判断card下标是否合法,不合法时退款给买家
        if (cardId >= cards.length || cardId < 0) {
            buyer.transfer(msg.value);
            BuyCardEvent(cardId, false, ErrorCode.ERROR_INDEX_OUT_OF_RANGE);
            return;
        }
        Card storage card = cards[cardId];
        // 判断消费金额是否小于card价格
        if (msg.value < card.sellingPrice) {
            buyer.transfer(msg.value);
            BuyCardEvent(cardId, false, ErrorCode.ERROR_PRICE_NOT_ENOUGH);
            return;
        } 
        // 判断卡片是否正在销售
        if (!card.isSelling) {
            buyer.transfer(msg.value);
            BuyCardEvent(cardId, false, ErrorCode.ERROR_CARD_IS_NOT_SELLING);
            return;
        }
        // 将卡片卖的钱还给卖家
        if (this.balance >= card.sellingPrice) {
            card.owner.transfer(card.sellingPrice);
        }
        card.owner = buyer;
        card.isSelling = false;
        card.sellingPrice = 0;
        BuyCardEvent(cardId, true, ErrorCode.ERROR_NO_ERROR);
    }

    // 获取用户所有卡片
    function getAllCardsForUser() public constant returns (uint[] cardIds, bytes32[] codes, uint[] values, uint len) {
        cardIds = new uint[](cards.length);
        codes = new bytes32[](cards.length);
        values = new uint[](cards.length);
        // codes = new string[](cards.length);
        len = 0;
        for (uint i = 0; i < cards.length; i++) {
            if (cards[i].owner == msg.sender) {
                cardIds[len] = cards[i].cardId;
                codes[len] = cards[i].code;
                values[len] = cards[i].value;
                len++;
            }
        }
    }

    event CreateNewCardEvent(uint cardId, bytes32 code, address owner, uint value);
    // 给用户掉落新卡片
    function createNewCardForUser(bytes32 code, uint value) public {
        Card memory card = Card({code: code, value: value, owner: msg.sender, isSelling: false, cardId: cards.length, sellingPrice: 0});
        cards.push(card);
        CreateNewCardEvent(card.cardId, card.code, card.owner, card.value);
    }

}
定义卡片结构与成员变量

合约内部可以定义多个结构体,关键字为struct,结构体内部也可定义成员变量,允许的类型和合约一样。此外,合约支持数据类型包括以下几种:

  • 整型,uintx / intx,其中x代表整型所占用的位数,从8到256,步长为8,如果我们直接使用uint / int,则与uint256 / int256等价。
  • 布尔型,bool,有true/false两个值。
  • 浮点型,fixedMxN / ufixedMxN,浮点数在Solidity中支持得不是很好,它与其他语言中的浮点数并不一样,Solidity中浮点数在声明时就必须确定长度,而其他语言是可变的,M代表的是浮点数占用的总位数,从8到256,步长为8,N代表小数部分的长度,范围是0-80。
  • 定长字节型,bytesx,其中x代表变量所占字节长度,范围是1-32,当变量打印出来时,显示的是十六进制。
  • 变长字节型,bytes或string,两者区别在于bytes使用十六进制标识,string是用UTF-8表示。
  • 地址,address, 等价于bytes20,而且Solidity为地址变量预设了几个方法,例如,balance方法获取地址对应账户的余额,transfer方法转账以太币到地址对应的账户中,转账者为调用者,收款者为address,另一个方法send类似于transfer也是转账,但值得注意的是,当transfer失败时,会回滚交易并抛出异常,而send方法则不会。
  • 枚举,enum,和其他语言一样,Solidity也支持枚举值,语法也类似,可参考代码中错误码枚举值的定义。

根据上述的数据类型,我们定义卡片的结构体,包括卡片代码、卡片价值、卡片拥有者、卡片是否正在出售、卡片出售价格以及卡片id。然后,定义了函数执行可能会发生的错误码,还有一个卡片的集合以及合约的创建者CEO。

代码语言:txt
复制
struct Card {
	bytes32 code; //卡片代码,决定卡片的功能
	uint256 value;
	address owner;
	bool isSelling;
	uint sellingPrice;
	uint cardId;
}
enum ErrorCode {ERROR_NO_ERROR, ERROR_INDEX_OUT_OF_RANGE, ERROR_WRONG_OWNER, ERROR_CARD_IS_SELLING, ERROR_CARD_IS_NOT_SELLING, ERROR_PRICE_NOT_ENOUGH}
Card[] cards;
address CEO;
函数

在Solidity中函数的定义语法是

function 函数名(参数列表) 修饰符 returns (返回值列表)

这里值得注意的是,在函数生命中返回值列表我们可以声明返回值的名字,类似于形参,当在函数体中给返回值变量赋值后,我们可以不用写return,但如果写了还是以return为主,同时,一个函数返回值支持多个,调用者拿到的将是一个返回值数组,和python有点像。

另外,EVM会给每个合约的函数传入一个名为msg的对象,该对象包含几个属性,如sender是调用者账户地址、value是调用者执行该函数支付的以太币(单位是wei)、data是函数调用的描述。除了data外,其他属性的值是由调用者传入,详见业务逻辑代码的介绍。

构建函数和匿名函数

和大部分语言一样,Solidity中每个合约也有构建函数,在构建函数中我们可以做一些初始化的操作,在下面的代码中我们注意到函数后有两个修饰符,分别是publicpayable,其中public说明该函数外部合约也可见,对应的还有externalprivateinternal,要说到这四者的区别,需要查看函数的调用方式和可见性,本文就不展开了。然后payable说明该函数会涉及货币交易,同时当我们在一个合约的其他函数中调用了转账操作,那么构建函数必须也得声明为payable

匿名函数,也就是没有名字的函数,每个合约中最多可定义一个,当其他地方调用该合约不存在的函数或者出现异常时,EVM(以太坊智能合约执行虚拟机)会自动调用合约的匿名函数,同样地,当合约内其他函数有转账操作时匿名函数也需要加上payable修饰。

代码语言:txt
复制
function PetCard() public payable {
	CEO = msg.sender;
}

// 匿名函数,当外部调用找不到时调用该函数
event FallbackTrigged(bytes data);
function() public payable {
	FallbackTrigged(msg.data);
}
事件

代码中我们定义了多个event,每个event只需要定义其名字和参数列表即可以,其作用相当于其他语言中的log,在函数中传入实参即可记录,虽说event的作用和log一样,但在Solidity中作用却非同小可,因为当一个函数是以transaction的形式被调用,调用者是无法拿到返回值的,因为transaction的调用是异步的,EVM无法立刻执行给出返回值,所以调用者只能通过event的记录取得函数执行后的数据,具体操作流程见业务逻辑代码的介绍。

购买卡片

定义购买卡片的函数,函数一开始我们写了三个是否合法的判断,这里可以使用require关键字对这些条件进行限定,但由于笔者希望调用者可以接收到错误信息,这里就使用了四个if判断,并且使用了事件通知调用者,同时当条件不满足时我们需要做一些回滚操作,例如将金额退还给调用者账户。而当条件满足后,我们将卡片定价转给卖家,转移卡片拥有者。

代码语言:txt
复制
event BuyCardEvent(uint cardId, bool isSuccess, ErrorCode errorCode);
// 从卡片商城中购买卡片
function buyCard(uint cardId) public payable {
    address buyer = msg.sender;
    // 判断card下标是否合法,不合法时退款给买家
    if (cardId >= cards.length || cardId < 0) {
        buyer.transfer(msg.value);
        BuyCardEvent(cardId, false, ErrorCode.ERROR_INDEX_OUT_OF_RANGE);
        return;
    }
    Card storage card = cards[cardId];
    // 判断消费金额是否小于card价格
    if (msg.value < card.sellingPrice) {
        buyer.transfer(msg.value);
        BuyCardEvent(cardId, false, ErrorCode.ERROR_PRICE_NOT_ENOUGH);
        return;
    } 
    // 判断卡片是否正在销售
    if (!card.isSelling) {
        buyer.transfer(msg.value);
        BuyCardEvent(cardId, false, ErrorCode.ERROR_CARD_IS_NOT_SELLING);
        return;
    }
    // 将卡片卖的钱还给卖家
    if (this.balance >= card.sellingPrice) {
        card.owner.transfer(card.sellingPrice);
    }
    card.owner = buyer;
    card.isSelling = false;
    card.sellingPrice = 0;
    BuyCardEvent(cardId, true, ErrorCode.ERROR_NO_ERROR);
}
遍历卡片

该函数的作用是获取所有属于调用者账户的卡片,值得注意的是,该函数在EVM中是一个昂贵的操作,首先我们声明了三个定长数组(定长是和临时变量存储的地方有关),每个长度都等于所有卡片数组的大小,因此每个数组都已经开销了不少gas,然后遍历又是一个耗时操作,又需要花费gas,而且函数在编译时并不知道cards的长度,所以即使调用者使用estimategas函数预估该函数所需gas也是不准确的,这对于调用者是危险的,随时都可能因为gas不够而执行失败。

代码语言:txt
复制
function getAllCardsForUser() public constant returns (uint[] cardIds, bytes32[] codes, uint[] values, uint len) {
    cardIds = new uint[](cards.length);
    codes = new bytes32[](cards.length); //这里不能用string,solidity不支持定长的变长数组
    values = new uint[](cards.length);
    // codes = new string[](cards.length);
    len = 0;
    for (uint i = 0; i < cards.length; i++) {
        if (cards[i].owner == msg.sender) {
            cardIds[len] = cards[i].cardId;
            codes[len] = cards[i].code;
            values[len] = cards[i].value;
            len++;
        }
    }
}
生成卡片

这里生成卡片的逻辑交给业务层,合约只负责根据参数创建一个新的卡片,最后通知调用者即业务层。

代码语言:txt
复制
event CreateNewCardEvent(uint cardId, bytes32 code, address owner, uint value);
// 给用户掉落新卡片
function createNewCardForUser(bytes32 code, uint value) public {
    Card memory card = Card({code: code, value: value, owner: msg.sender, isSelling: false, cardId: cards.length, sellingPrice: 0});
    cards.push(card);
    CreateNewCardEvent(card.cardId, card.code, card.owner, card.value);
}

编写业务逻辑

合约编写完成后,可先到Remix上测试,测试通过后再使用truffle编译和部署到区块链上。之后,便是业务逻辑的编写了。

由于truffleweb3等都是依赖于node.js,为了一致性与方便性,本demo也是使用node.js构建业务服务器,主要依赖的模块是expresstruffle-contract,前者用于更方便的业务路由和模块化,后者用于更方便调用合约。

打开app目录,我们会看到一下的文件结构:

  • PetCard.js:宠物卡片业务路由处理以及合约交互
  • UserCenter.js:用户中心,负责用户注册和获取所有用户的上层调用
  • UserCenterCore.js:用户中心核心,负责业务层与合约层交互
  • Web3Provider.js:定义Web3连接的是区块链地址
  • package.json:定义npm运行所需要的命令和依赖
  • server.js:业务层总入口,负责默认页面、404页面处理,以及各业务模块的中转路由,还有定义服务器绑定的端口

下面我们主要看PetCard.js中业务层是如何与合约层进行交互的。

获取合约示例

这一步我们首先获取宠物卡片合约和用户中心合约的实例,便于下面调用合约,这里我们需要依赖truffle-contract还有本地的Web3Provider模块。而truffle-contract的用法都是链式调用,通过then函数连接起来。

代码语言:txt
复制
contract = require('truffle-contract');
provider = require('./Web3Provider.js');
express = require("express");

const PetCard = contract(require('../../build/contracts/PetCard.json'));
PetCard.setProvider(provider);
var petCard;
PetCard.deployed().then(function(instance){
    petCard = instance;
});

var userCenter;
require('./UserCenterCore.js').then(function(instance) {
    userCenter = instance;
});
var app = module.exports = express();
购买卡片

从下面代码中可以看到,业务层接受客户端传递的路由参数,再传入合约层,这里合约层函数的参数分两种,一种是自定义参数,另一种就是EVM预设参数,而预设参数是一个对象,需要在最后传入,正如上面Solidity函数介绍,预设参数对象需要包括from为调用者地址,value为传入合约的以太币。最后,由于这是直接通过合约实例调用函数,是一个transaction操作,因此如上面Solidity事件介绍,我们需要从返回值的日志中获取合约执行后的数据。由于日志拿到的事件参数是一个对象,所以我们直接以json形式返回给客户端即可,例如下面的返回就表示卡片购买失败,原因是卡片当前不在销售:{"cardId":"1","isSuccess":false,"errorCode":"4"}。

代码语言:txt
复制
app.get('/buyCard/:address/:cardId/:price', function(req, res) {
    petCard.buyCard(req.params.cardId, {from: req.params.address, value: req.params.price}).then(function(result) {
        if (result.logs.length > 0) {
            var eventObj = result.logs[0].args;
            res.send(JSON.stringify(eventObj));
        }
    });
});
遍历所有卡片

遍历卡片的操作并不涉及永久写入合约数据的操作,因此遍历卡片这里我们不使用transaction,而使用call的形式,因此我们可以直接拿到函数的返回值,然后由于函数返回多个值,因此result是一个数组。这里需要注意的是,上面我们说到遍历卡片时合约需要创建三个未知长度的数组,而且遍历的次数也是未知的,因此,estimategas函数预估的gas会不准确,我们这里直接给一个比较大的gas值。该接口返回的例子如:{"cardId":"0","code":"0x616161666","value":"4"}。

代码语言:txt
复制
app.get('/getAllCardsForUser/:address', function(req, res) {
    // 因为这需要创建未知长度数组,estimate 估计的gas会不准确,该方法慎调
    petCard.getAllCardsForUser.call({from: req.params.address, gas: 3000000}).then(function(result) {
        if (result.length >= 4) {
            var cardIds = result[0], codes = result[1], values = result[2];
            var len = result[3];
            var cards = [];
            for (var i = 0; i < len; i ++) {
                cards.push({cardId: cardIds[i], code: codes[i], value: values[i]});
            }
            res.send(JSON.stringify(cards));
        }
    });
});
生成卡片

生成卡片的逻辑是在所有用户随机挑选一个用户作为卡片的拥有者,然后卡片的code这里先简单地写死了一串,后续可以想更好玩的code生成逻辑,接着就是调用estimateGas函数估计所需的gas,最后才是真正调用合约函数,传入预估的gas,其实比较好的交互应该像以太猫那样,在进行真正的调用之前告知用户交易所需的gas,并可以让用户调整,用户确认后再执行合约函数。下面是生成卡片调用后返回的一个例子:{"cardId":"2","code":"0x616161666","owner":"0x5727b589bca4500e896ffc82e3fedf56cae7017f","value":"52"}。

代码语言:txt
复制
app.get('/createRandomCard', function(req, res) {
    var allUsers,
        randomUser;
    userCenter.showAllPlayers.call().then(function(result){
        allUsers = result;
        randomIdx = Math.floor(Math.random() * allUsers.length);
        randomUser = allUsers[randomIdx];
        if (randomUser != undefined) {
            var cardCode = "aaaforestlinbbb";
            var cardValue = Math.floor(Math.random() * 100 + 1);
            petCard.createNewCardForUser.estimateGas(cardCode, cardValue).then(function(esti_gas) {
                return petCard.createNewCardForUser(cardCode, cardValue, {from: randomUser, gas: esti_gas});
            }).then(function(rest) {
                if (rest.logs.length > 0) {
                    var eventObj = rest.logs[0].args;
                    res.send(JSON.stringify(eventObj));
                }
            });
        } else {
            res.send("random user is undefined");
        }
    });
});

总结DApp开发中遇到的坑

一个DApp开发流程介绍到此结束,下面总结一下开发中值得注意的地方:

  • Solidity这个语言目前还不是很完善,版本还是0.4.x,而且文档相对其他语言较少,这里除了官网,还推荐两个论坛区块链技术博客以太坊爱好者供大家参考。
  • 合约函数中慎用未知长度的数组以及遍历操作,比较耗费gas,而且对于调用者极不友好,无法预估gas。
  • 对于不需要写操作的函数,我们可以加上constant修饰符或者调用时使用call的方法而非直接调用,不产生transaction,也就不需要写入区块链。
  • 对于不需要的数组我们可以使用delete操作删除整个数组或者某个元素,可以归还一些gas,但是最好复用,使用指示器标记当前使用的长度,因为delete操作本身也是需要耗费gas的。
  • 合约内不适合做业务过重的操作,如上面的生成卡片操作,应该将逻辑放在业务层,毕竟在EVM中没执行一行代码都是需要gas的,合约应该只有读写区块链的操作。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 以太坊DApp介绍
  • 开发准备
    • 开发以太坊DApp需要安装以下环境或工具,以Mac OS X为例
      • 以下是非必需工具
        • 编程语言
          • 智能合约
          • 业务逻辑
      • DApp实践
        • Demo功能介绍
          • Demo目录结构
            • Demo运行方式
              • 编写智能合约
                • 定义卡片结构与成员变量
                • 函数
                • 构建函数和匿名函数
                • 事件
                • 购买卡片
                • 遍历卡片
                • 生成卡片
              • 编写业务逻辑
                • 获取合约示例
                • 购买卡片
                • 遍历所有卡片
                • 生成卡片
            • 总结DApp开发中遇到的坑
            相关产品与服务
            腾讯云区块链服务平台 TBaaS
            腾讯云区块链服务平台(Tencent Blockchain as a Service,简称TBaaS)致力于打造全球领先的企业级区块链技术平台,帮助客户、开发者及合作伙伴轻松创建和管理可托管、可扩展的区块链网络,助力产业协同发展。TBaaS 支持长安链·ChainMaker、Hyperledger Fabric等区块链底层平台,简化部署、运维及开发流程,实现业务快速上链,提升链上治理效率。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档