Fabric 学习二:系统架构

张凯_9908 · · 1964 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

1、功能架构如下图所示。

image.png

从中可以看出包括三大组件:区块链服务(Blockchain)、链码服务(Chaincode)、成员权限管理(Membership)。

1.区块链服务

区块链服务提供一个分布式账本平台。一般地,多个交易被打包进区块中,多个区块构成一条区块链。区块链代表的是账本状态机发生变更的历史过程。

  • 交易
    交易意味着围绕着某个链码进行操作。
    交易可以改变世界状态。
    交易中包括的内容主要有:

    • 交易类型:目前包括 Deploy、Invoke、Query、Terminate 四种;
    • uuid:代表交易的唯一编号;
    • 链码编号 chaincodeID:交易针对的链码;
    • 负载内容的 hash 值:Deploy 或 Invoke 时候可以指定负载内容;
    • 交易的保密等级 ConfidentialityLevel;
    • 交易相关的 metadata 信息;
    • 临时生成值 nonce:跟安全机制相关;
    • 交易者的证书信息 cert;
    • 签名信息 signature;
    • metadata 信息;
    • 时间戳 timestamp。

交易的数据结构(Protobuf 格式)定义为

message Transaction {
    enum Type {
        UNDEFINED = 0;
        // deploy a chaincode to the network and call `Init` function
        CHAINCODE_DEPLOY = 1;
        // call a chaincode `Invoke` function as a transaction
        CHAINCODE_INVOKE = 2;
        // call a chaincode `query` function
        CHAINCODE_QUERY = 3;
        // terminate a chaincode; not implemented yet
        CHAINCODE_TERMINATE = 4;
    }
    Type type = 1;
    //store ChaincodeID as bytes so its encrypted value can be stored
    bytes chaincodeID = 2;
    bytes payload = 3;
    bytes metadata = 4;
    string uuid = 5;
    google.protobuf.Timestamp timestamp = 6;

    ConfidentialityLevel confidentialityLevel = 7;
    string confidentialityProtocolVersion = 8;
    bytes nonce = 9;

    bytes toValidators = 10;
    bytes cert = 11;
    bytes signature = 12;
}
  • 区块
    区块打包交易,确认交易后的世界状态。
    一个区块中包括的内容主要有:

    • 版本号 version:协议的版本信息;
    • 时间戳 timestamp:由区块提议者设定;
    • 交易信息的默克尔树的根 hash 值:由区块所包括的交易构成;
    • 世界观的默克尔树的根 hash 值:由交易发生后整个世界的状态值构成;
    • 前一个区块的 hash 值:构成链所必须;
    • 共识相关的元数据:可选值;
    • 非 hash 数据:不参与 hash 过程,各个 peer 上的值可能不同,例如本地提交时间、交易处理的返回值等;
      注意具体的交易信息并不存放在区块中。

区块的数据结构(Protobuf 格式)定义为

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

一个真实的区块内容示例:

{
    "nonHashData": {
        "localLedgerCommitTimestamp": {
            "nanos": 975295157,
                "seconds": 1466057539
        },
            "transactionResults": [
            {
                "uuid": "7be1529ee16969baf9f3156247a0ee8e7eee99a6a0a816776acff65e6e1def71249f4cb1cad5e0f0b60b25dd2a6975efb282741c0e1ecc53fa8c10a9aaa31137"
            }
            ]
    },
        "previousBlockHash": "RrndKwuojRMjOz/rdD7rJD/NUupiuBuCtQwnZG7Vdi/XXcTd2MDyAMsFAZ1ntZL2/IIcSUeatIZAKS6ss7fEvg==",
        "stateHash": "TiIwROg48Z4xXFFIPEunNpavMxnvmZKg+yFxKK3VBY0zqiK3L0QQ5ILIV85iy7U+EiVhwEbkBb1Kb7w1ddqU5g==",
        "transactions": [
        {
            "chaincodeID": "CkdnaXRodWIuY29tL2h5cGVybGVkZ2VyL2ZhYnJpYy9leGFtcGxlcy9jaGFpbmNvZGUvZ28vY2hhaW5jb2RlX2V4YW1wbGUwMhKAATdiZTE1MjllZTE2OTY5YmFmOWYzMTU2MjQ3YTBlZThlN2VlZTk5YTZhMGE4MTY3NzZhY2ZmNjVlNmUxZGVmNzEyNDlmNGNiMWNhZDVlMGYwYjYwYjI1ZGQyYTY5NzVlZmIyODI3NDFjMGUxZWNjNTNmYThjMTBhOWFhYTMxMTM3",
            "payload": "Cu0BCAESzAEKR2dpdGh1Yi5jb20vaHlwZXJsZWRnZXIvZmFicmljL2V4YW1wbGVzL2NoYWluY29kZS9nby9jaGFpbmNvZGVfZXhhbXBsZTAyEoABN2JlMTUyOWVlMTY5NjliYWY5ZjMxNTYyNDdhMGVlOGU3ZWVlOTlhNmEwYTgxNjc3NmFjZmY2NWU2ZTFkZWY3MTI0OWY0Y2IxY2FkNWUwZjBiNjBiMjVkZDJhNjk3NWVmYjI4Mjc0MWMwZTFlY2M1M2ZhOGMxMGE5YWFhMzExMzcaGgoEaW5pdBIBYRIFMTAwMDASAWISBTIwMDAw",
            "timestamp": {
                "nanos": 298275779,
                "seconds": 1466057529
            },
            "type": 1,
            "uuid": "7be1529ee16969baf9f3156247a0ee8e7eee99a6a0a816776acff65e6e1def71249f4cb1cad5e0f0b60b25dd2a6975efb282741c0e1ecc53fa8c10a9aaa31137"
        }
    ]
}
  • 世界观
    世界观用于存放链码执行过程中涉及到的状态变量,是一个键值数据库。典型的元素为 [chaincodeID, ckey]: value 结构。

    为了方便计算变更后的 hash 值,一般采用默克尔树数据结构进行存储。树的结构由两个参数(numBuckets 和 maxGroupingAtEachLevel)来进行初始配置,并由 hashFunction 配置决定存放键值到叶子节点的方式。显然,各个节点必须保持相同的配置,并且启动后一般不建议变动。

    • numBuckets:叶子节点的个数,每个叶子节点是一个桶(bucket),所有的键值被 hashFunction 散列分散到各个桶,决定树的宽度;
    • maxGroupingAtEachLevel:决定每个节点由多少个子节点的 hash 值构成,决定树的深度。
      其中,桶的内容由它所保存到键值先按照 chaincodeID 聚合,再按照升序方式组成。

一般地,假设某桶中包括M个 chaincodeID,对于chaincodeID_i,假设其包括 N 个键值对,则聚合G_i 内容可以计算为:

G_i = Len(chaincodeID_i) + chaincodeID_i + N + \sum_{1}^{N} {len(key_j) + key_j + len(value_j) + value_j}

该桶的内容则为

bucket = \sum_{1}^{M} G_i

2.链码服务(智能合约)

链码包含所有的处理逻辑,并对外提供接口,外部通过调用链码接口来改变世界观。

  • 1、接口和操作
    链码需要实现 Chaincode 接口,以被 VP 节点调用。
type Chaincode interface {

Init(stub *ChaincodeStub, function string, args []string) ([]byte, error)

Invoke(stub *ChaincodeStub, function string, args []string) ([]byte, error)

Query(stub *ChaincodeStub, function string, args []string) ([]byte, error)
}

链码目前支持的交易类型包括:部署(Deploy)、调用(Invoke)和查询(Query)。

  • 部署:VP 节点利用链码创建沙盒,沙盒启动后,处理 protobuf 协议的 shim 层一次性发送包含 ChaincodeID 信息的 REGISTER 消息给 VP 节点,进行注册,注册完成后,VP 节点通过 gRPC 传递参数并调用链码 Init 函数完成初始化;
  • 调用:VP 节点发送 TRANSACTION 消息给链码沙盒的 shim 层,shim 层用传过来的参数调用链码的 Invoke 函数完成调用;
  • 查询:VP 节点发送 QUERY 消息给链码沙盒的 shim 层,shim 层用传过来的参数调用链码的 Query 函数完成查询。

不同链码之间可能互相调用和查询。

  • 2、容器
    在实现上,链码需要运行在隔离的容器中,超级账本采用了 Docker 作为默认容器。
    对容器的操作支持三种方法:build、start、stop,对应的接口为 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 
}

链码部署成功后,会创建连接到部署它的 VP 节点的 gRPC 通道,以接受后续 Invoke 或 Query 指令。

  • 3、gRPC 消息
    VP 节点和容器之间通过 gRPC 消息来交互。消息基本结构为
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; 
  bytes payload = 3; 
  string uuid = 4;
}

当发生链码部署时,容器启动后发送 REGISTER 消息到 VP 节点。如果成功,VP 节点返回 REGISTERED 消息,并发送 INIT 消息到容器,调用链码中的 Init 方法。

当发生链码调用时,VP 节点发送 TRANSACTION 消息到容器,调用其 Invoke 方法。如果成功,容器会返回 RESPONSE 消息。

类似的,当发生链码查询时,VP 节点发送 QUERY 消息到容器,调用其 Query 方法。如果成功,容器会返回 RESPONSE 消息。

3.成员权限管理

通过基于 PKI 的成员权限管理,平台可以对接入的节点和客户端的能力进行限制。

证书有三种,Enrollment,Transaction,以及确保安全通信的 TLS 证书。

  • 注册证书 ECert:用于用户身份验证的注册证书,颁发给提供了注册凭证的用户或节点,一般长期有效;
  • 交易证书 TCert:用于交易签名的交易证书,颁发给用户,控制每个交易的权限,一般针对某个交易,短期有效。
  • 通信证书 TLSCert:加密传输的TSL证书,控制对网络的访问,并且防止窃听。
image.png

概念术语

  • Auditability(审计性):在一定权限和许可下,可以对链上的交易进行审计和检查。
  • Block(区块):代表一批得到确认的交易信息的整体,准备被共识加入到区块链中。
  • Blockchain(区块链):由多个区块链接而成的链表结构,除了首个区块,每个区块都包括前继区块内容的 hash 值。
  • Certificate Authority(CA):负责身份权限管理,又叫 Member Service 或 Identity Service。
  • Chaincode(链上代码或链码):区块链上的应用代码,扩展自“智能合约”概念,支持 golang、nodejs 等,运行在隔离的容器环境中。
  • Committer(提交节点):1.0 架构中一种 peer 节点角色,负责对 orderer 排序后的交易进行检查,选择合法的交易执行并写入存储。
  • Confidentiality(保密):只有交易相关方可以看到交易内容,其它人未经授权则无法看到。
  • Endorser(背书节点):1.0 架构中一种 peer 节点角色,负责检验某个交易是否合法,是否愿意为之背书、签名。
  • Enrollment Certificate Authority(ECA,注册 CA):负责成员身份相关证书管理的 CA。
  • Ledger(账本):包括区块链结构(带有所有的可验证交易信息,但只有最终成功的交易会改变世界观)和当前的世界观(world state)。Ledger 仅存在于 Peer 节点。
  • MSP(Member Service Provider,成员服务提供者):成员服务的抽象访问接口,实现对不同成员服务的可拔插支持。
  • Non-validating Peer(非验证节点):不参与账本维护,仅作为交易代理响应客户端的 REST 请求,并对交易进行一些基本的有效性检查,之后转发给验证节点。
  • Orderer(排序节点):1.0 架构中的共识服务角色,负责排序看到的交易,提供全局确认的顺序。
  • Permissioned Ledger(带权限的账本):网络中所有节点必须是经过许可的,非许可过的节点则无法加入网络。
  • Privacy(隐私保护):交易员可以隐藏交易的身份,其它成员在无特殊权限的情况下,只能对交易进行验证,而无法获知身份信息。
  • Transaction(交易):执行账本上的某个函数调用。具体函数在 chaincode 中实现。
  • Transactor(交易者):发起交易调用的客户端。
  • Transaction Certificate Authority(TCA,交易 CA):负责维护交易相关证书管理的 CA。
  • Validating Peer(验证节点):维护账本的核心节点,参与一致性维护、对交易的验证和执行。
  • World State(世界观):是一个键值数据库,chaincode 用它来存储交易相关的状态。

2、网络拓扑结构

image.png

从图中可以看出包含以下节点:客户端节点、CA节点、Peer节点、Orderer节点。

  • 客户端节点(应用程序/SDK/命令行工具)
    客户端或应用程序代表由最终用户操作的实体,它必须连接到某一个Peer节点或者排序服务节点上与区块链网络进行通信。客户端向背书节点(Endorser Peer)提交交易提案(Proposal),当收集到足够背书后,向排序服务节点广播交易,进行排序,生成区块

  • Peer节点(Leader主节点、Anchor锚节点、Endorser背书节点、Committer记账节点)
    从上图中可以看出每个组织可以拥有一到多个Peer节点。每个Peer节点可以担任如下多种角色:

    • Endorser Peer(背书结点)
      所谓背书(Endorsement),就是指特定peer执行交易并向生成交易提案( proposal )的客户端应用程序返回YES/NO响应的过程。
      背书节点是动态的角色,是与具体链码绑定的。每个链码在实例化的时候都会设置背书策略(Endorsement policy),指定哪些节点对交易背书才有效。
      也只有在应用程序向节点发起交易背书请求时才成为背书节点,其他时候是普通的记账节点,只负责验证交易并记账。

    • Leader Peer(主节点)
      主节点负责和Orderer排序服务节点通信,从排序服务节点处获取最新的区块并在组织内部同步。可以强制设置,也可以选举产生。

    • Committer Peer(记账节点)
      负责验证从排序服务节点接收的区块里的交易,然后将块提交(写入/追加)到其通道账本的副本。记账节点还将每个块中的每个交易标记为有效或无效。

    • Anchor Peer(锚节点)
      在一个通道( channel )上可以被所有其他peer发现的peer,通道上的每个成员都有一个Anchor Peer(或多个Anchor peer 来防止单点故障),允许属于不同成员的peer发现通道上的所有现有peer。
      注:每个Peer节点必定是一个记账节点,除记账节点外,它也可以担任其它一到多种角色,即某个节点可以同时是记账节点和背书节点,也可以同时是记账节点、背书节点、主节点,锚节点。

  • Orderer(排序节点)
    排序服务节点接收包含背书签名的交易,对未打包的交易进行排序生成区块,广播给Peer节点。

    排序服务提供的是原子广播,保证同一个链上的节点为接收到相同的消息,并且有相同的逻辑顺序。

    排序服务独立于peer进程存在并且以先来先服务的方式对网络上的所有信道进行排序交易。排序服务旨在支持超出现有的SOLO和Kafka品种的可插拔实现。排序服务是整个网络的公共绑定; 它包含绑定到每个成员的加密身份材料。

  • CA(可选)
    CA节点是fabric的证书颁发节点(Certificate Authority),由服务器(fabric-ca-server)和客户端(fabric-ca-client)组成。

    CA节点接收客户端的注册申请,返回注册密码用于登录,以便获取身份证书。在区块链网络上所有的操作都会验证用户的身份。

    CA节点是可选的,也可以用其他成熟的第三方CA颁发证书。

Fabric系统是通过组织来划分的,每个组织内都包含承担不同功能的Peer 节点,每个Peer节点又可以担任多种角色。所有的组织共用一个统一的Orderer集群。

3、交易流程

image.png
  1. 应用程序客户端首先构建交易的预案,预案的作用是调用通道中的链码来读取或者写入账本的数据。应用端使用 Fabric 的 SDK 打包交易预案,并使用用户的私钥对预案进行签名。

    应用打包完交易预案后,接着把预案提交给通道中的背书节点(Endorser),调用证书服务(CA)。
    通道的背书策略定义了哪些节点背书后交易才能有效,应用端根据背书策略选择相应的背书节点,并向它们提交交易预案。

  2. 背书(Endorser)节点收到交易预案后,首先校验交易的签名是否合法,然后根据签名者的身份,确认其是否具有权限进行相关交易。此外,背书节点还需要检查交易预案的格式是否正确以及是否之前提交过(防止重放攻击)。

    在所有合法性校验通过后,背书节点按照交易预案,调用链码。链码执行时,读取的数据(键值对)是节点中本地的状态数据库。
    需要指出的是,链码在背书节点中是模拟执行,即对数据库的写操作并不会对账本作改变,所有的写操作将归总到一个写入的集合( Write Set )中记录下来。

    在链码执行完成之后,将返回链码读取过的数据集( Read Set )和链码写入的数据集( Write Set )。读集和写集将在确认节点中用于确定交易是否最终写入账本。

  3. 背书(Endorser)节点把链码模拟执行后得到的读写集( Read-Write Set )等信息签名后发回给预案提交方(应用端)。

  4. 应用端在收到背书响应之后,检查背书节点的签名和比较不同节点背书的结果是否一致。
    如果预案是查询账本的请求,则应用端无需提交交易给排序节点。如果是更新账本的请求,应用端在收集到满足背书策略的背书响应数量之后,把背书预案中得到的读写集、所有背书节点的签名和通道号发给排序节点。

  5. 排序(Orderers)节点在收到各个节点发来的交易后,并不检查交易的全部内容,而是按照交易中的通道号对交易分类排序,然后把相同通道的交易打包成数据块( blob )。

  6. 排序(Orderers)节点把打包好的数据块广播给通道中所有的成员。
    数据块的广播有两种触发条件,一种是当通道的交易数量达到某个预设的阈值,另一种是在交易数量没有超过阈值但距离上次广播的时间超过某个特定阈值,也可触发广播数据块。两种方式相结合,使得排序过的交易可以及时广播出去。

  7. 记账(Committer)节点收到排序节点发来的交易数据块后,逐笔检查区块中的交易。先检查交易的合法性以及该交易是否曾经出现过。然后调用 VSCC( Validation System Chaincode )的系统链码检验交易的背书签名是否合法,以及背书的数量是否满足背书策略的要求。

    接下来进行多版本并发控制 MVCC 的检查,即校验交易的读集(Read Set)是否和当前账本中的版本一致(即没有变化)。如果没有改变,说明交易写集(Write Set)中对数据的修改有效,把该交易标注为有效,交易的写集更新到状态数据库中。

    如果当前账本的数据和读集版本不一致,则该交易被标注为无效,不更新状态数据库。数据块中的交易数据在标注成“有效”或“无效”后封装成区块(block)写入账本的区块链中。

上述的交易流程中,采用了 MVCC 的乐观锁( optimistic locking )模型,提高了系统的并发能力。需要注意的是,MVCC 也带来了一些局限性。例如,在同一个区块中若有两个交易先后对某个数据项做更新,顺序在后的交易将失败,因为它的读集版本和当前数据项版本已经不一致(因为之前的交易更新了数据)。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:张凯_9908

查看原文:Fabric 学习二:系统架构

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1964 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传