前边两篇教程可以称之为热身,从这里开始,进入正题。 这一次,我们要正式创建新的交易类型或者智能合约了。

1 创建合约

首先要进入dapp所在目录

cd dapps/<dapp id>/

然后执行asch-clicontract子命令

asch-cli contract -a

接下来会提示输入合约的名字,这里输入的是"Project"

? Contract file name (without .js) Project
New contract created: ./contracts/Project.js
Updating contracts list
Done

这个命令会帮我们做三件事

  1. 新增了合约模板文件modules/contracts/Project.js
  2. modules/helper/transaction-types.js注册了交易类型
  3. modules.full.json中注册了新的模块

2 定义实体字段

在实现一个智能合约之前,需要定义好合约执行后生成的交易数据实体,即最终存储到区块链上的是哪些数据,也就是相当于创建关系数据的表格 一个合约类型对应一张表格 表格的schema在blockchain.json中进行配置

project类型比较简单,只包含namedescription字段 另外transactionId字段是每个实体表格都需要的,是作为基础交易transactions的外键。

{"table": "asset_project","alias": "t_p","type": "table","tableFields": [{"name": "name","type": "String","length": 16,"not_null": true},{"name": "description","type": "Text","not_null": true},{"name": "transactionId","type": "String","length": 21,"not_null": true}],"foreignKeys": [{"field": "transactionId","table": "transactions","table_field": "id","on_delete": "cascade"}]}

然后需要在join字段种加入新的配置,还是为了联合查询以及序列化和反序列化时使用

{"type": "left outer","table": "asset_project","alias": "t_p","on": {"t.id": "t_p.transactionId"}}

将来,asch会把这些配置通过自动化的方式生成,开发者只需要输入实体字段的名称和类型即可。

3 实现合约接口

一个合约包含如下接口,有的必须要实现,有个则使用默认生成的代码即可

create              # 创建一个交易的数据对象,主要是赋值操作
calculateFee        # 设置交易费,即生成一次交易需要消耗的XAS数量
verify              # 验证交易数据,比如字段是否合法,依赖条件是否满足等
getBytes            # 返回交易的二进制数据,类型为Buffer
apply               # 合约的执行逻辑,在区块打包时调用,主要是分配和转移交易涉及到的各个账户的资产,以及账户其他字段的设置等
undo                # apply的相反操作,在区块回滚时会调用
applyUnconfirmed    # 合约的预执行逻辑,与apply类似,但是这个会实时的调用,就是说区块打包前就会调用,因此涉及到的账户操作都是临时、未确认的
undoUnconfirmed     # applyUnconfirmed的相反操作,回滚时使用
ready               # 交易是否准备完毕,是否满足打包的条件,这是个高级功能,大部分情况都不需要,以后会单独讲解
save                # 交易数据的序列化操作,就是将json字段映射到数据库表格字段
dbRead              # 交易的反序列化操作,将数据库表格字段映射到json字段
normalize           # 交易数据的格式化,把不相关的对象字段删除,相关的对象统一类型,一般情况不需要

上面的接口大部分情况下使用默认的就可以了 开发者需要注意的主要是applyapplyUnconfirmed两个接口,这是业务逻辑的主体部分。

4 实现Project合约

实现create

    trs.recipientId = null;// 创建项目只需要发起者,不需要接收者,所以设为nulltrs.amount = 0;// 也不需要金额,只需要手续费trs.asset.project = {name: data.name,description: data.description}// project对象的两个数据字段return trs;

设置交易费

这个项目不希望与XAS对接,那么就把交易费设置为0就行了

Project.prototype.calculateFee = function (trs) {return 0;
}

数据检验

这个没啥可解释的

Project.prototype.verify = function (trs, sender, cb, scope) {if (trs.recipientId) {return cb("Recipient should not exist");}if (trs.amount != 0) {return cb("Amount should be zero");}if (!trs.asset.project.name) {return cb("Project must have a name");}if (trs.asset.project.name.length > 16) {return cb("Project name must be 16 characters or less");}if (!trs.asset.project.description) {return cb("Invalid project description");}if (trs.asset.project.description.length > 1024) {return cb("Project description must be 1024 characters or less");}cb(null, trs);
}

获取二进制数据

二进制数据主要是为了生成签名数据,所以只需要把交易的实体数据组合起来打包成Buffer就可以了。 组合的方式可以随便,比如,可以通过bytebuffer,也可以通过简单的字符串连接。

Project.prototype.getBytes = function (trs) {try {var buf = new Buffer(trs.asset.project.name + trs.asset.project.description, "utf8");} catch (e) {throw Error(e.toString());}return buf;
}

合约执行逻辑

先看未确认合约的执行

Project.prototype.applyUnconfirmed = function (trs, sender, cb, scope) {if (sender.u_balance["POINTS"] < BURN_POINTS) {return setImmediate(cb, "Account does not have enough POINTS: " + trs.id);}if (private.uProjects[trs.asset.project.name]){return setImmediate(cb, "Project already exists");}modules.blockchain.accounts.mergeAccountAndGet({address: sender.address,u_balance: { "POINTS": -BURN_POINTS }}, function (err, accounts) {if (!err) {private.uProjects[trs.asset.project.name] = trs;}cb(err, accounts);}, scope);
}

在这一步,检查用户的余额是否足够,否则拒绝执行, 接着判断是否已经存在相同的项目名称, 最后会看到一个dapp开发中最重要的api,即modules.blockchain.accounts.mergeAccountAndGet

这个api的功能是对账户进行操作,这个操作包括对数字的加减法、数组的增删、字符串的设置等。 这里对账户余额执行了减法操作,即把u_balance中的POINTS资产,减去BURN_POINTS。 这里取名BURN_POINTS主要是为了表达这个合约的执行需要燃烧一定数量的资产,因为没有指定被消耗掉的资产的去向,那么这些被消耗的资产就只有消失了,也就是被燃烧了。 这里只是为了简单起见,如果业务逻辑不希望燃烧,可以把这些资产作为手续费,转给应用的开发者或者节点运营者,或者转移到一个基金账户中,用作将来的开发经费,完全由你自己决定。

接下来再看看确认合约的执行代码

Project.prototype.apply = function (trs, sender, cb, scope) {modules.blockchain.accounts.mergeAccountAndGet({address: sender.address,balance: {"POINTS": -BURN_POINTS}}, cb, scope);
}

非常简单,只有一个操作,仅仅是对账户资产进行一个减法操作。 大部分情况下, applyUnconfirmed是比apply要复杂的,特别是涉及到资产的减法操作时,因为前者要比后者执行的更早,后者就没必要做多余的条件检查了。 我们要注意到,apply修改的是balance字段,applyUnconfirmed修改的是u_balance字段,

所以如果u_balance满足条件(即有足够的剩余资产),那么balance一定也会满足条件,所以就没必要进行进一步检查了。

接下来的savedbRead就没必要解释了,开发者可以自己发现其中的规律,直接套用即可。

5 实现http接口

在上一个步骤,已经定义了一个project合约的所有逻辑了。 在这一步,我们需要增加两个接口,都是为客户端或前端服务的,一个是用于创建交易,一个是用于查询交易历史。

几乎所有的交易创建都是类似的,一般可以分解成一下几步

  1. 使用客户端传过来的secret生成密钥对keypair
  2. 使用公钥查询或新建账户数据,通过api modules.blockchain.accounts.getAccount
  3. 然后使用客户端传过来的交易实体数据和账户数据以及密钥对,创建一个交易对象,通过api modules.logic.transaction.create
  4. 最后是调用api modules.blockchain.transactions.processUnconfirmedTransaction来处理这个交易

有一点需要注意的是library.sequence.add接口的使用,这个接口可以保证多个交易按先后顺序严格执行,如果你的合约逻辑中涉及到异步操作,应该要使用这个api。

再来看一下list这个查询接口,熟悉sql的同学一眼就看出,这只不过是个联表查询操作。

为什么要联表查询呢?

因为transactionsasset_xxx表示的是一个交易的不同部分,前者是数据的基础数据,所有交易都通用,比如交易的发起者,交易数据的签名,金额等等, 后者则属于交易数据的扩展部分,是用户自定义的数据,与具体的业务逻辑相关。

6 实现投票合约

这个就不逐行解释了,开发者可以自己研究asch-mini-dao的源码,有了上面的基础后,不难理解。

Dapp开发教程三 Asch Dapp Mini DAO相关推荐

  1. Dapp开发教程二 Asch Dapp Asset

    前一篇文章介绍了asch dapp开发的基本流程,这一次打算创建一个拥有内置资产的dapp,并顺便介绍下前后端通讯的协议和常用接口. 1 创建一个带内置资产的dapp 其实这篇文章有些标题党,因为创建 ...

  2. Dapp开发教程四 Asch Dapp Dice Game

    这个dice game与上一个mini dao相比,代码规模大了许多,功能也复杂了很多,创建了三个合约类型,彼此之间有依赖关系,合约的执行还要依赖历史交易数据. 但是我觉得在原理上与上一个项目相比,并 ...

  3. Dapp开发教程四 Asch Dapp Dice Game 1

    这个dice game与上一个mini dao相比,代码规模大了许多,功能也复杂了很多,创建了三个合约类型,彼此之间有依赖关系,合约的执行还要依赖历史交易数据. 但是我觉得在原理上与上一个项目相比,并 ...

  4. Dapp开发教程一Asch Dapp

    1基本流程 Asch有三种网络类型,分别是localnet,testnet,mainnet,后两种是发布到线上的,可以通过公网访问.第一种localnet是运行在本地的,只有一个节点的私链,主要是为了 ...

  5. Dapp开发教程一 Asch Dapp Hello World

    1 基本流程 Asch有三种net,localnet,testnet,mainnet,后两种是发布到线上的,可通过公网访问. 第一种localnet是运行在本地的.只有一个节点的私链,主要是为了方便本 ...

  6. Dapp开发教程一 Asch Dapp Hello World 1

    1 基本流程 Asch有三种net,localnet,testnet,mainnet,后两种是发布到线上的,可通过公网访问. 第一种localnet是运行在本地的.只有一个节点的私链,主要是为了方便本 ...

  7. MIP开发教程(三) 使用MIP-CLI工具调试组件

    一 . 在 mip-extensions 仓库中创建新的组件 二 . 预览调试组件 三 . 在 MIP 页中引用自己编写的 MIP 组件 四 . 组件提交到 GitHub 仓库时需要进行校验 站长开发 ...

  8. Dapp开发教程二一 Asch Dapp Asset

    前一篇文章介绍了asch dapp开发的基本流程,这一次我打算创建一个拥有内置资产的dapp,并顺便介绍下前后端通讯的协议和常用接口. 1 创建一个带内置资产的dapp 其实这篇文章有些标题党,因为创 ...

  9. Libusb开发教程三 USB设备程序开发

    首先,需要指出本篇博客的基础仍然是基于 libusb V1.0 以下.主要考虑到保持与之前博客的连贯性,在上一篇博客中介绍了老版本中可以被调用的 API 以及功能介绍,对于库中 API 接口函数的使用 ...

最新文章

  1. Object.create()和Object.assign()
  2. [五]RabbitMQ-客户端源码之AMQChannel
  3. HTTP-FLV的两种方式
  4. java.sql.SQLException: connection disabled
  5. 2021.08.26学习内容 Win10+GeForce GTX1650安装NVIDIA显卡驱动及CUDA11.4+cuDNN8.2
  6. java案例代码5--编码的方式--密码
  7. 百度文库无下载券无法下载解决办法
  8. 阿铭Linux_网站维护学习笔记20190408
  9. Win10系统中破解软件的注册机被自动删除的解决方法
  10. 34个非常实用的JS一行代码
  11. 写代码之外,如何再赚一份工资?
  12. python生存曲线_生存曲线的估计方法(3):寿命表法
  13. DEVC++的几个实用小技巧
  14. lightroom 闪退_【HTTP网球iOS】Lightroom解锁
  15. Java中String类的学习
  16. Spring In Action读书笔记
  17. Spring之注解开发
  18. tf.dynamic_stitch
  19. 支持多光谱数据源!LiMapper 无人机影像处理软件2.1发布
  20. GBASE南大通用为行业信创发展提速 亮相全国信创与人工智能发展博士后论坛

热门文章

  1. 知名公司入职前的「背景调查」,到底在查什么?
  2. 域名怎么跳转到另外一个网站?常见网页跳转方法和特点对比
  3. 景区展馆客流量数据如何统计
  4. 将Spring Security OAuth2授权服务JWK与Consul 配置中心结合使用
  5. PHP 微信公众号发红包
  6. 自媒体人都在用的自媒体热点网站
  7. 禾穗HERS | 嘿,姑娘,SHOW UP
  8. 如何使用xlsxwriter 写入数据到xlsx excel 设置边框实线连续内存模式
  9. 鉴别打印机真假墨盒墨水小妙招,收好勿谢!
  10. 十个最能吸引眼球的新闻词汇