参考资料:
- 官方文档——私有数据
- 官方文档——私有数据定义
- 官方文档——在Fabric中使用私有数据
文章目录
什么是私有数据
在没有私有数据之前,在一个通道上的所有组织成员都可以访问通道账本上的数据(每个 peer 节点都是记账节点,需要记账)。如果通道中的一组组织想要对其他组织保持部分数据私密的话,它们可以选择创建一个新的通道,然后在这个新的通道上的组织可以访问这些数据。但是这种情况就会有新的通道的管理开销(维护链码版本,策略,MSP等),并且不允许在保持部分数据私有的同时,让所有通道参与者都看到交易。
Fabric 从 v1.2 开始提供了创建私有数据集合(Private Data Collection,PDC)的能力,这使得授权的组织对私有数据有访问的权利,而未授权的组织则没有权利访问私有数据
什么是私有数据集(PDC)
集合是两个元素的集合:
- 实际的私有数据,节点有了私有数据以后(通过链码调用),通过 Gossip 协议点对点地发送给授权可以看到它的组织。私有数据保存在被授权的组织的节点上的私有数据库上(在我之前讲账本的文章中有提到过这个数据库——Hyperledger Fabric从源码分析账本,有一个专门的数据库存储私有数据),它们可以被授权的组织节点上的链码访问。因为这种原因,就必须要设置好通道上的锚节点,它是通道内不同组织之间私有数据传输的关键,通道内不同组织间是通过组织内部的锚节点通信的。
- 该数据的哈希值,该哈希值被背书、排序之后写入通道上每个节点的账本。哈希值作为交易的证明用于状态验证,还可用于审计。也就是说未授权的节点不会有私有数据本身,但是会有私有数据的哈希值,用于验证交易。
什么时候使用通道,什么时候使用集合
- 当通道成员的一群组织必须对所有交易(和账本)保密时,使用通道
- 当交易(和账本)必须在一群组织间共享,并且这些组织中只有一部分组织可以访问交易中一些(或全部)私有数据时,使用集合。此外,由于私有数据是点对点传播的,而不是通过区块传播,所以当交易数据必须对排序服务节点保密时,应该使用私有数据集合。
私有数据的交易流程
当在链码中引用私有数据集合时,为了保护私有数据在提案、背书和提交交易过程中的机密性,私有数据的交易流程略有不同。
- 客户端应用程序向背书节点提交提案,请求调用一个链码函数(读取或写入私有数据),这些背书节点属于私有数据集合中已授权组织的一部分。私有数据,或用于在链码中生成私有数据的数据,被发送到提案的
transient
字段中(链码调用 GetTransient() 接口获取传进来的私有数据) - 背书节点模拟交易,并将私有数据存储在
transient data store
(节点的本地临时存储库)中。这些背书节点根据组织集合的策略将私有数据通过 Gossip 分发给授权的节点 - 背书节点将提案相应发送回客户端。提案相应中包含经过背书的读写集,这其中包含了公共数据,还包含任何私有数据键和值的哈希。私有数据不会被发送给客户端。
- 客户端将交易(包含带有数据哈希值的提案相应)提交给排序服务。带有私有数据哈希的交易同样被包含在区块中。带有私有数据哈希的区块被分发给所有节点(即所有节点拥有私有数据交易的哈希)。这样,通道中的所有节点就可以在不知道真实私有数据的情况下,用同样的方式来验证带有私有数据哈希值的交易。
- 在区块提交的时候,授权的节点会根据集合策略来决定它们是否有权限访问私有数据。如果节点有访问权,它们会先检查自己本地的
transient data store
,以确定它们是否在链码背书的时候已经接受到了私有数据。如果没有的话,它们会尝试从其他已授权节点那里拉取私有数据,然后对照公共区块上的哈希值来验证私有数据并提交交易和区块。当验证或提交结束后,私有数据会被移动到这些节点私有数据库和私有读写存储的副本中。随后transient data store
中存储的这些私有数据会被删除(这个相当于只是一个临时的私有数据存储库)
私有数据收集器的定义
收集器定义描述了谁可以持有私有数据、数据要分发到多少个节点上、多少节点可以传播私有数据和私有数据要在私有数据库中存放多久。
收集器(Collection)的定义包括以下属性
属性 | 描述 |
---|---|
name | 收集器的名字 |
policy | 定义了可以持有收集器的组织节点 |
requiredPeerCount | 在节点为背书签名并将提案响应返回给客户端前,每个背书节点必须将私有数据分发到的节点(在被授权的组织当中)的最小数量。设为0代表分发不是必须的,但是如果 maxPeerCount 比0大的话就需要分发。通常不建议设置为0,因为那会造成在背书节点不可用的时候,网络中的私有数据可能会丢失。通常在背书的时候你会希望分发私有数据到多个节点保证网络中私有数据的冗余存储 |
maxPeerCount | 为了数据冗余,每个背书节点将会尝试将私有数据分发到的其他节点(在被授权的组织中) 的最大数量。如果在背书和提交之间一个背书节点不可用了,其他节点就可以在背书的时候从已经收到私有数据的节点拉取私有数据。如果这个值被设置为0,私有数据在背书的时候就不会分发,这会在提交的时候强制节点从授权的背书节点上拉取私有数据。 |
blockToLive | 私有数据要在私有数据库存放多久,以区块个数为单位,数据将在私有数据库中存在指定数量的区块数然后会被清除, 也就是数据会从网络中废弃。要永久保存私有数据,永远不被清除,就设置为0 |
memberOnlyRead | 值为 true 表示节点会自动强制只有属于收集器成员组织的客户端才有读取私有数据的权限。如果一个非成员组织的客户端试图执行一个链码方法来读取私有数据的话,会结束链码的调用并产生错误。如果你想在单独的链码方法中进行更细粒度的访问控制的话,可以使用 false 值。 |
官方的示例文件
// 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
}
]
在 collectionMarbles
中的 policy
属性 定义了允许通道中(Org1 和 Org2)所有成员使用私有数据库中的私有数据。 collectionMarblePrivateDetails
收集器只允许 Org1 的成员使用私有数据库中的私有数据。
私有数据分发
由于私有数据不会被包含在提交到排序服务的交易中,因此也就不会被包含在区块中,背书节点扮演着将私有数据分发给其他授权组织的节点的重要角色。这确保了私有数据在背书节点完成背书之后变成不可用的时候的可用性。为了复制分发,在集合定义中的 maxPeerCount
和 requiredPeerCount
属性控制了在背书的时候分发的数量。
如果背书节点不能够成功地将私有数据分发到至少 requiredPeerCount
的要求,它将会返回一个错误给客户端。背书节点会尝试将私有数据分发到不同组织的节点,来确保每个被授权的组织具有私有数据的一个副本。因为交易在链码执行期间还没有被提交,背书节点和接收节点除了在它们的区块链之外,还在一个本地的 临时存储(transient store)
中存储了一个私有数据副本,直到交易被提交。
当一个被授权的节点在提交的时候,如果他们的临时存储中没有私有数据的副本(或者是因为他们不是一个背书节点,或者是因为他们在背书的时候没有接收到私有数据),他们会尝试从其他的被授权的节点那里拉取私有数据,尝试会持续一个可配置的时间长度 ,时间可以通过节点配置文件 core.yaml
中的属性 peer.gossip.pvtData.pullRetryThreshold
进行配置。
只有当提出请求的节点是私有数据分发策略定义的集合中的一员的时候,被询问的节点才会返回私有数据。
当使用 pullRetryThreshold
时候需要考虑的问题:
- 如果提出请求的节点能够在
pullRetryThreshold
时间内拿到私有数据的话,它将会把交易提交到自己的账本(包括私有数据的哈希值),并且将私有数据存储在与其他的通道状态数据进行了逻辑隔离的状态数据库中。 - 如果提出请求的节点没能在
pullRetryThreshold
时间内拿到私有数据的话,它将会把交易提交到自己的账本(包括私有数据的哈希值),但是不会存储私有数据。 - 如果某个节点有资格拥有私有数据,却没有得到的话,这个节点就无法为将来会引用这个丢失的私有数据的交易进行背书,背书时会发现无法查询到键 (基于在状态数据库中主键的哈希值),并且链码将会收到一个错误。
因此,将 requiredPeerCount
和 maxPeerCount
设置成足够大的值来确保在你的通道中的私有数据的可用性是非常重要的。比如,如果在交易提交之前,每个背书节点都不可用了,requiredPeerCount
和 maxPeerCount
属性将会确保私有数据在其他的节点上是可用的。
从链码中引用集合
我们可以用 shim APIS
设置和取回私有数据,需要指定相关的集合名字
PutPrivateData(collection,key,value)
GetPrivateData(collection,key)
在链码中传递私有数据
链码提案中有个特殊字段 transient
,可以用它把私有数据从客户端传递给节点上的链码调用。链码可以通过 GetTransient()
来获取该字段。该字段会从通道交易中被排除。
私有数据的访问控制
直到1.3版本,基于集合成员的私有数据的访问控制仅限制在 Peer 节点。基于链码提案的提交者所在组织的访问控制需要编码在链码逻辑中。从v1.4开始,集合配置中的选项 memberOnlyRead
能够自动地强制使用基于链码提案提交者组织的访问控制。
如果你想要更细粒度的访问控制,你可以将
memberOnlyRead
设置为 false。然后你可以在链码中应用你自己的访问控制逻辑,比如通过调用链码 API GetCreator() 或者使用客户端身份。
查询私有数据
私有集合数据能够像常见的通道数据那样使用 shim API 来进行查询:
GetPrivateDataByRange(collection, startKey, endKey string)
GetPrivateDataByPartialCompositeKey(collection, objectType string, keys []string)
对于 CouchDB 状态数据库,可以使用 shim API 查询 JSON 内容:
GetPrivateDataQueryResult(collection, query string)
私有数据的删除
在 Collection 配置的 blockToLive 字段可以指定周期性地删除私有数据,即私有数据可以存活几个区块。
在提交之前,私有数据存储在 Peer 节点的本地临时数据存储中。这些数据在交易提交之后会自动被删除。但是如果交易没有被提交,私有数据就会一直保存在临时数据存储中。Peer 节点会根据配置文件 core.yaml
中的 peer.gossip.pvtData.transientstoreMaxBlockRetention
的配置周期性的删除临时存储中的数据。
升级一个集合定义
如果要升级一个集合定义或者增加一个新的定义,你可以将链码更新到新版本并将集合配置新的集合配置传递给链码更新交易。例如在 CLI 中使用 --collections-config
标示。如果在链码更新期间指定了集合配置,所有已存在的集合的定义也必须包含其中。
升级链码的时候,你可以新增和更新私有数据集合,例如向已存在的集合中添加新成员或者改变一个集合定一个的属性。注意,你不能更新集合名称和 blockToLive 属性,因为无论节点区块高度如何,都需要一个一致的 blockToLive 。
在一个 Peer 节点提交包含链码更新交易的区块时,集合的更新才会生效。注意,集合是不能够被删除的,因为在通道的区块链上可能有之前的私有数据的哈希,而这些哈希值是不能被删除的。
如何在 Fabric 中使用私有数据
官方文档描述地再清楚不过了,我就不照搬了,直接看官方文档吧。
官方文档——在Fabric中使用私有数据