Hyperledger Fabric 专题 - 私有数据
有可能存在这样一种特殊的需求,对于同属于一个通道的多个组织,其中某些组织构成的子集需要提供额外的数据隐私性,即这些私有数据只能由通道中的子组织查看,而不能被所有组织查看。有一种方法是这些子组织重新定义一个通道,但这样做存在下列缺点:一是有可能创建过多的只包含少数组织的通道;二是通道之间的数据和链码是无法交互的,导致多个通道中的链码实现变得冗余和复杂。因此,Fabric 从 v1.2 开始,可以创建私有数据集合,从而使通道上已定义的组织子集能够背书,提交或查询私有数据,而无需创建单独的通道。
1.1 私有数据特性
- 私有数据存储在节点的私有数据库中 (或称作 SideDB)。
- 各个节点之间通过 gossip 协议交互私有数据。或者说私有数据是通过 gossip 协议传输,而非通过区块。
- 由于各个节点是通过 gossip 协议交互私有数据,因此需要在通道上设置锚定节点。
- 交易排序器无法查看私有数据,只能看到看到私有数据的哈希。对于通道中非授权的组织,也只能看到看到私有数据的哈希。
- 链码能够访问私有数据 (通道中的链码能够访问该通道中的所有私有数据。更准确的描述有可能是私有数据是属于链码级别的。)
- 私有数据相关的交易仍然能够在整个通道中看到,即仍然会产生新的区块和更新账本。只是对于非私有数据的所有者,看不到私有数据本身,只能看到私有数据的哈希。这样,既可以对私有数据进行审计、验证等,又不会泄露私有数据。
- 私有数据的验证流程:如果子组织成员之间发生争议或想要将资产转让给第三方,则该子组织成员可以决定与其他方共享私有数据。然后,第三方可以计算私有数据的哈希值,并查看其是否与通道帐本上的状态匹配,从而证明该状态在某个时间点存在于子组织之间。
- 可以清除私有数据,也可以不清除私有数据。
1.2 使用私有数据的交易流程
当在链码中引用私有数据集合时,交易流程略有不同,以便在提案,背书并提交到帐本时保护私有数据的机密性。
- 客户端应用程序提交提案请求以调用链码功能 (读取或写入私有数据) 给背书的对端节点,这些对端节点是私有数据的授权组织的一部分。私有数据或用于以链码生成私有数据的数据在提案的
transient
字段中发送。 - 背书对端节点模拟交易并将私有数据存储在
transient data store
(对端节点本地的临时存储) 中。他们根据私有数据策略通过 gossip 协议将私有数据分发给授权的对端节点。 - 背书对端节点将提案响应发送回客户端。提案响应包括背书的读/写集,其中包括公共数据,以及任何私有数据键和值的哈希。没有私有数据发送回客户端。
- 客户端应用程序将交易 (包括带有私有数据哈希的提案响应) 提交给交易排序器。带有私有数据哈希的交易通常包含在区块中。具有私有数据哈希的区块将分配给所有对端节点。这样,通道上的所有对端节点都可以以一致的方式用私有数据的哈希值验证交易,而无需知道实际的私有数据。
- 在区块提交时,授权对端节点使用集合策略来确定他们是否被授权有权访问私有数据。如果这样做,他们将首先检查其本地瞬态数据存储 (
transient data store
) 以确定在链码背书时是否已经接收到私有数据。如果没有,他们将尝试从另一个授权对端节点获取私有数据。然后,他们将根据公共区块中的哈希来验证私有数据,并提交交易和该区块。验证/提交后,私有数据将移至私有状态数据库和私有写入集存储的副本。然后,将私有数据从瞬态数据存储中删除。
1.3 清除私有数据
私有数据是可以从子组织中清除的,清除之后链码就无法再访问该私有数据,即使子组织也无法再使用该私有数据。
所以在清除之前,需要确保将这些私有数据导出到外部数据库中,并进行独立维护。
清除之后,通道中仍然保持着私有数据的哈希,因此仍然能够对该私有数据进行正常的审计、验证等。
1.4 私有数据的定义
私有数据的简单定义由以下属性组成,更详细的定义请参考官方文档:
- 名称 (
name
):私有数据的名称。 - 策略 (
policy
):私有数据的策略。 -
requiredPeerCount
:每个背书对端节点 (在所有授权组织中) 必须成功分发私有数据的最小对端节点数目。当requiredPeerCount
为 0 时,表示不需要分发。通常不建议将requiredPeerCount
设置为 0,因为如果背书的对端节点变得不可用,则可能导致网络中的私有数据丢失。但是在我们的当前项目中却需要设置为 0。 -
maxPeerCount
:出于数据冗余的目的,每个背书对端节点将尝试向其分发私有数据的其他对端节点 (跨授权组织) 的最大数量。如果将此值设置为 0,则在背书时不会传播私有数据,从而在提交时强制私有数据在所有授权对端节点上对背书对端节点进行拉取。在我们的当前项目中需要设置为 0。 -
blockToLive
:以区块为单位表示数据在私有数据库上应保留的时间。数据将在指定数量的私有数据库上保留,此后将被清除,从而使该数据已从网络中删除,因此无法从链码中查询它,也无法将其提供给请求对端节点。要无限期保留私有数据,即从不清除私有数据,请将blockToLive
属性设置为 0。在我们的当前项目中需要设置为 0。 -
memberOnlyRead
:值为 true 表示对端节点自动强制仅允许属于集合成员组织之一的客户端对私有数据进行读取访问。如果来自非成员组织的客户端尝试执行执行读私有数据的链码功能,则链码调用会因错误而终止。如果你想在单个链码函数中对更细粒度的访问控制进行编码,请使用 false 值。在我们的当前项目中建议设置为 true。
这是一个示例集合定义 JSON 文件 collections_config.json
,其中包含两个私有数据的定义:
[
{
"name": "collectionMarbles",
"policy": "OR('Org1MSP.member', 'Org2MSP.member')",
"requiredPeerCount": 0,
"maxPeerCount": 3,
"blockToLive":1000000,
"memberOnlyRead": true
},
{
"name": "collectionMarblePrivateDetails",
"policy": "OR('Org1MSP.member')",
"requiredPeerCount": 0,
"maxPeerCount": 3,
"blockToLive":3,
"memberOnlyRead": true
}
]
本示例使用来自官方 BYFN 示例网络 Org1 和 Org2 的组织。 collectionMarbles 定义中的策略授权两个组织使用私有数据。当链码数据需要对交易排序器节点保持私有时,这是一种典型配置。但是,collectionMarblePrivateDetails 定义中的策略将限制访问通道中的组织子集 (在本例中为 Org1)。在实际情况下,通道中将有许多组织,每个私有数据在两个或多个组织之间共享。
1.5 私有数据的部署
链码实例化 (或升级) 时,私有数据的定义将部署到通道。如果使用对端节点 CLI 实例化链码,则使用 --collections-config
标志将私有数据定义文件传递到链码实例化。如果使用客户端 SDK,请查看 SDK 文档以获取有关提供集合定义的信息。
1.6 示例
一个简单的使用私有数据的示例包括下列步骤。
Step 1. 定义私有数据的配置文件
请参考章节 1.4 的配置文件 collections_config.json
。
需要说明的是,这一步将配置哪些组织和对端节点能够访问和存储私有数据。
Step 2. 启动 Fabric 网络
这里假设已经启动 Fabric 网络,如官方示例中的 BYFN 网络。
到这一步,假设已经创建了通道,并且各组织及其对端节点都已经加入通道。特别需要注意是关于锚定节点的配置要正确。
Step 3. 安装链码
这一步已普通的链码安装并无不同。假设已经启动 BYFN 网络。
先进入 cli 工具:
$ sudo docker exec -it cli bash
#
在 peer0.org1 上安装:
peer chaincode install -n marblesp -v 1.0 -p github.com/chaincode/marbles02_private/go/
在 peer1.org1 上安装:
export CORE_PEER_ADDRESS=peer1.org1.example.com:8051
peer chaincode install -n marblesp -v 1.0 -p github.com/chaincode/marbles02_private/go/
先切换到 org2 环境变量:
export CORE_PEER_LOCALMSPID=Org2MSP
export PEER0_ORG2_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_TLS_ROOTCERT_FILE=$PEER0_ORG2_CA
export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
在 peer0.org2 上安装:
export CORE_PEER_ADDRESS=peer0.org2.example.com:9051
peer chaincode install -n marblesp -v 1.0 -p github.com/chaincode/marbles02_private/go/
在 peer1.org2 上安装:
export CORE_PEER_ADDRESS=peer1.org2.example.com:10051
peer chaincode install -n marblesp -v 1.0 -p github.com/chaincode/marbles02_private/go/
Step 4. 实例化链码 & 部署私有数据
私有数据的部署发生在链码实例化阶段。命令如下:
# export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
# peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C mychannel -n marblesp -v 1.0 -c '{"Args":["init"]}' -P "OR('Org1MSP.member','Org2MSP.member')" --collections-config $GOPATH/src/github.com/chaincode/marbles02_private/collections_config.json
Step 5. 链码
链码主要是通过两个接口 PutPrivateData()
和 GetPrivateData()
进行查询、新增或更新。
官方示例的链码实现请参考 大理石私有数据示例
Step 6. 应用程序
应用程序需要通过特殊的字段 transient
来传递私有数据的 Key & Value,同时需要在链码部分通过接口 getTransient()
获取应用程序传入的私有数据的 Key & Value。
由于,私有数据只有 Org1 有操作权限,所以需要先切换回 Org1 的环境变量:
export CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export CORE_PEER_LOCALMSPID=Org1MSP
export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export PEER0_ORG1_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
更新操作
通过下面的命令行来调用链码接口,注意私有数据的 Key & Value 是如何传递给链码接口的,同时还需要注意 Value 需要采用 Base64 编码,并且在 Linux 环境中还需要去掉编码后多出的换行符:
export MARBLE=$(echo -n "{\"name\":\"marble1\",\"color\":\"blue\",\"size\":35,\"owner\":\"tom\",\"price\":99}" | base64 | tr -d \\n)
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n marblesp -c '{"Args":["initMarble"]}' --transient "{\"marble\":\"$MARBLE\"}"
需要考虑,如果通过 SDK 来实现上面的操作,该如何实现。可以参考示例 marbles02_private 中 javascript 版本的实现。
查询操作
上面的是存储私有数据命令,下面是私有数据的查询命令:
# peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarble","marble1"]}'
或者
# peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'
1.7 私有数据相关的实现细节
私有数据相关的实现细节包含:私有数据的定义、私有数据的部署、私有数据的链码编写以及客户端交互等部分。
私有数据的定义请参考章节 1.4 的相关内容。
私有数据的部署请参考章节 1.5 和 1.6 的相关内容。
私有数据的链码编写以及客户端交互请参考章节 1.6 的相关内容。
需要说明的是,私有数据开发相关的工作量主要还是体现在链码编写及客户端交互这部分。
1.8 技术风险
- 假设存在两个节点 NodeA, NodeB,私有数据 P 只存储在节点 NodeA。从客户端发起一笔查询操作,但是这个操作是在节点 NodeB 上执行的,那么是否无法访问到私有数据的。从而会导致确实存在私有数据,但是查询却返回没有的错误结果。这需要能够控制,将客户端查询私有数据的请求操作发送到 NodeA 中。
- 同上,链码本身需要组织 NodeA 和 NodeB 同时背书,但是链码里面又使用了 NodeA 的私有数据,那么在 NodeB 中执行的链码是访问不到 NodeA 中的私有数据的,导致 NodeB 无法为链码进行背书,从而最终失败。当然,这里需要链码包含更新操作,如果只包含查询操作,是不需要经过排序器,也不需要其它组织背书,如果不从那个组织查询。这是不是仍然需要控制,将客户端查询私有数据的请求操作发送到 NodeA 中。
- 针对上面两个问题,最近一次测试表明,在客户端发起一笔和私有数据有关的交易时,并不需要指定 NodeA 和 NodeB 的信息,而是和不带私有数据的交易一样,只需要指定 Orderer 的信息。不过,需要将参数以
--transient
的形式传入。
参考文献
- Docs » Key Concepts » Private data, https://hyperledger-fabric.readthedocs.io/en/release-1.4/private-data/private-data.html
- Hyperledger Fabric - 核心概念 - 私有数据,https://www.jianshu.com/p/60d38bdc49ee
- Docs » Architecture Reference » Private Data, https://hyperledger-fabric.readthedocs.io/en/release-1.4/private-data-arch.html
- Hyperledger Fabric - 架构 - 私有数据,https://www.jianshu.com/p/101956f64861
- Docs » Tutorials » Using Private Data in Fabric, https://hyperledger-fabric.readthedocs.io/en/release-1.4/private_data_tutorial.html
- Hyperledger Fabric - 教程 - 在 Fabric 中使用私有数据,https://www.jianshu.com/p/710c989576b8
- 官方大理石私有数据示例,https://github.com/hyperledger/fabric-samples)
- https://github.com/windstamp/hyperledgercodes/tree/master/fabric-samples/marbles02_private
- As an application developer using the fabric-network API, I want to be able to pass in transient data to the transaction, https://jira.hyperledger.org/browse/FABN-929
项目源代码
项目源代码会逐步上传到 Github,地址为 https://github.com/windstamp。
Contributor
- Windstamp, https://github.com/windstamp
网友评论