5.6 Hyperledger Fabric - 教程 - 在 Fabric 中使用私有数据

在 Fabric 中使用私有数据

本教程将演示如何使用集合为组织的授权对端节点在区块链网络上提供私有数据的存储和检索。

本教程中的信息假定你掌握私有数据存储及其使用案例。有关更多信息,请查看 私有数据

本教程将带你完成以下步骤,以练习定义,配置和使用 Fabric 私有数据:

  1. 构建集合定义 JSON 文件
  2. 使用链码 API 读取和写入私有数据
  3. 安装并实例化使用集合的链码
  4. 存储私有数据
  5. 作为授权的对端节点查询私有数据
  6. 作为未经授权的对端节点查询私有数据
  7. 清除私有数据
  8. 对私有数据使用索引
  9. 额外资源

本教程将使用在构建你的第一个网络 (BYFN) 教程网络上运行的 大理石私有数据示例 来演示如何创建,部署和使用私有数据集合。大理石私有数据示例将部署到 构建你的第一个网络 (BYFN) 教程网络。你应该已经完成任务 安装示例,二进制文件和 Docker 映像;但是,运行 BYFN 教程不是本教程的前提条件。而是在整个教程中提供了使用网络所需的命令。我们将描述每个步骤发生的情况,从而可以在不实际运行示例的情况下理解本教程。

1. 构建集合定义 JSON 文件

私有化通道上数据的第一步是建立一个集合定义,该定义定义对私有数据的访问。

集合定义描述了谁可以保留数据,将数据分发给多少对端节点,需要多少对端节点来传播私有数据以及私有数据在私有数据库中保留的时间长短。稍后,我们将演示如何使用链码 API PutPrivateDataGetPrivateData 将集合映射到受保护的私有数据。

集合定义由以下属性组成:

  • 名称 (name):集合的名称。
  • 策略 (policy):定义允许持久保存集合数据的组织对端节点。
  • requiredPeerCount:传播私有数据所需的对端节点数目,以作为对链码的背书。
  • maxPeerCount:出于数据冗余的目的,当前背书对端节点将尝试向其分发数据的其他对端节点的数量。如果背书对端节点发生故障,则在有请求拉私有数据的请求时,这些其他对端节点也可以使用。
  • blockToLive:对于价格或个人信息等非常敏感的信息,此值表示数据应以区块为单位驻留在私有数据库上的时间。数据将在私有数据库上保留此指定数量的区块,然后将被清除,从而使该数据从网络中删除。要无限期保留私有数据,即从不清除私有数据,请将 blockToLive 属性设置为 0。
  • memberOnlyRead:值为 true 表示对端节点自动强制仅允许属于集合成员组织之一的客户端对私有数据进行读取访问。

为了说明私有数据的用法,大理石 (marbles) 私有数据示例包含两个私有数据集合定义:collectionMarblescollectionMarblePrivateDetailscollectionMarbles 定义中的 policy 属性允许通道的所有成员 (Org1 和 Org2) 在私有数据库中拥有私有数据。collectionMarblesPrivateDetails 集合仅允许 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
  }
]

这些策略保护的数据以链码映射,并将在本教程的后面显示。

使用 peer chaincode instantiate command 在通道上实例化其关联链码时,会将此集合定义文件部署在通道上。下文第 3 节提供了有关此过程的更多详细信息。

2. 使用链码 API 读取和写入私有数据

理解如何对通道上的数据进行私有化的下一步是在链码中建立数据定义。 大理石私有数据示例根据如何访问数据将私有数据分为两个单独的数据定义。

// 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 和 Org2) 可见
  • price 仅对 Org1 成员可见

因此,在大理石私有数据示例中定义了两组不同的私有数据。此数据到限制访问的集合策略的映射由链码 API 控制。具体来说,通过调用 GetPrivateData()PutPrivateData() 可以使用集合定义读取和写入私有数据,可以在此处找到它们。

下图说明了大理石私有数据示例使用的私有数据模型。

image
image

2.1 读取集合数据

使用链码 API GetPrivateData() 查询数据库中的私有数据。GetPrivateData() 具有两个参数,集合名称和数据键。回想一下集合 collectionMarbles 允许 Org1 和 Org2 的成员将私有数据保存在辅助数据库中,集合 collectionMarblePrivateDetails 只允许 Org1 的成员将私有数据保存在辅助数据库中。有关实现的详细信息,请参考以下两个 大理石私有数据函数

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

在本教程后面的内容中使用 peer 命令发出数据库查询时,我们将调用这两个函数。

2.2 写入私有数据

使用链码 API PutPrivateData() 将私有数据存储到私有数据库中。该 API 还需要集合的名称。由于大理石私有数据示例包含两个不同的集合,因此在链码中被调用两次:

  1. 使用名为 collectionMarbles 的集合编写私有数据的 name, color, size and owner
  2. 使用名为 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 中的所有对端节点在其私有数据库中存储和处理大理石私有数据 name, color, size, owner。但是,只有 Org1 中的对端节点可以在其私有数据库中存储和处理 price 私有数据。

作为附加的数据隐私优势,由于使用了集合,因此只有私有数据散列会通过排序器,而不是私有数据本身,从而使私有数据对排序器保持机密。

3. 启动网络

现在,我们准备逐步执行一些演示如何使用私有数据的命令。

在安装和实例化下面的大理石私有数据链码之前,我们需要启动 BYFN 网络。出于本教程的考虑,我们希望在已知的初始状态下进行操作。以下命令将杀死所有活动或陈旧的 Docker 容器并删除以前生成的工件。因此,让我们运行以下命令来清理以前的所有环境:

$ cd fabric-samples/first-network
$ sudo ./byfn.sh down

如果你已经完成了本教程,则还需要删除大理石私有数据链码的底层 Docker 容器。让我们运行以下命令来清理以前的环境:

$ sudo docker rm -f $(docker ps -a | awk '($2 ~ /dev-peer.*.marblesp.*/) {print $1}')
$ sudo docker rmi -f $(docker images | awk '($1 ~ /dev-peer.*.marblesp.*/) {print $3}')

通过运行以下命令,使用 CouchDB 启动 BYFN 网络:

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

这将创建一个简单的 Fabric 网络,该网络由一个名为 mychannel 的通道组成,该通道具有两个组织 (每个组织维护两个对端节点) 和一个排序服务,同时使用 CouchDB 作为状态数据库。LevelDB 或 CouchDB 都可以与集合一起使用。选择 CouchDB 来演示如何对私有数据使用索引。

注解

为了使集合正常工作,正确配置跨组织 gossip 很重要。请参阅我们关于 Gossip 数据分发协议 的文档,尤其要注意“锚点对端节点”部分。考虑到 gossip 已经在 BYFN 示例中进行了配置,因此我们的教程不关注 gossip,但是在配置通道时,gossip 锚点对端节点对于配置集合才能正常工作至关重要。

4. 安装和实例化使用集合的链码

客户端应用程序通过链码与区块链账本进行交互。因此,我们需要在将执行和背书我们交易的每个对端节点上安装并实例化链码。将链码安装到对端节点,然后使用 peer 命令将其实例化到通道上。

4.1 在所有对端节点上安装链码

如上所述,BYFN 网络包括两个组织 Org1 和 Org2,每个组织都有两个对端节点。因此,必须在四个对端节点上安装链码:

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

使用 peer chaincode install 命令在每个对端节点上安装 Marbles 链码。

假设你已启动 BYFN 网络,请进入 CLI 容器。

$ sudo docker exec -it cli bash

你的命令提示符将类似于以下内容:

root@81eac8493633:/opt/gopath/src/github.com/hyperledger/fabric/peer#
  1. 使用以下命令将 git 仓库中的 Marbles 链码安装到 BYFN 网络中的对端节点 peer0.org1.example.com 上。(默认情况下,启动 BYFN 网络后,活动对端节点设置为:CORE_PEER_ADDRESS=peer0.org1.example.com:7051):

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

    完成后,你应该看到类似以下内容:

    install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >
    
  2. 使用 CLI 将活动对端节点切换到 Org1 中的第二个对端节点并安装链码。将以下完整的命令块复制并粘贴到 CLI 容器中,然后运行它们。

    export CORE_PEER_ADDRESS=peer1.org1.example.com:8051
    peer chaincode install -n marblesp -v 1.0 -p github.com/chaincode/marbles02_private/go/
    
  3. 使用 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
    
  4. 将活动对端节点切换到 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/
  1. 将活动对端节点切换到 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/

4.2 在通道上实例化链码

使用 peer chaincode instantiate 命令可实例化通道上的大理石链码。要在通道上配置链码集合,请指定标志--collections-config 以及集合 JSON 文件的名称,在我们的示例中为 collections_config.json

运行以下命令以在 BYFN 通道 mychannel 上实例化大理石私有数据链码。

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
注解

当指定 --collections-config 标志的值时,你将需要指定 collections_config.json 文件的绝对路径。例如:--collections-config $GOPATH/src/github.com/chaincode/marbles02_private/collections_config.json

实例化成功完成后,你应该会看到类似以下内容的内容:

[chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
[chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc

5. 存储私有数据

充当 Org1 的成员,该成员已被授权与大理石私有数据示例中的所有私有数据进行交易,切换回 Org1 对端节点并提交添加大理石的请求:

将以下命令集复制并粘贴到 CLI 命令行。

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 函数,该函数将创建一个包含私有数据的大理石 — tom 拥有的名称为 marble1,其颜色为蓝色,大小为 35,价格为 99。回想一下,私有数据价格将与私有数据名称,所有者,颜色,大小分开存储。因此,initMarble 函数调用两次 PutPrivateData() API 来保存私有数据,每个集合一次。还要注意,私有数据是使用 --transient 标志传递的。作为瞬态数据传递的输入将不会保留在交易中,以保持数据私有。瞬态数据作为二进制数据传递,因此在使用 CLI 时,必须以 base64 编码。我们使用一个环境变量来捕获 base64 编码的值,并使用 tr 命令去除 linux base64 命令添加的有问题的换行符。

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

你应该看到类似于以下内容的结果:

[chaincodeCmd] chaincodeInvokeOrQuery->INFO 001 Chaincode invoke successful. result: status:200

6. 作为授权的对端节点查询私有数据

我们的集合定义允许 Org1 和 Org2 的所有成员在其辅助数据库中拥有 name, color, size, owner 私有数据,但是只有 Org1 中的对端节点可以在其辅助数据库中拥有 price 私有数据。作为 Org1 中的授权对端节点,我们将查询两组私有数据。

第一个查询命令调用 readMarble 函数,该函数将 collectionMarbles 作为参数传递。

// ===============================================
// readMarble - read a marble from chaincode state
// ===============================================

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

第二条查询命令调用 readMarblePrivateDetails 函数,该函数将 collectionMarblePrivateDetails 作为参数传递。

// ===============================================
// readMarblePrivateDetails - read a marble private details from chaincode state
// ===============================================

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

现在,查询作为 Org1 成员的 marble1 的 name, color, size and owner 私有数据。请注意,由于查询不会记录在帐本中,因此无需将大理石名称传递为瞬时输入。

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

你应该看到以下结果:

{"color":"blue","docType":"marble","name":"marble1","owner":"tom","size":35}

作为 Org1 的成员查询 marble1 的价格私有数据。

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

你应该看到以下结果:

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

7. 作为未经授权的对端节点查询私有数据

现在,我们将切换到 Org2 的成员,该成员的边数据库中具有大理石私有数据 name, color, size, owner,但边数据库中没有大理石价格私有数据。我们将查询两组私有数据。

7.1 切换到 Org2 中的对端节点

从 docker 容器内部,运行以下命令以切换到未经授权访问大理石价格私有数据的对端节点。

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

7.2 Org2 被授权查询私有数据

Org2 中的对端节点应在其侧边数据库中具有第一组大理石私有数据 (name, color, size and owner),并且可以使用 readMarble() 函数 (通过 collectionMarbles 参数调用) 访问它。

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

你应该看到类似于以下结果的结果:

{"docType":"marble","name":"marble1","color":"blue","size":35,"owner":"tom"}

7.3 Org2 未经授权查询私有数据

Org2 中的对端节点在其辅助数据库中没有大理石价格私有数据。当他们尝试查询此数据时,他们将获取与公共状态匹配但没有私有状态的键的哈希。

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

你应该看到类似于以下结果:

{"Error":"Failed to get private details for marble1: GET_STATE failed:
transaction ID: b04adebbf165ddc90b4ab897171e1daa7d360079ac18e65fa15d84ddfebfae90:
Private data matching public hash version is not available. Public hash
version = &version.Height{BlockNum:0x6, TxNum:0x0}, Private data version =
(*version.Height)(nil)"}

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

8. 清除私有数据

对于仅在将私有数据复制到链外数据库之前才需要将其保留在账本上的用例,可以在一定数量的区块之后清除数据,而仅保留那些数据的哈希。作为交易的不变证据。

可能存在包括个人或机密信息的私额数据,例如我们的示例中的价格数据,交易双方不希望在通道上向其他组织披露。因此,它的寿命有限,并且可以使用集合定义中的 blockToLive 属性在指定数量的区块上在区块链上保持不变后清除。

我们的 collectionMarblePrivateDetails 定义的 blockToLive 属性值为 3,这意味着该数据将在边数据库中保留 3 个区块,然后将其清除。将所有部分捆绑在一起,回想一下此集合定义 collectionMarblePrivateDetails 在调用 PutPrivateData() API 并将 collectionMarblePrivateDetails 作为参数时,与 initMarble() 函数中的价格私有数据相关联。

我们将逐步在链中添加区块,然后通过发布四个新交易 (创建一个新的大理石,然后进行三个大理石转移) 来观察价格信息是否被清除,这会在链中添加四个新区块。在第四次交易 (第三次大理石转移) 之后,我们将验证价格私有数据是否已清除。

使用以下命令在 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

打开一个新的终端窗口,并通过运行以下命令查看此对端节点的私有数据日志:

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

你应该看到与以下类似的结果。注意列表中的最高区块编号。在下面的示例中,最高区块高度为 4。

[pvtdatastorage] func1 -> INFO 023 Purger started: Purging expired private data till block number [0]
[pvtdatastorage] func1 -> INFO 024 Purger finished
[kvledger] CommitWithPvtData -> INFO 022 Channel [mychannel]: Committed block [0] with 1 transaction(s)
[kvledger] CommitWithPvtData -> INFO 02e Channel [mychannel]: Committed block [1] with 1 transaction(s)
[kvledger] CommitWithPvtData -> INFO 030 Channel [mychannel]: Committed block [2] with 1 transaction(s)
[kvledger] CommitWithPvtData -> INFO 036 Channel [mychannel]: Committed block [3] with 1 transaction(s)
[kvledger] CommitWithPvtData -> INFO 03e Channel [mychannel]: Committed block [4] with 1 transaction(s)

返回对端节点容器,通过运行以下命令来查询 marble1 价格数据。(由于没有交易处理,因此查询不会在帐本上创建新交易)。

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

你应该看到类似于以下内容的结果:

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

价格数据仍在私有数据帐本中。

通过发出以下命令来创建新的 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\"}"

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

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

返回对端节点容器,通过运行以下命令再次查询 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\"}"

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

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

返回对端节点容器,通过运行以下命令来查询 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\"}"

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

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

返回对端节点容器,通过运行以下命令来查询 marble1 价格数据:

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

你仍然应该能够看到价格数据。

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

最后,通过运行以下命令将 marble2 转移到 “jerry”。该交易将在链上创建第四个新区块。价格私有数据应在此交易后清除。

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

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

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

返回对端节点容器,通过运行以下命令来查询 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\"}"

9. 对私有数据使用索引

通过将索引包装在链码旁边的 META-INF/statedb/couchdb/collections/<collection_name>/indexes 目录中,索引也可以应用于私有数据集合。此处提供示例索引。

为了将链码部署到生产环境,建议在链码旁边定义任何索引,以便一旦链码已安装在对端节点并在通道上实例化后,链码和支持索引将作为一个单元自动部署。当指定 --collections-config 标志指向集合 JSON 文件的位置时,关联索引将在通道上链码实例化时自动部署。

10. 额外资源

为了提供更多私有数据教育,已创建了视频教程。

视频

Reference

项目源代码

项目源代码会逐步上传到 Github,地址为 https://github.com/windstamp

Contributor

  1. Windstamp, https://github.com/windstamp
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容