Hyperledger Fabric 生产环境搭建---私有数据集

466 阅读20分钟

2.5 在fabric中使用私有数据

2.5.1 私有数据的重要概念

2.5.1.1 什么是私有数据? 在某个通道上的一组组织需要对该通道上的其他组织保持数据私密的情况下,它们可以选择创建一个新通道,其中只包含需要访问数据的组织。但是,在每一种情况下创建单独的通道会产生额外的管理开销(维护链码版本、策略、MSP 等),并且不允许在保持部分数据私有的同时,让所有通道参与者都看到交易。这就是为什么从v1.2开始,Fabric就提供了创建私有数据集合的能力,这使得通道上定义的组织子集能够背书、提交或查询私有数据,而不必创建单独的通道。

2.5.1.2 什么是私有数据集?

集合是两个元素的组合:

  • 实际的私有数据,通过 Gossip 协议点对点地发送给授权可以看到它的组织。私有数据保存在被授权的组织的节点上的私有数据库上(有时候成为“侧”数据库或者“SideDB”),它们可以被授权节点的链码访问。排序节点不能影响这里也不能看到私有数据。注意,由于 gossip 以点对点的方式向授权组织分发私有数据,所以必须设置通道上的锚节点,也就是每个节点上的 CORE_PEER_GOSSIP_EXTERNALENDPOINT 配置,以此来引导跨组织的通信。

  • 该数据的哈希值,该哈希值被背书、排序之后写入通道上每个节点的账本。哈希值作为交易的证明用于状态验证,还可用于审计。

下图说明了一个被授权拥有私有数据的节点和一个未授权的节点的账本内容。

2.5.1.3 在通道内什么时候使用私有数据集,什么时候使用单独通道?

当通道成员的一群组织必须对所有交易(和账本)保密时,使用通道。

当交易(和账本)必须在一群组织间共享,并且这些组织中只有一部分组织可以访问交易中一些(或全部)私有数据时,使用集合。此外,由于私有数据是点对点传播的,而不是通过区块传播,所以当交易数据必须对排序服务节点保密时,应该使用私有数据集合。

2.5.1.4 解释集合的一个案例 设想一个通道上有五个交易农产品的组织:

农民把产品销往国外 分销商将货物运往国外 托运商负责参与方之间的货物运输 批发商从分销商那里采购商品 零售商从托运人和批发商那里采购商品 分销商可能希望与农民和托运商之间保持私密交易,从而使得批发商和零售商无从得知这些交易条款(以免暴露他们加价的事实)。

分销商还可能希望与批发商建立单独的私有数据关系,因为它向批发商收取的价格比向零售商收取的价格低。

批发商还可能希望与零售商和托运商建立私有数据关系。

与其为这些私有数据关系定义许多小通道,不如定义多个私有数据集合(PDC)在以下各方之间共享私有数据:

  1. PDC1:分销商,农民和托运商
  2. PDC2:分销商和批发商
  3. PDC3:批发商、零售商和托运商

拿上述例子来说,分销商拥有的节点在其账本储存了多个私有数据库,这些数据库中包含了来自分销商、农民和托运商关系以及分销商和批发商关系的私有数据。

2.5.1.5 私有数据的交易流程

当在链码中引用私有数据集合时,为了保护私有数据在提案、背书和提交交易过程中的机密性,私有数据的交易流程略有不同。

  • 客户端应用程序向背书节点提交一项提案,请求调用一个链码函数(读取或写入私有数据),这些背书节点属于私有数据集合中已授权组织的一部分。私有数据,或用于在链码中生成私有数据的数据,被发送到提案的 transient 字段中。
  • 背书节点模拟交易,并将私有数据存储在 transient data store (节点的本地临时存储库)中。这些背书节点根据组织集合的策略将私有数据通过 gossip 分发给授权的节点。
  • 背书节点将提案响应发送回客户端。提案响应中包含经过背书的读写集,这其中包含了公共数据,还包含任何私有数据键和值的哈希。私有数据不会被发送回客户端。更多关于带有私有数据的背书的信息,请查看这里。
  • 客户端应用程序将交易(包含带有私有数据哈希值的提案响应)提交给排序服务。带有私有数据哈希的交易同样被包含在区块中。带有私有数据哈希的区块被分发给所有节点。这样,通道中的所有节点就可以在不知道真实私有数据的情况下,用同样的方式来验证带有私有数据哈希值的交易。
  • 在区块提交的时候,授权的节点会根据集合策略来决定它们是否有权访问私有数据。如果节点有访问权,它们会先检查自己的本地 transient data store ,以确定它们是否在链码背书的时候已经接收到了私有数据。如果没有的话,它们会尝试从其他已授权节点那里拉取私有数据,然后对照公共区块上的哈希值来验证私有数据并提交交易和区块。当验证或提交结束后,私有数据会被移动到这些节点私有数据库和私有读写存储的副本中。随后 transient data store 中存储的这些私有数据会被删除。

2.5.1.6 私有数据共享案例

在多数情况下,一个私有数据集合中的私有数据键或值可能需要与其他通道成员或者私有数据集合共享,例如,当你需要和一个通道成员或者一组通道成员交易私有数据,而初始私有数据集合中并没有这些成员时。私有数据的接收方一般都会在交易过程中对照链上的哈希值来验证私有数据。

一项资产交易的进行情况如下:

  1. 链下,资产所有者和意向买家同意以某一特定价格交易该资产。
  2. 卖家通过以下方式提供资产所有权证明:利用带外数据传输私有信息;或者出示证书以供买家在其自身节点或管理员节点上查询私有数据。
  3. 买家验证私有信息的哈希是否匹配链上公共哈希。
  4. 买家通过调取链码来在其自身私有数据集合中记录投标细节信息。一般在买家自己的节点上调取链码,但如果集合背书策略有相关规定,则也可能会在管理员节点上调取链码。
  5. 当前的资产所有者(卖家)调取链码来卖出、转移资产,传递私有信息和投标信息。在卖家、买家和管理员的节点上调用链码,以满足公钥的背书策略以及买家和买家私有数据集合的背书策略。
  6. 链码验证交易发送方是否为资产拥有者,根据卖家私有数据集合中的哈希来验证私有细节信息,同时还会根据买家私有数据集合中的哈希来验证投标细节信息。随后链码为公钥书写提案更新(将资产拥有者设定为买家,将背书策略设定为买家和管理员),把私有细节信息写入买家的私有数据集合中,以上步骤成功完成后链码会把卖家私有数据集合中的相关私有细节信息删除。在最终背书之前,背书节点会确保已将私有数据分布给卖家和管理员的所有授权节点。
  7. 卖家提交交易等待排序,其中包括了公钥和私钥哈希,随后该交易被分布到区块中的所有通道节点上。
  8. 每个节点的区块验证逻辑都将一致性地验证背书策略是否得到满足(买家、卖家和管理员都作出背书),同时还验证链码中已读取的公钥和私钥自链码执行以来未被任何其他交易更改。
  9. 所有节点提交的交易都是有效的,因为交易已经通过验证。如果买家和管理员节点在背书时未收到私有数据的话,它们将会从其他授权节点那里获取这些私有数据,并将这些数据保存在自己的私有数据状态数据库中(假定私有数据与交易的哈希匹配)。
  10. 交易完成后,资产被成功转移,其他对该资产感兴趣的通道成员可能会查询公钥的历史以了解该资产的来历,但是它们无法访问任何私有细节信息,除非资产拥有者在须知的基础上共享这些信息。

以上是最基本的资产转移场景,我们可以对其进行扩展,例如,资产转移链码可能会验证一项付款记录是否可用于满足付款和交付要求,或者可能会验证一个银行是否在执行资产转移链码之前已经提交了信用证。交易各方并不直接维护节点,而是通过那些运行节点的托管组织来进行交易。

2.5.1.7 在通道上私有化数据

  • 在通道中数据私有化的第一步是创建一个定义了私有数据权限的收集器。

收集器定义描述了谁可以持有数据、数据要分发到多少个节点上、多少节点可以传播私有数据和私有数据要在私有数据库中存放多久,收集器为区块链网络上已授权的组织节点提供私有数据的存储和检索。

收集器的定义包括一下属性:

  • name: 收集器的名字。
  • policy: 定义了可以持有数据收集器的组织节点。
  • requiredPeerCount: 作为链码的背书条件,需要将私有数据传播到的节点数量。
  • maxPeerCount: 为了数据冗余,现有背书节点需要尝试将数据分发到其他节点的数量。如 果背书节点发生故障,当有请求提取私有数据时,则其他节点在提交时可用。
  • blockToLive: 对于非常敏感的信息,比如价格或者个人信息,这个值表示在数据要以区块 的形式在私有数据库中存放的时间。数据将在私有数据库中存在指定数量的区块数然后会被清除, 也就是数据会从网络中废弃。要永久保存私有数据,永远不被清除,就设置 blockToLive 为 0 。
  • memberOnlyRead: 值为 true 则表示节点会自动强制只有属于收集器成员组织的客户端才 有读取私有数据的权限。

为了说明私有数据的用法,marbles02_private示例包含了两个私有数据收集器的定义(如下图所示): collectionMarbles 和 collectionMarblePrivateDetails 。在 collectionMarbles 中的 policy 属性 定义了允许通道中(Org1 和 Org2)所有成员使用私有数据库中的私有数据。 collectionMarblePrivateDetails 收集器只允许 Org1 的成员使用私有数据库中的私有数据。

// 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
  }
]

被这些策略保护的数据会被映射到链码,和它关联的链码在通道上初始化以后,这个收集器定义文件会被部署到通道上。

  • 在通道上私有化数据的下一步工作是构建链码的数据定义

marbles02_private示例根据数据的使用权限将私有数据分成了两个部分

// Peers in Org1 and Org2 will have this private data in a side database
type marble struct {
  ObjectType string `json:"docType"`
  Name       string `json:"name"`
  Color      string `json:"color"`
  Size       int    `json:"size"`
  Owner      string `json:"owner"`
}

// Only peers in Org1 will have this private data in a side database
type marblePrivateDetails struct {
  ObjectType string `json:"docType"`
  Name       string `json:"name"`
  Price      int    `json:"price"`
}

私有数据的特定权限将会被限制为如下:

  • name, color, size, and owner 通道中所有成员可见(Org1 and Org2)
  • price 只有 Org1 的成员可见

在marbles02_private示例中定义了两个不同的私有数据收集器。数据映射到收集器策略(权限限制)是通过链码 API 控制的。特别地,使用收集器定义进行读和写私有数据是通过调用 GetPrivateData() 和 PutPrivateData() 来实现的。

GetPrivateData:读取私有数据(读) PutPrivateData:写入私有数据(写)

下边的图片阐明了marbles02_private示例所使用的私有数据模型

通过对比不难发现,组织1比组织2多了一个存储price的私有状态数据库,因此组织1有权访问自己的私有数据price,而组织2没有权限访问。

  • 如何读取收集器中的数据

使用链码 API GetPrivateData() 来查询数据库中的私有数据。 GetPrivateData() 需要两个参数, 收集器名和数据的键值。再说一下收集器 collectionMarbles 允许 Org1 和 Org2 的成员使用侧数据库中的私有数据,收集器 collectionMarblePrivateDetails 只允许 Org1 的成员使用侧数据库中的私有数据。在节点上执行数据库查询的命令时,我们会调用marbles的两个函数。

  • readMarble 用于查询 name, color, size and owner 属性的值
  • readMarblePrivateDetails 用于查询 price 属性的值

  • 写入私有数据

使用链码 API PutPrivateData() 将私有数据存入私有数据库。这个 API 同样需要收集器的名字。因为marbles02_private示例包含两个不同的收集器,它在链码中会被调用两次:

  • 第一次调用,使用名为 collectionMarbles 的收集器写入私有数据 name, color, size and owner 。
  • 第二次调用,使用名为 collectionMarblePrivateDetails 的收集器写入私有数据 price 。

例如,在下边的 initMarble 函数片段中, PutPrivateData() 被调用了两次, 每个私有数据集合各一次。

// ==== Create marble object, marshal to JSON, and save to state ====
      marble := &marble{
              ObjectType: "marble",
              Name:       marbleInput.Name,
              Color:      marbleInput.Color,
              Size:       marbleInput.Size,
              Owner:      marbleInput.Owner,
      }
      marbleJSONasBytes, err := json.Marshal(marble)
      if err != nil {
              return shim.Error(err.Error())
      }

      // === Save marble to state ===
      err =stub.PutPrivateData("collectionMarbles", marbleInput.Name, marbleJSONasBytes)
      if err != nil {
              return shim.Error(err.Error())
      }

      // ==== Create marble private details object with price, marshal to JSON, and save to state ====
      marblePrivateDetails := &marblePrivateDetails{
              ObjectType: "marblePrivateDetails",
              Name:       marbleInput.Name,
              Price:      marbleInput.Price,
      }
      marblePrivateDetailsBytes, err := json.Marshal(marblePrivateDetails)
      if err != nil {
              return shim.Error(err.Error())
      }
      err = stub.PutPrivateData("collectionMarblePrivateDetails", marbleInput.Name, marblePrivateDetailsBytes)
      if err != nil {
              return shim.Error(err.Error())
      }

总结一下,上边我们为 collection.json 定义的策略允许 Org1 和 Org2 的所有节点在他们的私有数据库中存储和交易marbles的私有数据 name, color, size, owner ,但是只有 Org1 的节点可以在他的私有数据库中存储和交易 price 私有数据。

数据私有的一个额外的好处是,当使用了收集器以后,只有私有数据的哈希会通过排序节点, 而不是私有数据本身,从排序方面保证了私有数据的机密性。

2.5.2 启动网络

在安装和初始化marbles(弹珠)私有数据链码之前,我们需要启动 BYFN 网络。为了本教程,我们需要在一个已知的初始化环境下操作。下边的命令会关闭所有活动状态的或者存在的 docker 容器并删除之前生成的构件,我们运行下边的命令来清理之前的环境:

cd fabric-samples/first-network ./byfn.sh down docker volume prune (y)

如果这不是你第一次运行本教程,那么你还需要删除marbles私有数据链码的docker容器,您可以运行以下命令清理之前的环境:

docker stop (dockerpsaq)dockerrm(docker ps -aq) docker rm (docker ps -aq) docker rmi $(docker images -q)

你也可以使用下边的一些列命令查看你的网络环境是否清理干净:

  • docker image ls
  • docker container ls
  • docker ps -a

如果你想清理的级别为网络级或者系统级,可以使用以下命令:

docker network prune (输入y) docker system prune (输入y)

网络环境清理好之后,我们可以运行下边的命令来启动使用了 CouchDB 的 BYFN 网络:

./byfn.sh up -c mychannel -s couchdb

这会创建一个简单的 Fabric 网络,包含一个名为 mychannel 的通道,其中有两个组织 (每个组织有两个 peer 节点)和一个排序服务,同时使用 CouchDB 作为状态数据库。LevelDB 或者 CouchDB 都可以使用收集器。这里使用 CouchDB 来演示如何对私有数据进行索引。

2.5.3 安装和初始化带有收集器的链码

客户端应用通过链码和区块链账本交互。所以我们需要在每一个要执行和背书交易的节点 上安装和初始化链码。链码安装在节点上然后在通道上使用 peer-commands 进行初始化。

2.5.3.1 在所有节点上安装链码

就像上边讨论的,BYFN 网络包含两个组织, Org1 和 Org2 ,每个组织有两个节点。所以链码需要安装在四个节点上:

  • peer0.org1.example.com
  • peer1.org1.example.com
  • peer0.org2.example.com
  • peer1.org2.example.com

我们的网络已经启动,接下来进入 CLI 容器操作

docker exec -it cli bash

使用下边的命令在 BYFN 网络上,安装 git 仓库的marbles链码到节点 peer0.org1.example.com (默认情况下,启动 BYFN 网络以后,激活的节点被设置成了 CORE_PEER_ADDRESS=peer0.org1.example.com:7051 )

  • 在peer0.org1.example.com上安装链码

peer chaincode install -n marblesp -v 1.0 -p github.com/chaincode/marbles02_private/go/

  • 在peer1.org1.example.com上安装链码:(利用 CLI 切换当前节点为 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/

  • 在peer0.org2.example.com上安装链码:(利用 CLI 切换到 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 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.example.com上安装链码:(切换当前节点为 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/

2.5.3.2 在通道上初始化链码

在 BYFN 的 mychannel 通道上运行下边的命令来初始化marbles02_private链码

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 /opt/gopath/src/github.com/chaincode/marbles02_private/collections_config.json

为了在通道上配置链码收集器,使用 --collections-config 标识来指定收集器的 JSON 文件,我们的示例中是 collections_config.json。当指定了 --collections-config 的时候,你需要指明 collections_config.json 文件完整清晰的路径

2.5.4 存储私有数据

以 Org1 成员的身份操作,Org1 的成员被授权可以交易marbles02_private示例中的所有私有数据,切换回 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

调用 initMarble 函数来创建一个带有私有数据的弹珠 — 名字为 marble1 , 拥有者为 tom ,颜色为 blue ,尺寸为 35 ,价格为 99 。重申一下,私 有数据 price 将会和私有数据 name, owner, color, size 分开存储。因为这个原 因, initMarble 函数存储私有数据的时候调用两次 PutPrivateData() API ,每个收集器一次。

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"}"

此处注意一下,官方文档中因为文档乱码,所以命令不能顺利执行,使用上述改正过的命令执行即可。

2.5.5 以授权节点的身份查询私有数据

我们收集器的定义允许 Org1 和 Org2 的所有成员在他们的侧数据库中使用 name, color, size, owner 私有数据,但是只有 Org1 的节点可以在他们的侧数据库中保存 price 私有数据。作为一个 Org1 中的授权节点,我们将查询两个私有数据集合。

第一个 query 命令调用传递了 collectionMarbles 作为参数的 readMarble 函数。

func (t *SimpleChaincode) readMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response {
     var name, jsonResp string
     var err error
     if len(args) != 1 {
             return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
     }
 
     name = args[0]
     valAsbytes, err := stub.GetPrivateData("collectionMarbles", name) //get the marble from chaincode state
 
     if err != nil {
             jsonResp = "{\"Error\":\"Failed to get state for " + name + "\"}"
             return shim.Error(jsonResp)
     } else if valAsbytes == nil {
             jsonResp = "{\"Error\":\"Marble does not exist: " + name + "\"}"
             return shim.Error(jsonResp)
     }
 
     return shim.Success(valAsbytes)
}

第二个 query 命令调用传递了 collectionMarblePrivateDetails 作为参数 的 readMarblePrivateDetails 函数。

func (t *SimpleChaincode) readMarblePrivateDetails(stub shim.ChaincodeStubInterface, args []string) pb.Response {
     var name, jsonResp string
     var err error
 
     if len(args) != 1 {
             return shim.Error("Incorrect number of arguments. Expecting name of the marble to query")
     }
 
     name = args[0]
     valAsbytes, err := stub.GetPrivateData("collectionMarblePrivateDetails", name) //get the marble private details from chaincode state
 
     if err != nil {
             jsonResp = "{\"Error\":\"Failed to get private details for " + name + ": " + err.Error() + "\"}"
             return shim.Error(jsonResp)
     } else if valAsbytes == nil {
             jsonResp = "{\"Error\":\"Marble private details does not exist: " + name + "\"}"
             return shim.Error(jsonResp)
     }
     return shim.Success(valAsbytes)
}

2.5.5.1 以组织1节点的身份查询私有数据

以 Org1 成员的身份查询 marble1 的私有数据 name, color, size and owner

peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarble","marble1"]}'

以 Org1 成员的身份查询 marble1 的私有数据 price

peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'

2.5.5.2 以组织2节点的身份查询私有数据 现在我们将切换到 Org2 成员,在它的侧数据库中有弹珠私有数据的 name, color, size, owner ,但是没有私有数据 price 。我们将查询两个私有数据集合

export CORE_PEER_ADDRESS=peer0.org2.example.com:9051 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

Org2 的节点在它们的数据库中有弹珠私有数据的第一个集合 ( name, color, size and owner ) 并且有权限使用 readMarble() 函数和 collectionMarbles 参数访问它。

peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarble","marble1"]}'

查询 Org2 没有权限的私有数据

peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'

Error: endorsement failure during query. response: status:500 message:"{\"Error\":\"Failed to get private details for marble1: GET_STATE failed: transaction ID: de8cc7ef4822a0e31ad2c6c54d1c1183bd6ea7e322e4a0b6fb1f3a398d9fbbf8: tx creator does not have read access permission on privatedata in chaincodeName:marblesp collectionName: collectionMarblePrivateDetails\"}" 

Org2 的成员只能看到私有数据的公共哈希

2.5.6 清除私有数据

对于一些案例,私有数据仅需在账本上保存到在链下数据库复制之后就可以了,我们可以 数据在过了一定数量的区块后进行 “清除”,仅仅把数据的哈希作为不可篡改的证据保存下来。

私有数据可能会包含私人的或者机密的信息,比如我们例子中的价格数据,这是交易伙伴不想 让通道中的其他组织知道的。但是,它具有有限的生命周期,就可以根据收集器定义中的,在固定的区块数量之后清除。

我们的 collectionMarblePrivateDetails 中定义 blockToLive 属性的值为 3 , 表明这个数据会在侧数据库中保存三个区块的时间,之后它就会被清除。将所有内容放在一 起,回想一下绑定了私有数据 price 的收集器 collectionMarblePrivateDetails , 在函数 initMarble() 中,当调用 PutPrivateData() API 并传递了参数 collectionMarblePrivateDetails 。

我们将从在链上增加区块,然后来通过执行四笔新交易(创建一个新弹珠,然后转移三个 弹珠)看一看价格信息被清除的过程,增加新交易的过程中会在链上增加四个新区块。在 第四笔交易完成之后(第三个弹珠转移后),我们将验证一下价格数据是否被清除了。

使用如下命令切换到 Org1 的 peer0

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

打开一个新终端窗口,通过运行如下命令来查看这个节点上私有数据日志:

docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'

注意列表中最高的区块号,最高的区块高度是 6 。

查询私有数据price是否还在私有数据库账本上:

peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'

你将看到类似下边的信息:

{"docType":"marblePrivateDetails","name":"marble1","price":99}

price 数据仍然在私有数据账本上。

通过执行如下命令创建一个新的 marble2 ,这个交易将在链上创建一个新区块。

export MARBLE=(echo -n "{\"name\":\"marble2\",\"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"}"

2021-07-27 03:20:43.138 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 

再次切换回终端窗口并查看节点的私有数据日志,你将看到区块高度增加了 1 。

docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'

2021-07-27 03:20:45.143 UTC [gossip.privdata] StoreBlock -> INFO 09d [mychannel] Received block [7] from buffer
2021-07-27 03:20:45.422 UTC [kvledger] CommitWithPvtData -> INFO 09f [mychannel] Committed block [7] with 1 transaction(s) in 278ms (state_validation=24ms block_and_pvtdata_commit=14ms state_commit=237ms) commitHash=[a023aca1ae8764da787b8a118ac410c18920741fd044c9ef31f3ff64e983ff5c]

返回到节点容器,再次运行如下命令查询 marble1 的价格数据:

peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'

{"docType":"marblePrivateDetails","name":"marble1","price":99}

私有数据没有被清除,之前的查询也没有改变查询结果

运行下边的命令将 marble2 转移给"joe",这个交易将使链上增加第二个区块

export MARBLE_OWNER=(echo -n "{\"name\":\"marble2\",\"owner\":\"joe\"}" | 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":["transferMarble"]}' --transient "{\"marble_owner\":\"MARBLE_OWNER"}"

2021-07-27 03:22:33.371 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 

再次切换回终端窗口并查看节点的私有数据日志,你将看到区块高度增加了 1

docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'

2021-07-27 03:22:35.374 UTC [gossip.privdata] StoreBlock -> INFO 0a6 [mychannel] Received block [8] from buffer
2021-07-27 03:22:35.554 UTC [kvledger] CommitWithPvtData -> INFO 0a8 [mychannel] Committed block [8] with 1 transaction(s) in 177ms (state_validation=20ms block_and_pvtdata_commit=6ms state_commit=142ms) commitHash=[e788903b263d10c34d31b05851597ec41097bead520875264de77f7c035234df]

返回到节点容器,再次运行如下命令查询 marble1 的价格数据

peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'

{"docType":"marblePrivateDetails","name":"marble1","price":99}

运行下边的命令将 marble2 转移给"tom",这个交易将使链上增加第三个区块

export MARBLE_OWNER=(echo -n "{\"name\":\"marble2\",\"owner\":\"tom\"}" | 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":["transferMarble"]}' --transient "{\"marble_owner\":\"MARBLE_OWNER"}"

2021-07-27 03:24:07.724 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 

再次切换回终端窗口并查看节点的私有数据日志。你将看到区块高度又增加了 1

docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'

2021-07-27 03:24:09.728 UTC [gossip.privdata] StoreBlock -> INFO 0af [mychannel] Received block [9] from buffer
2021-07-27 03:24:09.989 UTC [kvledger] CommitWithPvtData -> INFO 0b1 [mychannel] Committed block [9] with 1 transaction(s) in 260ms (state_validation=13ms block_and_pvtdata_commit=19ms state_commit=222ms) commitHash=[719c247eea52db699169ec92580853b5c9e0816ced19bccf89a251c7f87ec5d8]

返回到节点容器,再次运行如下命令查询 marble1 的价格数据:

peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'

{"docType":"marblePrivateDetails","name":"marble1","price":99}

最后,运行下边的命令将 marble2 转移给 “jerry” 。这个交易将使链上增加第四个区块。在 此次交易之后, price 私有数据将会被清除。

export MARBLE_OWNER=(echo -n "{\"name\":\"marble2\",\"owner\":\"jerry\"}" | 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":["transferMarble"]}' --transient "{\"marble_owner\":\"MARBLE_OWNER"}"

2021-07-27 03:27:51.449 UTC [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200 

再次切换回终端窗口并查看节点的私有数据日志。你将看到区块高度增加了 1 。

docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'

2021-07-27 03:27:53.452 UTC [gossip.privdata] StoreBlock -> INFO 0b8 [mychannel] Received block [10] from buffer
2021-07-27 03:27:53.814 UTC [kvledger] CommitWithPvtData -> INFO 0ba [mychannel] Committed block [10] with 1 transaction(s) in 359ms (state_validation=49ms block_and_pvtdata_commit=4ms state_commit=304ms) commitHash=[a39ec0d5a4b140768c3cb16f78bd52e197e2f86c4924448221f086e0b4e5cf44]

返回到节点容器,再次运行如下命令查询 marble1 的价格数据:

peer chaincode query -C mychannel -n marblesp -c '{"Args":["readMarblePrivateDetails","marble1"]}'

因为价格数据已经被清除了,你就查询不到了。你应该会看到类似下边的结果:

Error: endorsement failure during query. response: status:500 message:"{\"Error\":\"Marble private details does not exist: marble1\"}" 

以上就是关于私有数据的介绍