用正确的姿势开发Hyperledger Fabric系列智能合约&账本存储

计划要写一系列关于Hyperledger Fabric开发相关的技术文章,基于版本Fabric v1.2.0,面向有一定区块链开发基础的编程人员,侧重标准和规范方面,如果您有任何疑问、建议,欢迎通过公众号下方回复!这是系列的第二篇,关于智能合约以及账本存储。

0x00 准备工作

在Fabric中,通常把chaincode分为系统chaincode和用户chaincode,我们可以把用户的chaincode称为智能合约,Fabric官方example02的例子就是一个很有代表性的智能合约,A和B两个实体转移资产的场景,合约中有三个操作,分别是:资产分配、资产转移、当前资产查询,当然这三个操作是比较基本的,更进一步能想到的潜在需求,

历史记录问题:查看A实体或者B实体的资产历史转移记录

按条件查询记录问题:查看某个时间段,比如2018年7月内,A实体和B实体资产转移记录

带着以上问题,我们看看智能合约以及智能合约在Fabric中的存储。

0x01 智能合约单元测试

在chaincode/shim下的账本操作中,大部分API都可以通过shim.MockStub进行模拟测试而无需启动网络环境,比如State的操作,包括init、invoke、GetStateByPartialCompositeKey等,某些涉及history的API目前还没有现实可以模拟测试,比如GetHistoryForKey,具体单元测试方法可以参考e2e_cli下example02的例子,比如对于State,

0x02 智能合约部署

合约的部署,可以通过cli方式部署,也可以通过SDK方式部署,按照上一篇文章中,关于Node.js SDK里面讲到的,代码类似如下方式,

假如智能合约中使用了外部依赖,那么可以将依赖包通过vender的方式放置到平级目录下,系统在打包时会收集当前智能合约下的第三方依赖库。

智能合约一般是部署到某个peer上,也可以指定具体的channel「也就是账本」,而接下来实例化则需要由channel来完成,只要部署了这个智能合约的peer都可以进行实例化操作,而且只需一次,关于这方面的workflow,后面会有专门的文章说明。

0x03 更多逻辑的实现

文章开始提到的第一个问题,从API中可以直接找到答案,GetHistoryForKey的作用就是提供对于某个Key的历史记录查询;对于第二个问题,可以通过CompositeKey的方式来处理,这是一种前端匹配的方式,代码实现也比较简单,首先设计类似这样的一个结构体,

再设计一个组合Key方法,

这个方法在invoke时调用「attributes用字符串数组方式记录invoke的年、月、日、具体交易数额以及交易方向」,

然后,通过

GetStateByPartialCompositeKey(searchIndex, []string{"2018","7"})

方法就可以匹配出条件为2018年7月份的所有交易记录,再根据Lender判断交易的方向「是从A到B,还是从B到A」,当然,也可以把交易方向也做为查询的属性之一,这种设计相对来说缺乏了一些灵活性「如果同时查询A发出的交易和B发出的交易需要两次调用API」。

0x04 账本的存储结构

根据上面的API方法,我们可以大致看到,Fabric数据的存储,分为三种形式,账本数据库、历史数据库、状态数据库,其中账本数据库采用文件存储方式,区块链的不可篡改特性往往就是指这个账本数据库,LevelDB充当了账本数据库索引的角色,可以想象当复杂的查询时,存在性能的问题;状态数据库,顾名思义,就是只记录当前状态「比如上面的智能合约中,A和B交易之后的值」,也可以说是invoke操作的快照;而历史数据库,可以认为是为了查询需要而设计的一种类似缓存的做法。

目前,Fabric通过内置的LevelDB或者couchDB来存储状态,我们可以通过couchDB更直观的看到交易中存储的变化,

上面的三个结构分别是在创建channel、部署智能合约、实例化智能合约三个操作之后产生的。

上面是ID为mycc的智能合约中,执行了两次invoke后产生的变化,前两条记录就是上面复合Key产生的记录,表示2018年7月29日,A到B交易1,以及2018年7月29日,B到A交易9,比如这条记录,

0x05 账本设计的思考

关于上面描述的状态数据库和历史数据库的设计,状态数据库是为了高效的执行智能合约的需要,历史数据库,很大程度上是为了来自查询的需要。

我们知道,对于账本的查询,并不会进行节点之间的共识,那么很容易产生一个疑问,如果是因为数据的查询需要,而把数据结构设计得更复杂,是否违背设计原则?

另外一个值得思考的问题:因为Fabric在整个区块链的商业环节中,还存在着应用层,那么应用层的数据存储和Blockchain的存储要怎么权衡轻重?既然Blockchain底层的历史数据库是一种类似缓存的计方案,那么为什么把这种存储设计到底层中,而不是留给应用层面去处理?

采用Fabric开发的实际的项目,开发者往往会掉到坑里,比如couchDB支持富查询,那么有的项目干脆把翻页的功能也都设计在智能合约层面,应用层面就不需要数据存储了,把Blockchain当成了传统数据库使用,这是一种错误的做法。

假想,来自Blockchain底层数据存储只有账本数据库,负责保存原始的「区块+链」数据结构和数据,SDK层设计一种同步策略,根据应用层的业务需要,按照策略「比如网络Idle的时候」同步某些账本信息「比如某些符合需要的History」到应用层数据库,这样会大大减轻账本查询的负担。

这种设计还有另外一个优势,假设联盟链有10个参与方,每个参与方都可以根据自己的需要来设计应用层存储,以上面的智能合约为例,10个参与方中,如果只有一方有需求,要按照时间段查询,那么很显然,这种情况下如果把这种条件查询写到智能合约里,对另外9个参与方来说是一种浪费,如果在SDK层面设置账本同步策略到应用层存储,即满足的不同方的不同需求,又肯定了应用层在整个Blockchain商业环节中的意义。

0x06 小结

智能合约要做的事情,要偏重交易的产生,对于交易的查询应该设计出更灵活的接口给应用层面去处理,智能合约以及账本,都是一种崭新的应用,类似LevelDB这种数据库在未来的区块链存储中可能会逐渐暴露出不足,随着技术的发展,也许未来会诞生一种针对「区块+链」的全新存储形式。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180729G0LSWE00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券