1.智能合约和链码
管理员将相关智能合约组织起来用于部署–链码。
智能合约中存储各方交易的业务模型,定义了术语、处理流程等。利用区块链可以将智能合约转换为可执行程序。应用通过调用智能合约来产生交易与在账本进行记录。使用智能合约可以实现自动化,比如定时交易等,而且也会比人工的手动操作效率更高。
如上图中ORG1和ORG2定义了一个可以查询、转移、更新汽车的智能合约,这些组织的应用可以通过调用这个智能合约来实现将一辆汽车从一个组织转移到另一个组织上。
链码包含多个智能合约,之后再部署到区块链上。当一个链码部署完成之后,其中所有的智能合约就都可以被应用访问到了。
1.1.智能合约与账本
智能合约可以对账本的两个组成部分进行程序化的访问,包括对世界状态的put、get和delete以及对区块链上的不可变记录进行查询。
- get通常是指一个当前业务对象的状态的查询。
- put通常是指创建一个业务对象或者是修改一个已经存在的世界状态值。
- delete通常代表从当前的世界状态移除业务对象,但是无法删除该业务对象的历史存在。
所有对世界状态的改变都会在区块链上留下不可变的记录。
1.2.智能合约开发
智能合约以链码的方式部署在区块链,而链码部署之后,其中所有智能合约都可以被访问到,这样就可以进行一个关注点的分离,只有管理员需要从链码的角度去考虑问题,而其他人只需要从智能合约的层次去考虑问题即可。
智能合约的核心是一系列交易的定义,如下的代码就定义了一个新的资产:
async CreateAsset(ctx, id, color, size, owner, appraisedValue) {
const asset = {
ID: id,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
};
return ctx.stub.putState(id, Buffer.from(JSON.stringify(asset)));
}
智能合约开发者要做的事情就是将业务处理流程解释为Go、Java等编程语言的智能合约。
1.3.智能合约中的背书
背书策略定义在智能合约中,是必不可少的一部分,其指明了为了证明一个交易是有效的必须得到哪些企业的签名。
有效和无效的交易都会添加到分布式账本中,但是只有有效的交易才会更新世界状态。
多于一个背书组织的智能合约必须要由足够多的组织去执行与签名,上图中的智能合约来说就必须要ORG1和ORG2都执行并签署智能合约才能使其产生的交易是有效的。
背书策略也是Fabric区别于比特币和以太坊的重要特征,而且这个也更接近于现实世界,比如只有买卖双方才需要在购买协议上去签名,这个购买操作也只会牵扯到他们双方。
1.4.有效交易
当一个智能合约执行的时候,它在组织拥有的peer节点上运行,并将交易提议作为智能合约的输入,然后结合程序逻辑来对账本进行读写。对于账本的修改被捕获为交易提议回复(交易回复),这个回复包含读写集,这个读写集中包含读取的状态以及将要写入的新状态(是否能够真正写入还需要之后对交易进行验证),此时账本还未真正更新。
如上图,在car transfer transaction这个标识符为t3的交易中,输入为CAR1、ORG1和ORG2,输出为{CAR1.owner=ORG1, CAR1.owner=ORG2}
,代表CAR1的所有者发生了变化,并且收集到了ORG1和ORG2的签名,这个签名是被这两个组织参与者的私钥签名的,因此可以被其他参与者使用公钥很容易的验证签名的正确性。
交易在每一个peer节点上通过两个阶段完成验证,第一个阶段,根据背书协议来检查是否已经有足够多的组织完成了背书。第二个阶段,要检查当前的世界状态是和读写集中的状态是相匹配的。当交易通过这两个测试之后,才说明是有效的。
在图中,t3是有效的,所以它成功更新了世界状态,t4是无效的,所以它只记录在账本中,但是没有更新世界状态。
1.5.智能合约与通道
可以简单的将通道理解为网络中的网络,通过通道共享了基础设施,又使得组织之间实现了逻辑上的独立。
链码定义在通道上,通道上的其他组织都可以访问链码中的智能合约。通道中的成员只有在链码在通道中被定义之后才能够执行智能合约。链码定义的参数包括链码名称、版本以及背书策略。当通道中足够多的成员同意链码定义之后,链码就被提交到通道中正式生效。
链码定义提供给通道中成员一种链码治理生效的确认机制,保证了最后生效的链码得到了所有组织成员的支持。
除了业务链码之外Fabric中还存在一些系统链码,但这些系统链码修改频率较低,一般只有底层开发者以及管理员会去为了某些自定义的原因来修改这些系统链码。
2.Fabric链码生命周期
链码运行在和背书peer进程相隔离的docker容器中,通过应用提交的交易来对账本进行初始化和管理。链码生命周期可以用于指导部署和管理网络上的链码。一个网络管理者可以通过Fabric链码生命周期实现如下的任务:
- 安装和定义链码
- 升级链码
- 一些特定的部署场景
为了使用新的生命周期,需要新建一个通道并将其能力设置为V2_0,但是这时就不能用之前老的那一套生命周期了,可是仍然可以调用在设置V2_0之前部署的链码。
2.1.安装和定义链码
Fabric链码生命周期要求组织对定义链码的参数达成一致,如名称、版本号、背书策略。通过如下的步骤来让各组织达成一致(不是通道中每一个组织都必须完成如下步骤中的每一步):
2.1.1.打包链码:
该过程可以被一个或者所有组织完成。
链码需要被打包成一个tar压缩包,可以通过Fabric peer二进制文件、Node Fabric SDK以及第三方的GNU tar等来实现。创建链码时,需要提供一个简洁且具有可读性的标签来描述这个压缩包。
创建的压缩包需要满足如下的格式,其中如果用fabric自带工具打包会自动满足该格式,如果是第三方工具可能需要手动实现。
- 以tar.gz结尾
- tar文件需要包含两个文件:metadata.json和code.tar.gz,前者是一个元数据描述文件,后者是具体的链码文件
- metadata.json包含链码语言、代码路径、包标签等信息:
{"Path":"fabric-samples/asset-transfer-basic/chaincode-go","Type":"golang","Label":"basicv1"}
2.1.2.安装链码到peer上:
每一个将会用该链码来背书交易或者查询账本的都必须完成该步。当使用CLI或者SDK时,需要使用管理员身份来完成该步。
当安装之后,peer会对安装的链码进行构建,然后返回一个构建结果来提示构建是否出现问题。如果安装成功会返回一个包标识符,该标识符由包标签和包的哈希码组成。这个标识符在随后的步骤中也有用,当然也可以通过查询当前peer上所有的链码的方式来查出这个包标识符。
2.1.3.为自己的组织批准链码定义:
每一个会用到该链码的组织都需要完成该步,需要足够数量的组织批准链码的定义,这个数量是由通道的生命周期策略决定的,只有满足数量的组织批准链码之后,链码才可以应用到通道中,链码的批准实质上相当于投票。
链码的定义包括如下的参数:
- 名称:应用调用链码时会使用的名称
- 版本号:每次升级链码的二进制文件时,都会修改这个版本号
- 序列:链码定义的次数,用于追踪链码的升级
- 背书策略:定义了哪些组织需要执行或者验证交易输出,表示为一个字符串,通道默认的背书策略是大多数同意,即一半以上。
- 集合配置:和隐私数据有关
- ESCC/VSCC插件:应用在链码中的自定义背书或者验证的插件
- 初始化:决定是否调用链码的init方法来进行初始化
链码定义还包含包标识符。
批准会被提交到排序服务中,然后分发到各peer上。这个批准需要组织管理员来进行提交,在批准交易被成功提交之后,会被存在一个可以让组织中的所有peer访问的一个集合中,这样可以保证即使一个组织有多个peer,也只需要定义一次。
2.1.4.提交链码定义到通道中:
提交者首先从其他组织中收集批准的背书,然后通过提交交易的方式来提交链码定义,这个交易首先会被提交到排序服务中,然后会被提交到通道的链码定义中,整个提交操作需要由管理员来完成。
需要注意的是,如果某个组织不需要某个链码,那么它可以不安装该链码到自己的节点上,但是他仍然可以对链码进行批准。
在链码定义提交之后,所有安装了链码的peer都会启动链码容器,允许通道中成员来调用链码,这可能会花费几分钟。
2.2.升级链码
2.2.1.重新打包链码:
2.2.2.安装新的链码包:
安装之后,会生成新的包id,之后会用于新的链码定义。除此之外还需要更新链码的版本,之后生命周期过程会追踪链码的二进制文件是否已经被更新。
2.2.3.批准新的链码定义:
升级链码二进制文件时,需要更新链码版本和包id,此外还可以在不重新打包链码的情况下更新链码的背书策略。通道的成员只需要简单的批准新的策略即可。新的定义需要把序列属性值增加1。
2.2.4.提交定义到通道中:
当足够的组织完成背书之后,一个组织就可以提交定义到通道中来完成升级。
提交之后,将会有一个新的链码容器启动起来,如果更新链码定义时没有更新链码版本号,那么不会有新的容器启动。
fabric 链码生命周期中,sequence用来追踪链码的升级,而升级可能只设计背书策略,而version则关注的是链码的二进制文件的升级,所以有可能出现sequence增加而version不增加的情况。
2.3.部署场景
利用fabric链码生命周期来管理通道和链码时可能出现的一些情况。
2.3.1.加入通道
一个新的组织可以加入已经有链码定义的通道,并且可以在安装已经被提交到通道中的链码包并且批准链码定义之后使用链码。
在成功批准链码定义之后,新的组织就可以使用链码了,这里不需要再次提交链码定义到通道中,链码容器将会在首次调用新组织的链码之后启动起来。
2.3.2.更新背书策略
可以在不重新打包链码的情况下升级背书策略,通道成员可以配准拥有新背书策略的链码然后将其提交到通道中。
新的背书策略会在新的定义提交到通道之后就开始生效。
2.3.3.在不安装链码的情况下批准链码定义
如果确认应该用不到某个链码,可以不用安装,只去批准对链码的定义即可。
2.3.4.某个组织不同意链码的定义
如果一个组织不批准已经提交到通道的链码定义,或者批准的是不同配置的链码定义(如下图,Org3的链码定义是需要全部组织同意),则不能使用该链码。
2.3.5.通道不同意链码的定义
如果通道上的组织不同意链码的定义(如下图三个组织对背书策略没有达成一致),那么定义就无法提交到通道中,通道中的所有成员就都不能使用链码。
2.3.6.通道安装了不同的链码包
每个组织在批准链码定义时可以使用不同的packageID。这允许通道成员安装使用相同背书策略的不同链码二进制文件,并读写相同链码名称空间中的数据。
组织可以使用这个特性来安装包含他们自己业务逻辑的智能合约。
2.3.7.使用一个包创建多个链码
可以通过创建多个链码的实例然后提交多个链码定义来实现多个链码的创建,每个链码定义都需要指定一个不同的链码名称。通过这种方式可以让一个链码拥有不同的背书策略。
3.隐私数据
如果一个通道上的一组组织需要对该通道上的其他组织保持数据的私密性,那么它们可以选择创建一个新通道,该通道只包含需要访问数据的组织。但是单独创建通道会带来额外的开销,并且会导致不能让通道中的其他参与者见证交易的存在(当然在交易也需要保密时可以选择创建新通道的方式来实现)。
因此Fabric提供了创建隐私数据集合的能力,它允许通道上组织的一个子集拥有背书、提交或者查询隐私数据的能力,而不需要创建新的通道。
隐私数据集合可以在链码定义中被显示的定义,此外,每个组织也拥有隐式的私有数据命名空间,可以用于存储特定的私有数据。
数据集合由两部分组成,如下图:
- 真正的隐私数据:这些数据以键值对形式存在授权组织peer的私有状态数据库中,可以被这些peer上的链码访问到,并且可以通过gossip协议进行p2p的传输,只有授权的组织才能看到他们。
- 隐私数据的哈希:存储在节点的通道账本中,被用于当成交易存在的证明,可用于验证和审计,如下图,Peer1的通道账本中有隐私数据的哈希,但是没有隐私数据。
这些隐私数据可以被分享给第三方或者其他通道的成员,对方收到之后可以通过哈希的计算来确认通道账本的状态。
3.1.案例说明:
考虑有五个组织
- 农民:向海外售卖自己的产品
- 分销商:把产品移动到海外
- 托运人:在双方之间运输货物
- 批发商:从分销商手中购买货物
- 零售商:从批发商和托运人手中去购买货物
分销商可能希望与农民和托运人进行私下交易,以完成对批发商和零售商的保密交易条款(不暴露交易价格等信息)。
分销商可能会和批发商有隐私数据关系,因为他们之间的交易价格可能会远低于之后和零售商的交易价格。
批发商可能也会和零售商以及托运人有私有数据交换的关系。
我们采用私有数据集合(PDC)方式来实现私有数据的分享。
- PDC1:分销商、农民和托运人。
- PDC2:分销商和批发商。
- PDC3:批发商、零售商和托运人。
按照这个设计,分销商的账本内部会有多个私有数据库,包括分销商、农民工和托运人的以及分销商和批发商的。
3.2.使用私有数据时的交易流
当链码中使用私有数据时,交易的提议、背书和向账本提交的流程会有一些不同。
- 客户端提交一个产生了对私有数据的读写的提议请求到背书节点调用链码的功能。私有数据、或者链码中用于生成私有数据的数据,被发送到提议的
transient
字段。 - 背书peer节点模拟交易执行并且将私有数据存储到
transient data store
(一个peer的临时数据存储地),然后根据私有数据集合策略来使用gossip协议向其他被授权查看私有数据的节点传播私有数据。 - 背书节点发送提议回复到客户端。回复包含背书的读写集,包含公有数据以及和私有数据键值对的哈希。私有数据不会被返回到客户端去。
- 客户端将交易(包括提议回复,私有数据哈希)提交到排序服务中。包含私有数据哈希的交易会像正常情况下一样被打包到块中,这样,所有peer都可以正常的验证交易,而不需要知道真实的私有数据。
- 在区块提交时,授权节点会根据私有数据集合策略来查看自己是否有权限查看私有数据。如果有的话,其首先会查看
transient data store
来知道自己之前是否已经收到了真正的私有数据。如果没有的话,将会从其他授权的peer来拉取私有数据,然后他们会验证私有数据和是否和块内的哈希相匹配并将交易和区块提交。在验证/提交时,私有数据被移动到私有状态数据库和私有写集存储的副本。然后私有数据会从transient data store
被删除。
3.3.分享私有数据
在有些情况下,私有数据键值对可能需要和通道内其他成员(不包含在私有数据集合中的成员)或者其他私有数据集合来进行分享。接收方通常会想要验证私有数据和链上哈希是否匹配。
3.3.1.私有数据共享模式
以下有一些模式可以用于在避免创建多个真正集合的情况来实现数据共享。
使用相应的公钥来追踪公共状态:原文意思太过抽象,这里说下个人理解,将私有数据利用私钥加密时候放在公有信道上大家都能够读到,给想要授权的企业公钥让其有权进行访问,每个企业将其私钥存在自己的私有数据集合中。
链码访问控制:客户端通过链码调用来获取私有数据,而访问权限控制则由链码实现,具体方式可以是口令或者令牌等,使用这种方式也可以实现对于一些公有数据的访问的控制。
带外共享私有数据:在线下对私有数据进行分享,然后利用API–
GetPrivateDataHash()
获取存储在线上的私有数据哈希来验证得到的私有数据是否正确。和其他集合分享私有数据:直接通过提议的
transient
字段来发送私有数据,接收方只需要调用GetPrivateDataHash
从自己的私有数据集合中计算哈希来比对接受数据是否正确即可。将私有数据转移到其他集合中:实现方法和上一条一样,只是多了一步从自己的私有集合中删除私有数据中,但是是否删除可能需要引入第三方监管机构来进行确认。
使用私有数据来做交易批准:原文意思太过抽象,这里说下个人理解,在交易和某方交易完成之前(比如确认用特定价格购买某件商品),首先进行一个预证明,具体实现就是将私有数据的键写到自己或者对方的私有数据集合中,链码会用
GetPrivateDataHash()
来进行检查对方是否同意交易条件,同意的话调用Get函数应该是能得到一个哈希的。事实上,通道中各个组织在提交链码到通道前同意链码定义时用的就是这种机制。保持交易者私密性:前一模式的变体还可以避免交易者身份被泄露。例如,买方表示同意在他们自己的集合上购买,然后在随后的交易中,卖方在他们自己的私有数据集合中引用买方的私有数据。使用哈希引用的交易证明被记录在链上,只有买方和卖方知道他们是交易者,但如果出现需要知道的情况,比如在与另一方进行的后续交易中,他们可以透露预映像,而另一方可以验证哈希值来证明双方的交易身份。听起来有点拗口,感觉实质意思是这样:以买方举例,他持有卖方才有的私有数据,而提交到区块中的交易中是不含卖方身份的,这样卖方的身份就不会被泄露出去,而如果有人想质问买方到底是不是真的和卖方有过交易时,只需要计算买方这边的私有数据哈希,再和买方的私有数据哈希一对比,就知道是不是真的是卖方了。
结合以上模式,可以发现,具有私有数据的事务可以绑定到与常规通道状态数据相同的条件,具体来说:
- 键级别的交易访问控制:将拥有者凭证信息存储在私有数据值中,随后的交易就可以用它来验证交易提交者是否有权来分享或者转移某个数据。具体来说,链码可以调用API(如GetCreator()等)来获取提交者的身份凭证,进行哈希之后,与
GetPrivateDataHash()
获取到的预先存储的凭证哈希进行比对来知道这个提交着是否有权利发起这个交易。 - 键级别的背书策略:可以使用基于状态的背书策略来指明哪些组织必须背书那些分享或者转移私有数据的交易。说实话没太看懂。
3.3.2.案例说明:使用私有数据集合的资产(这个资产可能是一辆车或者一瓶酒之类的)转移
通过私有数据共享模式可以实现一些很有用的链码应用。以下是案例描述。
- 有一个可以通过公有链码状态中的UUID进行追踪的资产。只有资产的拥有者被记录下来,其他所有信息都是未知的。
- 该链码将要求任何转移请求必须来自所有者客户端,而密钥受基于状态的背书约束,要求来自所有者组织和监管机构组织的peer必须支持任何转移请求。
- 资产所有者的私有数据集合包含关于资产的细节信息,键值为UUID的哈希,其他组织以及排序服务将只能看到资产细节的哈希。
- 假设监管者也是每个集合的成员之一,因此会监管者也会持久化一份私有数据,尽管实际情况并非如此。
因此资产交易的流程详细展开会如下:
- 在链下,资产拥有者(也即卖家)和潜在的买家谈好价格准备交易。
- 卖家提供他所有权的证明,并在线下提供资产的细节,或者给买家提供凭证来让其访问监管者或者卖家节点上的私有数据。
- 购买者通过链上的哈希来验证查询到的私有数据的正确性。
- 买家调用链码在他们自己的私有数据集合中记录投标细节。这个链码在买家的peer上调用,并且如果背书策略允许还可以在监管者的peer上调用。
- 卖家调用链码来售出并且转移资产,传递资产细节数据和投标信息。链码在卖家、买家和监管者的peer上调用以达成相关的背书策略。
- 链码验证提交客户端是拥有者,并通过卖家集合中的哈希来验证资产数据的真实性,然后通过买家集合中的哈希来验证投标信息的真实性。之后链码会更新资产所有权到买方,并设置背书策略到购买组织和监管者,然后把资产细节写入买家的私有数据集合,并且将其从卖家的集合中删除掉。在背书彻底完成之前,背书节点会确保私有数据已经到达了卖家和监管者的其他peer。
- 卖家提交带有公开数据和私有数据哈希的交易用于排序,他会将其装块之后发布到通道内的其他peer中。
- 每个peer的块验证逻辑将会检查背书策略是否达到,并且会验证块的完整性等。
- 通过验证之后,所有peer都会提交交易,买家和监管者节点如果没有私有数据的话会尝试从他旗下的其他节点获取私有数据,然后将其持久化到自己的私有数据库中。
- 交易完成,资产从卖家的私有数据库转移到卖家的私有数据库。
此外私有数据还可以从链上被抹除,但是这个操作不能通过链码实现,而且也不会被其他请求peer知晓。