第7章 实现数据隔离的多链及多通道

多链(multi-chain)是Hyperledger Fabric 1.0新增的一个重要功能。在0.6版中,所有的节点都属于一个链,所有的节点都会同步相同的数据,这会带来几个问题:

·随着业务量的增加,数据会越来越大,每个节点都会同步和存储一些不必要的数据,这增加了数据同步的压力、数据存储的压力和数据处理的压力;

·网络中所有的节点都能读取到所有的数据,一些敏感数据可能分发给其他不应该访问这些数据的节点,这会带来数据安全隐患。

在Hyperledger Fabric 1.0的版本中,增加了对多链的支持。在一个由很多Peer节点组成的区块链网络中,可能同时存在多个链。每个链可能由不同的节点组成,这些节点维护着相 同的数据,包括账本数据和状态数据等,不同链的数据是相互隔离的。一个节点根据应用需求,可以加入到不同的链中,在同步数据的时候只同步已加入链的数据, 这能减少数据同步的时间,减少数据的存储空间。基于多链的部署结构,在不同链上运行的智能合约访问的是不同的数据,以实现数据的隐私保护,也不会受到数据 依赖的限制,提高了并行处理的效率。

多链是全局设计,其实现需要底层架构的支持:

·数据存储对多链的支持;

·链码对多链的支持;

·多通道对多链的支持。

下面我们分开来看每个部分是如何支持多链实现的。

7.1 数据存储对多链的支持

数据存储包含账本数据、索引数据、状态数据和历史数据等几个部分,记账节点包含所有的数据,排序节点只包含账本数据及其索引数据,不包含状态数据及其历史数据。¤

7.1.1 账本数据

记账节点和排序节点都会存储账本数据,即区块文件。加 入 会 员 微 信 dedao555

前面的章节已经介绍过,记账节点的账本数据是基于文件系统存储的,每个链的账本数据存储在不同的目录下。只有属于某个链,才会存在以这个链的通道命名的账本目录。

记账节点的账本数据存储目录一般是/var/hyperledger/production /ledgersData/chains/chains,其中/var/hyperledger/production可以通过core.yaml文件中 的peer.fileSystemPath选项指定,后面的ledgersData/chains/chains是记账节点中固定的目录后缀。下面是一个 记账节点的账本数据目录结构:


root@peer0:/var/hyperledger/production/ledgersData/chains/chains# tree .
.
|-- businesschannel
|   `-- blockfile_000000
`-- pocchannel
    `-- blockfile_000000

2 directories, 2 files

这个目录下面有两个目录:businesschannel和pocchannel。它们代表的是两个通道,也就是 两个链的数据,每个链现在只有一个区块文件,blockfile_是文件名中固定的前缀,000000是固定的6位占位符,下一个文件名会依次递增。从这 个目录结构可以看到,记账节点在底层账本数据存储的时候就对不同链的数据进行了隔离。

排序节点会存储所有链的账本数据,排序节点除了可以选择序列化区块文件的格式外,还支持JSON文件格式和内存数 据结构的账本数据,后面两种都只在测试环境下使用。序列化区块文件和JSON文件格式区块文件的存储目录一般是/var/hyperledger /production/orderer/chains,其中,orderer/chains是固定的目录后缀。同样地,不同链的账本数据存储在以通道名 称为目录名称的目录中,以实现不同链账本数据的物理隔离。内存数据的账本数据没有持久化的存储,不同链的账本数据存储在不同的数据结构中。

7.1.2 索引数据

记账节点和排序节点都会给账本数据建立索引,不同的是排序节点只会建立以BlockNum为属性的索引。

索引文件存储的目录是/var/hyperledger/production/ledgersData /chains/index,其中,ledgers Data/chains/index是记账节点上固定的目录后缀,排序节点上的目录后缀是orderer/index。下面是一个记账节点上的索引数据的 目录:


root@peer0:/var/hyperledger/production/ledgersData/chains/index# tree
.
|-- 000002.ldb
|-- 000007.log
|-- CURRENT
|-- LOCK
|-- LOG
`-- MANIFEST-000008

0 directories, 6 files

索引数据是存储在LevelDB数据库里的,数据库的类型目前是不可选的。LevelDB是持久化的K-V数据 库,在保存索引的时候会加上ledgerid作为前缀,当然生成的组合键在构造的时候是要先转换成[]byte数组的。由于索引数据存储在同一个数据库 中,所以对于不同链的数据,索引数据的实现是逻辑隔离的,并非是物理隔离的。

7.1.3 状态数据

排序节点不需要查询具体的交易信息和状态数据,也不会存储状态数据及其历史数据。

Peer节点上状态数据存储的目录是/var/hyperledger/production/ledgersData/stateLeveldb,其中,ledgersData/stateLeveldb是固定的后缀:


root@peer0:/var/hyperledger/production/ledgersData/stateLeveldb# tree
.
|-- 000002.ldb
|-- 000007.log
|-- CURRENT
|-- LOCK
|-- LOG
`-- MANIFEST-000008

0 directories, 6 file

状态数据也是基于K-V存储的,同一个节点的状态存储在同一个数据库中,没有进行物理隔离。和索引数据不同的是, 状态数据是和chaincodeID相关的,不同chaincodeID的数据是逻辑隔离的,而chaincodeID同样是以chainID为前缀进行 了逻辑隔离。

7.1.4 历史数据

历史数据存储的目录是/var/hyperledger/production/ledgersData/historyLeveldb,其中,ledgersData/historyLeveldb是固定的后缀:


root@peer0:/var/hyperledger/production/ledgersData/historyLeveldb# tree
.
|-- 000002.ldb
|-- 000010.ldb
|-- 000012.log
|-- CURRENT
|-- LOCK
|-- LOG
|-- LOG.old
|-- MANIFEST-000013
`-- level-party.sock

0 directories, 9 files

历史数据目前内置的数据库是LevelDB,也是不可替换的。记录的是状态数据的历史记录,同状态数据一样,通过在构建chaincodeID的时候增加ChainID前缀来逻辑隔离不同链的数据。

7.2 链码对多链的支持

链码是Hyperleger Fabric 1.0提供的智能合约方案,实现了交易的模拟执行。链码从多个纬度对多链提供了支持,比如链码的生命周期管理、链码和背书节点的通信、链码的部署方法等。

7.2.1 链码的生命周期管理

智能合约在Hyperledger Fabric 1.0上称为链码(Chaincode),是独立运行的应用程序,只接收启动它的背书节点的指令,执行指定的业务逻辑。在多链的情况下,同一个智能合约可 能会在不同的链上运行。为了重用智能合约代码,智能合约的部署拆分成了安装和实例化两个步骤,安装只是把链码的源代码序列化后和链码名称、版本等封装成 ChaincodeDeploymentSpec保存到Peer节点上,链码安装跟具体的链没有关系,也不需要ChainID参数。

换个说法,不同链的链码是没有隔离的,也就是说,在一个链安装的链码可能和另外一个链的链码产生冲突。链码安装的时候会检查是否存在相同名称和版本的链码,如果不同的上层应用同时都部署了相同的链码和版本,可能存在一个链的链码安装成功,另外一个链的链码安装失败的情况。

实例化和链码升级,是在指定的链上操作的,实际过程分为两个步骤,第一步是调用系统链码LSCC的部署操作,通过 LSCC把链码计算哈希后生成的ChaincodeData存放在状态数据库中;第二步是从文件系统中读取保存的链码源码,生成镜像后执行初始化操作。链 码操作包括初始化和调用,它们都是在具体链上操作的,链码镜像的命名规则是:


 

NetworkID-PeerID-ChaincodeName-ChaincodeVersion-SHA256(ChainID)


链码镜像名称的最后一部分是对ChainID计算SHA256哈希后再转换成十六进制的字符串,在逻辑上不同链的 链码会有不同的镜像名称。启动的链码容器命名和镜像一样,只是会把“:”替换成“_”。这样,不同链的链码执行是可以在不同的环境中隔离的。不过,实际在 启动链码容器的时候并没有指定ChainID这个参数,就是说目前不同链上相同链码是运行在同一个容器中的。但即使运行在相同的链码容器中,也会通过 ChainID进行逻辑隔离。详细的通信机制见下一节的介绍。

7.2.2 链码和背书节点的通信

链码容器启动以后,会和启动它的背书节点建立gRPC连接。应用程序或者命令行通过gRPC连接给背书节点发送请 求,背书节点校验通过后会通过链码和背书节点建立的gRPC连接将请求发送给链码去执行。链码的执行本身是和具体链无关的,链码容器也不会在本地保存任何 数据,是一个无状态的执行环境。需要访问或者写入状态数据时,则通过建立好的gRPC连接发送请求给背书节点,再进行后续的业务逻辑处理。就是说,在链码 这一端,是不区分链的,所以不同链才可以共用相同的链码容器。

链码容器启动的时候,和背书节点建立的gRPC连接没有和链相关的信息,链码通过建立好的gRPC连接发送给背书 节点的第一个信息是:ChaincodeMessage_REGISTER,内容是序列化后的ChaincodeID,在不同链上其也可能是相同的(虽然 ChaincodeID的命名规则是:ChaincodeName:ChaincodeVersion/ChainID,目前的命名也是没有 ChainID标识的)。不管是多个容器执行链码,还是在同一个容器中执行链码,链码运行的结果都会通过gRPC发送给背书节点,背书节点怎么知道是哪个 链上的操作呢?

链码运行是以交易号作为标识的,客户端发起Proposal请求时需要指定发送到哪个链上,背书节点会把交易号和 链标识进行关联,再把消息发送给链码去执行,接收到链码返回的请求就知道属于哪个链了。背书节点的内部维护了一个运行中的链码映射表,键是 ChaincodeID,值是封装了Handler的链码运行时环境chaincodeRTEnv,就是同一个链码都对应同一个链码运行时环境,负责处理 链码发送过来的消息。Handler内部维护了一个交易的上下文映射表txCtxs,键是交易号txid,值是transactionContext的结 构体,如下所示:


type transactionContext struct {
    chainID          string
    signedProp       *pb.SignedProposal
    proposal         *pb.Proposal
    responseNotifier chan *pb.ChaincodeMessage

    // 记录范围查询的迭代器
    queryIteratorMap map[string]commonledger.ResultsIterator

    txsimulator          ledger.TxSimulator
    historyQueryExecutor ledger.HistoryQueryExecutor
}

其中,chainID就是链的标识,通过txsimulator对不同链的状态数据库进行操作,实现不同链数据处 理的逻辑隔离。交易上下文映射表txCtxs在每次调用链码时都会更新,链码容器启动后就会给Handler发送sendReady消息,给每个交易创建 一个映射表项。接收到链码发送过来的消息后,通过txCtxs能对不同的链进行操作。

7.2.3 链码的部署和调用

多链的实现也会对链码的部署和调用方式有影响,首先需要创建一个链,fabric-sdk-go的接口是:


type FabricClient interface {
    NewChannel(name string) (Channel, error)
    CreateChannel(request CreateChannelRequest) (txn.TransactionID, error)
}

type CreateChannelRequest struct {
    // 必填 - channel名称
    Name string
    // 必填 - 发送更新请求的Orderer
    Orderer Orderer
    // 可选 - envelope object包含了初始化channel所需的设置以及签名
    // 可通过命令行工具configtx来创建
    Envelope []byte
    // 可选 - 通过package中的buildChannelConfig()方法来构建ConfigUpdate对象
    Config []byte
    // 可选 - 使用`config`参数时,指定创建策略所需的签名集合
    // 详细参考 signChannelConfig()方法
    Signatures []*common.ConfigSignature

    // InvokeChannelRequest允许传入TransactionID参数
    // 该请求结构虽然包含一致性字段,但也可能删除
    TxnID txn.TransactionID
}

其中,NewChannel只是在本地创建一个Channel对象,用来进行后续的初始化和其他操作。实际的创建链请求需要调用CreateChannel,请求参数CreateChannelRequest里的Name是和NewChannel中的name对应的。

然后链码的部署分成两个步骤,链码安装InstallChaincode和链码的实例化Send InstantiateProposal,其中InstallChaincode是定义在FabricClient中的,是和链无关的操 作,SendInstantiateProposal是定义在Channel中的。


type FabricClient interface {
    InstallChaincode(chaincodeName string, chaincodePath string, chaincodeVersion
    string, chaincodePackage []byte, targets []Peer) ([]*txn.TransactionProposal
    Response, string, error)
}

type Channel interface {
    SendInstantiateProposal(chaincodeName string, args []string, chaincodePath
    string, chaincodeVersion string, chaincodePolicy *common.SignaturePolicy
    Envelope, targets []txn.ProposalProcessor) ([]*txn.TransactionProposalResponse, txn.
    TransactionID, error)
}

最后,在链码调用的时候需要使用创建好的Channel对象:


func InvokeChaincode(client fab.FabricClient, channel fab.Channel, targets []
apitxn.ProposalProcessor,
    eventHub fab.EventHub, chaincodeID string, fcn string, args []string,
    transientData map[string][]byte) (apitxn.TransactionID, error)

7.3 多通道对多链的支持

排序节点同时会给多个链提供服务,会接收到多个链提交过来的交易并形成不同链的区块。Hyperledger Fabric 1.0采用多通道的方法来隔离不同链的数据。

发送给排序服务的节点怎么区分是哪个链的数据呢?客户端在接收到背书节点返回的执行结果后,会生成最终的交易。其实,交易里面会包含发送给背书节点的Proposal请求,每个Proposal都会包含请求头common.Header,其定义如下:


type Header struct {
    ChannelHeader   []byte `protobuf:"bytes,1,opt,name=channel_header,json=chann
    elHeader,proto3" json:"channel_header,omitempty"`
    SignatureHeader []byte `protobuf:"bytes,2,opt,name=signature_header,json=sig
    natureHeader,proto3" json:"signature_header,omitempty"`
}

其中的ChannelHeader是包含了通道编号的序列化字节数组,如下所示:


// Header是一种通用的重播预防,包含重放签名的身份信息
type ChannelHeader struct {
    Type int32 `protobuf:"varint,1,opt,name=type" json:"type,omitempty"`
    // 协议版本信息
    Version int32 `protobuf:"varint,2,opt,name=version" json:"version,omitempty"`
    // 发件人创建消息的本地时间
    Timestamp *google_protobuf.Timestamp `protobuf:"bytes,3,opt,name=timestamp"
    json:"timestamp,omitempty"`
    // ChannelId用于表示消息绑定channel的标识
    ChannelId string `protobuf:"bytes,4,opt,name=channel_id,json=channelId"
    json:"channel_id,omitempty"`
    // 端到端的唯一标识符
    //  -  高级设置,如用户终端或SDK
    //  -  传递给背书节点 (用于唯一性检查)
    //  -  header会随消息一直传递,将被committer获取(也用于唯一性检查)
    //  -  会存入账本
    TxId string `protobuf:"bytes,5,opt,name=tx_id,json=txId" json:"tx_id,
    omitempty"`
    // header中的Epoch的定义取决于块高度
    // response中的Epoch表示逻辑窗口时间. 以下两种情况时,peer节点才接受提案响应
    // 1. 消息中的epoch与当前epoch匹配
    // 2. 消息只在当前epoch中出现一次(即没有被重播)
    Epoch uint64 `protobuf:"varint,6,opt,name=epoch" json:"epoch,omitempty"`
    // 可以根据header类型进行扩展
    Extension []byte `protobuf:"bytes,7,opt,name=extension,proto3"
    json:"extension,omitempty"`
}

排序服务在接收到交易请求后,会先反序列化得到ChannelId,在不同的通道上进行排序打包生成区块。多通道(Multi-channel)部分的内容详见第6章。

7.4 命令行和SDK对多链的支持

我们在前面的章节已经介绍过命令行的使用,其中可以指定一个-C参数代表在指定的通道上操作,在入口处提供了对多链的支持。

我们在第10章还会看到在超级账本提供给应用程序的SDK中,也提供了多链的接口。

7.5 关于系统链

系统链是一个特殊的链,含有系统层面全局配置区块链网络的联盟及组织信息、MSP信息和策略信息等,只存在于排序 服务中。涉及一些信息的修改,比如增加一个组织,增加排序服务节点,这些都会在系统链上增加一个配置区块。整个系统有且只有一个系统链,系统链是通过创世 区块配置的,排序服务启动的时候通过ORDERER_GENERAL_GENESISFILE环境变量指定创世区块文件并创建系统链。系统链的名称可以在 创建创世区块的时候通过工具configtxgen的channelID参数指定,默认的系统链名称是testchainid,是否是系统链的判断方法就 是配置区块信息里是否有联盟信息配置项。关于创世区块的详细信息也请参考前面已经介绍过的第6章的相关内容。

7.6 本章小结

在本章中,我们介绍了在Hyperledger Fabric 1.0中支持的多链及其内部实现。在整个区块链网络中,支持多个链同时运行是一个系统工程,涉及所有的参与方,包括应用程序、Peer节点、排序服务节点 等,如何从这些节点中动态地组建一个链,需要很多方面的支持。从业务流程上看,也涉及多个环节,包括创世区块的创建、链码的部署、链码的调用、链码的运 行、交易排序、交易验证和记账等。不同的节点加入到不同的链中,还有很多权限和策略的控制。这是一种全新的架构,增加了很多的复杂度。 Hyperledger Fabric 1.0相对于0.6版本而言,很多比较难理解和操作的部分都跟多链和多通道相关。理解了多链的目的后,其实自然就知道为什么要这么设计了加 入 会 员 微 信 dedao555。

来源:我是码农,转载请保留出处和链接!

本文链接:http://www.54manong.com/?id=1061

'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646208", container: s }); })();
'); (window.slotbydup = window.slotbydup || []).push({ id: "u3646147", container: s }); })();
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值