1. 区块链项目(Hyperledger Fabric)的介绍
1.1 区块链起源之比特币的介绍
1.2 Hyperledger Fabric深入解读
比特币被认为是区块链技术 1.0 时代的代表平台,随着以智能合约为主要特征的以太坊平台的诞生,区块链结束进入 2.0 时代,而开源项目Hyperledger Fabric平台则标志着区块链技术3.0时代的到来。
Fabric在v1.4.1(LTS)版本(第一个长期支持版本)提出了很多新的设计概念,添加了诸多新的特性,提供了高度模块化和可配置的架构,支持通用编程语言(如java、GO、Node.js)编写智能合约,支持可插拔的共识协议,使得基于该平台开发企业级应用的以变为现实,平台的关注度也越来越高。
本节我们开始走进Hyperledger Fabric的世界,探究基本的运行原理,从而加深对该平台的了解,为后续学习基于Fabric的应用开发技术打下基础。
Fabric简介
Hyperledger Fabric是分布式账本技术(DLT)的独特实现,它可在模块化的区块链架构基础上提供企业级的网络安全性、可扩展性、机密性以及高性能。
v1.4版本针对安全、保密、部署、维护、实际业务场景需求等方面进行了很多改进,例如架构设计上的Peer节点的功能分离、多通道的隐私隔离、共识的可插拔实现等,功能上引入了Raft崩溃容错共识服务,改进可维护性和可操作性,加入私有数据支持等,都为Fabric提供了更好的服务支持。因此,本节后面关于Fabric的内容均将基于v1.4版本进行描述。
Hyperledger Fabric v1.4具有如下特性:
-
身份管理(identity management)
Fabric区块链是一个许可链网络,因此Fabric提供了一个成员服务
(member service ),用于管理用户ID并对网络上的所有参与者进行认证。在Hyperledger Fabric区块链网络中,成员之间可以通过身份信息互相识别,但是他们并不知道彼此在做什么,这就是Fabric提供的机密性和隐私性。 -
隐私和保密(privacy abd confidentiality)
Hyperledger Fabric允许竞争的商业组织机构和其他任意对交易信息有隐私和机密需求的团体在相同的许可链网络中共存。其通过通道来限制消息的传播路径,为网络成员提供了交易的隐私性和机密性保护。在通道中的所有数据,包括交易、成员以及通道信息都是不可见的,并且未订阅该通道的网络实体都是无法访问的。 -
高效的性能(efficient processing)
Hyperledger Fabric按照节点类型分配网络角色。为了提供更好的网络并发行和并行性,Fabric对事务执行、事务排序、事务提交进行了有效的分离。于排序之前执行事务可以使得每个peer节点同时处理多个事务,这种并发执行极大地提供了Peer节点的处理效率,确保数字资产所有权转让的所有交易都遵守相同的规则和要求。 -
模块化设计(module design)
Hyperledger Fabric实现的模块化架构可以为网络设计者提供功能选择。例如,特定的身份识别、共识和加密算法可以作为可插拔组件插入Fabric网络中,基于此,任何行业或公共领域都可以采用通用的区块链架构,并确保其网络可跨市场、监管和地理边界进行互操作。 -
可维护性和可操作性(serviceability and operations)
日志记录的改进以及健康检查机制和运营指标的加入,使得
v1.4版本在可维护性和可操作性上实现了巨大飞跃。新的RESTful运营服务为生产运营商提供三种服务来监控和管理对等节点和共识服务节点运营。第一种服务使用日志记录/logspec端点,允许操作员动态获取和设置对等节点和共识服务节点的日志记录级别;第二种服务使用健康检查/healthz端点,允许运营商和业务流程容器检查对等节点和共识服务节点的活跃度和健康情况;第三种服务使用运营指标/metrics端点,允许运营商利用Prometheus记录来自对等节点和共识服务节点的运用指标(本教程将介绍第三种服务)。
核心概念
本节介绍Hyperledger Fabric所使用的核心概念
1.锚节点
Gossip协议使用锚节点确保不同组织中的对等节点彼此了解。当提交包含锚节点更新的配置块时,对等节点能探测到锚节点并能获知锚节点已知的所有对等节点。由于组织间通过Gossip协议通信,因此通道配置中必须定义至少一个锚节点。每个组织都提供一组锚节点则可实现高可用性和减少冗余。
2.访问控制列表(ACL)
访问控制列表(ACL)将对特定对等节点资源(如系统合约代码APIs或事务服务)的访问与策略(指定所需的组织或角色的数量和类型)相关联。ACL是通道配置的一部分,可使用标准配置更新机制更新。
3.区块
区块包含一组有序的交易,由共识系统创建,由对等节点验证。在通道中以加密的方式先与前序区块链接,然后连接到后序区块。第一个区块被称为创世区块。
4.区块链
区块链是一个交易日志,由交易区块经过哈希连接结构化得到。对等节点从共识服务收到交易区块后,基于背书策略和并发冲突,标注区块的交易为有效或者无效,并将区块追加到对等节点文件系统的哈希链中。
5.智能合约
智能合约是由区块链网络外部客户端调用的代码,可以用于管理世界状态中键值对的访问和修改,安装在对等节点上,并在通道上实例化。智能合约也被称为合约代码。
6.通道
==通道是构建在Fabric网络上的私有区块链==,由配置块定义,保障数据的隔离及隐私性。==所有对等节点共享通道中特定的账本,交易方与账本的交互必须通过通道的正确性验证==。
7.提交
一个通道中的每个对等节点都会验证交易区块的有效性,然后将区块提交(写或附加)至该通道上账本的各个副本。对等节点也会标记交易是否有效。
8.并发版本控制检查(CCVC)(防止数据篡改)
==并发控制版本检查(CCVC)可以保持通道中的对等节点状态同步==。对等节点并行地执行交易,在交易提交至账本之前,对等节点会检查交易在执行期间读取的数据是否被修改。如若被修改,则引发CCVC冲突,该交易就会在账本上被标记为无效,其值不会更新到状态数据库中。
9.配置区块
包含系统链(共识服务)或通道定义成员和策略的配置数据。对某个通道或整个网络的配置修改(比如, 成员离开或加入)将导致生成一个新的配置区块并追加到适当的链上。这个配置区块会包含创世区块的内容加上增量。
10.共识
共识用于确认交易的排序以及交易集本身的正确性。
11.同意集
在Raft共识服务中,同意集是通道上积极参与共识机制的排序节点。如果系统通道上存在其他排序节点,但是不属于通道的一部分,则这些排序节点不属于通道的排序集。在configtx.yaml文件的系统级配置Profiles中可以找到该项配置。
12.联合体(联盟)
联合体是区块链网络上无序组织的集合。这些集合组件并加入通道,且拥有自己的对等节点,虽然区块链网络可以拥有多个联合体,但大多数网络只有一个联合体。在通道创建时,所有加入通道的组织必须时联合体的一部分。未在联合体中定义的组织可能会被添加到现有通道中。
13.世界状态
世界状态也被称为账本的当前状态,==表示区块链交易日志中所有key的最新值。==对等节点将最近处理过的每笔交易对应修改的value值更新到账本的世界状态。由于世界状态可以直接访问key的最新值,而不是通过遍历整个交易日志,所以合约代码必须先知道key-value的世界状态,然后针对这个世界状态执行交易提案。
14.动态成员管理
Fabric支持在不影响整个网络操作性的情况下,动态添加/移除成员、对等节点和共识服务节点。动态成员管理在也无关序调整或因各种原因需添加/移除实体时至关重要。
15.创世区块
创世区块是初始化区块链网络或通道的配置区块,也是区块链上的第一个区块。
16.Gossip协议
Gossip数据传输协议有三项功能:
- 管理对等节点,发现通道上的成员;
- 通道上的所有对等节点间广播账本数据;
- 通道上的所有对等节点间同步账本数据。
17.账本
账本由区块链和世界状态组成。区块链不可变,一旦将一个区块添加到链中,它就无法更改。而==世界状态是一个数据库==,包含已由区块链验证和提交事务集添加、修改或删除的键值集合的当前值。网络中每个通道都有一个逻辑账本,实际上,通道中每个对等节点都维护着属于自己的账本副本,这些副本通过共识过程与其他对等节点的副本保持一致,逻辑上是单一的,但在一组网络节点(对等节点和共识服务)中分布着许多相同的副本。术语分布式账本技术(DLT)通常与这种账本相关联。
18.追随者(follower)
在基于领导者的共识协议(如Raft)中,追随者复制由领导者生成的日志条目的节点。在Raft中,追随者也会收到领导者的"心跳"信息,如果领导者在可配置的时间内停止发送这些信息,追随者将发起领导者选举,其中一名追随者会被选为领导者。
19.领导者(leader)
在基于领导者的共识协议(如Raft)中,领导者负责提取新的日志记录,将其复制到追随者共识几诶单,并且管理记录何时被认为是已提交。
20.主要对等节点
每个组织可以在它们订阅的通道上拥有多个对等节点,它们中至少有一个作为主要对等节点,以便代表该组织与网络共识服务通信。共识服务向通道上主要对等节点提供块,然后将其分为给同一组织内的其他对等节点。
21.日志记录
日志记录是Raft共识服务中的主要工作单元,从领导者分发给追随者。这些记录的完整序列称为"日志"。如果所有成员就记录及其排序达成一致,则该日志被认为是一致的。
22.成员服务提供组件
成员服务提供组件(MSP)是指==为客户端节点和对等节点提供证书的系统抽象组件==。客户端节点用证书来认证他们的交易;对等节点用证书认证其交易(背书)。该接口与系统的交易处理组件密切相关,旨在使已定义的成员身份服务组件以这种方式顺利插入,而不会修改系统的交易处理组件的核心。
23.成员管理服务
成员管理服务在许可区块链上==认证、授权和管理身份==。在对等节点和排序服务节点中运行成员管理服务的代理。
24.排序服务或共识服务
将交易排序放入区块的节点的集合。排序服务独立于对等节点流程之外,并以先到先得的方式为网络上所有的通道做交易排序。排序服务支持可插拔实现,目前默认实现了Solo和Kafka,但有Kafka模式配置过于繁琐,所以现在很多使用Raft排序模式。
25.组织
组织也称为"成员",由区块链服务提供商邀请加入区块链网络。组织通过将其MSP添加到网络加入网络。组织的交易端点是对等节点,一群组织形成了一个联合体。虽然网络上的所有组织都是成员,但并非每个组织都会成为联盟的一部分。
26.节点
维护账本并运行合约容器来对账本执行读写操作的网络实体。节点由成员拥有和维护。
27.策略
策略是由数字标识(digital identity)的属性组成的表达式,如Org.Peer And Org2.Peer,用于限制对区块链网络上资源的访问。策略可以在引导共识服务或者创建通道之前定义,也可以在实例化通道上的链码时指定。
28.私有数据
==私有数据是存储在每一个授权对等节点的私有数据库中的机密数据,在逻辑上与通道账本数据分开==。对私有数据的访问仅限于私有数据集上定义的组织。未经授权的组织只能在通道账本上拥有私有数据的哈希值,作为交易数据的证据。此外,为了进一步保护隐私,私有数据的哈希值通过共识服务而不是私有数据本身传递,从而使得私有数据对共识服务节点保密。
29.私有数据集
私有数据集用于管理通道上两个或多个希望与该通道上其他组织保密的组织,描述了通道上有权存储私有数据的组织子集,只有这些组织能与私有数据交易。
30.Raft
Raft是 v1.4.1新增的功能,基于Raft协议etcd库的崩溃容错(CFT)共识服务实现。Raft遵循"领导者和追随者"模型。与基于Kafka的共识服务相比,Raft共识服务更容易设置和管理,并且允许组织为分布式共识服务贡献节点。
架构详解
Fabric在架构设计上采用了模块化的设计理念,从下图所示的整体逻辑架构来看,Fabric主要由3个服务模块部组成,分别是成员服务(member service)、区块链服务(Blockchain service)和合约代码服务(Chaincode service)。
其中,成员服务提供会员注册、身份管理和认证服务,使平台访问更加安全,且有利于权限管理;
区块链服务负责节点之间的共识管理、账本的分布式计算、P2P网络协议的实现以及账本存储,作为区块链的核心组成部分,为区块链的主体功能提供底层服务支撑;
合约代码服务则提供一个智能合约的执行引擎,为Fabric的合约代码(智能合约)程序提供部署运行环境。
同时在逻辑架构图中,还能看到事件流(event stream)贯穿三大服务组件间,它的功能是为各个组件的异步通信提供技术支持。在Fabric的接口部分,提供了API、SDK和CLI这3种接口,用户可以用来对Fabric进行操作管理。
成员服务
成员服务可以为Fabric的参与者提供网络上的身份管理、隐私性、保密性和认证服务。下面重点介绍PKI体系的相关内容及用户的注册过程。
PKI(public key infrastructure,公钥基础设施)的目标就是实现不同成员在不见面的情况下进行安全通信,Fabric当前采用的模型是基于可信的第三方机构,也就是证书颁发机构(certification authority,CA)签发的证书。
CA会在确认申请者身份后签发证书,同时会在线提供其所签发证书的最新吊销信息,这样使用者就可以验证证书是否依然有效。证书是一个包含公钥、申请者相关信息以及数字签名的文件。数字签名保证了证书中的内容不能被任何攻击者篡改,而且验证算法可以发现任何伪造的数字签名,就可以实现成员管理。
成员服务将PKI体系和去中心化共识协议结合在一起,将非许可区块链转变成为了一个许可区块链。在非许可区块链中,实体不需要经过授权,网络中的所有节点并不存在角色区别,都是统一的对等实体,都拥有平等提交交易及记账的权利。
而在非许可区块链中,实体需要注册来获取长期的身份证书(例如注册证书),这个身份证书可以根据实体类型来进行区分。对于用户而言,在注册时,交易证书颁发机构(transanction cetificate authority, TCA)会给注册的用户颁发一个匿名的证书;而对于交易来说,需要提交的交易是通过交易证书的认证,并且交易证书会一直存储于区块链上以供认证服务追溯交易使用。实际上,成员服务是一个认证中心,负责为用户提供证书认证和权限管理的功能,对区块链网络中的节点和交易进行管理和认证。
在Fabric的系统实现中,成员服务由几个基本实体组织组成,它们互相协作来管理网络上用户的身份和隐私。这些实体有的负责验证用户的身份,有的负责在系统中为用户注册身份,有的为用户在进入网络或者调用交易时提供所需的证书凭据。PKI是一个基于公钥加密的框架体系,它不仅可以确保网络上的数据安全交换,而且还可以用来确认管理对方的身份。同时在 Fabric系统中,PKI还被运用于管理密钥和数字证书的生成、分发以及撤销。
通常情况下,PKI体系包含证书颁发机构(CA)、注册机构(RA)、证书数据库和证书存储实体。其中RA是一个信任实体,它负责对用户进行身份验证以及对数据、证书或者其他用于支持用户请求的材料进行合法性审查,同时还负责创建注册所需的注册凭证。CA则会根据RA的建议,给指定用户颁发数字证书,这些证书由根CA直接或分层进行认证。
下面针对实体进一步介绍说明:
Root Certificate Authority: 根CA,代表PKI体系中信任的实体,同时也是PKI体系结构中的最顶层认证机构。
Enrollment CA(ECA): 在验证用户提供的注册凭证后,ECA负责发出注册证书(ECerts)。
Transcation CA(TCA): 在验证用户提供的注册凭证后,TCA负责发出交易证书(TCerts).
TLS CA:负责颁发TLS(transport layer security,传输层安全协议)证书和凭据,以允许用户使用加密网路。
ECerts(enrollment certificates): Ecerts是长期证书,针对所有角色颁发。
TCerts(transaction certificates): TCerts是每个交易的短期证书,由TCA根据授权的用户请求颁发。用户可以配置TCerts为不携带用户身份的信息从从而匿名地参与系统,还可以防止事务的可链接性。
TLS-Certs(TLS-Certificates): TLS-Certs携带其所有者的身份,用于系统和组件之间进行通信以及维护网络级安全。
CodeSignerCerts(Code Signer Certificates): 负责对软件代码进行数字签名,标识软件来源及软件开发者的真实身份,以此保证代码在签名之后不被恶意篡改。
用户、客户端注册过程
前面介绍了成员服务的PKI体系的实体及其基本功能,接下来针对具体用户注册流程做一个简单的介绍。下图展示了一个用户登记流程的高层描述,它分为两个阶段:离线过程和在线过程。
-
离线过程 (1) 每个用户或者
peer节点必须向RA注册机构提供身份证件(ID证明),同时这个流程必须通过带外数据(out-of-band,OOB)进行传输,以提供RA为用户创建(和存储)账户所需的证据。 (2)RA注册机构返回用户有关的用户名和密码,以及信任锚(包含TLS-CA Cert)。如果用户可以访问本地客户端,那么客户端可以将TLS-CA证书作为信任锚的一种方式。 -
在线过程 (1) 用户连接客户端以请求登陆系统,在这一过程中,用户将用户名和密码发送给客户端。 (2) 用户端代表用户向成员服务发送请求,成员服务接受请求。 (3) 成员服务将包含几个证书的包发送给客户端 (4) 一旦客户端验证完成所有的加密材料是正确有效的,它就会将证书存储于本地数据库中并通知用户,至此,用户注册完成。
区块链服务
区块链服务包含四个模块: 共识管理、分布式账本、账本存储以及P2P网络协议。
共识管理用于在多个节点的分布式复杂网络中使消息达成共识,分布式账本与账本存储负责区块链系统中所有的数据存储,比如交易信息、世界状态、私有数据等。而P2P网络协议则是网络中节点的通信方式,负责Fabric中各节点间的通信与交互。
P2P网络这种分布式应用架构是对等计算模型在应用层形成的一种组网或网络形式,用于对等实体间分配任务和工作负载。彼此连接的多台计算机在P2P网络环境中处于对等地位,有相同的功能,不分主从。
一台计算机可作为服务器,设定供网络中其他计算机使用的共享资源,又可以作为工作站来请求服务。一般来说,整个网络不依赖专用的集中服务器,也没有专用的工作站。而区块链所处的分布式环境中,各个节点间本应该是平等的,天然适合P2P网络协议。
在Fabric的网络环境汇中,节点是区块链的通信实体。存在3类不同的节点,分别是客户端节点(Client)、Peer节点、以及共识服务节点(Ordering Service Node或者Orderer),又称为排序节点。
客户端节点代表着终端用户实体。它必须连接到Peer节点后才可以与区块链进行通信交互。同时客户端节点可以根据它自己的选择连接到任意的Peer节点上,创建交易和调用交易。在实际系统运行环境中,客户端负责与Peer节点通信提交实际交易调用,与共识服务通信请求广播交易的任务。
Peer节点负责与共识服务节点通信来进行世界状态的维护和更新。它们会收到共识服务广播的消息,以区块的形式接收排序好的交易信息,然后更新和维护本地的世界状态与账本。
与此同时,Peer节点可以额外地担当背书节点的角色,负责为交易背书。背书节点的特殊功能是针对特定的交易设置的,在它提交前对其进行背书操作。每个合约代码程序都可以指定一个包含多个背书节点集合的背书策略。这个背书策略将定义一个有效的交易背书(通常情况下是背书节点签名的集合)的充要条件。需要注意的是,存在一个特殊情况,在安装新的合约代码的部署交易中,(部署)背书策略是由一个系统合约代码的背书策略指定的,而不能自己指定。
共识服务节点Orderer是共识服务的组成部分。共识服务可以看作一个提供交付保证的通信组织。共识服务节点的职责就是对交易进行排序,确保最后所有的交易以同样的序列输出,并提供送达保证服务的广播通信服务。关于共识服务,之后我们还将详细介绍。
共识服务
网络中的Orderer节点聚集在一起形成了共识服务。它可以看作一个提供交付保证的通信组织。共识服务为客户端和Peer节点提供了一个共享的通信通道,还为包含交易的消息提供了广播服务的功能。
客户端连接到通道后,可以通过共识服务广播消息将消息发送给所有的Peer节点。共识服务可以为所有消息提供==原子交付保证==,也就是说,在Fabric中共识服务保证了消息通信是序列化和可靠的。换句话说,共识服务输出给所有连接在通道上的Peer节点相同的消息,并且输出的逻辑顺序也是相同的。
共识服务可以有不同的实现方式,在 v1.4版本中,Fabric将共识服务设计成了可插拔模块,可以根据不同的应用场景配置不同的共识选项。目前,Fabric提供了三种模式实现:Solo、Kafka和Raft。
Solo是一种部署在单个节点上的简单时序服务,主要用于开发测试,它只支持单链和单通道。
Kafka是一种支持多通道分区的集群共识服务,可以支持CFT(crash faults tolerance)。
Raft遵循"领导者和追随者"模型,每个通道都选举一个“领导者”,它的决定将被复制给“追随者”,支持CFT。只要总节点数与失效节点数满足n>=2f+1,它就允许包括领导者在哪的部分节点宕机失效。与基于Kalfa的共识服务相比,Raft应该更容易设置和管理,并且它们的设计允许组织为分散的共识服务贡献节点。
分布式账本
区块链技术从其底层构造上分析,可以视为一种共享账本技术。账本是区块链的核心组成部分,==在区块链的账本中存储了所有的历史交易和状态改变记录==。在Fabric中,每个通道都对应着一个共享账本,而每个连接在共享账本上的Peer节点,都能参与网络和查看账本信息,即它允许网络中的所有节点参与和查看账本信息。账本上的信息是公开共享的,并且在每个Peer节点上都维持着一份账本的副本。
共享账本以文件系统的形式存储于本地。共享账本由两部分组成: 链式结构的Chain部分和存储状态数据的State部分。其中,Chain部分存储着所有交易的信息,只可添加查询,不可删改。State部分存储着交易日志中所有变量的最新值,因为它表示的是通道中所有变量键值对的最新值,所以有时称为"世界状态"。
==合约代码调用执行交易来更改目前的状态数据==,为了使这些合约代码高效交互,设计将最新的键值对存储于状态数据库中。默认的状态数据库采用的是LeverDB,但是可以通过配置切换到CouchDB或者其他数据库。
合约代码服务
合约代码服务提供了一种安全且轻量级的方式,沙箱验证节点上的合约代码执行,提供安全容器服务以及安全的合约代码注册服务。其运行环境是一个"锁定"和安全的容器,合约代码首先会被编译成一个独立的应用程序,运行于隔离的Docker容器中。
在合约代码部署时,将会自动生成一组带有签名的智能合约的Docker基础镜像。在Docker容器中,合约代码与Peer节点的交互过程的步骤如下:
(1) Peer节点收到客户端发来的合约代码执行请求后,通过gRPC与合约代码交互,发送一个合约代码消息对象给对应的合约代码。
(2) 合约代码通过调用Invoke()方法,执行GetState()操作和PutState()操作,向Peer节点获取账本状态数据库和发送账本预提交状态数。若要读取和写入私有数据,则通过GetPrivateDate()和PutPrivateDate()方法。
(3) 合约代码执行成功后,将输出结果发送给Peer节点,背书节点对输入和输出信息进行背书签名,完成后应答给客户端。
合约代码分析
通过前述的结构解读,可以得知合约代码服务是Fabric架构中的核心组成部分,本节将进一步研究合约代码服务中所运行的合约代码,介绍如何编写、部署及调用具体的合约代码。
合约代码概述
合约代码是区块链上运行的一段代码,是Fabric中智能合约的实现方式。同时在Fabric中,==合约代码还是交易生成的唯一来源==。
共享总账是由区块连接而成的一条不断增长的哈希链,而区块中包含了以Merkle树的数据结构表示的所有交易信息,可以说==交易是区块链上最基础的实体单元==。那么交易又是怎样产生的呢?交易只能通过合约代码调用操作而产生,所以合约代码是fabric的核心组件,也是与共享账本交互的唯一渠道。
目前,Fabric支持使用Java、Go和Node.js语言通过实现接口的方式来编写合约代码。Fabric中的合约代码运行在Peer节点上,并且与合约代码相关的操作诸如部署、安装、调用等也都是在Peer节点上进行的。
合约代码通过SDK或者CLI在Fabric网络的Peer节点上进行安装和初始化,使用户与Fabric网络的共享账本之间的交互成为可能。
目前,合约代码的节点运行模式有两种:==一般模式==和==开发模式==。
一般模式是系统默认模式,合约代码运行于Docker容器中。运用Dokcer容器来运行Fabric系统,这样就给Fabric系统和合约代码运行提供了一个隔离的环境,可以提高整个系统的安全性。但是在这种模式下,对于开发人员而言,开发调试过程非常复杂和麻烦,因为每次修改代码之后度需要重新启动Docker容器,这会极大地降低程序开发的效率。
因此,考虑到开发人员的效率问题,Fabric提供了另外一种运行模式,即开发模式。在开发模式下,合约代码不再运行于Docker容器中,而是直接在本地部署、运行、调试,极大地简化了开发过程。
合约代码结构
合约代码是Fabric开发中最主要的部分之一,通过合约代码可以实现对账本和交易等实体的交互与操作,同时实现各种业务逻辑。目前,合约代码支持使用Go、Java、Node.js语言进行编写,通过实现合约代码接口的方式来编写合约代码程序。下面以Go语言为例进行介绍。
合约代码结构的结构主要包括以下3个方面。
合约代码接口
在Fabric v1.4 版本中,合约代码接口包含两个方法:Init()方法和Invoke()方法。Init()方法会在第一次部署合约代码时进行调用,有点类似于类中的构造方法。
就如同其方法名所表达的,Init()方法中一般执行一些合约代码需要的初始化操作。Invoke()方法则是在调用合约代码方法进行一些实际操作时调用,每次调用会被视为一次交易执行。Go语言中的合约代码接口代码如下所示:
Type Chaincode interface {
//初始化工作,一般情况下仅被调用一次
Init(stub ChaincodeStubInterface) pb.Response
//查询或更新world state,可被多次调用
Invoke(stub ChaincodeStubInterface) pb.Response
}
API方法
当合约的Init或者Invoke接口被调时,Fabric传递给合约shim.ChaincodeStubInterface参数并返回pb.Response结果,这些参数可以通过调用API方法去操作账本服务,产生交易信息或者调用其他的合约代码。
目前API方法定义在/core/chaincode目录下的shim包中,并且可以由以下命令生成:
==godoc github.com/hyperledger/fabric/core/chaincode/shim==
主要的API方法可以分为6类,分别是State读写操作、Args读写操作、Transaction读写操作、PrivateData读写操作、合约代码相互调用以及Event设置。下表展示了这些方法及其对应的功能。
合约代码返回信息
合约代码是以protobuffer的形式返回的,定义如下所示:
message Response{
//状态码
int32 status = 1;
//响应码信息
string message = 2;
//响应内容
bytes payload = 3;
}
合约代码还会返回事件信息,包括Message events 和 Chaincode events,定义如下所示:
messageEvent{
oneof Event{
Register register = 1;
Block block = 2;
ChaincodeEvent chaincodeEvent = 3;
Rejection rejection = 4;
Unregister unregister = 5;
}
}
messageChaincodeEvent {
string chaincodeID = 1;
string txID = 2;
string eventName = 3;
bytes payload = 4;
}
一旦完成了合约代码的开发,有两种方式可以与合约代码交互: 通过SDK或者通过CLI命令行。
通过CLI命令行的交互将在下一节进行介绍。
CLI命令行调用
编写完合约代码之后,就要了解如何部署合约代码以及如何调用合约代码。要想进行部署合约代码等相关操作,必然需要启动Fabric系统。Fabric提供CLI接口,支持以命令行的形式完成与Peer节点相关的操作。通过CLI接口,Fabric支持Peer节点的启动停止操作、合约代码的各种相关操作以及通道的相关操作。
合约代码执行泳道图
- 客户端(
SDK/CLI)创建交易提案,包含合约代码函数和调用参数,并以proto消息格式发送到背书节点; - 背书节点调用
shim包的方法创建合约代码仿真交易执行内容; - 背书节点初始化合约、调用参数,基于读取和写入的
key生成读写操作集; - 背书集群节点模拟提案执行:执行读操作,向账本发送查询状态数据库的请求;模拟写操作,获取
key的value值版本号,模拟更新状态数据; - 若返回执行成功,则执行背书操作;若返回失败,则推送错误码
500; - 背书节点对交易结果执行签名,将提案结果返回给客户端(
SDK/CLI),提案结果包括执行返回值、交易结果、背书节点的签名和背书结果(同意或者拒绝)。
交易流程
本节主要通过翻译Hyperledger Fabric官方文档中的架构,分析Fabric中的交易背书过程,首先介绍了Fabric交易背书过程的机制,然后通过一个简单的案例描述了其通用流程,之后详细分析背书过程,最后简单地介绍了Fabric的背书策略以及验证账本和PeerLedger检查点的使用。
通用流程
在Fabric系统中,交易就是一次合约代码的调用,可能有如下两种类型。
- 部署交易: 部署交易使用一个程序作为参数创建新的合约代码以及它所提供的功能,当成功执行调用交易时,合约代码执行指定的参数,可能修改相应账本的状态,并返回输出。
- 调用交易:调用交易在先前部署的交易上下文中执行合约代码以及它所提供的功能,当成功执行调用交易时,合约代码执行指定的函数,可能修改相应账本的状态,并返回输出。
区块链中执行的交易会打包成区块,区块连接起来就形成了共享账本中的哈希链。本节将介绍Fabric系统中一次交易的执行流程。为了更好理解Fabric系统的交易背书过程,本节将先使用一个案例来展示一次成功的交易执行过程。
首先在这个案例中,需要做一些假设,也就是真实开发时需要进行的配置工作。假设如下。
- 节点类型:
E0、E1、E2、E3、E5均为Peer节点,其中特殊的是,E0、E1、E2为此次交易的背书节点,Ordering Service为共识服务节点组成的共识服务。 - 通道配置:本案例中存在两个通道,其中
E0、E1、E2、E3均连接在同一个通道Channel1中,而E4和E5位于另一个通道Channel2中。 - 背书策略:
E0、E1必须签名背书,E2、E3、E4、E5则不属于策略。
做好假设之后,开始案例流程:
(1) 客户端应用通过SDK发送出一个交易提案(transaction propose)给背书节点E0。它用来接收智能合约中相关功能函数的请求,然后更新账本数据(即资产的键/值)。同时在发送前客户端会将这一交易提案打包为一种可识别的格式(如gRPC上的protocol buffer),并使用用户的加密凭证为该交易提案签名。
(2)背书节点E0收到客户端发送的交易提案之后,将先验证客户端签名是否正确,然后将交易提案的参数作为输入模拟执行,执行操作会生成包含执行返回值、读操作集合和写操作集合的交易结果(此时不会更新账本),再对这个交易提案进行背书操作,附上anchor信息发送回客户端。若这个交易提案中的数据带有私有数据,那么背书节点E0会先将私有数据存储到本地的临时数据库中,再通过Gossip协议向其他背书节点传播,只有传播到一定数量,背书节点才能向客户端返回交易结果,交易结果中不携带私有数据,只是私有数据键值对的哈希值。
(3) 客户端想要进一步得到E1的认可,因此需要发送交易提议给E1,并且此时可以决定是否附上从E0处得到的 anchor信息。
(4) 背书节点E1与先前E0的方式一样,验证客户端签名,验证之后模拟执行,再将验证后的Transaction-valid信息发送回客户端。
(5) 客户端会一直等待,直到收集到了足够的背书信息之后,将交易提案和结果以广播的形式传给共识服务。交易中包括readset、背书节点的签名、通道ID以及私有数据的哈希值。共识服务并不会读取交易的详细信息,而是对接收到的交易信息按通道分类进行排序,打包生成区块,因此共识服务无法看到私有数据。
(6) 共识服务会将达成一致的交易打包进区块并传送给连接在这一通道上的所有节点,E4和E5接收不到任何消息,因为它们没有连接在当前交易的通道上。
(7) 各节点验证收到的区块,验证是否满足背书策略以及验证账本上的状态值是否改变来判断交易是否有效。验证成功之后更新账本和世界状态,然后节点会通过事件机制通知客户端交易是否已被加入区块链和交易是否有效。若区块中含有私有数据的哈希值,在验证成功之后将临时数据库中的私有数据存入私有数据库。
流程详解
在 Fabric中,交易是就指是一次合约代码调用,下面将详细分析一次交易背书的过程。
1. 客户端发送交易提案给指定背书节点
为了调用一个交易,客户端会向它所选择的一组背书节点发送一个 PROPOSE消息(这些消息可能不是同时发送的,比如上一节的例子)。对于如何选择背书节点集合,client可以通过Peer使用给定 chaincodeID的背书节点集合, 反过来也可以通过背书策略获取背书节点集合。例如这个交易会被客户端通过 chaincodeID发送给所有相关的背书节点。
除此之外,某些背书节点存在离线或反对的可能,所以存在不签署该交易的背书节点。提交客户端会通过有效的背书节点来尽力满足背书策略。
提案消息的格式
一条PROPOSE的格式为PROPOSE = <PROPOSE, tx, [anchor]>, 包含两个参数, tx交易消息字段是必需的, anchor是可选的参数。 下面对这两个参数进行详细分析。
tx 参数包含了与交易相关的各种信息,字段格式如下:
tx = <clientID, chaincodeID, txPadload, timestamp, clientSig>
clientID: 提交客户端ID
chaincodeID: 调用合约代码ID
txPadload: 包含交易信息的载体
timestamp:时间戳
clientSig: 客户端签名
而对于txPayload字段,调用交易和部署交易的详细信息会有些不同。如果当前交易是调用交易,txPayload只包含2个字段:txPayload = <operation, metadata>
operation: 指合约代码调用的函数和参数
metadata: 指与此次调用相关的其他属性(元数据)
如果当前交易是部署交易,txPayload还将包含一个policies字段
txPayload = <source, metadata, policies>
source: 指合约代码的源代码
metadata: 指与此次调用相关的其他属性
policies: 指与合约代码相关的策略,例如背书策略
anchor参数中包含了readset(一个从原始账本中读取到的版本依赖键值对集合),也就是世界状态中的版本化依赖。如果客户端发送的PROPOSE消息中携带了anchor参数,那么背书节点还需要验证anchor参数中是否与本地匹配。
同时tx字段的加密哈希tid还会被所有节点用来作为交易的标识(tid=HASH(tx)),并且客户端会将它存储在内存中一直等待背书节点相应。
消息模式
因为客户端的消息是需要发送给一组背书节点的,所以对于它的发送顺序是可以由客户端控制的。例如, 通常情况下,客户端会先发送不携带 anchor参数的 PROPOSE消息给单个背书节点, 背书节点接收后,会处理消息并加上 anchor参数返回给客户端。
然后客户端将携带anchor参数的 PROPOSE消息发送给剩下的其他背书节点。而另一种模式,客户端会直接将不携带 anchor参数的 PROPOSE消息发送给背书节点集合,等待它们的返回。客户端可以自由选择消息模式来进行与背书节点间的交互。
2. 背书节点模拟交易执行,产生背书签名
背书节点收到客户端的< PROPOSE,tx,[anchor]>消息之后,它会先验证客户端的签名,验证通过后就会模拟执行交易的内容。需要注意的是,如果客户端指定了 anchor字段,那么需要验证本地KVS中相应键的值,只有与anchor参数中的一致时,背书节点才会模拟执行交易。
模拟执行将会通过调用 chaincodelD对应的合约代码来试验性地执行 txPayload中的操作,同时还会获取背书节点本地维护的世界状态的一个副本。在执行完成后,背书节点会更新 readset和 writeset(存储着状态更新)两个键值对集合的信息,这个机制在DB数据库中也被称为MVCC+ postimage信息。具体键值对操作如下。
给定背书节点执行交易之前的状态s,对于交易读取的每个键k,(k,s(k),version)将会被添加到 readset中(读集记录交易执行前的状态)。对于交易修改的每个键k,(k,v')将会被添加到 writeset中,其中v'是更新后的新值(记录交易执行之后的各个键值的状态)。另外,v'也可以是相对于之前值(s(k).vaue)的差值。
读写集,读集记录了交易执行之前交易中对应键的状态,写集记录了交易执行之后交易中对应键的状态
在模拟执行之后,Peer节点会根据所谓的背书逻辑来决定是否为这一交易进行背书,默认情况下,Peer节点会接收 tran-proposal消息并简单地为其签名。
如果决定为这一交易背书,它就会发送< TRANSACTION-ENDORSED,tid,REJECTED>消息给客户端。
需要注意的是,背书节点模拟执行不会更改任何的哈希链与世界状态信息,它只是模拟执行(模拟执行后的结果记录在 writeset 中),然后将操作所引起的状态改变存储于writeset中。
3.客户端收集交易背书后并通过共识服务广播
在一定的时间间隔内, 如果客户端收到了足够多的背书节点发回的背书消息(TRANSACTION
,ENDORSED, tid), 则背书策略被满足, 那么这笔交易就会被认为背书成功, 需要注意的是此时还没有提交。否则,如果一定时间间隔内没有收到足够多的背书消息, 那么客户端就会抛弃该笔交易或者稍后进行重试。
对于有效的背书成功的交易, 客户端会通过 broadcast(blob)方法调用共识服务, 其中blob指的就是背书消息。如果 client没有直接调用共识服务的能力, 它可以选择某个Peer节点代理调用, 当然这个Peer节点必须是可信的, 否则这个交易可能会被视为背书无效。
4.共识服务传送区块给Peer节点
在共识服务对交易进行排序并打包成区块之后, 共识服务将会触发 deliver(seqno, prevhash, blob)事件,然后将这一区块广播给所有链接在 Fabric和同一通道上的Peer节点。
Peer节点在收到共识服务广播的区块之后会进行两类校验。
第一类是通过(blob, tran-proposal, chaincodeID)指向的合约代码所包含的背书策略来验证blob, endorsement是否有效;
第二类则是在完成第一类验证之后,还将会验证blob, endorsement, tran-proposal, readset集合是否正确。
针对 readset集合的验证,根据一致性和隔离保证,可以采用不一样的方式。如果在合约代码中未指定相应的背书策略,那么可串行化( serializability) 则是默认的验证方式。对于可串行化要求每个readset中的键的版本要与state中的版本对应,然后拒绝不满足条件的交易。
假如上面的验证都通过了,这个交易就可以被视为有效或者已提交的了。在验证之后,Peer节点就会在 peerLedger账本对应的位掩码中用1标记这笔交易,并将 writeset中的更新应用到Fabric区块链的state世界状态中。而如果验证失败,那么这个交易就被认为是无效的,Peer节点则会在peerledger的位掩码中用0标记这笔交易,并且无效的交易不会引起任何改变更新。
在共识服务的保证下,上述流程会保证所有正常的Peer节点在执行一个deliver事件之后拥有相同的世界状态。即所有正确的节点将会收到一个完全一样的deliver事件的序列。至此,本次交易流程结束。
背书策略
Fabric所提供的背书策略机制是用于指定区块链节点交易验证的规则。每当背书节点收到交易请求的时候,系统就会通过VSCC( validation system Chaincode, 系统合约代码验证)对交易的有效性进行验证。在交易流程中,一个交易可能会包含来自于背书节点的一个或多个背书,而VSCC机制将会根据以下规则决定交易的有效性。
- 背书的数量是否符合要求;
- 背书是否来自预期的来源;
- 所有来自背书节点的背书是否有效(即是否来自预期消息上的有效证书的有效签名)
背书策略就是用来指定以上的背书数量要求和背书来源预期集合。每个背书策略由两个部分组成,原则( principal)和定限闸(threshold gate)。原则P用来识别预期签名的实体;定限闸T有两个输入参数,t表示背书数量,n表示背书节点列表,即满足t的条件,背书节点属于n。例如,T(2, 'A' , 'B', 'C')表示需要获得2个以上来自于'A','B','C'的背书。T(1, 'A', T(2, 'B', 'C'))表示需要收到来自'A'的背书或者来自'B'和'C'的两个背书。
在CLI命令行交互中,背书策略的表示语法是EXPR([E, E...]),EXPR有两个选项AND或者OR,其中AND表示"与",表示都需要,而OR则表示"或"。比如AND('Org1.member,'Org2.member','Org3.member')表示请求3个组的签名,OR('Org1.member',Org2.member')表示请求两个组中任意一个的签名即可。而OR('Org1.member',AND('Org2.member','Org3.member'))表示有两种选择,第一种是请求组织1的签名,第二种是请求组织2和组织3的签名。
小结
本章对Hyperledger Fabric进行了深入解读,有助于读者深入理解Fabric的底层实现原理。对Hyperledger Fabric架构进行了深入分析,从成员服务、区块链服务、以及合约代码服务三个方面探讨Hyperledger Fabric的架构组成与特点,给出了Fabric架构设计和模块组件。再次,给出了合约代码的代码结构、调用方式和执行流程。最后,对交易背书流程展开了详细分析。