翻译:https://hyperledger-fabric.readthedocs.io/en/latest/private_data_tutorial.html

本教程将演示如何使用区块链上的私有数据。本文档介绍如何使用是由数据存储和用例。更多信息,查看 Private data.

通过以下几步来定义,配置,使用私有数据。

1.建集合定义JSON文件。

2.使用链码读写私有数据。

3.使用集合安装定义链码。

4.存储私有数据。

5.有权限的peer查询私有数据

6.无权限的peer查询私有数据

7.清除私有数据

8.在私有数据中使用索引

9.附加资源

文档会部署 marbles private data sample到test network来证明怎样创建,部署,且使用私有数据。

建集合定义JSON文件

第一步需要建立个集合定义的文件,用来进入通道上的私有数据。

集合定义描述了谁拥有数据,数据分发到多少个peer上,需要多少个peer来分发数据,私有数据在数据库中保存多久。然后,我们将演示如何使用链码API PutPrivateData 和GetPrivateData 来将集合映射到受保护的私有数据。

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

  • name:结合的名字
  • policy:定义允许拥有集合数据的组织peer
  • requiredPeerCount:作为链码认可的条件,需要传播私有数据peer的数量。
  • maxPeerCount:出于数据冗余的目的,当前的背书peer将尝试将数据分发到其它peer的数量。如果背书的peer关停了,此时有请求拉取私有数据,其它peer可用。
  • blockToLive:对于敏感信息(如定价或个人信息),这代表了数据在私有数据库中保存的时间,以块为单位。数据将保留指定数量的块,然后会清除,标记数据从网络中清除。如果无限期保留,将此属性设置为 0
  • .memberOnlyRead:为true表示peer强制只允许集合成员组织之一的客户端读取私有数据。

为说明私有数据的用法,示例marbles包含两个私有数据集合定义: collectionMarbles 和collectionMarblePrivateDetails。collectionMarbles 中的policy 允许所有通道成员(Org1和Org2)拥有私有数据。collectionMarblesPrivateDetails 集合只允许Org1拥有私有数据。

策略定义的更多信息查看 Endorsement policies。

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

这些策略要保护的数据映射到链码中,稍后教程中介绍

当链码定义提交到通道时,集合定义文件也会部署。以下第三部分会介绍。

 使用链码API读写私有数据

了解如何私有化通道上的数据的下一步是在链码中构建数据定义。示例marbles根据访问权限将私有数据分为两种 。

// 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对所有通道成员开放
  • price 只能被Org1访问

这样两种不同的私有数据的设置就被定义了。数据和集合策略映射,策略由链码API控制。读写私有数据由集合定义中的GetPrivateData() 和 PutPrivateData(),可以在这里查看 here。

下图说了marbles私有数据示例使用的私有数据模型。

Hyperledge Fabric介绍 hyperledger fabric中文文档_Hyperledge Fabric介绍

读数据

使用链码接口GetPrivateData()来查询私有数据。需要两个参数,集合名字和数据的key。调用集合collectionMarbles 允许Org1和Org2操作,调用集合collectionMarblePrivateDetails 允许Org1操作。更多细节查看marbles private data functions:

  • readMarble 查找name, color, size and owner
  • readMarblePrivateDetails  查找price

稍后使用peer命令调用

写数据

链码接口PutPrivateData()用来存储私有数据。接口也需要集合名称做参数。因为有两个集合,要在链码中调用两次:

1.写私有数据name, color, size and owner使用集合名collectionMarbles

2.写私有数据price,使用集合collectionMarblePrivateDetails

下面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的peer读写name, color, size, owner。但只有Org1中的peer才能读写price。

因为使用集合,所以只有私有数据hash通过orderer,而数据本身对orderer保密。

启动网络

下面使用一些命令来展示怎样使用私有数据。

首先初始化test network

cd fabric-samples/test-network
./network.sh down

如果还没有运行过本文档,那首先需要安装依赖包,命令如下:

cd ../chaincode/marbles02_private/go
GO111MODULE=on go mod vendor
cd ../../../test-network

如果已经运行过本文档,你需要删除之前运行链码的容器。运行下面的命令清除之前的容器:

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

在test-network目录下,你可以使用如下命令来启动网络(使用了CouchDB)

./network.sh up createChannel -s couchdb

命令会创建一个通道mychannel,通道上两个组织(每个组织含一个peer),一个ordering service且使用CouchDB作为状态数据库。collection可以使用 LevelDB和CouchDB。选择CouchDB是为了在私有数据中使用索引。

注意:保证collection正常工作,必须正确设置跨组织的gossip。在Gossip data dissemination protocol部分,特别注意“anchor peers”部分。本文档的test network已经正确配置了。当配置了通道,gossip锚节点就会正确的去工作。

在collection安装和定义链码

客户端应用通过链码和区块链账本交互。因此我们需要在每个peer上安装链码这样会有足够的交易背书。然而,在我们和链码交互之前,通道成员需要就链码定义达成一致,包含了私有数据collection配置。使用 peer lifecycle chaincode,来打包,安装,审议定义。

复制如下的环境变量切换为Org1 admin的身份。在test-network目录中:

export PATH=${PWD}/../bin:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

1.打包marbles链码

peer lifecycle chaincode package marblesp.tar.gz --path ../chaincode/marbles02_private/go/ --lang golang --label marblespv1

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

peer lifecycle chaincode install marblesp.tar.gz

成功安装后返回:

2019-04-22 19:09:04.336 UTC [cli.lifecycle.chaincode] submitInstallProposal -> INFO 001 Installed remotely: response:<status:200 payload:"\nKmarblespv1:57f5353b2568b79cb5384b5a8458519a47186efc4fcadb98280f5eae6d59c1cd\022\nmarblespv1" >
2019-04-22 19:09:04.336 UTC [cli.lifecycle.chaincode] submitInstallProposal -> INFO 002 Chaincode code package identifier: marblespv1:57f5353b2568b79cb5384b5a8458519a47186efc4fcadb98280f5eae6d59c1cd

3.切换为Org2 admin

export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

4.Org2 peer上安装链码

peer lifecycle chaincode install marblesp.tar.gz

审议链码定义

通道成员在使用链码之前需要审议链码定义。本文档中每个组织都要使用链码,因此两个组织都要审议链码定义peer lifecycle chaincode approveformyorg。链码定义中包含了私有数据collection的定义。需要--collections-config指向collection JSON文件。

1.查询安装在peer上的package ID

peer lifecycle chaincode queryinstalled

2.将package ID放入环境变量

export CC_PACKAGE_ID=marblespv1:f8c8e06bfc27771028c4bbc3564341887881e29b92a844c66c30bac0ff83966e

3.确保以Org1运行,复制如下环境变量

export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

4.审议链码定义,此命令中包含了collection的定义文件。

export ORDERER_CA=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name marblesp --version 1.0 --collections-config ../chaincode/marbles02_private/collections_config.json --signature-policy "OR('Org1MSP.member','Org2MSP.member')" --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA

5.切换到Org2

export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

6.Org2审议链码定义

peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name marblesp --version 1.0 --collections-config ../chaincode/marbles02_private/collections_config.json --signature-policy "OR('Org1MSP.member','Org2MSP.member')" --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA

提交链码定义

一旦有足够(通常是大多数)的组织审议了链码定义,其中的某个组织就可以提交定义到通道上。

使用 peer lifecycle chaincode commit 提交链码定义,此命令也会部署collection定义到链码上。

marbles 链码中包含有初始化函数,因此首先需要peer chaincode invoke调用Init()。

1.提交链码定义到通道

export ORDERER_CA=${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
export ORG1_CA=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export ORG2_CA=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name marblesp --version 1.0 --sequence 1 --collections-config ../chaincode/marbles02_private/collections_config.json --signature-policy "OR('Org1MSP.member','Org2MSP.member')" --tls --cafile $ORDERER_CA --peerAddresses localhost:7051 --tlsRootCertFiles $ORG1_CA --peerAddresses localhost:9051 --tlsRootCertFiles $ORG2_CA

保存私有数据

Org1在示例中有操作私有数据的权限,切换到Org1,且增加一条数据

export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

调用initMarble 函数创建一条数据,name:marble1 ,owned:tom,color:blue,size:35,price:99。记得prce是独立存储的。initMarble 函数会调用PutPrivateData()两次来保存数据,每个collection一次。注意,私有数据的传值使用--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 localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/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

以授权peer的身份查询私有数据

Org1有两个collection的权限。

第一步查询调用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)
}

请注意,由于查询不会记录在分类帐上,因此不必将marble作为临时值传递。

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

你会看到如下结果

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

Org1查price

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

结果:

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

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

现在切换到Org2,Org2没有储存price的数据。

切换到Org2

export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

查询有权限的私有数据

Org2可以使用readMarble()查询,此函数有collectionMarbles 参数。

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

结果:

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

查询无权限私有数据 

Org2没有price私有数据在它这边的数据库中,当查询这个数据时,会返回一个哈希值,代表了公开状态的key,但不会有私有状态。

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: d9c437d862de66755076aeebe79e7727791981606ae1cb685642c93f102b03e5:
tx creator does not have read access permission on privatedata in chaincodeName:marblesp collectionName: collectionMarblePrivateDetails\"}"

Org2只允许看私有数据的公开哈希值。

清除私有数据

用例中,私有数据在复制到链外数据库之前需要存储在链上,可以在一定数量区块之后清除数据,仅留下数据的哈希作为事务存在过的证据。

私有数据包含了个人和保密信息,例如本例中price数据。事务方不想让通道上的其它组织知道。因此,它的生命是有限的,并且可以使用集合定义中的blockToLive属性在区块链上对指定数量的区块保持不变后进行清除。

collectionMarblePrivateDetails 的定义中blockToLive 是3,意味着数据会存在3个区块然后消失。将所有的部分结合在一起,回想一下这个集合定义collectionMarblePrivateDetails 是在initMarble()函数,当在PutPrivateData()接口中传入collectionMarblePrivateDetails 参数。

我们将添加区块,并且观察当有四个事务后price清空(创建一条,后面三个交易)。

切换到Org1,并在peer容器中执行:

export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

新的终端中,观看私有数据日志,注意区块高度:

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

回到peer容器中,查询price数据(query查询不会创建区块)

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

结果:

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

此时price数据还在。

创建新区块:

export MARBLE=$(echo -n "{\"name\":\"marble2\",\"color\":\"blue\",\"size\":35,\"owner\":\"tom\",\"price\":99}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/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

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

再回到peer容器,查询marble1的price

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

结果:

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

交易marble2,增加区块

export MARBLE_OWNER=$(echo -n "{\"name\":\"marble2\",\"owner\":\"joe\"}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/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

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

再到peer容器,查marble1 price

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

结果:

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

交易marble2 ,增加第三个区块

export MARBLE_OWNER=$(echo -n "{\"name\":\"marble2\",\"owner\":\"tom\"}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/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

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

返回peer容器,查price

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

结果:

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

最后再交易marble2,price会在交易结束后消失

export MARBLE_OWNER=$(echo -n "{\"name\":\"marble2\",\"owner\":\"jerry\"}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/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

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

到容器查看marble1 price

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

price已经清空,结果:

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

在私有数据中使用索引

私有数据collection可以使用索引,打包索引在META-INF/statedb/couchdb/collections/<collection_name>/indexes目录在链码旁。一个例子 here 

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