私有数据¶
注解
本主题假设你已经理解了在 私有数据文档 中所描述概念。
私有数据集合定义¶
一个集合定义包含了一个或者多个集合,每个集合具有一个策略列出了在集合中的所有组织,还包括用来控制在背书阶段私有数据的传播所使用的属性,另外还有一个可选项来决定数据是否会被删除。
这个集合的定义会在链码实例化(或者升级)的时候被部署到通道中。如果使用的是 peer CLI 来初始化链码的话,会使用 --collections-config 标识将这个集合定义文件发送给链码的实例化过程。如果使用的是一个客户端 SDK,查看 SDK 文档 来了解更多关于如何提供集合定义的内容。
集合定义由下边的属性组成:
name: 集合的名字.policy: 私有数据集合分发策略,它通过Signature策略语法定义了允许哪些组织的 Peer 节点持久化集合数据。为了支持读/写交易,私有数据的分发策略必须要定义一个比链码背书策略更大范围的一个组织的集合,因为 Peer 节点必须要拥有这些私有数据才能来对这些交易提案进行背书。比如,在一个具有10个组织的通道中,其中5个组织可能会被包含在一个私有数据集合的分发策略中,但是背书策略可能会调用其中任意的3个组织来进行背书。requiredPeerCount: 在节点为背书签名并将提案响应返回给客户端前,每个背书节点必须将私有数据分发到的节点(在被授权的组织当中)的最小数量。将传播作为背书的一个条件可以确保即使背书背书不可用,私有数据在网络中也还是可用的。requiredPeerCount设为0代表分发并不是 必须 的,但是如果maxPeerCount比0大的话,就需要分发。通常不建议requiredPeerCount设为0通常,因为那会造成在背书节点不可用的时候,网络中的私有数据可能会丢失。通常在背书的时候你会希望分发私有数据到多个节点以保证网络中私有数据的冗余存储。maxPeerCount: 为了数据的冗余存储,每个背书节点将会尝试将私有数据分发到的其他节点(在被授权的组织中) 的最大数量。如果在背书和提交之间一个背书节点不可用了,其他节点就可以在背书的时候从已经收到私有数据的节点拉取私有数据。如果这个值被设置为0,私有数据在背书的时候就不会被分发,这会在提交的时候强制节点从授权的背书节点上拉取私有数据。blockToLive: 代表了数据应该以区块的形式在私有数据库中存在多久。数据会在私有数据库上存在指定数量个区块,然后它会被删除,然后链码就无法查询到该数据,其他节点也无法请求到该数据。如果要无限期的保持私有数据,也就是从来不删除私有数据的话,将blockToLive设置为0。memberOnlyRead:true值表示节点自动会强制只有属于这些集合的组织的客户端才可以读取私有数据。如果一个非成员组织的客户端试图执行一个链码方法来读取私有数据的话,会结束链码的调用并产生错误。如果你想在单独的链码方法中进行更细粒度的访问控制的话,可以使用false值。
下边是一个集合定义的 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) 中组织的子集。在真实场景中,通道中会有很多组织,在每个集合中的两个或者多个组织间会彼此共享数据。
私有数据分发¶
由于私有数据不会被包含在提交到排序服务的交易中,因此也就不会被包含在区块中,背书节点扮演着将私有数据分发给其他授权组织的节点的重要角色。这确保了私有数据在背书节点完成背书之后变成不可用的时候的可用性。为了辅助分发,在集合定义中的 maxPeerCount 和 requiredPeerCount 属性控制了在背书的时候分发的数量。
如果背书节点不能够成功地将私有数据分发到至少 requiredPeerCount 的要求,它将会返回一个错误给客户端。背书节点会尝试将私有数据分发到不同组织的节点,来确保每个被授权的组织具有私有数据的一个副本。因为交易在链码执行期间还没有被提交,背书节点和接收节点除了在它们的区块链之外,还在一个本地的 临时存储(transient store) 中存储了一个私有数据副本,直到交易被提交。
当一个被授权的节点在提交的时候,如果他们的临时存储中没有私有数据的副本(或者是因为他们不是一个背书节点,或者是因为他们在背书的时候没有接收到私有数据),他们会尝试从其他的被授权的节点那里拉取私有数据,尝试会*持续一个可配置的时间长度* ,在时间可以通过节点配置文件 core.yaml 中的属性 peer.gossip.pvtData.pullRetryThreshold 进行配置。
注解
只有当提出请求的节点是私有数据分发策略定义的集合中的一员的时候,被询问的节点才会返回私有数据。
当使用 pullRetryThreshold 时候需要考虑的问题:
- 如果提出请求的节点能够在
pullRetryThreshold时间内拿到私有数据的话,它将会把交易提交到自己的账本(包括私有数据的哈希值),并且将私有数据存储在与其他的通道状态数据进行了逻辑隔离的状态数据库中。 - 如果提出请求的节点没能在
pullRetryThreshold时间内拿到私有数据的话,它将会把交易提交到自己的账本(包括私有数据的哈希值),但是不会存储私有数据。 - 如果某个节点有资格拥有私有数据,却没有得到的话,这个节点就无法为将来会引用这个丢失的私有数据的交易进行背书,背书时会发现无法查询到键 (基于在状态数据库中主键的哈希值),并且链码将会收到一个错误。
因此,将 requiredPeerCount 和 maxPeerCount 设置成足够大的值来确保在你的通道中的私有数据的可用性是非常重要的。比如,如果在交易提交之前,每个背书节点都不可用了,requiredPeerCount 和 maxPeerCount 属性将会确保私有数据在其他的节点上是可用的。
注解
为了让集合能够工作,正确配置跨组织的 gossip 非常重要的。请阅读 Gossip 数据传播协议,尤其注意“锚节点”这部分。
从链码中引用集合¶
我们可以用 shim APIs 设置和取回私有数据。
相同的链码数据操作也可以应用到通道状态数据和私有数据上,但是对于私有数据,要在链码 API 中指定和数据相关的集合的名字,比如 PutPrivateData(collection,key,value) 和 GetPrivateData(collection,key)。
一个链码可以引用多个集合。
如何在一个链码提案中传递私有数据¶
因为链码提案被存储在区块链上,不要把私有数据包含在链码提案中也是非常重要的。在链码提案中有一个特殊的字段 transient,可以用它把私有数据来从客户端(或者链码将用来生成私有数据的数据)传递给节点上的链码调用。链码可以通过调用 GetTransient() API 来获取 transient 字段。这个 transient 字段会从通道交易中被排除。
私有数据的访问控制¶
直到1.3版本,基于集合成员的私有数据的访问控制仅限制在 Peer 节点。基于链码提案的提交者所在组织的访问控制需要编码在链码逻辑中。从v1.4开始,集合配置中的选项 memberOnlyRead 能够自动地强制使用基于链码提案提交者组织的访问控制。关于集合配置定义以及如何设置的更多信息,请查看 `Private data collection definition`_ 章节。
注解
如果你想要更细粒度的访问控制,你可以将 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)
限制:
- 客户端调用执行范围查询或者富查询链码的时候应该知道,根据上边关于私有数据分发部分的解释,如果他们查询的节点有丢失的私有数据的话,他们可能会接收到结果集的一个子集。客户端可以查询多个节点并且比较返回的结果,以确定一个节点是否丢失了结果集中的部分数据。
- 不支持在单个交易中既执行范围查询或者富查询并且更新数据,因为查询结果无法在以下类型的节点上进行验证:不能访问私有数据的节点或者对于那些他们可以访问相关的私有数据但是私有数据是丢失的。如果一个链码的调用既查询又更新私有数据的话,这个提案请求将会返回一个错误。如果你的应用程序能够容忍在链码执行和验证/提交阶段结果集的变动,那么你可以调用一个链码方法来执行这个查询,然后再调用第二个链码方法来执行变更。注意,调用 GetPrivateData() 来获取单独的键值可以跟 PutPrivateData() 调用放在同一个交易中,因为所有的节点都能够基于键版本的哈希来验证键的读取。
在集合中使用索引¶
使用 CouchDB 作为状态数据库 章节讲解了可以在安装阶段,通过将索引打包在一个 META-INF/statedb/couchdb/indexes 的路径下的方式,将索引应用到通道的状态数据库。类似的,也可以通过将索引打包在一个 META-INF/statedb/couchdb/collections/<collection_name>/indexes 路径下的方式将索引应用到私有数据集合中。一个索引的实例可以查看 这里。
使用私有数据时的思考¶
私有数据的删除¶
Peer 可以周期性地删除私有数据。更多细节请查看上边集合定义属性中的 blockToLive 。
另外,重申一下,在提交之前,私有数据存储在 Peer 节点的本地临时数据存储中。这些数据在交易提交之后会自动被删除。但是如果交易没有被提交,私有数据就会一直保存在临时数据存储中。Peer 节点会根据配置文件 core.yaml 中的 peer.gossip.pvtData.transientstoreMaxBlockRetention 的配置周期性的删除临时存储中的数据。
升级一个集合定义¶
如果要升级一个集合定义或者增加一个新的定义,你可以将链码更新到新版本并将集合配置新的集合配置传递给链码更新交易。例如在 CLI 中使用 --collections-config 标示。如果在链码更新期间指定了集合配置,所有已存在的集合的定义也必须包含其中。
升级链码的时候,你可以新增和更新私有数据集合,例如向已存在的集合中添加新成员或者改变一个集合定一个的属性。注意,你不能更新集合名称和 blockToLive 属性,因为无论节点区块高度如何,都需要一个一致的 blockToLive 。
在一个 Peer 节点提交包含链码更新交易的区块时,集合的更新才会生效。注意,集合是不能够被删除的,因为在通道的区块链上可能有之前的私有数据的哈希,而这些哈希值是不能被删除的。
私有数据对账¶
从 v1.4 开始,加入到已存在的集合中的 Peer 节点在私有数据加入到集合之前,可以自动获取提交到集合的私有数据。
私有数据“对账”也应用在 Peer 节点上,用于确认该接收却未接收到的私有数据,比如由于网络原因没有收到的。以此来追踪在区块提交期间“丢失”的私有数据。
私有数据对账根据 core.yaml 文件中的属性 peer.gossip.pvtData.reconciliationEnabled 和 peer.gossip.pvtData.reconcileSleepInterval 周期性的发生。Peer 节点会从集合成员节点中定期获取私有数据。
注意私有数据对账特性只适用于 v1.4 以上的 Fabric 节点。