区块链技术应用研究与软件开发实践分布式共享的超级账本的数据隔离多链多通道技术.docx
- 文档编号:6332000
- 上传时间:2023-01-05
- 格式:DOCX
- 页数:10
- 大小:24.05KB
区块链技术应用研究与软件开发实践分布式共享的超级账本的数据隔离多链多通道技术.docx
《区块链技术应用研究与软件开发实践分布式共享的超级账本的数据隔离多链多通道技术.docx》由会员分享,可在线阅读,更多相关《区块链技术应用研究与软件开发实践分布式共享的超级账本的数据隔离多链多通道技术.docx(10页珍藏版)》请在冰豆网上搜索。
区块链技术应用研究与软件开发实践分布式共享的超级账本的数据隔离多链多通道技术
分布式共享的超级账本
数据隔离的多链及多通道实现技术
多链(multi-chain)是HyperledgerFabric1.0新增的一个重要功能。
在0.6版中,所有的节点都属于一个链,所有的节点都会同步相同的数据,这会带来几个问题:
·随着业务量的增加,数据会越来越大,每个节点都会同步和存储一些不必要的数据,这增加了数据同步的压力、数据存储的压力和数据处理的压力;
·网络中所有的节点都能读取到所有的数据,一些敏感数据可能分发给其他不应该访问这些数据的节点,这会带来数据安全隐患。
在HyperledgerFabric1.0的版本中,增加了对多链的支持。
在一个由很多Peer节点组成的区块链网络中,可能同时存在多个链。
每个链可能由不同的节点组成,这些节点维护着相同的数据,包括账本数据和状态数据等,不同链的数据是相互隔离的。
一个节点根据应用需求,可以加入到不同的链中,在同步数据的时候只同步已加入链的数据,这能减少数据同步的时间,减少数据的存储空间。
基于多链的部署结构,在不同链上运行的智能合约访问的是不同的数据,以实现数据的隐私保护,也不会受到数据依赖的限制,提高了并行处理的效率。
多链是全局设计,其实现需要底层架构的支持:
·数据存储对多链的支持;
·链码对多链的支持;
·多通道对多链的支持。
下面我们分开来看每个部分是如何支持多链实现的。
1数据存储对多链的支持
数据存储包含账本数据、索引数据、状态数据和历史数据等几个部分,记账节点包含所有的数据,排序节点只包含账本数据及其索引数据,不包含状态数据及其历史数据。
1.1账本数据
记账节点和排序节点都会存储账本数据,即区块文件。
前面的章节已经介绍过,记账节点的账本数据是基于文件系统存储
的,每个链的账本数据存储在不同的目录下。
只有属于某个链,才会存在以这个链的通道命名的账本目录。
记账节点的账本数据存储目录一般是/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
2directories,2files
这个目录下面有两个目录:
businesschannel和pocchannel。
它们代表的是两个通道,也就是两个链的数据,每个链现在只有一个区块文件,blockfile_是文件名中固定的前缀,000000是固定的6位占位符,下一个文件名会依次递增。
从这个目录结构可以看到,记账节点在底层账本数据存储的时候就对不同链的数据进行了隔离。
排序节点会存储所有链的账本数据,排序节点除了可以选择序列化区块文件的格式外,还支持JSON文件格式和内存数据结构的账本数据,后面两种都只在测试环境下使用。
序列化区块文件和JSON文件格式区块文件的存储目录一般是/var/hyperledger/production/orderer/chains,其中,orderer/chains是固定的目录后缀。
同样地,不同链的账本数据存储在以通道名称为目录名称的目录中,以实现不同链账本数据的物理隔离。
内存数据的账本数据没有持久化的存储,不同链的账本数据存储在不同的数据结构中。
1.2索引数据
记账节点和排序节点都会给账本数据建立索引,不同的是排序节点只会建立以BlockNum为属性的索引。
索引文件存储的目录是/var/hyperledger/production/ledgersData/chains/index,其中,ledgersData/chains/index是记账节点上固定的目录后缀,排序节点上的目录后缀是orderer/index。
下面是一个记账节点上的索引数据的目录:
root@peer0:
/var/hyperledger/production/ledgersData/chains/index#tree
.
|--000002.ldb
|--00000log
|--CURRENT
|--LOCK
|--LOG
`--MANIFEST-000008
0directories,6files
索引数据是存储在LevelDB数据库里的,数据库的类型目前是不可选的。
LevelDB是持久化的K-V数据库,在保存索引的时候会加上ledgerid作为前缀,当然生成的组合键在构造的时候是要先转换成[]byte数组的。
由于索引数据存储在同一个数据库中,所以对于不同链的数据,索引数据的实现是逻辑隔离的,并非是物理隔离的。
1.3状态数据
排序节点不需要查询具体的交易信息和状态数据,也不会存储状态数据及其历史数据。
Peer节点上状态数据存储的目录是/var/hyperledger/production/ledgersData/stateLeveldb,其中,ledgersData/stateLeveldb是固定的后缀:
root@peer0:
/var/hyperledger/production/ledgersData/stateLeveldb#tree
.
|--000002.ldb
|--00000log
|--CURRENT
|--LOCK
|--LOG
`--MANIFEST-000008
0directories,6file
状态数据也是基于K-V存储的,同一个节点的状态存储在同一个数据库中,没有进行物理隔离。
和索引数据不同的是,状态数据是和chaincodeID相关的,不同chaincodeID的数据是逻辑隔离的,而chaincodeID同样是以chainID为前缀进行了逻辑隔离。
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
0directories,9files
历史数据目前内置的数据库是LevelDB,也是不可替换的。
记录的是状态数据的历史记录,同状态数据一样,通过在构建chaincodeID的时候增加ChainID前缀来逻辑隔离不同链的数据。
2链码对多链的支持
链码是HyperlegerFabric1.0提供的智能合约方案,实现了交易的模拟执行。
链码从多个纬度对多链提供了支持,比如链码的生命周期管理、链码和背书节点的通信、链码的部署方法等。
2.1链码的生命周期管理
智能合约在HyperledgerFabric1.0上称为链码(Chaincode),是独立运行的应用程序,只接收启动它的背书节点的指令,执行指定的业务逻辑。
在多链的情况下,同一个智能合约可能会在不同的链上运行。
为了重用智能合约代码,智能合约的部署拆分成了安装和实例化两个步骤,安装只是把链码的源代码序列化后和链码名称、版本等封装成ChaincodeDeploymentSpec保存到Peer节点上,链码安装跟具体的链没有关系,也不需要ChainID参数。
换个说法,不同链的链码是没有隔离的,也就是说,在一个链安装的链码可能和另外一个链的链码产生冲突。
链码安装的时候会检查是否存在相同名称和版本的链码,如果不同的上层应用同时都部署了相同的链码和版本,可能存在一个链的链码安装成功,另外一个链的链码安装失败的情况。
实例化和链码升级,是在指定的链上操作的,实际过程分为两个步骤,第一步是调用系统链码LSCC的部署操作,通过LSCC把链码计算哈希后生成的ChaincodeData存放在状态数据库中;第二步是从文件系统中读取保存的链码源码,生成镜像后执行初始化操作。
链码操作包括初始化和调用,它们都是在具体链上操作的,链码镜像的命名规则是:
NetworkID-PeerID-ChaincodeName-ChaincodeVersion-SHA256(ChainID)
链码镜像名称的最后一部分是对ChainID计算SHA256哈希后再转换成十六进制的字符串,在逻辑上不同链的链码会有不同的镜像名称。
启动的链码容器命名和镜像一样,只是会把“:
”替换成“_”。
这样,不同链的链码执行是可以在不同的环境中隔离的。
不过,实际在启动链码容器的时候并没有指定ChainID这个参数,就是说目前不同链上相同链码是运行在同一个容器中的。
但即使运行在相同的链码容器中,也会通过ChainID进行逻辑隔离。
详细的通信机制见下一节的介绍。
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的结构体,如下所示:
typetransactionContextstruct{chainIDstring
signedProp*pb.SignedProposalproposal*pb.ProposalresponseNotifierchan*pb.ChaincodeMessage
//记录范围查询的迭代器
queryIteratorMapmap[string]commonledger.ResultsIterator
txsimulatorledger.TxSimulatorhistoryQueryExecutorledger.HistoryQueryExecutor
}
其中,chainID就是链的标识,通过txsimulator对不同链的状态数据库进行操作,实现不同链数据处理的逻辑隔离。
交易上下文映射表txCtxs在每次调用链码时都会更新,链码容器启动后就会给Handler发送sendReady消息,给每个交易创建一个映射表项。
接收到链码发送过来的消息后,通过txCtxs能对不同的链进行操作。
2.3链码的部署和调用
多链的实现也会对链码的部署和调用方式有影响,首先需要创建一个链,fabric-sdk-go的接口是:
typeFabricClientinterface{NewChannel(namestring)(Channel,error)
CreateChannel(requestCreateChannelRequest)(txn.TransactionID,error)
}
typeCreateChannelRequeststruct{
//必填-channel名称
Namestring
//必填-发送更新请求的Orderer
OrdererOrderer
//可选-envelopeobject包含了初始化channel所需的设置以及签名
//可通过命令行工具configtx来创建
Envelope[]byte
//可选-通过package中的buildChannelConfig()方法来构建ConfigUpdate对象
Config[]byte
//可选-使用`config`参数时,指定创建策略所需的签名集合
//详细参考signChannelConfig()方法
Signatures[]*common.ConfigSignature
//InvokeChannelRequest允许传入TransactionID参数
//该请求结构虽然包含一致性字段,但也可能删除
TxnIDtxn.TransactionID
}
其中,NewChannel只是在本地创建一个Channel对象,用来进行后续的初始化和其他操作。
实际的创建链请求需要调用CreateChannel,请求参数CreateChannelRequest里的Name是和NewChannel中的name对应的。
然后链码的部署分成两个步骤,链码安装InstallChaincode和链码的实例化SendInstantiateProposal,其中InstallChaincode是定义在FabricClient中的,是和链无关的操作,SendInstantiateProposal是定义在Channel中的。
typeFabricClientinterface{
InstallChaincode(chaincodeNamestring,chaincodePathstring,chaincodeVersionstring,chaincodePackage[]byte,targets[]Peer)([]*txn.TransactionProposalResponse,string,error)
}
typeChannelinterface{
SendInstantiateProposal(chaincodeNamestring,args[]string,chaincodePathstring,chaincodeVersionstring,chaincodePolicy*common.SignaturePolicyEnvelope,targets[]txn.ProposalProcessor)([]*txn.TransactionProposalResponse,
txn.
TransactionID,error)
}
最后,在链码调用的时候需要使用创建好的Channel对象:
funcInvokeChaincode(clientfab.FabricClient,channelfab.Channel,targets[]apitxn.ProposalProcessor,
eventHubfab.EventHub,chaincodeIDstring,fcnstring,args[]string,transientDatamap[string][]byte)(apitxn.TransactionID,error)
3多通道对多链的支持
排序节点同时会给多个链提供服务,会接收到多个链提交过来的交易并形成不同链的区块。
HyperledgerFabric1.0采用多通道的方法来隔离不同链的数据。
发送给排序服务的节点怎么区分是哪个链的数据呢?
客户端在接收到背书节点返回的执行结果后,会生成最终的交易。
其实,交易里面会包含发送给背书节点的Proposal请求,每个Proposal都会包含请求头common.Header,其定义如下:
typeHeaderstruct{
ChannelHeader[]byte`protobuf:
"bytes,1,opt,name=channel_header,json=channelHeader,proto3"json:
"channel_header,omitempty"`
SignatureHeader[]byte`protobuf:
"bytes,2,opt,name=signature_header,json=signatureHeader,proto3"json:
"signature_header,omitempty"`
}
其中的ChannelHeader是包含了通道编号的序列化字节数组,如下所示:
//Header是一种通用的重播预防,包含重放签名的身份信息typeChannelHeaderstruct{
Typeint32`protobuf:
"varint,1,opt,name=type"json:
"type,omitempty"`
//协议版本信息
Versionint32`protobuf:
"varint,2,opt,name=version"json:
"version,omitempty"`
//发件人创建消息的本地时间
Timestamp*google_protobuf.Timestamp`protobuf:
"bytes,3,opt,name=timestamp"json:
"timestamp,omitempty"`
//ChannelId用于表示消息绑定channel的标识
ChannelIdstring`protobuf:
"bytes,4,opt,name=channel_id,json=channelId"json:
"channel_id,omitempty"`
//端到端的唯一标识符
//-高级设置,如用户终端或SDK
//-传递给背书节点(用于唯一性检查)
//-header会随消息一直传递,将被committer获取(也用于唯一性检查)
//-会存入账本
TxIdstring`protobuf:
"bytes,5,opt,name=tx_id,json=txId"json:
"tx_id,omitempty"`
//header中的Epoch的定义取决于块高度
//response中的Epoch表示逻辑窗口时间.以下两种情况时,peer节点才接受提案响应
//1.消息中的epoch与当前epoch匹配
//2.消息只在当前epoch中出现一次(即没有被重播)
Epochuint64`protobuf:
"varint,6,opt,name=epoch"json:
"epoch,omitempty"`
//可以根据header类型进行扩展
Extension[]byte`protobuf:
"bytes,7,opt,name=extension,proto3"json:
"extension,omitempty"`
}
排序服务在接收到交易请求后,会先反序列化得到ChannelId,在不同的通道上进行排序打包生成区块。
4命令行和SDK对多链的支持
我们在前面的章节已经介绍过命令行的使用,其中可以指定一个-C参数代表在指定的通道上操作,在入口处提供了对多链的支持。
我们在第10章还会看到在超级账本提供给应用程序的SDK中,也提供了多链的接口。
5关于系统链
系统链是一个特殊的链,含有系统层面全局配置区块链网络的联盟及组织信息、MSP信息和策略信息等,只存在于排序服务中。
涉及一些信息的修改,比如增加一个组织,增加排序服务节点,这些都会在系统链上增加一个配置区块。
整个系统有且只有一个系统链,系统链是通过创世区块配置的,排序服务启动的时候通过ORDERER_GENERAL_GENESISFILE环境变量指定创世区块文件并创建系统链。
系统链的名称可以在创建创世区块的时候通过工具configtxgen的channelID参数指定,默认的系统链名称是testchainid,是否是系统链的判断方法就是配置区块信息里是否有联盟信息配置项。
关于创世区块的详细信息也请参考前面已经介绍过的第6章的相关内容。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 区块 技术 应用 研究 软件 开发 实践 分布式 共享 超级 账本 数据 隔离 多链多 通道