1. 关键概念
1.1. 网络架构
1.1.1. 交易
和其他区块链系统一样地,Fabric也是通过交易来实现与状态机的交互。交易是由具备写权限的账户发起,调用链码接口,修改对应状态的请求,所有节点执行该交易应该获得同样的结果,从而使整个状态机保持一致性。
和其他系统略有不同的是交易执行成功与否的标准。在以太坊中,节点按顺序执行区块中的交易,每执行一笔交易,即对账本做出更改,下一笔交易在新的状态下执行,比如小明有100块钱,第一笔交易扣掉小明10块,还有90块,第二笔扣掉20块,还有70块,两笔交易都成功落账。可是,在Fabric中,第二笔交易就不会成功,而会被标记为invalid。
这里就要提到RWSet这个概念。每笔交易会有一个执行状态,称为读集,以及一个执行结果,称为写集,读写集是背书时候确定的。在交易执行的时候,读集对应的KVPair必须和当前状态相同,若相同,则可以按照写集修改状态。
经过背书,第一笔交易TX1{<RSet: (K: 小明, V: 100)>, <WSet: (K: 小明, V: 90)>},第二笔交易TX2{<RSet: (K: 小明, V: 100)>, <WSet: (K: 小明, V: 80)>},它们的读集是一样的,当执行第二笔交易的时候,发现TX2的读集和当前状态不同了,那这笔交易就会被标志为错误。
Fabric主要的特点就是读集不同,即执行的预期状态不同,则交易失败,对比以太坊等公链多了这个限制条件,更加严格。
1.1.2. 通道
Hyperledger Fabric 中的通道是两个或两个以上特定网络成员之间通信的专用“子网”,用于进行私有和机密的交易。通道由成员(组织)、每个成员的锚点节点、共享账本、链码应用程序和排序服务节点定义。网络上的每个交易都在一个通道上执行,在这个通道上,每一方都必须经过身份认证和授权才能在该通道上进行交易。
通道的定义如上,通道即channel,是由多个组织构成的子网络,具有自己的区块链账本,成员管理也是由channel自己负责的。channel可以分为两种,一种是系统通道,运行着Fabric排序服务,维护了所有可以创建通道的组织的列表,这个列表称之为”联盟“,其他通道的创建、更新都要经过系统通道,系统通道可以通过Raft等协议启动,是整个系统的共识部分,其他通道依赖于排序服务实现自己的一致性;另一种是应用通道,即由多个组织联合创建的通道,可以运行链码,执行某些业务。
通道的准入都是通过CA来控制的,在Fabric中CA可以看做账户,CA中包含了很多该实体的属性,比如角色、权限和公钥等,每个组织可以创建自己的私钥和对应的根CA,为组织的其他成员颁发CA,Fabric提供了FabricCA等一系列工具来完成这些工作。
通道的所有组织的根证书等信息都会存储在账本中,所以任意组织签发的证书都可以被其他组织的节点验证。
1.2. 节点角色
1.2.1. Orderer
Orderer节点就是排序服务的节点,负责为所有通道打包区块,同时维护了联盟组织列表,只有这些组织可以创建通道,但是非联盟成员也可以加入通道。
1.2.2. Peer
Peer节点负责维护一个通道的区块链账本,每个组织可以有多个Peer节点,Peer节点负责为该通道的交易进行背书,当交易满足背书策略的时候,就可以提交给排序服务,通道将获得新的区块,Fabric的区块是惰性产生的,不会产生空块。应用程序就是直接和Peer节点交互的。总之,Peer身份具备背书、提交交易和查询等权力。
无论是什么角色,都对应于一个CA证书,证书中包含了相对应的信息。
1.2.3. Client
Client一般是应用程序需要具备的身份,具备提出交易、查询Peer等权限。
1.3. 账户管理
1.3.1. 证书体系
在Fabric中,证书就是账户,只有具备有效证书才可以向网络发送交易。
每个证书对应一个公私钥对,但是一个公私钥对可以不止一个证书。证书的唯一ID可以由其Subject和Issuer确定。
每个组织需要自行维护证书体系,需要处理组织内证书的签发、回收和记录。
组织的证书体系由一个组织根证书和组织成员证书组成。根证书往往是一个自签证书,用来给组织成员颁发证书,这个过程可以通过Fabric CA工具来实现。组织成员可以分为多种角色,比如管理员、Peer和Client等,可以在CA中添加各种属性,也可以是被根CA签发的中间CA,可以定义各种权限,比如撤销证书、颁发证书和定义属性的权限,FabricCA的可拓展性很强,尤其是可以自定义属性,这些属性可以在不同的业务中发挥权限控制的作用,比如在链码中检查调用者是否具备某个属性。
1.3.2. MSP
面对复杂的证书体系和权限控制,Fabric将这些抽象成一个实体,称为Membership Provider,即MSP。MSP中包含了证明自己或他人权限的所有信息,具体实例如下:
Client@org1.example.com
├── msp
│ ├── admincerts // 管理员文件夹,存储着当前组织的管理员证书,这里没有
│ ├── cacerts // 存储当前组织的根证书
│ │ └── ca.org1.example.com-cert.pem
│ ├── config.yaml
│ ├── keystore // 存储当前账户的私钥
│ │ └── d6a78efa7db939cd68e059a9593cbbb89bc68d14e7caebbbc38e4fb81e0fe579_sk
│ ├── signcerts // 当前账户的证书,由组织根证书颁发
│ │ └── cert.pem
│ └── tlscacerts // TLS的根证书
│ └── tlsca.org1.example.com-cert.pem
└── tls
├── ca.crt // TLS的根证书
├── client.crt // 本地TLS证书,由TLS根证书颁发
└── client.key // TLS私钥
MSP的实体就是一些文件,包含证书和私钥等,都是PEM格式,这在Fabric中称为LocalMSP,是供本地账户使用的,Peer节点、Orderer节点都会维护一份,对于整个channel来说,还维护了所有组织的根证书等信息,这是存储在账本里的,供全通道所有组织使用。
1.3.3. TLS
Fabric可以开启双向TLS保护网络内信息的隐私,比如证书等信息都不会泄露出去,毕竟证书中包含了很多权限和组织信息,这可能是很关键的商业信息。
在CS模式的TLS协议中,往往是仅有服务端有证书,客户端仅需要在验证服务端证书之后,用服务端公钥加密关键信息后发送给服务端,服务端用私钥解出该信息,双方都在本地生成同样的对称密钥,后续通信使用该对称密钥加密即可。而双向TLS则需要服务端验证客户端的TLS证书,后续流程与单向相同。
2. 交易流程
2.1. 交易背书
不像以太坊,把交易广播出去就不关用户的事情了,Fabric用户发出交易分两步,第一步交易背书,第二步提交交易到Orderer。
在这里用户指的是一个应用程序,它的CA具备写权限。假设通道有两个组织Org1和Org2,现在要调用链码CC,要发交易调用CC需要满足它的背书策略,这是链码要求的,而背书就是指满足背书策略的过程,举例来讲,就是要求某些组织的Peer为该交易签名。
应用程序首先向Org1和Org2的Peer节点发送自己签名的交易请求,Peer节点会验证应用程序是否有足够的权限,然后执行交易判断是否有效,此时是不写入账本的,如果交易请求通过检验,Peer会为其签名,返回给应用程序。
然后,应用程序收集Peer的返回,组装交易,发送给Orderer节点,Orderer节点会将该交易打包到当前通道的区块中。
区块分发给各个Peer节点,Peer执行交易,同时修改账本状态,此时交易也不一定会成功,因为RSet可能与当前状态不符,交易会被标记为invalid。
具体流程可见
3. 链码开发
3.1. 背书策略
背书实际上就是组织对某个提案进行检查并签名,需要哪些组织、哪些角色签名,就是背书策略。
背书策略是针对链码来说的,在链码安装或者升级的时候会设置背书策略,这是对链码整体的背书策略,除此之外,还可以针对某些特殊的Key设置策略来覆盖整体背书策略,这称作基于状态的背书策略。
简单举例:AND('Org1.member', 'Org2.member')
这个就是背书策略的表达式,简单来看,就是要求该链码的调用要经过Org1和Org2任何成员的背书。详情可见
针对键级别的背书策略,需要在编写链码时以代码的形式预先写好,这里有具体的接口。
3.2. 安装与初始化
在Fabric中链码都是通过docker容器的形式存在的。
安装的命令可以走一遍教程,走完就熟悉了。
安装实际上就是把代码发送到各个Peer节点,实例化会编译链码并为其启动一个容器,Peer节点与其通过gRPC通信。
3.3. 标准接口
链码编写比较简单,1.4提供的接口和2.0有较大差距,后面再研究一下。
链码实际上就是实现接口:
// Chaincode interface must be implemented by all chaincodes. The fabric runs
// the transactions by calling these functions as specified.
type Chaincode interface {
// Init is called during Instantiate transaction after the chaincode container
// has been established for the first time, allowing the chaincode to
// initialize its internal data
Init(stub ChaincodeStubInterface) pb.Response
// Invoke is called to update or query the ledger in a proposal transaction.
// Updated state variables are not committed to the ledger until the
// transaction is committed.
Invoke(stub ChaincodeStubInterface) pb.Response
}
这里作者实现了几个例子,这是Poly跨链协议中关于Fabric端的协议实现。不像solidity合约,单纯一本合约就是一个contract实体,就像是写了一个类或者结构体,在这里需要实现main入口,是完整的可执行程序。
3.4. 跨链码调用
在使用跨链码调用的时候,存在一些问题。
首先Fabric不存在合约账户这种概念,链码单纯就是逻辑、业务,当跨链码调用的时候,交易上下文除了参数外没有变化,原始的信息都可以从Proposal中手动解出来。
跨链码的时候,被调用的链码的日志不会写到区块中,具体还需要看一下代码。