改版中,提建议就可获得币视界股 
里程碑 | 基于全新SDK的ASCH开发者文档正式发布
浏览数:155 

Asch Dapp开发介绍

我们在白皮书里提到过,asch使用的是不同于以太坊和比特币的侧链架构,dapp是运行在侧链上的,每套侧链对应一个dapp。

侧链的独立性

侧链架构的好处是代码和数据独立,不增加主链的负担,避免数据过度膨胀,实际上是一种天然的分片机制。 侧链有独立的区块链,有独立的受托人或者说见证人,同时也有独立的节点网络,就是说一个侧链产生的区块只会在所有安装了该侧链的节点之间进行广播。

独立性既是优点,也是缺点。开发者完成了dapp的开发工作之后,还要考虑运维,也就是说要寻找足够的节点来运行它,否则难以维持足够的安全性。 从这一点来看,与以太坊相比,是缺陷,以太坊上一旦发布一个dapp,所有的节点都可以为它工作,并为它提供安全保障。

侧链的灵活性

但是侧链架构依然有存在的必要性,因为并非所有的应用都需要那么高的安全性。Asch给用户提供了一种可以选择的灵活性。 比方说,假设asch有1000个节点,那么其中有的dapp比较重要,需要500个节点来运行它,有的dapp不那么重要,可能只需要100个节点,这完全是由用户和开发者决定的。 在以太坊上,则只能选择一种,即全部节点都来运行它。据说以太坊也打算实现一种分片的机制,实际上这在asch系统中是一个早就解决了的问题。 另外,对于比较重要的、安全性要求较高的应用,我们依然可以选择在主链上开发,但是不可能对所有开发者开放这种权力,还是为了避免主链的膨胀和不可控(DAO事件)。

侧链的灵活性还表现在,所有的区块链参数是可以定制的,简单的比如区块间隔、区块奖励、交易费的去向等,高级用户还可以修改共识算法。 不过最重要的还是业务逻辑,侧链上可以轻易的开发出与你的业务相关的交易类型或者智能合约。 这一点是与侧链的发明者blockstreams不同,blockstreams的侧链,只是一个不同的货币系统而已,开发新型交易和智能合约很不容易。

如下图所示 黑色的圆圈代表一个节点,64个节点组成了主链的网络。红色方框之内的是1号侧链,其侧链网络是由12个节点组成。蓝色方框之内的是2号侧链,其网络是由36个节点组成。

sidechain deploy architecture

主链与侧链的互利关系

Asch的主链与侧链之间是互惠互利的关系,asch为侧链提供基础设施,比如数据库写入的api,网络通讯api,加密api等等,侧链则可以为asch补充更多的节点,以壮大整个系统。 侧链的开发者不需要提供所有的机器,可以利用已经存在主链节点,只需要节点主人安装该应用即可。 另外,主链的代币XAS可以转入侧链中,由于XAS可以在交易所交易,就相当于为侧链的资产提供了一种价值的媒介。 开发者在侧链发行一种资产后,可以直接与XAS兑换,不需要考虑交易平台的问题。

解决生产力问题

我们在sdk中为侧链提供了一套模板,或者说脚手架,也可以叫做开发框架,侧链的大部分底层功能都是写好的,dapp开发者只需要关心具体的业务逻辑,在这一点上,与以太坊的dapp开发难度相当。 Asch的侧链框架使用的语言是nodejs,我们内置了不少常用的库让开发者直接使用,当然开发者也可以自行安装所需要的,有一个庞大的javascript社区作为后盾。

下面是asch内置的模块 inbuilt modules

解决安全问题

这里的安全问题与上面提到的因为节点不足导致的问题不一样,这里说的安全问题是指侧链代码对安装者造成侵犯的问题。 我们希望每一个asch节点都能安装一个侧链,而且asch节点的主人无需信任侧链的开发者。这就需要提供一种安全防范的措施,比如,防止侧链代码读取文件系统、进行网络操作。 在asch系统中,侧链代码会以子进程的方式启动,子进程首先加载一个使用沙箱机制隔离的javascript虚拟机,这个js虚拟机就是没有require以及任何多余模块的裸体虚拟机。 然后我们为这个虚拟机植入一个定制的require和一些常用且安全的模块, 最后再加载侧链的代码。 我们还通过进程间通讯的方式提供一系列的api。 这样,侧链的框架就拥有了足够多的api,同时侧链的安装者也没有任何风险。

process structure

实例教程

接下来我们会提供一个由浅入深的教程,帮助开发者快速入门。

Dapp开发教程一 Asch Dapp Hello World

1 基本流程

Asch有三种网络类型,分别是localnet,testnet,mainnet,后两种是发布到线上的,可通过公网访问。 第一种localnet是运行在本地的、只有一个节点的私链,主要是为了方便本地测试和开发。 Dapp的开发同样要涉及到这三种网络,即

  • 第一步,在localnet开发、本地测试

  • 第二步,在testnet测试

  • 第三步,正式发布到mainnet

2 启动localnet

每个开发者都可以在本地启动自己的localnet,需要先下载asch源码

git clone https://github.com/AschPlatform/asch

下载后就可以参照该项目的README进行后面的安装、运行操作。

3 安装asch-cli

npm install -g asch-cli

注意这一步不要用淘宝的cnpm有bug

4 在本地创建一个应用

首先要进入你的asch源码目录,并确保localnet启动

cd <asch source code dir>node app.js

4.1 创建你的受托人账户

每个dapp都有独立的受托人,这些受托人也是默认的记账人,他们负责区块的生产,跨链资产的中转,与此同时可以获得交易手续费。 注册dapp的时候,我们只需要收集受托人的公钥就行,为了权力分散,最好每个秘钥分别由一个人保管 这里为了演示,我们一次性创建5个账户,一个dapp最多有101个受托人,最少是5个。

asch-cli crypto -g# 接下来输入 5 即可生成5个账户[ { address: 'AijfU9bAE6Jpt5ve7zG3LoriDamH67eLb',    secret: 'easy snap cable harvest plate tone planet yellow spot employ humble what',    publicKey: 'a437a1d4bedf738e8620920ef29542644e3366c635b16fc9faa6f5db744bcd5c' },  { address: 'ABGGUL5D2SoBaQTqDMAb3u9RdUjYBcmRxx',    secret: 'adjust edge exist hurry joke carbon spice envelope battle shuffle hawk thought',    publicKey: '522cdc822d3bec74aa5c4e972ed6cba84850f9c4d521e43fe08675e9e4759bb9' },  { address: 'AMg37s4avDUojJd6d3df7HPA3vqtRRwved',    secret: 'survey spoil submit select warm chapter crazy link actual lonely pig grain',    publicKey: '6ee3ae36166f69e8b9408d277486c9870f40c1b7c16016328737d6445409b99f' },  { address: 'AL5p8BHzhCU3e5pkjMYbcjUSz771MrQcQr',    secret: 'march struggle gap piece entry route kind pistol chunk spell honey summer',    publicKey: 'ad558e44b347a54981295fcb5ee163c2915ca03536496129103e9d72c5025d69' },  { address: 'A2WassKticpB7cx15RZfenBekthwmqXRXd',    secret: 'response modify knife brass excess absurd chronic original digital surge note spare',    publicKey: '6b2594ebeee9b072087e5f1e89e5c41ee2d73eb788b63abeedf5c04664f0ce5b' } ]

4.2 生成应用模板

应用模板包括注册dapp必须的元信息、创世块以及一个初始的目录结构

生成应用模板需要使用dapps子命令,如下所示

# 生成应用模板的时候,最好建立一个新目录mkdir asch-test-dapp && cd asch-test-dappasch-cli dapps -a

接下来,我们要回答一系列的问题,以生成应用的注册信息与创世块

? Enter DApp name Asch-test-dapp? Enter DApp description Demo of asch dapp? Enter DApp tags asch,dapp,demo? Choose DApp category Common? Enter DApp link https://github.com/AschPlatform/Asch-test-dapp.zip? Enter DApp icon url https://yourdomain.com/logo.png? Enter public keys of dapp delegates - hex array, use ',' for separator a437a1d4bedf738e8620920ef29542644e3366c635b16fc9faa6f5db744bcd5c,522cdc822d3bec74aa5c4e972ed6cba84850f9c4d521e43fe08675e9e4759bb9,6ee3ae36166f69e8b9408d277486c9870f40c1b7c16016328737d6445409b99f,ad558e44b347a54981295fcb5ee163c2915ca03536496129103e9d72c5025d69,6b2594ebeee9b072087e5f1e89e5c41ee2d73eb788b63abeedf5c04664f0ce5b? How many delegates are needed to unlock asset of a dapp? 3DApp meta information is saved to ./dapp.json ...? Enter master secret of your genesis account [hidden]? Do you want publish a inbuilt asset in this dapp? Yes? Enter asset name, for example: BTC, CNY, USD, MYASSET XCT? Enter asset total amount 1000000? Enter asset precision 8

有几个注意事项

  1. DApp link是为了方便普通用户自动安装,必须以.zip结尾, 如果您的dapp不打算开源或者没有准备好,可以把这个选项当做占位符,它所在的地址不必真实存在

  2. DApp icon url这是在阿希应用中心展示用的应用图标, 必须以.jpg.png结尾,如果该图片无法访问,阿希应用中心会展示一个默认的图标

  3. How many delegates ...这个选项表示从dapp跨链转账资产时需要多少个受托人联合签名,该数字必须大于等于3小于等于101,数字越大越安全,但效率和费用越高

  4. 创世块中可以创建内置资产,但不是必须的,内置资产无法跨链转账,只能在链内使用

4.3 注册应用到主链

注意这里的主链不是指mainnet, 每个net下都有相应的主链

我们可以使用registerdapp注册应用到主链,如下所示

asch-cli registerdapp -f dapp.json -e "someone manual strong movie roof episode eight spatial brown soldier soup motor"0599a6100280df0d296653e89177b9011304d971fb98aba3edcc5b937c4183fb

0599a6100280df0d296653e89177b9011304d971fb98aba3edcc5b937c4183fb是应用id

使用浏览器访问http://localhost:4096/api/dapps/get?id=0599a6100280df0d296653e89177b9011304d971fb98aba3edcc5b937c4183fb, 可以查询到该dapp了

{    "success": true,     "dapp": {        "name": "asch-dapp-helloworld",         "description": "A hello world demo for asch dapp",         "tags": "asch,dapp,demo",         "link": "https://github.com/AschPlatform/asch-dapp-helloworld/archive/master.zip",         "type": 0,         "category": 1,         "icon": "http://o7dyh3w0x.bkt.clouddn.com/hello.png",         "delegates": [            "a518e4390512e43d71503a02c9912413db6a9ffac4cbefdcd25b8fa2a1d5ca27",             "c7dee266d5c85bf19da8fab1efc93204fed7b35538a3618d7f6a12d022498cab",             "9cac187d70713b33cc4a9bf3ff4c004bfca94802aed4a32e2f23ed662161ea50",             "01944ce58570592250f509214d29171a84f0f9c15129dbea070251512a08f5cc",             "f31d61066c902bebc80155fed318200ffbcfc97792511ed18d85bd5af666639f"        ],         "unlockDelegates": 3,         "transactionId": "0599a6100280df0d296653e89177b9011304d971fb98aba3edcc5b937c4183fb"    }}

4.4 部署应用代码及子网络

现在我们把第二步中创建的模板代码拷贝到asch的安装目录下的dapp子目录,并改名为dapp的id

cp -r asch-test-dapp path/to/asch/dapps/0599a6100280df0d296653e89177b9011304d971fb98aba3edcc5b937c4183fb

然后把第一步创建的受托人密码写入这个dapp的配置文件中

cat path/to/asch/dapps/0599a6100280df0d296653e89177b9011304d971fb98aba3edcc5b937c4183fb/config.json{    "secrets": [        "easy snap cable harvest plate tone planet yellow spot employ humble what",         "adjust edge exist hurry joke carbon spice envelope battle shuffle hawk thought",         "survey spoil submit select warm chapter crazy link actual lonely pig grain",         "march struggle gap piece entry route kind pistol chunk spell honey summer",         "response modify knife brass excess absurd chronic original digital surge note spare"    ]}

这里我们把所有受托人的配置到同一个节点了,在生产环境中不推荐这样做,应该把秘钥尽量分散到多个节点,以防止单点故障

4.5 重启asch节点程序

path/to/asch/aschd restart

使用浏览器打开http://localhost:4096/dapps/0599a6100280df0d296653e89177b9011304d971fb98aba3edcc5b937c4183fb/,可以访问默认的一个前端页面,该页面可以进行一些简单的接口测试

也可以观察dapp的日志来排查一些问题

tail -f path/to/asch/dapps/0599a6100280df0d296653e89177b9011304d971fb98aba3edcc5b937c4183fb/logs/debug.*.log

4.6 跨链充值

dapp的前后端通讯协议一般可以分为两大类:读和写 读指的是数据查询,比如内置的区块查询、交易查询、转账记录,以及自己定义和实现的一些查询接口 写指的是合约调用或事务执行,比如发起转账、设置昵称、提现等,同样,也包括其他的由开发者实现的各种合约或事务

每一个写入操作都需要消耗燃料资产,模板dapp默认的燃料是XAS,开发者可以通过调用相关接口改为适合您的燃料类型,可以设置成任意其他资产,包括内置资产。 如果你您设置的燃料为外部资产,则需要从主链转入资产到这个dapp,这个过程叫充值,相反的过程叫做提现,这都是通过asch的跨链协议实现的。

充值有三种方式:

  1. 使用交互式的web图型界面,在【应用中心】的【已安装应用列表】

  2. 使用asch-cli deposit命令

  3. 调用asch-jscreateInTransfer函数,具体可参考asch-js接口文档

4.7 查询接口调用

查询接口一把你通过http get协议,比如

curl http://localhost:4096/api/dapps/0599a6100280df0d296653e89177b9011304d971fb98aba3edcc5b937c4183fb/blocks{    "blocks": [        {            "id": "9fae0c8200b7f4ef8c96f264e621f01a39a0b365ff42b80232aece0f3136b0e5",             "timestamp": 0,             "height": 1,             "payloadLength": 103,             "payloadHash": "c3674e36954811f869865a3b106ada847d47b6bc1ffc0a69c1859756d34cb5ad",             "prevBlockId": "",             "pointId": "",             "pointHeight": 0,             "delegate": "8065a105c785a08757727fded3a06f8f312e73ad40f1f3502e0232ea42e67efd",             "signature": "fd7423c1ce4cb82e79125e39fc13e040cefce158af69b45d035aaf5a4c78db8f66aa3e93bbdfb72bfa0dd604f64f8bebc66dd08fd17715bb77225fc0743f680b",             "count": 1        }    ],     "count": 1,     "success": true}

更多接口可以参考dapp默认接口文档

4.8 合约或事务调用

合约调用也有三种方式

  1. 在模板应用的默认前端页面,通过交互式web图型界面进行

  2. 使用asch-cli dapptransaction命令, 具体可参考asch-cli使用说明

  3. 使用asch-jscreateInnerTransaction函数, 具体可参考asch-js接口文档

5 目录结构

下面我们分析下asch dapp的目录结构

dapps/0599a6100280df0d296653e89177b9011304d971fb98aba3edcc5b937c4183fb/├── blockchain.db         // 数据库文件├── config.json           // 应用的节点配置文件,目前主要用于配置受托人秘钥├── contract              // 合约目录│   └── domain.js         // 域名合约的实现代码├── dapp.json             // 注册dapp时用到的元文件├── genesis.json          // 创世区块├── init.js               // 应用初始化代码,可以在该文件进行一些设置、事件注册等├── interface             // 查询接口的实现目录│   ├── domain.js         // 域名查询接口实现│   └── helloworld.js├── logs│   └── debug.20170928.log├── model│   └── domain.js         // 域名业务数据模型定义└── public    └── index.html        // 默认前端页面

6 实现你的业务逻辑

我曾经在这个博客里写过我们的开发理念

在asch dapp中实现一个业务逻辑,大概思路如下

6.1 定义你的数据模型

在这个环节,你需要考虑的是在区块链中保存什么数据或状态,你的账本内容是什么 哪些字段需要建立索引,以提高客户端查询速度

6.2 实现合约逻辑

这个环节,你需要考虑的是一个事务或一个调用会修改哪些状态,比如资产余额,账户属性等 我们在sdk中提供了丰富的接口可供调用,具体可参考sdk接口文档

6.3 实现查询接口

在这个环节,你需要考虑的是如何给前端返回数据,比如区块、交易,各种合约业务状态的查询等 也可以可用这个通道将一些非全局状态保存到本地节点,我们会在后续章节介绍这些高级用法。

Asch SDK API 使用说明

数据库

aync app.sdb.load(model, fields, indices)

  • model 模型名称

  • fields 加载到内存中的字段

  • indices 索引数组, 单字段索引时, 元素为字符串; 多字段索引时, 元素为字符串数组

无返回值, 出现错误时抛异常 将指定模型的数据加载到内存并建立索引, 这样可以提高查询和更新一个状态的效率 当一个数据模型需要频繁更新和查询时, 建议使用这个接口, 比如系统内置的账户余额、自增ID都使用了这个功能

示例:

await app.sdb.load('Balance', app.model.Balance.fields(), [['address', 'currency']])await app.sdb.load('Variable', ['key', 'value'], ['key'])

app.sdb.get(model, cond)

  • model 模型名称

  • cond 查询条件

返回一个数据项, 包含的字段为load时指定的字段 按指定条件查询内存中的数据, 如果该模型没有被载入内存, 会抛出异常; 查询条件包换未建索引的字段时也会抛出异常

示例:

app.sdb.get('Variable', { key: 'foo' })/* output:{  key: 'foo',  value: 'bar'}*/let balance = app.sdb.get('Balance', { address: 'foo', currency: 'XAS' })/* output:{  address: 'foo',  currency: 'XAS',  balance: '1000000'}*/

app.sdb.keys(model)

  • model 模型名称

返回一个数据模型的全部索引字段

示例:

let keys = app.sdb.keys('Variable')for (let i of keys) {  console.log(i)}/* output:foofoo1foo2*/

app.sdb.entries(model)

  • model 模型名称

返回一个数据模型的所有缓存项

示例:

let entries = app.sdb.entries('Variable')for (let [key, value] of entries) {  console.log(key, value)}/* output:foo barfoo1 bar1foo2 bar2*/

ap.sdb.lock(key)

  • key

无返回值 对一个key进行加锁, 有效期为一个区块间隔, 在同一个区块生命周期内不允许对一个key二次加锁, 否则会抛异常 该功能主要是为了解决对未确认数据的依赖问题。比如, 一个合约中需要对某账户设置昵称, 在这个合约调用被确认之前, 我们需要防止再次调用, 这种情况下可以使用加锁功能

示例:

app.sdb.lock('AC3pinmvz9qX9cj6c7VrGigq7bpPxVJq85@nickname'

app.sdb.create(model, values)

  • model 模型名称

  • values 待创建的数据项

无返回值 创建一个数据项, 如果该模型有缓存, 会实时更新缓存. 在区块确认后, 持久化到磁盘数据库

示例:

app.sdb.create('Article', {  title: 'This is an article title',  content: 'article contents',  author: 'qingfeng',  tag: 'Science'})

app.sdb.replace(model, values)

  • model 模型名称

  • values 待创建或更新的数据项

无返回值 创建或更新一个数据项, 如果数据库中无此项则创建, 否则更新. 模型必须包含主键, values必须包含主键

示例:

app.sdb.replace('Account', {  address: 'AC3pinmvz9qX9cj6c7VrGigq7bpPxVJq85',  nickname: 'Nakamoto'})

app.sdb.update(model, modifier, cond)

  • model 模型名称

  • modifier 待更新的数据项

  • cond 更新条件

无返回值 按指定条件更新一个模型的若干个数据项

示例:

app.sdb.update('Account', { nickname: 'Nakamoto' }, { nickname: 'Satoshi' })

app.sdb.increment(model, modifier, cond)

  • model 模型名称

  • modifier 待更新的数据项

  • cond 更新条件

按指定条件增量更新一个模型的若干个数据项, 只能用于更新整数类型

示例:

app.sdb.increment('Article', { votes: -10 }, { id: '10000' })app.sdb.increment('Article', { comments: 1 }, { id: '10000' })

app.sdb.del(model, cond)

  • model 模型名称

  • cond 删除条件

无返回值 按条件删除一个模型中的数据项 删除操作的底层实现目前是标记为deleted, 默认的查询接口都会过滤掉被标记的数据, 但非标准接口或协议仍然可以获取到这些已经被删除的数据

示例:

app.sdb.del('Article', { id: '100001' })

余额

app.balances.get(address, currency)

  • address 账户地址

  • currency 币种

获取指定账户、指定币种的余额

示例:

app.balances.get('AC3pinmvz9qX9cj6c7VrGigq7bpPxVJq85', 'XAS')/* output:{  address: 'AC3pinmvz9qX9cj6c7VrGigq7bpPxVJq85',  currency: 'XAS',  balance: '10000000'}*/

app.balances.increase(address, currency, amount)

  • address 账户地址

  • currency 币种

  • amount 增加的数额

无返回值 增加指定账户、指定币种的余额

示例:

app.balances.increase('AC3pinmvz9qX9cj6c7VrGigq7bpPxVJq85', 'XAS', '100000')

app.balances.decrease(address, currency, amount)

  • address 账户地址

  • currency 币种

  • amount 减少的数额

无返回值 减少指定账户、指定币种的余额

示例:

app.balances.decrease('AC3pinmvz9qX9cj6c7VrGigq7bpPxVJq85', 'XAS', '100000')

app.balances.transfer(currency, amount, from, to)

  • currency 币种

  • amount 转移的数额

  • from 源地址(发款人)

  • to 目的地址(收款人)

无返回值 两个账户之间转移资产

示例:

app.balances.transfer('XAS', '100000', 'AC3pinmvz9qX9cj6c7VrGigq7bpPxVJq85', 'A4MFPoF3c9vCzZ3GGf9sNQ3rDy2q8aXuVF')

数据模型

app.model[name]

  • name 模型名称

返回一个模型的实例, 主要用于查询已确认的数据

fields()

返回该模型所有字段

count(cond)

  • cond 查询条件

返回Number 表示指定条件的数据项总数

示例:

app.model.Block.count({ height: { $lt: 100 } })/* output:99/*

exists(cond)

  • cond 查询条件

返回Boolean 表示指定条件的数据项是否存在

示例:

app.model.Transaction.exists({ id: '9a5ec0669c79b9f5a1d5a4dbb2c200bc28c9ea829dbff71f41cbb2ad5a7d9b01' })/* output:false/*app.model.Account.exists({ nickname: 'Nakamoto' })/* output:true*/

findOne(options)

options是一个对象, 包含以下元素

  • condition 查询条件

  • fields 返回的字段

查询一个指定条件的数据项

示例:

app.model.Account.findOne({ nickname: 'Nakamoto' })/* output:{  address: 'AC3pinmvz9qX9cj6c7VrGigq7bpPxVJq85',  nickname: 'Nakamoto',  ...other values}*/

findAll(options)

options是一个对象, 包含以下元素

  • condition 查询条件

  • fields 返回的字段

  • sort 排序字段

  • limit 返回的最大数量

  • offset 偏移量

查询指定条件的所有数据项

示例:

app.model.Transfer.findAll({ senderId: 'AC3pinmvz9qX9cj6c7VrGigq7bpPxVJq85'})/* output:[  {    tid: "50e062f25946d220b924cb5ec6e52e260e44c9417d9f3c8ea041b704e06895f7",    senderId: "AFnwUuET2XddPtqpFb2ns78CQEqc7KZ6vD",    recipientId: "asdasdasd",    currency: "CCTime.XCT",    amount: "100000000",    t_timestamp: 38660145,    t_type: 3,    t_height: 93953  },  {    tid: "f15ce92add809b4a132936d514dce7fa7bdc15e850e7c026a001625b48595af3",    senderId: "AFnwUuET2XddPtqpFb2ns78CQEqc7KZ6vD",    recipientId: "asdasd",    currency: "CCTime.XCT",    amount: "100000000",    t_timestamp: 38660096,    t_type: 3,    t_height: 93948  }]*/

路由

  • path 路径

  • handler http请求处理函数, async类型

app.route.get(path, handler)

注册一个get类型的http请求处理函数

app.route.post(path, handler)

注册一个post类型的http请求处理函数

app.route.put(path, handler)

注册一个put类型的http请求处理函数

费用池

app.feePool.add(currency, amount)

  • currency 币种

  • amount 数额

无返回值 将资产加入费用池(在每一个round结尾平均分给记账人)

示例:

app.feelPool.add('XAS', '10000000')

自增ID

app.autoID.get(name)

  • name ID类型名称

返回String 获取一个类型的当前最大ID

app.autoID.increment(name)

  • name ID类型名称

返回String 对指定类型的ID增加1并以字符串形式返回更新后的数值, 相当于原子的++1, 超大数也适用

示例:

const AID = 'article_id'app.autoID.get(AID) === '0'app.autoID.increment(AID) === '1'app.autoID.get(AID) === '1'

日志

app.logger.setLevel(level)

app.logger.log()

app.logger.trace()

app.logger.debug()

app.logger.info()

app.logger.warn()

app.logger.error()

示例:

app.logger.setLevel('debug')app.logger.setLevel('info')logger.log('hello');logger.trace('hello', 'world');logger.debug('hello %s',  'world', 123);logger.info('hello %s %d',  'world', 123, {foo:'bar'});logger.warn('hello %s %d %j', 'world', 123, {foo:'bar'});logger.error('hello %s %d %j', 'world', 123, {foo:'bar'}, [1, 2, 3, 4], Object);

工具类

app.validate(type, value)

  • type 待验证的数据类型

  • value 待验证的数据值

验证一个数据是否符合规范, 不符合则抛出异常

示例:

app.validate('amount', '10000') // paseapp.validate('amount', 10000) // throwsapp.validate('amount', 'abc') // throwsapp.validate('amount', '1e10') // throws

app.registerContract(type, name)

  • type 合约数值类型或编号

  • name 合约的字符串名称

无返回值 为合约注册一个数字类型, 未注册的合约无法被外部调用

示例:

app.registerContract(1001, 'cctime.postArticle')

app.getContractName(type)

  • type 合约的数字类型或编号

根据合约编号查询名称

示例:

app.getContractName(1001) === 'cctime.postArticle'

app.registerFee(type, min, currency)

  • type 合约的数字类型或编号

  • min 最小费用

  • currency 币种

为一个合约注册最小费用, 不固定资产, 可通过currency参数指定收哪种资产作为手续费 min表示最小费用, 实际调用合约的时候, 费用不能小于min, 但可以大于, 超过的部分自动放入费用池

示例:

app.registerFee(1001, '100000', 'XAS')

app.getFee(type)

  • type 合约的数字类型或编号

获取指定合约的费用设定

示例:

app.getFee(1001)/* output:{  min: '100000',  currency: 'XAS'}*/

app.setDefaultFee(min, currency)

  • min 最小费用

  • currency 币种

为系统的所有合约设置默认手续费

示例:

app.setDefaultFee('10000', 'XAS')

app.getRealTime(epochTime)

  • epochTime 距离创世区块生成时间的秒数

返回完整的时间戳, 即区块创世时间加上偏移量, 单位为毫秒 Asch系统中底层存储和上层查询的时间戳均为一个偏移量, 并非实际时间戳, 可以调用这个函数转换为真实的时间戳

示例:

app.getRealTime(4353634)

app.registerHook

// TBD

app.custom[]

应用的名字空间, 可用来保存应用本身自定义的一些全局变量, 主要是为了与系统级的全局变量进行隔离