[关闭]
@zhenxi 2018-01-25T20:47:22.000000Z 字数 12738 阅读 2851

Asch DAPP核心开发流程解析

Asch DAPP


1.准备工作

基础知识:明白什么是区块链?什么是侧链?怎么用linux?怎么用nodejs?bitcoin的基本运行原理?什么是共识?什么是dpos?什么是受托人?什么是主密码?什么是私钥、公钥、地址?什么是创世块?什么是资产?这些弄明白了(最起码得大体了解)再往下看,否则就是事倍功半,真没必要往下看了。
概念理解:Asch的DAPP是运行在侧链上的,每个DAPP也可以简单理解为一条侧链,具有区块链的基本属性,比如共识机制(默认是dpos,高端玩家可以定制自己的共识算法)、区块信息、转账交易记录、P2P广播通讯、数据库文件等,跟Asch主链有相同的加密算法、地址生成算法,也就是说同一个账户在主链和DAPP中是通用的(通用指的是同一个密码登陆进去后地址相同)。
OS:Ubuntu 14.04.x 或者 16.04.x(物理机、虚拟机或者Bash on Ubuntu on Windows都可以)
IDE:vscode
nodejs: 8.4.0
npm:5.3.0

如下链接为 DAPP开发前必读文章 (包含里面的那些链接文章也要读)
https://github.com/AschPlatform/asch/blob/master/docs/asch_dapps_introduction.md
https://github.com/AschPlatform/asch/blob/master/docs/dapp_docs/1_hello.md
https://github.com/AschPlatform/asch/blob/master/docs/asch_sdk_api.md
https://github.com/AschPlatform/asch/blob/master/docs/asch_dapp_default_api.md
https://github.com/AschPlatform/asch/blob/master/docs/asch_cli_usage.md
https://github.com/AschPlatform/asch/blob/master/docs/asch_http_interface.md

在开发期间遇到的任何的技术问题都可以去https://github.com/AschPlatform/asch/issues查找或者创建新的issue。

友情提示:区块链是块大蛋糕,但吃之前先给自己做个评估,看是否有能力吃的下、能吃多少、是否能吃的顺心。

2.搭建本地localnet

localnet简单理解就是Asch私有链,这里是为了方便DAPP开发而搭建的。localnet上的DAPP如果开发、测试顺利通过,相当于整个DAPP已完成90%的工作。

  1. # Install dependency package
  2. sudo apt-get install curl sqlite3 ntp wget git libssl-dev openssl make gcc g++ autoconf automake python build-essential -y
  3. # libsodium for ubuntu 14.04
  4. sudo apt-get install libtool -y
  5. # libsodium for ubuntu 16.04
  6. sudo apt-get install libtool libtool-bin -y
  7. # Install nvm
  8. curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash
  9. # This loads nvm
  10. export NVM_DIR="$HOME/.nvm"
  11. [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
  12. [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
  13. # Install node and npm for current user.
  14. nvm install v8
  15. # check node version and it should be v8.x.x
  16. node --version
  17. # git clone sourece code
  18. git clone https://github.com/AschPlatform/asch && cd asch && chmod u+x aschd
  19. # Install node packages
  20. npm install
  21. 至此,完成localnet服务端部分的构建。
  1. # Web Wallet
  2. cd public/
  3. npm install bower -g
  4. npm install browserify -g
  5. npm install gulp -g
  6. npm install
  7. # angular chose "angular#~1.5.3 which resolved to 1.5.11 and is required by ASCH"
  8. bower install
  9. npm run build
  10. gulp build-test #This make the front-end files in public dir.
  11. 至此,完成localnet前端页面(钱包UI)的构建。
  1. // 启动Asch
  2. cd asch && node app.js // 日志打印到屏幕终端,ctrl+c结束进程
  3. 或者
  4. cd asch && ./aschd start // 守护进程方式启动,日志记录到logs/debug.log中。./aschd stop结束进程
  5. 或者其它的启动方式,比如pm2 start app.js

然后在浏览器中打开 http://localhost:4096,如果能看到Asch钱包登陆页面说明localnet的搭建和启动成功(localnet的创世账户密码:"someone manual strong movie roof episode eight spatial brown soldier soup motor",里面有一亿XAS)。如果页面报错,则需要检查app.js进程是否存在、日志报错信息等。

3.注册并启动第一个DAPP

3.1 发行资产(UIA)

发行自己的资产,请参考这个链接http://docs.asch.mobi/docs/asch_issue_assets.html
这里我们在钱包页面生成一个新账户A,主密码为“almost journey future similar begin type write celery girl month forget breeze”,对应的地址是AAjoobuMcmkQ1gS8vTfBy3dQavBiH7sBCF,该账户将作为下面的cctime.XCT资产发行者、cctime DAPP的注册者以及给其它账户转账的发送者,创世账户给该地址转1000个xas。

A账户去注册发行商cctime,然后注册一个资产XCT(上限100亿,精度8,描述为:cctime.org的token时讯币,其它用默认的),最后发行10亿的cctime.XCT。

3.2 利用模板启动第一个DAPP

Asch作为一个区块链开发平台,为开发者提供了一个安全且高性能的虚拟机(asch-sandbox)以及一系列的内部接口、框架等。其中的一个脚手架就是利用模板来生成自定义的DAPP,通过回答几个问题就可以完成DAPP框架代码的开发工作。
模板包含了启动一个dapp需要的最少数据

3.2.1 生成DAPP元信息(创世块)

  1. // 生成5个受托人账号
  2. zhenxi@MiAir:~/Codes/github/AschPlatform/asch$ asch-cli crypto -g
  3. ? Enter number of accounts to generate 5
  4. [ { address: 'A9pDhjc7NuYMWYLxkgAgVmHE2NQ7diMcWX',
  5. secret: 'fit night someone unveil dwarf believe middle evidence puzzle hotel common choose',
  6. publicKey: 'afdf69f0da9ff333218f2cd10cb0a907c2e76788f752b799cb1dab3a9f03bf63' },
  7. { address: 'A2jvQUNowLgP9hHMN3tSCAUkakuigGRytB',
  8. secret: 'lawsuit ride civil slice kitchen unfold unable lumber prevent suspect finger chunk',
  9. publicKey: '67d52a0265f9e5366660c8b384cee56d3f8b5737b2dd3c617d22df83b5ebef02' },
  10. { address: 'A7JjHgx7ACCJ6AxypBn4Qt9NrGaY4JuZDF',
  11. secret: 'absurd sweet blast dinner battle zero ladder steak coral fork venture coffee',
  12. publicKey: '39c2322600a0c81ecfa97119ec8e2d5bfb73394914d92b54e961846a987e4e22' },
  13. { address: 'AMu7kuP9TjywkUQQQgALid96So2VCve5QB',
  14. secret: 'topic ramp throw cloud moment jungle bar series task protect erupt answer',
  15. publicKey: '4740d2c16bf6c5a174eba1e0f859253a64851d30acbc9655b01394af82d3e325' },
  16. { address: 'A9BzaJDkyzb9RVAFjsePMemSVXMDLiQpjJ',
  17. secret: 'shoot tired know dish rally kiwi snack patrol bunker ocean panel this',
  18. publicKey: 'b433c226645981477642491f77de7b8d63274aa51f932bbe1fe3f445a8aaecc9' } ]
  19. Done
  1. // 根据模板和自定义参数生成自己的dapp框架,包含创世块、配置文件、数据和合约、接口、日志等目录
  2. cd dapps
  3. mkdir cctime && cd cctime
  4. zhenxi@MiAir:~/Codes/github/AschPlatform/asch/dapps$ asch-cli dapps -a
  5. Copying template to the current directory ...
  6. ? Enter DApp name cctime
  7. ? Enter DApp description cctime.org
  8. ? Enter DApp tags news
  9. ? Choose DApp category News
  10. ? Enter DApp link http://github/aschplatform/cctime.zip
  11. ? Enter DApp icon url http://a.com/x.png
  12. ? Enter public keys of dapp delegates - hex array, use ',' for separator afdf69f0da9ff333218f2cd10cb0a907c2e76788f752b799cb1dab3a9f03bf63,67d52a0265f9e5366660c8b384cee56d3f8b5737b2dd3c617d22
  13. df83b5ebef02,39c2322600a0c81ecfa97119ec8e2d5bfb73394914d92b54e961846a987e4e22,4740d2c16bf6c5a174eba1e0f859253a64851d30acbc9655b01394af82d3e325,b433c226645981477642491f77de7b8d63274aa51f932bb
  14. e1fe3f445a8aaecc9
  15. ? How many delegates are needed to unlock asset of a dapp? 3
  16. DApp meta information is saved to ./dapp.json ...
  17. ? Enter master secret of your genesis account [hidden] //这里输入的密码为:'almost journey future similar begin type write celery girl month forget breeze'
  18. ? Do you want publish a inbuilt asset in this dapp? Yes
  19. ? Enter asset name, for example: BTC, CNY, USD, MYASSET XCT
  20. ? Enter asset total amount 100000000
  21. ? Enter asset precision 8
  22. New genesis block is created at: ./genesis.json
  23. // 生成的文件如下
  24. zhenxi@MiAir:~/Codes/github/AschPlatform/asch/dapps/cctime$ ls
  25. config.json contract dapp.json genesis.json init.js interface model public

3.2.2 注册DAPP到localnet上

  1. zhenxi@MiAir:~/Codes/github/AschPlatform/asch/dapps/cctime$ asch-cli registerdapp -f dapp.json -e "almost journey future similar begin type write celery girl month forget breeze"
  2. // 返回结果为dappId
  3. 75d084dc91221b380e7a3c6b3b7467935572b4ebaa1e9a3db91e1239377c1fed

此时钱包的“应用列表”就可以看到该应用了。

将cctime目录改名为75d084dc91221b380e7a3c6b3b7467935572b4ebaa1e9a3db91e1239377c1fed,这样就完成了dappp在本地节点的安装(整个过程是手工安装,以后正式上线后,其他节点安装时无须这么麻烦,只需要在页面点击就可以安装)。

  1. cd ..
  2. mv cctime 75d084dc91221b380e7a3c6b3b7467935572b4ebaa1e9a3db91e1239377c1fed

3.2.3 启动DAPP

重启asch服务,默认会加载dapps目录下的所有的dapp。此时dapp中只有模板预置的信息,虽然此时没有自定义的数据、合约、接口等信息,但dapp已经是一条具备最小功能的侧链了,只需要配置好受托人就可以产块和转账。
此时钱包的“已安装应用列表”就可以看到该应用了。

启动后,dapps/75d084dc91221b380e7a3c6b3b7467935572b4ebaa1e9a3db91e1239377c1fed下会多出一个blockchain.db文件。dapp第一次启动时会创建2种表,1:用户自定义表,由model下的数据模型文件决定,2:asch_sandbox预置表,这些都是区块链的系统表。

目前我们这个DAPP的blockchain.db包含如下表:
accounts dapp内账户信息,asch_sandbox预置
blocks dapp区块表,asch_sandbox预置
domains 域名表,非asch_sandbox预置表,而是根据model下的domain.js定义生成的表,可以删除
transactions dapp交易表,asch_sandbox预置
variables dapp变量表,asch_sandbox预置
balances dapp余额表,asch_sandbox预置
deposits 主链往dapp上充值记录表,asch_sandbox预置
round_fees dapp的dpos共识下,每轮的手续费详情表,asch_sandbox预置
transfers dapp内部转账表,asch_sandbox预置

浏览器打开 http://localhost:4096/dapps/75d084dc91221b380e7a3c6b3b7467935572b4ebaa1e9a3db91e1239377c1fed/ 如果能看到“Asch DApp Example 1 - hello world”页面说明dapp启动成功。

此时用'almost journey future similar begin type write celery girl month forget breeze'登陆后看到XCT(这是dapp内置的资产,只能在dapp内使用,不是我们之前在主链发行的用户资产cctime.XCT)余额为100000000,与我们注册dapp时设定的参数一致。

3.2.4 配置DAPP的受托人

编辑dapps/75d084dc91221b380e7a3c6b3b7467935572b4ebaa1e9a3db91e1239377c1fed/config.json文件,将上面5个受托人的密码加入到该文件中

  1. {
  2. "secrets": [
  3. "fit night someone unveil dwarf believe middle evidence puzzle hotel common choose",
  4. "lawsuit ride civil slice kitchen unfold unable lumber prevent suspect finger chunk",
  5. "absurd sweet blast dinner battle zero ladder steak coral fork venture coffee",
  6. "topic ramp throw cloud moment jungle bar series task protect erupt answer",
  7. "hoot tired know dish rally kiwi snack patrol bunker ocean panel this"
  8. ]
  9. }

然后重启asch,此时dapp已经可以产块了。具体可以看dapp的日志输出 dapps/75d084dc91221b380e7a3c6b3b7467935572b4ebaa1e9a3db91e1239377c1fed/logs/debug.20180125.log

3.2.5 DAPP基本操作

3.2.5.1 往DAPP中进行充值

用密码“almost journey future similar begin type write celery girl month forget breeze”登陆Asch钱包,往DAPP中充值100 XAS(这个充值动作实质就是跨链操作),消耗0.1XAS手续费。

10秒后(也有可能是1秒、3秒或者8秒,这里实质是要等一个区块确认后才能看到充值余额,而10秒是一个块的默认出块时间),去 http://localhost:4096/dapps/75d084dc91221b380e7a3c6b3b7467935572b4ebaa1e9a3db91e1239377c1fed/ 页面验证充值

此时AAjoobuMcmkQ1gS8vTfBy3dQavBiH7sBCF这个账户在主链的XAS余额减少了100.1(0.1是充值手续费),而在侧链cctime中XAS余额增加了100。

同理充值100 cctime.XCT资产。

3.2.5.2 DAPP内部转账

生成另一个账户B,主密码“attack exist tuna tunnel enhance coach favorite safe buffalo faculty robot blue”,地址为APoBYfxx266FH5HCrkBdebAZ2wFPjJ3Q4z。
A调用DAPP的系统内置合约给B用户转账10 cctime.XCT,这里展示的是通过页面调用接口(该页面的Contract invoke可以理解为postman),该合约的详细描述见DAPP_默认API

转账时的数额是 真实数额10*资产精度数值(区块链上前端看到的有小数,但后端都是按照整数计算的)
这里选择编号为3的system合约就是dapp内部转账(小于1000的都是system内置合约,目前已经1-4共4个合约,详情见文档:https://github.com/AschPlatform/asch/blob/master/docs/asch_dapp_default_api.md#3-%E4%BA%8B%E5%8A%A1transactions

此时A账户在DAPP中的XAS余额减少0.1(DAPP默认手续费是XAS,可以自定义为其它资产或者不收手续费),cctime.XCT余额减少10。B账户的cctime.XCT余额增加10.

账户A的余额:

账户B的余额:

3.2.5.3 DAPP内给地址设置昵称

OK,上面BB了那么多,其实就干了一件事:没有写一行代码只是利用现有的工具就搭建起来一条具备跨连充值、跨链提现、内部转账功能的侧链,并且还可以创建内置资产。从下面章节开始,才是用户自定义数据、智能合约、外部接口的代码编写。

4 DAPP核心开发流程

开发dapp跟把大象放进冰箱的步骤一毛一样,本章节就是围绕下面这三步来做 :)

下面这些操作都是在dapps/75d084dc91221b380e7a3c6b3b7467935572b4ebaa1e9a3db91e1239377c1fed/目录下进行的。

4.1 自定义用户数据模型(RDBMS表)

注意事项:

在model目下创建article.js文件,该文件定义了articles表,内容如下

  1. module.exports = {
  2. name: 'articles',
  3. fields: [
  4. // 文章id
  5. {
  6. name: 'id',
  7. type: 'String',
  8. length: '20',
  9. not_null: true,
  10. primary_key: true
  11. },
  12. // 发表文章时的交易id
  13. {
  14. name: 'tid',
  15. type: 'String',
  16. length: 64,
  17. not_null: true,
  18. unique: true
  19. },
  20. // 文章作者的地址
  21. {
  22. name: 'authorId',
  23. type: 'String',
  24. length: 50,
  25. not_null: true
  26. },
  27. // 时间戳,距离cctime创世块经历的秒数
  28. {
  29. name: 'timestamp',
  30. type: 'Number',
  31. not_null: true
  32. },
  33. // 文章标题
  34. {
  35. name: 'title',
  36. type: 'String',
  37. length: 256,
  38. not_null: true
  39. },
  40. // 如果文章是引用的,其url地址定义
  41. {
  42. name: 'url',
  43. type: 'String',
  44. length: 256
  45. },
  46. // 文章内容
  47. {
  48. name: 'text',
  49. type: 'String',
  50. length: 4096,
  51. not_null: true,
  52. },
  53. // 文章标签
  54. {
  55. name: 'tags',
  56. type: 'String',
  57. length: 20
  58. },
  59. // 文章得到的投票数
  60. {
  61. name: 'votes',
  62. type: 'Number',
  63. not_null: true
  64. },
  65. // 文章评论
  66. {
  67. name: 'comments',
  68. type: 'Number',
  69. not_null: true,
  70. default: 0
  71. },
  72. // 举报
  73. {
  74. name: 'reports',
  75. type: 'Number',
  76. not_null: true,
  77. default: 0
  78. }
  79. ]
  80. }

4.2 自定义用户合约

这一步里面主要用到的文档是:https://github.com/AschPlatform/asch/blob/master/docs/asch_sdk_api.md
app.xxx这种接口都来自asch_sandbox。
合约就是业务逻辑处理。

  1. // 在contrac目录下创建cctime.js
  2. module.exports = {
  3. // 定义发布文章的智能合约函数
  4. postArticle: async function (title, url, text, tags) {
  5. // 下面这些if判断是用来校验文章信息是否合法,有些是可以用 app.validate(type, value) 接口进行重写的
  6. if (!url && !text) {
  7. return 'Should provide url or text'
  8. }
  9. if (url && text) {
  10. return 'Both url and text are not supported'
  11. }
  12. if (!tags) {
  13. return 'Should provide tags'
  14. }
  15. if (tags.length > 20) {
  16. return 'Invalid tags size'
  17. }
  18. if (!title) {
  19. return 'Should provide title'
  20. }
  21. if (title.length > 256) {
  22. return 'Invalid title size'
  23. }
  24. if (url && url.length > 256) {
  25. return 'Url too long'
  26. }
  27. if (text && text.length > 4096) {
  28. return 'Text too long'
  29. }
  30. //TODO validate url format
  31. // 对key进行加锁,防止重复数据。这里的意思是如果用户发表的文章是转载其它url的,则需要检查该url是否已经被发布过了。需要在内存中将这个url的key锁住,防止同一个区块内其他人再次发布这个url然后去检查db中是否已经有这个url了
  32. if (url) {
  33. app.sdb.lock('postArticle@' + url)
  34. let exists = await app.model.Article.exists({ url: url })
  35. if (exists) {
  36. return 'Url already exists'
  37. }
  38. }
  39. // 调用app.sdb.create将校验过的文章信息插入到articles表中
  40. app.sdb.create('Article', {
  41. title: title,
  42. url: url || '',
  43. text: text || '',
  44. tags: tags,
  45. id: app.autoID.increment('article_max_id'),
  46. votes: 0,
  47. tid: this.trs.id,
  48. authorId: this.trs.senderId,
  49. timestamp: this.trs.timestamp,
  50. comments: 0
  51. })
  52. }
  53. }
  1. // 修改init.js
  2. // 注册合约
  3. module.exports = async function () {
  4. console.log('enter dapp init')
  5. // 注册合约,用户自定义合约编号是从1000开始的,之前的都是system保留合约。
  6. app.registerContract(1000, 'cctime.postArticle')
  7. app.events.on('newBlock', (block) => {
  8. console.log('new block received', block.height)
  9. })
  10. }

4.3 自定义查询接口

// interface目录下新增index.js,内容如下

  1. // 根据条件查询被举报次数小于的文章,并按照时间倒序排
  2. async function getArticlesByTime(options) {
  3. // 查询符合条件的记录数
  4. let count = await app.model.Article.count({ reports: { $lt: 3 } })
  5. // 查询符合条件的记录详情
  6. let articles = await app.model.Article.findAll({
  7. condition: {
  8. reports: { $lt: 3 }
  9. },
  10. limit: options.limit || 50,
  11. offset: options.offset || 0,
  12. sort: { timestamp: -1 }
  13. })
  14. return { count: count, articles: articles }
  15. }
  16. // 定义url路由,根据条件获取文章
  17. app.route.get('/articles', async (req) => {
  18. // 获取url传进来的参数
  19. let query = req.query
  20. // 默认按照timestamp来排序
  21. if (!query.sortBy) {
  22. query.sortBy = 'timestamp'
  23. }
  24. // 设定本次查询的key
  25. let key = ['articles', query.sortBy, query.limit, query.offset].join('_')
  26. // 如果内存中有本次查询的key,则直接返回其对应的结果
  27. if (app.custom.cache.has(key)) {
  28. return app.custom.cache.get(key)
  29. }
  30. let res = null
  31. if (query.sortBy === 'timestamp') {
  32. res = await getArticlesByTime(query)
  33. } else {
  34. throw new Error('Sort field not supported')
  35. }
  36. // 从articles表只能获取到地址,没有昵称,下面一系列的操作是把昵称加入到返回结果中
  37. let addresses = res.articles.map((a) => a.authorId)
  38. let accounts = await app.model.Account.findAll({
  39. condition: {
  40. // sql的in查询
  41. address: { $in: addresses }
  42. },
  43. fields: ['str1', 'address']
  44. })
  45. let accountMap = new Map
  46. for (let account of accounts) {
  47. accountMap.set(account.address, account)
  48. }
  49. for (let article of res.articles) {
  50. let account = accountMap.get(article.authorId)
  51. if (account) {
  52. article.nickname = account.str1
  53. }
  54. }
  55. // 将key和其值加入到内存中
  56. app.custom.cache.set(key, res)
  57. return res
  58. })

4.4 测试
重启asch服务。
此时已经能看到我们刚才定义的“发布文章的”智能合约

根据编号为1000的智能合约的定义,我们可以传4个参数给后台。
titile:news_title
url:null
test:This is news text.
tags:tag1

成功创建文章,交易id为:746279f5c7831968fb8e507456651f946b4aa5127125de6070143a8f82f00ffb

根据interface定义的接口去查询数据

  1. http://localhost:4096/api/dapps/75d084dc91221b380e7a3c6b3b7467935572b4ebaa1e9a3db91e1239377c1fed/articles

未完待续

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注