Open cssmagic opened 5 years ago
感谢, 受教了, 有个疑问想请教一下. DAPP 有办法应用到普通用户身上么? 因为目前看来 一定要安装星云链的扩展程序, 还要有自己的钱包.
DApp 确实有这样的门槛存在。
有一些游戏的做法是在自己的后端帮用户管理钱包、帮用户与链交互。这也是一种解决方案。
如果由后端统一处理, 创建钱包的动作也是后端来做么? 所以引导用户氪金之前, 钱包内余额是0, 这之前所有产生的交易所用到的 Gas 都要游戏所有者进行垫付吧? 还是其实只有一个钱包, 所有用户用的都是这个钱包产生交易..... 这样好像用区块链没什么意义了呀.
另外, 关于不可篡改这个优点. 如果小明建立合约时设置了一个 update
方法, 不就可以修改之前的预言了?
刚刚接触这个, 一头雾水 问题比较多 还请前辈赐教
如果由后端统一处理, 创建钱包的动作也是后端来做么?……这样好像用区块链没什么意义了呀.
这确实很考验游戏的设计者,在便捷性和公信力之间找平衡。
关于不可篡改这个优点……
这个在文章末尾专门有讲,可以扫码阅读公众号里的完整版。
你问的都是好问题,欢迎多交流!
已关注. 感谢科普.
再次手动点赞
(本文是演讲的下半部分,建议您先读完 上半部分 再阅读本文。)
在做好准备工作之后,小明终于开始动手了!
我们回顾一下传统 Web App 和 DApp 的架构图。
在开发传统 Web 应用时,我们一般会先确定后端接口。同理,在开发 DApp 时,我们通常也会先把合约端的接口准备好。
首先,我们需要了解合约代码的编写模式。
讲到这里,终于可以给大家看前面那段 “被打码” 的代码了。这是一段最小化的星云链智能合约代码。我们来看一看它有哪些要素:
CommonJS 模块(必须)
先看一眼最后一行,前端同学们肯定秒懂。整个合约其实就是一个 CommonJS 模块。
导出一个类(必须)
这个模块导出的是一个类,这个类正是合约的主体。
构造器方法(可选)
既然合约主体是一个类,而类本身并不能直接运行,那么这就意味着合约每次被调用时,这个类都会被实例化。类可以有(也可以没有)一个构造器方法,这个方法在每次实例化时执行(这是类的基本行为,大家应该都很熟悉了)。构造器方法并不是必需的,我们可以根据实际需要来编写它。
初始化方法(必须)
这个类需要有一个
init()
方法,这个方法只会在合约部署成功后被自动执行一次,以后就再也无法被调用了。因此,这个初始化方法通常用来完成整个 “应用” 级别的初始化工作。它是必需的,哪怕你的应用不需要初始化,也需要提供一个空的init()
方法。私有方法(可选)
这个类可以有一些私有方法,私有方法可以在类的内部被调用,但它不会暴露成为合约的公开接口。私有方法的特征是下划线开头,比如上图代码中的
_helper()
。我们可以根据实际需要定义私有方法,便于合约逻辑的实现,但它不是必需的。这个 “下划线” 的命名约定并不是 JS 语言本身的特性,而是星云链智能合约运行环境的设计。这个设计也很符合前端开发者的命名习惯。
公开接口(必须)
除去上述特定名称的方法和私有方法以外,剩下的所有方法都会作为合约的公开接口暴露出来,以供客户端调用。比如上图代码中的
method()
。只要满足以上条件,就是一个合法的智能合约了。也就是说,你可以按照自己的开发习惯来组织合约代码,只要满足上述条件就可以了。
其次,另一项重要的基础知识是 “合约存储区”。
前面提到过,每个合约都有自己的独立存储区,那么智能合约自然也提供了操作存储区的 API。星云链合约存储区提供了多种操作方式,本文为方便讲解,只介绍其中最基础的用法。
在智能合约的运行环境中,有一个
LocalContractStorage
全局对象,它提供了三个最基本的 API(参见上图)。前端工程师看到这里应该会会心一笑,因为这跟浏览器里的本地存储 API 几乎一致。我们在浏览器端要实现持久化存储,最常用的就是本地存储(localStorage);而在智能合约中,实现持久化存储也是通过类似的 API 和类似的思路来实现的。
接下来,我们就要开始实现合约接口了。
既然智能合约要提供与传统后端功能相当的接口,那不妨先来看盾,传统的后端接口是怎样的。
在小明的网站中,后端至少要提供两个接口,分别对应以下两项功能:
获取预言列表
/api/getAllItems
这是一个 GET 接口。不需要参数。返回所有预言组成的数组,每个数组成员都是一个对象,用于记录某条预言的基本数据——内容(
content
)和发布时间(published_at
)。发布预言
/api/create
这显然是一个 POST 接口。在向它提交数据时,需要提供一个
content
参数,以便传入预言内容;并不需要提供发布时间,因为服务端会记录时间。在设计这种 “新增” 操作的接口时,我们通常会把发布成功的这一条数据返回,以便客户端立即把新内容渲染出来。
如何在合约端实现这两个接口的功能?别着急,慢慢来。小明先把合约的大致结构写好:
这个合约似乎并不需要在构造器里做什么事儿,留空就行。
然后需要做一些初始化工作。小明需要做的就是在合约存储区里建一个 key,取名
'items'
,用来保存所有预言。在初始化阶段,显然啥预言也没有,就存一个空数组进去。随后就要进入重点了,小明开始写两个合约接口。
第一个接口是 “获取预言列表”,即
getAllItems()
方法。实现这个方法其实非常简单——从存储区里读出
'items'
这个 key 的值,返回就行了。写出的代码也是一目了然。第二个接口是 “发布预言”,即
create()
方法。这个接口稍稍复杂一些,我们一步一步来。
我们先把这条新预言的数据准备好,存到
newItem
变量中备用。接下来更新存储区:我们先取出所有预言的列表,然后把新预言 push 进去,最后把预言列表写回存储区。
在这个方法的最后,我们把这一条新预言的数据作为函数返回值,以便客户端在拿到调用结果后立即把新预言渲染出来——这与传统后端接口
/api/create
的设计保持一致。好,两个接口已经全部实现,合约代码就写好了。
合约需要部署到链上才能真正发挥作用。这里请出 “星云 Web 钱包” 来完成合约的部署和测试。
部署合约的操作比较简单,这里就不赘述了。
部署成功之后,还可以测试一下合约行为是否符合预期。(如果发现合约代码有 bug,则需要重新部署。)
准备好合约端之后,小明接下来开始实现客户端。
网页形态的客户端开发大家都很熟悉了,这里不赘述,只着重讲一下 DApp 客户端独有的行为——合约调用。
调用合约有两种情况,或者说,有两种方式:
不需要写数据时
有些合约接口是不需要向存储区写入数据的,比如仅仅读数据或纯计算。此类调用可以直接调用,并可立即得到结果。
需要写数据时
有些合约接口是需要向存储区写入数据的,即需要 “上链”。此类接口需要通过发起交易来调用,具体过程在 demo 环节已经展示过了。
在实现这两种类型的合约调用时,需要用到客户端 SDK。它的作用是帮助我们与链交互,我们不用操心区块链网络的具体地址、接口和参数等细节,直接使用 SDK 提供的接口就可以完成调用合约、转账、查询网络状态等操作了。
星云链官方的客户端 SDK 功能完备,但设计风格偏底层,在实际开发中略显繁琐。因此这里推荐一款第三方的 SDK——Nasa.js。
Nasa.js 的安装方式很简单,直接用 npm 就可以。
加载方式也很简单,在页面中引入
nasa.js
文件,随后就可以使用它提供的各种 API 了。Nasa.js 提供了近 30 个 API,在日常开发中,最常用的是以下三个:
Nasa.query()
Nasa.call()
Nasa.getTxResult()
这三者都与合约调用相关。第一项用于 “不需要写数据” 类型的合约调用,后两项涉及 “需要写数据” 的情况。具体用法会在下面的代码中详细讲解。
小明开始写网页端代码了!他先实现 “展示预言列表” 这个功能。
第一行很简单,就是把合约地址保存好。
接下来,小明需要调用合约的
getAllItems
接口,以获得所有的预言数据,并在页面中渲染出来。这个接口并不需要写数据,因此使用Nasa.query()
API 来调用它。这个 API 接受三个参数,分别是合约地址、合约接口的函数名、传给函数的参数。
通过
Nasa.query()
发生的合约调用,可以很快得到调用结果,这个过程就好像是在调用一个 Ajax 接口。Nasa.query()
的返回值是一个 Promise,Promise 包裹的值就是合约调用结果——这个设计和常见的 Ajax 库也是十分类似的。小明在 Promise 的
.then()
回调中拿到合约接口的调用结果。调用结果有一个execResult
字段,用来保存合约接口函数的返回值。小明通过它可以得到一个包含所有预言数据的数组,然后在网页上就可以渲染出预言列表了。小明接着实现第二个功能——“发布预言”。
发布页面有一个表单,小明需要给表单的提交事件绑定事件处理函数(代码略),并在这个事件处理函数中实现发布预言的操作(以下代码均写在事件处理函数中)。
前两行是准备工作,把合约地址和已经输入的文字保存好。
接下来,小明需要调用合约的
create
接口,以完成新预言的发布。这个接口需要通过发起交易来完成上链,于是小明使用Nasa.call()
API 来调用它。这个 API 同样接受三个参数,参数含义与
Nasa.query()
的三个参数完全一致。小明依次传入合适的值。这个 API 会唤起钱包插件,引导用户完成交易。
这个 API 也返回一个 Promise,但 Promise 包裹的值与
Nasa.query()
不同。因为通过交易来调用合约,无法直接得到调用结果,只有当这一笔交易被矿工处理完成并打包上链,我们才能得到这次合约调用的结果。因此,这个 Promise 只能给我们一个交易流水号,我们随后可以拿着这个流水号向链查询交易的状态和调用结果。当钱包插件把交易发出后,小明就可以在
.then()
回调中拿到交易流水号(payId
)。接下来就轮到Nasa.getTxResult()
这个 API 出场了。这个 API 接受一个参数,就是交易流水号;返回一个 Promise,Promise 包裹的值就是本次交易的状态和本次调用的结果。(查询交易结果是一个轮询的过程,不过我们不用操心这些细节,Nasa.js 会自动完成这个过程,我们只需要关心这个 Promise 就可以了。)
在
.then()
回调中,小明把交易流水号传给Nasa.getTxResult()
。由于这个 API 也返回 Promise,小明可以把它 return 出去,让这个 Promise 链条继续往下走。当查询发现交易被矿工处理完成之后,可以在下一个
.then()
回调中拿到本次交易的状态和本次调用的结果。同样还是在execResult
字段中,我们可以获取合约接口函数的返回值。当然保险起见,我们最好先判断一下交易是不是成功了。如果
status
字段的值为1
,则表示交易已被正常处理。小明在这里拿到合约接口函数
create
的返回值(也就是发布成功的这条新预言的数据),把它渲染并添加到页面中就可以了。至此,整个 DApp 从合约端到客户端,就全部开发完成了。小明把这两端的功能跑通,网站顺利上线。
网站看起来还是那个网站,不过它的本质已经升级为基于区块链的 DApp 了。
万事具备,小明静静等待下一届世界杯的到来。
2018 年世界杯,小明又一次正确预测出了冠军得主。
小明终于成功地在女朋友面前秀了一把。
从此他们幸福地生活在一起……(全剧终)
故事到这里就讲完了。不过在听故事的过程中,大家头脑中可能会冒出一些问题。
首先,这个实战案例是不是太简单了?
时间关系,我们无法在短短一次演讲中实现更多的功能。不过,互联网产品都是迭代式开发的嘛,小明确实也打算升级一下这个 DApp。他设想的新功能有:
支持多用户:这么好的应用,只有自己在用太可惜了。小明打算让它支持多用户。
可点赞:有了多用户,就可以加入一些社交元素。比如当你看到一条有意思的预言,可以给它点赞。
可打赏:不止是点赞,你甚至还可以给预言的作者打赏。
先说 “点赞”,这个功能相对比较好实现,合约端新增一个点赞接口,再给预言数据增加一个点赞数的字段,差不多就可以了。
再看 “多用户” 和 “打赏” 功能,乍一看很复杂,但其实在区块链上非常容易实现。
在开发传统 Web App 的时候,如果要识别不同用户,往往需要自己做一套账号系统,或者对接第三方社交账号。而在区块链上就不用这么麻烦,因为每个在链上的用户都有自己独一无二的身份标识——“地址”。DApp 通过地址就可以区分每位用户。
此外,在传统应用中如果要实现打赏功能,就需要接入支付平台,比如银联、支付宝或微信支付等。但这对个人开发者来说是极为困难的。别急,这里又体现出 DApp 的优势了——区块链的老本行就是记账,每条公链通常都有自己的原生货币系统,DApp 直接使用链的支付功能就可以了。
区块链 “原生的账号系统” 和 “原生的支付系统”,堪称 DApp 的两大天生神力!
这样一个 DApp 最终确实被做了出来,叫作 “我是预言帝”。这实际上也是魔法哥自己学习开发 DApp 的第一款作品。
另一个经常被问到的问题是:大家经常提到区块链 “不可篡改”,到底是指什么?小明的女朋友凭什么相信他?
在看完上面的实战案例之后,大家很可能会产生一个疑惑:我可以通过调用合约来更新合约存储区,这算不算 “篡改” 呢?
简单解释一下,区块链的 “不可篡改” 特性,主要有以下两个层面来构成:
不可撤消
区块链的精髓之一在于链式的数据结构,链式结构保证了新区块一旦追加到链上,就再也无法修改或撤消——这是因为链上的所有区块环环相扣,如果有节点试图修改其中一个区块,后续的区块就无法接上,这个修改会立刻被其它节点发现并判为非法。
因此,一笔交易一旦上链,就成为历史的一部分,永久地铭刻在那里。
公开透明
对于公链来说,链上的所有数据都是公开可查的。各条公链通常都会提供一种叫 “区块浏览器” 的工具,通过它可以方便地查到所有区块的内容、每笔交易的详情、任意地址的行为记录,甚至还有智能合约的源代码。
这意味着区块链上的每一次操作都是公开透明的,智能合约的运行逻辑和数据记录也都是公开透明的,没有暗箱操作的可能性。
说到这里,我们再来回顾一下 “修改合约存储区” 的过程:调用合约来更新存储区,本质上是一次交易,需要打包上链才能生效。因此,“修改合约存储区” 这个操作并不是直接修改已经在链上的历史记录,而是通过新记录在老记录上打补丁,我们在任一时刻得到的数据其实是所有修改记录叠加的结果。
因此,对于 DApp 来说,“不可篡改” 并不是说我们无法修改合约的存储区,而是说任何人都无法 “偷偷摸摸地修改”,因为每一次的修改记录都会明明白白地记录在链上。
讲完这个原理,我们可以把故事的结尾再补充一下。如果小明的女朋友想验证这个 DApp 上展示的预言是否真实可靠,她还需要做这两件事情:
第三个问题,开发者特别关心 DApp 的应用场景。目前已经明朗的应用场景有以下一些:
存证:利用区块链的不可篡改特性来实现存证的需求。比如小明的预言 DApp 就可以归为这一类。
数字资产:加密猫很好向大众普及了 “数字资产” 的概念,区块链和智能合约可以对数字资产进行确权。
游戏:游戏中虚拟货币和道具的概念与区块链天然契合,目前最火的 DApp 也几乎都是从这个领域里涌现出来的。
实际上区块链应用还处在早期,更多的落地场景还有待我们去探索和发现。
第四个问题,有兴趣尝试 DApp 开发的同学肯定希望获取演讲中提到的各种工具和资源。
由于 DApp 开发需要掌握和学习的资料非常多,魔法哥特别整理了一个资料库,并已在 GitHub 开源:
https://github.com/NasaTeam/Awesome-Nebulas
文中提到的第三方 SDK Nasa.js 实际上也是由魔法哥组队开发的,同样在 GitHub 开源,欢迎你一起来打磨它:
https://github.com/NasaTeam/Nasa.js
最后,说几句题外话吧,聊一聊我接触区块链这小半年来的感悟。
由于某些原因,区块链技术和区块链行业一直被广泛误解。一方面它被夸大为万能的银弹,一方面它的基础设施还极为简陋,这令很多人敬而远之,甚至 “路转黑”。区块链虽然已经诞生了近十年,但在应用层面仍然非常早期,它还有极大的不确定性,还有无数的未知等待我们去探索。
就像这张图,一片荒芜。探索者们独孤前行,然而他们相信光明的未来。
有人说,“区块链是风口、是潮流”。而我想说的是,“我们不跟随潮流,我们推动潮流——因为我们是开发者”。区块链是什么、能做什么,将由我们这些开发者来定义。期待有更多的开发者加入到这个新世界,我在这里等你!
今天的分享就到这里,谢谢!
相关阅读
© Creative Commons BY-NC-ND 4.0 | 我要订阅 | 我要打赏