HyperLedger Fabric协议规范

协议规范

前言

这份文档是带有权限的区块链的工业界实现的协议规范。它不会详细的解释实现细节,而是描述系统和应用之间的接口和关系。

目标读者

这份规范的目标读者包括:

  • 想实现符合这份规范的区块链的厂商
  • 想扩展 fabric 功能的工具开发者
  • 想利用区块链技术来丰富他们应用的应用开发者

作者

下面这些作者编写了这份分档: Binh Q Nguyen, Elli Androulaki, Angelo De Caro, Sheehan Anderson, Manish Sethi, Thorsten Kramp, Alessandro Sorniotti, Marko Vukolic, Florian Simon Schubert, Jason K Yellick, Konstantinos Christidis, Srinivasan Muralidharan, Anna D Derbakova, Dulce Ponceleon, David Kravitz, Diego Masini.

评审

下面这些评审人评审了这份文档: Frank Lu, John Wolpert, Bishop Brock, Nitin Gaur, Sharon Weed, Konrad Pabjan.

致谢

下面这些贡献者对这份规范提供了技术支持:
Gennaro Cuomo, Joseph A Latone, Christian Cachin


目录

1. 介绍
  • 1.1 什么是 fabric ?
  • 1.2 为什么是 fabric ?
  • 1.3 术语
2. Fabric
  • 2.1 架构
  • 2.1.1 Membership 服务
  • 2.1.2 Blockchain 服务
  • 2.1.3 Chaincode 服务
  • 2.1.4 事件
  • 2.1.5 应用程序接口
  • 2.1.6 命令行界面
  • 2.2 拓扑
  • 2.2.1 单验证 Peer
  • 2.2.2 多验证 Peers
  • 2.2.3 多链
3. 协议
  • 3.1 消息
  • 3.1.1 发现消息
  • 3.1.2 交易消息
  • 3.1.2.1 交易数据结构
  • 3.1.2.2 交易规范
  • 3.1.2.3 交易部署
  • 3.1.2.4 交易调用
  • 3.1.2.5 交易查询
  • 3.1.3 同步消息
  • 3.1.4 共识消息
  • 3.2 总账
  • 3.2.1 区块链
  • 3.2.1.1 块
  • 3.2.1.2 块 Hashing
  • 3.2.1.3 非散列数据(NonHashData)
  • 3.2.1.4 交易
  • 3.2.2 世界状态(World State)
  • 3.2.2.1 世界状态的 Hashing
  • 3.2.2.1.1 Bucket-tree
  • 3.3 Chaincode
  • 3.3.1 Virtual Machine 实例化
  • 3.3.2 Chaincode 协议
  • 3.3.2.1 Chaincode 部署
  • 3.3.2.2 Chaincode 调用
  • 3.3.2.3 Chaincode 查询
  • 3.3.2.4 Chaincode 状态
  • 3.4 可插拔的共识框架
  • 3.4.1 共识者接口
  • 3.4.2 共识程序接口
  • 3.4.3 Inquirer 接口
  • 3.4.4 Communicator 接口
  • 3.4.5 SecurityUtils 接口
  • 3.4.6 LedgerStack 接口
  • 3.4.7 Executor 接口
  • 3.4.7.1 开始批量交易
  • 3.4.7.2 执行交易
  • 3.4.7.3 提交与回滚交易
  • 3.4.8 Ledger 接口
  • 3.4.8.1 ReadOnlyLedger 接口
  • 3.4.8.2 UtilLedger 接口
  • 3.4.8.3 WritableLedger 接口
  • 3.4.9 RemoteLedgers 接口
  • 3.4.10 Controller 包
  • 3.4.11 Helper 包
  • 3.5 事件
  • 3.5.1 事件流
  • 3.5.2 事件结构
  • 3.5.3 事件适配器
4. 安全
    1. 安全
  • 4.1 商业安全需求
  • 4.2 使用成员管理的用户隐私
  • 4.2.1 用户/客户端注册过程
  • 4.2.2 过期和废止证书
  • 4.3 基础设施层面提供的交易安全
  • 4.3.1 交易的安全生命周期
  • 4.3.2 交易保密性
  • 4.3.2.1 针对用户的保密
  • 4.3.2.2 针对验证器的保密
  • 4.3.3 防重放攻击
  • 4.4 应用的访问控制功能
  • 4.4.1 调用访问控制
  • 4.4.2 读访问控制
  • 4.5 在线钱包服务
  • 4.6 网络安全(TLS)
  • 4.7 当前版本的限制
  • 4.7.1 简化客户端
  • 4.7.2 简化交易保密
5. 拜占庭共识
  • 5.1 概览
  • 5.2 Core PBFT
6. 应用编程接口
  • 6.1 REST 服务
  • 6.2 REST API
  • 6.3 CLI
7. 应用模型
  • 7.1 应用组成
  • 7.2 应用样例
8. 未来发展方向
  • 8.1 企业集成
  • 8.2 性能与可扩展性
  • 8.3 附加的共识插件
  • 8.4 附加的语言
9. References

1. 介绍

这份文档规范了适用于工业界的区块链的概念,架构和协议。

1.1 什么是 fabric?

fabric 是在系统中数字事件,交易调用,不同参与者共享的总账。总账只能通过共识的参与者来更新,而且一旦被记录,信息永远不能被修改。每一个记录的事件都可以根据参与者的协议进行加密验证。

交易是安全的,私有的并且可信的。每个参与者通过向网络membership服务证明自己的身份来访问系统。交易是通过发放给各个的参与者,不可连接的,提供在网络上完全匿名的证书来生成的。交易内容通过复杂的密钥加密来保证只有参与者才能看到,确保业务交易私密性。

总账可以按照规定规则来审计全部或部分总账分录。在与参与者合作中,审计员可以通过基于时间的证书来获得总账的查看,连接交易来提供实际的资产操作。

fabric 是区块链技术的一种实现,比特币是可以在fabric上构建的一种简单应用。它通过模块化的架构来允许组件的“插入-运行”来实现这份协议规范。它具有强大的容器技术来支持任何主流的语言来开发智能合约。利用熟悉的和被证明的技术是fabric的座右铭。

1.2 为什么是 fabric?

早期的区块链技术提供一个目的集合,但是通常对具体的工业应用支持的不是很好。为了满足现代市场的需求,fabric 是基于工业关注点针对特定行业的多种多样的需求来设计的,并引入了这个领域内的开拓者的经验,如扩展性。fabric 为权限网络,隐私,和多个区块链网络的私密信息提供一种新的方法。

1.3 术语

以下术语在此规范的有限范围内定义,以帮助读者清楚准确的了解这里所描述的概念。

交易(Transaction) 是区块链上执行功能的一个请求。功能是使用链节点(chainnode)来实现的。

交易者(Transactor) 是向客户端应用这样发出交易的实体。

总账(Ledger) 是一系列包含交易和当前世界状态(World State)的加密的链接块。

世界状态(World State) 是包含交易执行结果的变量集合。

链码(Chaincode) 是作为交易的一部分保存在总账上的应用级的代码(如智能合约)。链节点运行的交易可能会改变世界状态。

验证Peer(Validating Peer) 是网络中负责达成共识,验证交易并维护总账的一个计算节点。

非验证Peer(Non-validating Peer) 是网络上作为代理把交易员连接到附近验证节点的计算节点。非验证Peer只验证交易但不执行它们。它还承载事件流服务和REST服务。

带有权限的总账(Permissioned Ledger) 是一个由每个实体或节点都是网络成员所组成的区块链网络。匿名节点是不允许连接的。

隐私(Privacy) 是链上的交易者需要隐瞒自己在网络上身份。虽然网络的成员可以查看交易,但是交易在没有得到特殊的权限前不能连接到交易者。

保密(Confidentiality) 是交易的内容不能被非利益相关者访问到的功能。

可审计性(Auditability) 作为商业用途的区块链需要遵守法规,很容易让监管机构审计交易记录。所以区块链是必须的。

2. Fabric

fabric是由下面这个小节所描述的核心组件所组成的。

2.1 架构

这个架构参考关注在三个类别中:会员(Membership),区块链(Blockchan)和链码(chaincode)。这些类别是逻辑结构,而不是物理上的把不同的组件分割到独立的进程,地址空间,(虚拟)机器中。

Reference architecture

2.1.1 成员服务

成员服务为网络提供身份管理,隐私,保密和可审计性的服务。在一个不带权限的区块链中,参与者是不需要被授权的,且所有的节点都可以同样的提交交易并把它们汇集到可接受的块中,如:它们没有角色的区分。成员服务通过公钥基础设施(Public Key Infrastructure
(PKI))和去中心化的/共识技术使得不带权限的区块链变成带权限的区块链。在后者中,通过实体注册来获得长时间的,可能根据实体类型生成的身份凭证(登记证书enrollment certificates)。在用户使用过程中,这样的证书允许交易证书颁发机构(Transaction Certificate Authority
(TCA))颁发匿名证书。这样的证书,如交易证书,被用来对提交交易授权。交易证书存储在区块链中,并对审计集群授权,否则交易是不可链接的。

2.1.2 区块链服务

区块链服务通过 HTTP/2 上的点对点(peer-to-peer)协议来管理分布式总账。为了提供最高效的哈希算法来维护世界状态的复制,数据结构进行了高度的优化。每个部署中可以插入和配置不同的共识算法(PBFT, Raft, PoW, PoS)。

2.1.3 链码服务

链码服务提供一个安全的,轻量的沙箱在验证节点上执行链码。环境是一个“锁定的”且安全的包含签过名的安全操作系统镜像和链码语言,Go,Java 和 Node.js 的运行时和 SDK 层。可以根据需要来启用其他语言。

2.1.4 事件

验证 peers 和链码可以向在网络上监听并采取行动的应用发送事件。这是一些预定义好的事件集合,链码可以生成客户化的事件。事件会被一个或多个事件适配器消费。之后适配器可能会把事件投递到其他设备,如 Web hooks 或 Kafka。

2.1.5 应用编程接口(API)

fabric的主要接口是 REST API,并通过 Swagger 2.0 来改变。API 允许注册用户,区块链查询和发布交易。链码与执行交易的堆间的交互和交易的结果查询会由 API 集合来规范。

2.1.6 命令行界面(CLI)

CLI包含REST API的一个子集使得开发者能更快的测试链码或查询交易状态。CLI 是通过 Go 语言来实现,并可在多种操作系统上操作。

2.2 拓扑

fabric 的一个部署是由成员服务,多个验证 peers、非验证 peers 和一个或多个应用所组成一个链。也可以有多个链,各个链具有不同的操作参数和安全要求。

2.2.1 单验证Peer

功能上讲,一个非验证 peer 是验证 peer 的子集;非验证 peer 上的功能都可以在验证 peer 上启用,所以在最简单的网络上只有一个验证peer组成。这个配置通常使用在开发环境:单个验证 peer 在编辑-编译-调试周期中被启动。

Single Validating Peer

单个验证 peer 不需要共识,默认情况下使用noops插件来处理接收到的交易。这使得在开发中,开发人员能立即收到返回。

2.2.2 多验证 Peer

生产或测试网络需要有多个验证和非验证 peers 组成。非验证 peer 可以为验证 peer 分担像 API 请求处理或事件处理这样的压力。

Multiple Validating Peers

网状网络(每个验证peer需要和其它验证peer都相连)中的验证 peer 来传播信息。一个非验证 peer 连接到附近的,允许它连接的验证 peer。当应用可能直接连接到验证 peer 时,非验证 peer 是可选的。

2.2.3 多链

验证和非验证 peer 的各个网络组成一个链。可以根据不同的需求创建不同的链,就像根据不同的目的创建不同的 Web 站点。

3. 协议

fabric的点对点(peer-to-peer)通信是建立在允许双向的基于流的消息gRPC上的。它使用Protocol Buffers来序列化peer之间传输的数据结构。Protocol buffers 是语言无关,平台无关并具有可扩展机制来序列化结构化的数据的技术。数据结构,消息和服务是使用 proto3 language注释来描述的。

3.1 消息

消息在节点之间通过Messageproto 结构封装来传递的,可以分为 4 种类型:发现(Discovery), 交易(Transaction), 同步(Synchronization)和共识(Consensus)。每种类型在payload中定义了多种子类型。

message Message {
   enum Type {
        UNDEFINED = 0;

        DISC_HELLO = 1;
        DISC_DISCONNECT = 2;
        DISC_GET_PEERS = 3;
        DISC_PEERS = 4;
        DISC_NEWMSG = 5;

        CHAIN_STATUS = 6;
        CHAIN_TRANSACTION = 7;
        CHAIN_GET_TRANSACTIONS = 8;
        CHAIN_QUERY = 9;

        SYNC_GET_BLOCKS = 11;
        SYNC_BLOCKS = 12;
        SYNC_BLOCK_ADDED = 13;

        SYNC_STATE_GET_SNAPSHOT = 14;
        SYNC_STATE_SNAPSHOT = 15;
        SYNC_STATE_GET_DELTAS = 16;
        SYNC_STATE_DELTAS = 17;

        RESPONSE = 20;
        CONSENSUS = 21;
    }
    Type type = 1;
    bytes payload = 2;
    google.protobuf.Timestamp timestamp = 3;
}

payload是由不同的消息类型所包含的不同的像TransactionResponse这样的对象的不透明的字节数组。例如:typeCHAIN_TRANSACTION那么payload就是一个Transaction对象。

3.1.1 发现消息

在启动时,如果CORE_PEER_DISCOVERY_ROOTNODE被指定,那么 peer 就会运行发现协议。CORE_PEER_DISCOVERY_ROOTNODE是网络(任意peer)中扮演用来发现所有 peer 的起点角色的另一个 peer 的 IP 地址。协议序列以payload是一个包含:

message HelloMessage {
  PeerEndpoint peerEndpoint = 1;
  uint64 blockNumber = 2;
}
message PeerEndpoint {
    PeerID ID = 1;
    string address = 2;
    enum Type {
      UNDEFINED = 0;
      VALIDATOR = 1;
      NON_VALIDATOR = 2;
    }
    Type type = 3;
    bytes pkiID = 4;
}

message PeerID {
    string name = 1;
}

这样的端点的HelloMessage对象的DISC_HELLO消息开始的。

域的定义:

  • PeerID 是在启动时或配置文件中定义的 peer 的任意名字
  • PeerEndpoint 描述了端点和它是验证还是非验证 peer
  • pkiID 是 peer 的加密ID
  • addressip:port这样的格式表示的 peer 的主机名或IP和端口
  • blockNumber 是 peer 的区块链的当前的高度

如果收到的DISC_HELLO 消息的块的高度比当前 peer 的块的高度高,那么它马上初始化同步协议来追上当前的网络。

DISC_HELLO之后,peer 会周期性的发送DISC_GET_PEERS来发现任意想要加入网络的 peer。收到DISC_GET_PEERS后,peer 会发送payload
包含PeerEndpoint的数组的DISC_PEERS作为响应。这是不会使用其它的发现消息类型。

3.1.2 交易消息

有三种不同的交易类型:部署(Deploy),调用(Invoke)和查询(Query)。部署交易向链上安装指定的链码,调用和查询交易会调用部署号的链码。另一种需要考虑的类型是创建(Create)交易,其中部署好的链码是可以在链上实例化并寻址的。这种类型在写这份文档时还没有被实现。

3.1.2.1 交易的数据结构

CHAIN_TRANSACTIONCHAIN_QUERY类型的消息会在payload带有Transaction对象:

message Transaction {
    enum Type {
        UNDEFINED = 0;
        CHAINCODE_DEPLOY = 1;
        CHAINCODE_INVOKE = 2;
        CHAINCODE_QUERY = 3;
        CHAINCODE_TERMINATE = 4;
    }
    Type type = 1;
    string uuid = 5;
    bytes chaincodeID = 2;
    bytes payloadHash = 3;

    ConfidentialityLevel confidentialityLevel = 7;
    bytes nonce = 8;
    bytes cert = 9;
    bytes signature = 10;

    bytes metadata = 4;
    google.protobuf.Timestamp timestamp = 6;
}

message TransactionPayload {
    bytes payload = 1;
}

enum ConfidentialityLevel {
    PUBLIC = 0;
    CONFIDENTIAL = 1;
}

域的定义:
- type - 交易的类型, 为1时表示:
- UNDEFINED - 为未来的使用所保留.
- CHAINCODE_DEPLOY - 代表部署新的链码.
- CHAINCODE_INVOKE - 代表一个链码函数被执行并修改了世界状态
- CHAINCODE_QUERY - 代表一个链码函数被执行并可能只读取了世界状态
- CHAINCODE_TERMINATE - 标记的链码不可用,所以链码中的函数将不能被调用
- chaincodeID - 链码源码,路径,构造函数和参数哈希所得到的ID
- payloadHash - TransactionPayload.payload所定义的哈希字节.
- metadata - 应用可能使用的,由自己定义的任意交易相关的元数据
- uuid - 交易的唯一ID
- timestamp - peer 收到交易时的时间戳
- confidentialityLevel - 数据保密的级别。当前有两个级别。未来可能会有多个级别。
- nonce - 为安全而使用
- cert - 交易者的证书
- signature - 交易者的签名
- TransactionPayload.payload - 交易的payload所定义的字节。由于payload可以很大,所以交易消息只包含payload的哈希

交易安全的详细信息可以在第四节找到

3.1.2.2 交易规范

一个交易通常会关联链码定义及其执行环境(像语言和安全上下文)的链码规范。现在,有一个使用Go语言来编写链码的实现。将来可能会添加新的语言。

message ChaincodeSpec {
    enum Type {
        UNDEFINED = 0;
        GOLANG = 1;
        NODE = 2;
    }
    Type type = 1;
    ChaincodeID chaincodeID = 2;
    ChaincodeInput ctorMsg = 3;
    int32 timeout = 4;
    string secureContext = 5;
    ConfidentialityLevel confidentialityLevel = 6;
    bytes metadata = 7;
}

message ChaincodeID {
    string path = 1;
    string name = 2;
}

message ChaincodeInput {
    string function = 1;
    repeated string args  = 2;
}

域的定义:
- chaincodeID - 链码源码的路径和名字
- ctorMsg - 调用的函数名及参数
- timeout - 执行交易所需的时间(以毫秒表示)
- confidentialityLevel - 这个交易的保密级别
- secureContext - 交易者的安全上下文
- metadata - 应用想要传递下去的任何数据

当 peer 收到chaincodeSpec后以合适的交易消息包装它并广播到网络

3.1.2.3 部署交易

部署交易的类型是CHAINCODE_DEPLOY,且它的payload包含ChaincodeDeploymentSpec对象。

message ChaincodeDeploymentSpec {
    ChaincodeSpec chaincodeSpec = 1;
    google.protobuf.Timestamp effectiveDate = 2;
    bytes codePackage = 3;
}

域的定义:
- chaincodeSpec - 参看上面的3.1.2.2节.
- effectiveDate - 链码准备好可被调用的时间
- codePackage - 链码源码的gzip

当验证 peer 部署链码时,它通常会校验codePackage的哈希来保证交易被部署到网络后没有被篡改。

3.1.2.4 调用交易

调用交易的类型是CHAINCODE_DEPLOY,且它的payload包含ChaincodeInvocationSpec对象。

message ChaincodeInvocationSpec {
    ChaincodeSpec chaincodeSpec = 1;
}

3.1.2.5 查询交易

查询交易除了消息类型是CHAINCODE_QUERY其它和调用交易一样

3.1.3 同步消息

同步协议以3.1.1节描述的,当 peer 知道它自己的区块落后于其它 peer 或和它们不一样后所发起的。peer 广播SYNC_GET_BLOCKSSYNC_STATE_GET_SNAPSHOTSYNC_STATE_GET_DELTAS并分别接收SYNC_BLOCKS, SYNC_STATE_SNAPSHOTSYNC_STATE_DELTAS

安装的共识插件(如:pbft)决定同步协议是如何被应用的。每个小时是针对具体的状态来设计的:

SYNC_GET_BLOCKS 是一个SyncBlockRange对象,包含一个连续区块的范围的payload的请求。

message SyncBlockRange {
    uint64 start = 1;
    uint64 end = 2;
    uint64 end = 3;
}

接收peer使用包含 SyncBlocks对象的payloadSYNC_BLOCKS信息来响应

message SyncBlocks {
    SyncBlockRange range = 1;
    repeated Block blocks = 2;
}

startend标识包含的区块的开始和结束,返回区块的顺序由startend的值定义。如:当start=3,end=5时区块的顺序将会是3,4,5。当start=5,end=3时区块的顺序将会是5,4,3。

SYNC_STATE_GET_SNAPSHOT 请求当前世界状态的快照。 payload是一个SyncStateSnapshotRequest对象

message SyncStateSnapshotRequest {
  uint64 correlationId = 1;
}

correlationId是请求 peer 用来追踪响应消息的。接受 peer 回复payloadSyncStateSnapshot实例的SYNC_STATE_SNAPSHOT信息

message SyncStateSnapshot {
    bytes delta = 1;
    uint64 sequence = 2;
    uint64 blockNumber = 3;
    SyncStateSnapshotRequest request = 4;
}

这条消息包含快照或以0开始的快照流序列中的一块。终止消息是len(delta) == 0的块

SYNC_STATE_GET_DELTAS 请求连续区块的状态变化。默认情况下总账维护500笔交易变化。 delta(j)是block(i)和block(j)之间的状态转变,其中i=j-1。 payload包含SyncStateDeltasRequest实例

message SyncStateDeltasRequest {
    SyncBlockRange range = 1;
}

接收 peer 使用包含 SyncStateDeltas实例的payloadSYNC_STATE_DELTAS信息来响应

message SyncStateDeltas {
    SyncBlockRange range = 1;
    repeated bytes deltas = 2;
}

delta可能以顺序(从i到j)或倒序(从j到i)来表示状态转变

3.1.4 共识消息

共识处理交易,一个CONSENSUS消息是由共识框架接收到CHAIN_TRANSACTION消息时在内部初始化的。框架把CHAIN_TRANSACTION转换为 CONSENSUS然后以相同的payload广播到验证 peer。共识插件接收这条消息并根据内部算法来处理。插件可能创建自定义的子类型来管理共识有穷状态机。3.4节会介绍详细信息。

3.2 总账

总账由两个主要的部分组成,一个是区块链,一个是世界状态。区块链是在总账中的一系列连接好的用来记录交易的区块。世界状态是一个用来存储交易执行状态的键-值(key-value)数据库

3.2.1 区块链

3.2.1.1 区块

区块链是由一个区块链表定义的,每个区块包含它在链中前一个区块的哈希。区块包含的另外两个重要信息是它包含区块执行所有交易后的交易列表和世界状态的哈希

message Block {
  version = 1;
  google.protobuf.Timestamp timestamp = 2;
  bytes transactionsHash = 3;
  bytes stateHash = 4;
  bytes previousBlockHash = 5;
  bytes consensusMetadata = 6;
  NonHashData nonHashData = 7;
}

message BlockTransactions {
  repeated Transaction transactions = 1;
}

域的定义:
* version - 用来追踪协议变化的版本号
* timestamp - 由区块提议者填充的时间戳
* transactionsHash - 区块中交易的merkle root hash
* stateHash - 世界状态的merkle root hash
* previousBlockHash - 前一个区块的hash
* consensusMetadata - 共识可能会引入的一些可选的元数据
* nonHashData - NonHashData消息会在计算区块的哈希前设置为nil,但是在数据库中存储为区块的一部分
* BlockTransactions.transactions - 交易消息的数组,由于交易的大小,它们不会被直接包含在区块中

3.2.1.2 区块哈希
  • previousBlockHash哈希是通过下面算法计算的

    1. 使用protocol buffer库把区块消息序列化为字节码

    2. 使用FIPS 202描述的SHA3 SHAKE256算法来对序列化后的区块消息计算大小为512位的哈希值

  • transactionHash是交易merkle树的根。定义merkle tree实现是一个代办

  • stateHash在3.2.2.1节中定义.

3.2.1.3 非散列数据(NonHashData)

NonHashData消息是用来存储不需要所有 peer 都具有相同值的块元数据。他们是建议值。

message NonHashData {
  google.protobuf.Timestamp localLedgerCommitTimestamp = 1;
  repeated TransactionResult transactionResults = 2;
}

message TransactionResult {
  string uuid = 1;
  bytes result = 2;
  uint32 errorCode = 3;
  string error = 4;
}
  • localLedgerCommitTimestamp - 标识区块提交到本地总账的时间戳

  • TransactionResult - 交易结果的数组

  • TransactionResult.uuid - 交易的ID

  • TransactionResult.result - 交易的返回值

  • TransactionResult.errorCode - 可以用来记录关联交易的错误信息的代码

  • TransactionResult.error - 用来记录关联交易的错误信息的字符串

3.2.1.4 交易执行

一个交易定义了它们部署或执行的链码。区块中的所有交易都可以在记录到总账中的区块之前运行。当链码执行时,他们可能会改变世界状态。之后世界状态的哈希会被记录在区块中。

3.2.2 世界状态

peer 的世界状态涉及到所有被部署的链码的状态集合。进一步说,链码的状态由键值对集合来表示。所以,逻辑上说,peer 的世界状态也是键值对的集合,其中键有元组{chaincodeID, ckey}组成。这里我们使用术语key来标识世界状态的键,如:元组{chaincodeID, ckey} ,而且我们使用cKey来标识链码中的唯一键。

为了下面描述的目的,假定chaincodeID是有效的utf8字符串,且ckeyvalue是一个或多个任意的字节的序列

3.2.2.1 世界状态的哈希

当网络活动时,很多像交易提交和同步 peer 这样的场合可能需要计算 peer 观察到的世界状态的加密-哈希。例如,共识协议可能需要保证网络中最小数量的 peer 观察到同样的世界状态。

应为计算世界状态的加密-哈希是一个非常昂贵的操作,组织世界状态来使得当它改变时能高效效的计算加密-哈希是非常可取的。将来,可以根据不同的负载条件来设计不同的组织形式。

由于fabric是被期望在不同的负载条件下都能正常工作,所以需要一个可拔插的机制来支持世界状态的组织。

3.2.2.1.1 Bucket-tree

Bucket-tree 是世界状态的组织方式的实现。为了下面描述的目的,世界状态的键被表示成两个组件(chaincodeID and ckey) 的通过nil字节的级联,如:key = chaincodeID+nil+cKey

这个方法的模型是一个merkle-treehash table桶的顶部来计算世界状态的加密-哈希

这个方法的核心是世界状态的key-values被假定存储在由预先决定的桶的数量(numBuckets)所组成的哈希表中。一个哈希函数(hashFunction) 被用来确定包含给定键的桶数量。注意hashFunction不代表SHA3这样的加密-哈希方法,而是决定给定的键的桶的数量的正规的编程语言散列函数。

为了对 merkle-tree建模,有序桶扮演了树上的叶子节点-编号最低的桶是树中的最左边的叶子节点。为了构造树的最后第二层,叶子节点的预定义数量 (maxGroupingAtEachLevel),从左边开始把每个这样的分组组合在一起,一个节点被当作组中所有叶子节点的共同父节点来插入到最后第二层中。注意最后的父节点的数量可能会少于maxGroupingAtEachLevel这个构造方式继续使用在更高的层级上直到树的根节点被构造。

下面这个表展示的在{numBuckets=10009 and maxGroupingAtEachLevel=10}的配置下会得到的树在不同层级上的节点数。

Level Number of nodes
0 1
1 2
2 11
3 101
4 1001
5 10009

为了计算世界状态的加密-哈希,需要计算每个桶的加密-哈希,并假设它们是merkle-tree的叶子节点的加密-哈希。为了计算桶的加密-哈希,存储在桶中的键值对首先被序列化为字节码并在其上应用加密-哈希函数。为了序列化桶的键值对,所有具有公共chaincodeID前缀的键值对分别序列化并以chaincodeID的升序的方式追加在一起。为了序列化一个chaincodeID的键值对,会涉及到下面的信息:

  1. chaincodeID的长度(chaincodeID的字节数)
    • chaincodeID的utf8字节码
    • chaincodeID的键值对数量
    • 对于每个键值对(以ckey排序)
      • ckey的长度
      • ckey的字节码
      • 值的长度
      • 值的字节码

对于上面列表的所有数值类型项(如:chaincodeID的长度),使用protobuf的变体编码方式。上面这种编码方式的目的是为了桶中的键值对的字节表示方式不会被任意其他键值对的组合所产生,并减少了序列化字节码的总体大小。

例如:考虑具有chaincodeID1_key1:value1, chaincodeID1_key2:value2, 和 chaincodeID2_key1:value1这样名字的键值对的桶。序列化后的桶看上去会像:12 + chaincodeID1 + 2 + 4 + key1 + 6 + value1 + 4 + key2 + 6 + value2 + 12 + chaincodeID2 + 1 + 4 + key1 + 6 + value1

如果桶中没有键值对,那么加密-哈希为nil

中间节点和根节点的加密-哈希与标准merkle-tree的计算方法一样,即:应用加密-哈希函数到所有子节点的加密-哈希从左到右级联后得到的字节码。进一步说,如果一个子节点的加密-哈希为nil,那么这个子节点的加密-哈希在级联子节点的加密-哈希是就被省略。如果它只有一个子节点,那么它的加密-哈希就是子节点的加密-哈希。最后,根节点的加密-哈希就是世界状态的加密-哈希。

上面这种方法在状态中少数键值对改变时计算加密-哈希是有性能优势的。主要的优势包括:
- 那些没有变化的桶的计算会被跳过
- merkle-tree的宽度和深度可以通过配置numBucketsmaxGroupingAtEachLevel参数来控制。树的不同深度和宽度对性能和不同的资源都会产生不同的影响。

在一个具体的部署中,所有的 peer 都期望使用相同的numBuckets, maxGroupingAtEachLevel, 和 hashFunction的配置。进一步说,如果任何一个配置在之后的阶段被改变,那么这些改变需要应用到所有的 peer 中,来保证 peer 节点之间的加密-哈希的比较是有意义的。即使,这可能会导致基于实现的已有数据的迁移。例如:一种实现希望存储树中所有节点最后计算的加密-哈希,那么它就需要被重新计算。

3.3 链码(Chaincode)

链码是在交易(参看3.1.2节)被部署是分发到网络上,并被所有验证 peer 通过隔离的沙箱来管理的应用级代码。尽管任意的虚拟技术都可以支持沙箱,现在是通过Docker容器来运行链码的。这节中描述的协议可以启用不同虚拟实现的插入与运行。

3.3.1 虚拟机实例化

一个实现VM接口的虚拟机

type VM interface {
   
    build(ctxt context.Context, id string, args []string, env []string, attachstdin bool, attachstdout bool, reader io.Reader) error
    start(ctxt context.Context, id string, args []string, env []string, attachstdin bool, attachstdout bool) error
    stop(ctxt context.Context, id string, timeout uint, dontkill bool, dontremove bool) error
}

fabric在处理链码上的部署交易或其他交易时,如果这个链码的VM未启动(崩溃或之前的不活动导致的关闭)时实例化VM。每个链码镜像通过build函数构建,通过start函数启动,并使用stop函数停止。

一旦链码容器被启动,它使用gRPC来连接到启动这个链码的验证 peer,并为链码上的调用和查询交易建立通道。

3.3.2 链码协议

验证 peer 和它的链码之间是通过gRPC流来通信的。链码容器上有shim层来处理链码与验证 peer 之间的protobuf消息协议。

message ChaincodeMessage {

    enum Type {
        UNDEFINED = 0;
        REGISTER = 1;
        REGISTERED = 2;
        INIT = 3;
        READY = 4;
        TRANSACTION = 5;
        COMPLETED = 6;
        ERROR = 7;
        GET_STATE = 8;
        PUT_STATE = 9;
        DEL_STATE = 10;
        INVOKE_CHAINCODE = 11;
        INVOKE_QUERY = 12;
        RESPONSE = 13;
        QUERY = 14;
        QUERY_COMPLETED = 15;
        QUERY_ERROR = 16;
        RANGE_QUERY_STATE = 17;
    }

    Type type = 1;
    google.protobuf.Timestamp timestamp = 2
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值