二十分钟教你如何将区块链应用与函数计算相结合
前言
本篇文章适合对区块链应用感兴趣或是想要通过函数计算服务进一步开发区块链应用的新人。本文将结合阿里云区块链服务、阿里云函数计算服务、阿里云日志服务 以及社区应用 Marbles,手把手教大家如何将阿里云区块链服务与阿里云函数计算服务相结合,并进一步提供业务上的结合场景,供大家开拓思路。
本文分为以下几部分:
- 函数计算与区块链
- Marbles 区块链应用介绍
- Marbles 区块链应用结合函数计算进行扩展示例
- 区块链应用与函数计算在业务上结合的场景与价值探讨
函数计算与区块链
函数计算
函数计算是事件驱动的全托管计算服务。使用函数计算,无需采购与管理服务器等基础设施,只需编写并上传代码。函数计算为用户准备好计算资源,弹性地可靠地运行任务,并提供日志查询、性能监控和报警等功能。借助函数计算,可以快速构建任何类型的应用和服务,并且只需为任务实际消耗的资源付费。
下图为函数计算工作流程:
区块链
区块链可以理解为去中心的分布式记账系统,其是一种 分布式、去中心化的计算与存储架构 。区块链通过某种方式来记录数据,使用户可以信任区块链系统记录的数据。区块链中的记账节点会按照一致性协议记账。记账节点愿意按照一致性协议记账,是因为在一致性协议的设计中,诚实的记账节点会得到相应的奖赏,且诚实的记录比恶意篡改记录的收益更大。
依托于区块链网络的可信度,衍生出了智能合约的概念。什么是智能合约呢?现实生活中,买家与卖家要进行一笔交易,为了保证交易的顺利进行,双方会签订一份合约,合约中会声明双方各自的身份、权利以及义务。当交易出现纠纷时,买家与卖家根据当时签订的合约通过法律的手段解决纠纷。这种方式的不足之处在于解决纠纷的过程需要第三方权威来仲裁以及需要大量时间。那么,假使我们现在有一位可信公正的交易代理人。卖家将商品交给代理人,买家与代理人双方之间一手交钱一手交货。若买家拒绝购买,代理人会将商品归还给买家。买家也不会付了钱拿不到商品。智能合约就可以充当这样的代理人,其为区块链上一个包含合约代码和存储空间的虚拟账户,合约的代码控制智能合约的行为,合约的账户存储合约的状态。
由于有了智能合约,DApp (Decentralized Application 即去中心化应用)也应运而生。DApp 是运行在区块链网络上的应用软件,其上运行的代码我们称之为智能合约。
Marbles 区块链应用介绍
Marbles 区块链应用是一个 资产转移 应用演示。在 Marbles 区块链应用中多个用户可以创建并相互转移弹珠。 ( 即弹珠就是资产转移中的资产 )
上图中:
- Amy、Alice、Ava 所在的小长方形是她们每个人的账户
- 小长方形中的圆形弹珠是每个人账户中的资产,弹珠的颜色和大小是资产的属性
- 点击小长方形中的加号是为某个账户创建弹珠(资产)
- 将某个小长方形中的弹珠拖拽到右上方的垃圾桶中,是为某个账户删除弹珠(资产)
- 将某个弹珠从一个小长方形拖拽到到另一个小长方形,是弹珠(资产)的转移
- 每一步的操作会在下方的 BLOCKS 创建一个新的小正方形,这个小正方形就代表包含交易内容的区块
Marbles 区块链应用代码分成三部分:
- 链码 - 区块链网络中,对等节点所运行的代码。链码在此次介绍的 Marbles 应用中的主要作用是处理创建以及转移弹珠的逻辑。
- 客户端 - 浏览器中运行的代码,负责 Marbles 应用页面的渲染与交互。
- 服务端 - 服务器中运行的代码,充当 Marbles 应用与区块链网络之间的桥梁,其与客户端以及区块链网络中运行着链码的节点进行通信。
在 Marbles 应用中,当客户端发送消息给服务端,服务端与区块链网络通信的时序图大体上如下图所示:( 读者对详细过程感兴趣,可以阅读 Github IBM-Blockchain/marbles )
其中,Peer 节点(对等节点) 存在于区块链网络中,拥有账本并且可安装链码。Orderer 节点负责接收包含签名的交易,对未打包的交易进行排序生成区块,广播给 Peer 节点。
上图中:Client 以及 Server 是上文中所说的客户端以及服务端。
- 当用户创建或转移弹珠时,Client 客户端触发相应事件,并向 Server 服务端发起请求。
- Server 接收到事件信息后,首先会构建提案(也就是交易),提案是将事件信息进行封装,比如:此次交易的两方以及交易内容是什么。
- Server 将构建好的提案发送给区块链网络中的一个 Peer 节点,由 Peer 节点来对提案进行模拟,校验其合法性。为什么要这么做呢?因为链码的作用就是处理交易逻辑,而链码安装在 Peer 节点上,并没有安装在 Server 上。
- 如果 Peer 节点模拟提案成功,认为其合法,则会对提案进行背书,并向 Server 返回背书后的提案。背书可以理解为,当我们去购买东西并忘记带现金,付给对方一张 别人 给的支票。对方怀疑支票不可兑现的时候,我们在支票上签字并表明若这张 他人 给的支票不能兑换,则可以来找我们要现金。这个签字的动作就是背书,声明对事物或被认可人的支持。
- Server 将背书后的提案发送给 Orderer 节点。
- Orderer 节点对提案进行排序并打包进区块,将区块广播给区块链网络中的所有 Peer 节点。
Marbles 区块链应用结合函数计算进行扩展示例
假设说,现在有这么一个业务场景,需要在每次 Marbles 应用有事件发生时,要对事件进行相应处理,且这部分的处理代码会 经常迭代 。那么,在区块链应用更新需要较多时间的情况下,通过函数计算来处理是较为方便的。( 具体缘由在下一节将会继续探讨 )
接下来,笔者将带着大家模拟这个业务场景,扩展 Marbles Server 端代码。在每次事件发生时,将事件信息打包并通过函数计算的 HTTP 触发器,由函数计算来对事件信息做相应处理。
1. 准备工作
开通阿里云日志服务、函数计算服务、区块链服务
2. 在阿里云区块链服务中创建组织、创建联盟
3. 在阿里云区块链服务中创建通道
- 点击相应组织
- 点击通道
- 点击添加通道,填写名称与组织,并创建
- 点击新创建的通道右侧的待审批链接,同意审批
4. 部署 Marbles 应用以及链码
参考 阿里云区块链服务开发指南
注意事项:nodejs 版本为 v8
5. 下载并配置阿里云函数计算开发工具 fun
- fun 是一个 Node.js 编写的命令行工具,通过 npm 进行安装:$ npm install @alicloud/fun -g
- 通过在命令行输入 fun config,根据提示依次配置 Account ID、Access Key Id、Access Key Secret 以及 Default Region Name。可参考:服务地址 、 创建 AccessKey
- 配置 template.yml
在项目根目录下创建一个 template.yml 文件:
ROSTemplateFormatVersion: '2015-09-01' Transform: 'Aliyun::Serverless-2018-04-03' Resources: marblesFC: # 服务名称 Type: 'Aliyun::Serverless::Service' Properties: Description: 'fc test' LogConfig: # 日志配置 Project: test-log-project # 日志 Project Logstore: test-log-store # 日志 LogStore processEvent: # 函数名 Type: 'Aliyun::Serverless::Function' Properties: Handler: httpTrigger.handler # 文件名.方法名 Runtime: nodejs8 CodeUri: './' Timeout: 60 Events: http-test: # 触发器名 Type: HTTP # 触发器类型 Properties: AuthType: ANONYMOUS Methods: ['GET', 'POST', 'PUT'] test-log-project: # LogProject 名称 Type: 'Aliyun::Serverless::Log' Properties: Description: 'just for test' test-log-store: # LogStore 名称 Type: 'Aliyun::Serverless::Log::Logstore' Properties: TTL: 10 ShardCount: 1
上述文件做了如下事项:
- 在函数计算中创建了 marblesFC 服务
- 为 marblesFC 服务配置了 test-log-project 日志 Project 以及 test-log-store 日志 LogStore
- 在 marblesFC 服务中创建了 processEvent 函数,并为其设置入口函数为 httpTrigger.js 文件中的 handler 方法
- 为 processEvent 函数配置了 HTTP 触发器,触发器名为 http-test
- 创建了 test-log-project 日志 Project 以及 test-log-store 日志 LogStore
- 在项目根目录下创建 httpTrigger.js 文件
var getRawBody = require('raw-body') module.exports.handler = function (request, response, context) { // get request info getRawBody(request, function (err, data) { var params = { path: request.path, queries: request.queries, headers: request.headers, method: request.method, body: data, url: request.url, clientIP: request.clientIP, } // you can deal with your own logic here console.log(JSON.stringify(params.queries)) // set response var respBody = new Buffer.from(JSON.stringify(params)); // var respBody = new Buffer( ) response.setStatusCode(200) response.setHeader('content-type', 'application/json') response.send(respBody) }) };
- 在项目根目录下执行 fun deploy 进行部署
- 在阿里云函数计算控制台中,进入到 marblesFC 服务下的 processEvent 函数,在代码执行页面记录下调用 HTTP 触发器的地址
6. 修改 Marbles Server 端代码
- 打开 Marbles 源代码根目录文件夹下的 app.js 文件
- 添加 var https = require('https'); https模块
- 修改 setupWebSocket 函数
function setupWebSocket() { console.log('------------------------------------------ Websocket Up ------------------------------------------'); wss = new ws.Server({ server: server }); // start the websocket now wss.on('connection', function connection(ws) { // -- Process all websocket messages -- // ws.on('message', function incoming(message) { console.log(' '); console.log('-------------------------------- Incoming WS Msg --------------------------------'); logger.debug('[ws] received ws msg:', message); var data = null; try { data = JSON.parse(message); // it better be json } catch (e) { logger.debug('[ws] message error', message, e.stack); } // --- [5] Process the ws message --- // if (data && data.type == 'setup') { // its a setup request, enter the setup code logger.debug('[ws] setup message', data); startup_lib.setup_ws_steps(data); // <-- open startup_lib.js to view the rest of the start up code } else if (data) { // its a normal marble request, pass it to the lib for processing https.get("此处填写触发 HTTP 触发器的 URL 地址?type="+data.type, function(res){ console.log('test http trigger'); }); ws_server.process_msg(ws, data); // <-- the interesting "blockchainy" code is this way (websocket_server_side.js) } }); // log web socket errors ws.on('error', function (e) { logger.debug('[ws] error', e); }); // log web socket connection disconnects (typically client closed browser) ws.on('close', function () { logger.debug('[ws] closed'); }); // whenever someone connects, tell them our app's state ws.send(JSON.stringify(ws_server.build_state_msg())); // tell client our app state }); // --- Send a message to all connected clients --- // wss.broadcast = function broadcast(data) { var i = 0; wss.clients.forEach(function each(client) { // iter on each connection try { logger.debug('[ws] broadcasting to clients. ', (++i), data.msg); client.send(JSON.stringify(data)); // BAM, send the data } catch (e) { logger.debug('[ws] error broadcast ws', e); } }); }; ws_server.setup(wss, null); }
7. 启动 Marbles
在控制台中进入 Marbles 项目,通过 gulp marbles_baas 启动 Marbles 应用
8. 创建弹珠或转移弹珠
试着在浏览器中创建弹珠或者用鼠标拖拽转移弹珠
9. 查看日志
对 Marbles 应用做了相应操作后,进入阿里云日志服务 test-log-project Project 下的 test-log-store LogStore,查看日志
我们看到当 Marbles Server 接收到事件后,会触发 HTTP 触发器,由函数计算 FC 来对事件做相应处理。( 简单起见,demo 的处理目前仅仅是记录日志,读者们可以自行扩展 )
区块链应用与函数计算在业务上结合的场景与价值探讨
通过制作上面的 demo,相信大家现在对于如何将区块链应用与函数计算相结合有了一定的认识。接下来,就让我们一起探讨下,区块链应用与函数计算在业务上有哪些结合的场景与价值。
下图是最原始的 Fabric SDK 时序图
为了让读者方便理解,我们仍旧使用 Marbles 应用的时序图,读者可以将 Marbles 应用中的 Server 理解为 Fabric SDK 时序图中的 Application,
在笔者看来,函数计算可以与区块链应用相结合的场景主要有以下三点:
1. 处理事件
处理事件的场景刚刚在 demo 中各位读者想必已经体验过了。那么,为什么笔者倡导用函数计算来处理事件呢?
通过函数计算可以将每一次的事件进行相应的处理,处理完成后发送给日志服务。同时,还可以在函数计算中设定定时触发器,在指定时间内,再次统计事件的信息,由此对区块链状态进行一个数据分析。
而关于这种方式的统计逻辑,很有可能是需要经常迭代的。因此,不适合将其逻辑放入区块链应用中,而是更适合放在小巧易迭代的函数计算场景中。
2. 附加业务
考虑一个这样的场景:现在 X 公司推出了一款新的支付 App,为了鼓励用户使用该公司的 App,该公司对外宣传当用户用该公司的 App 成功完成交易后,该公司会送大量优惠券以及积分。若该 App 是一款区块链应用,那么把优惠活动的业务逻辑放在哪里最合适呢?
笔者目前觉得是放在 Orderer 将交易打包成区块并广播的时候最合适,因为优惠活动的业务逻辑是经常会变化的,这类业务逻辑可以统称为附加业务,将附加业务抽象为一个个函数并放在函数计算中,容易更新迭代。
3. 验证交易
Peer 节点在模拟提案时,若是有复杂多变的逻辑,可以放入函数计算中,由 Peer 节点来负责调用。
以上三点就是笔者对于如何将区块链服务与函数计算相结合的思考,有不准确的地方,欢迎大家指出。
作者:泽尘