深度学习
多链(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
微信号:qq444848023 QQ号:444848023
加入【我是码农】QQ群:864689844(加群验证:我是码农)
全站首页 | 数据结构 | 区块链| 大数据 | 机器学习 | 物联网和云计算 | 面试笔试
var cnzz_protocol = (("https:" == document.location.protocol) ? "https://" : "http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_1276413723'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s23.cnzz.com/z_stat.php%3Fid%3D1276413723%26show%3Dpic1' type='text/javascript'%3E%3C/script%3E"));本站资源大部分来自互联网,版权归原作者所有!
评论专区