在Fabric中使用私人数据
本教程将演示如何使用私有数据收集(PDC)在区块链网络上为组织的授权peer提供私有数据的存储和检索。 使用包含管理该集合策略的集合定义文件来指定该集合。
本教程中的信息假定您了解私有数据存储及其使用案例。 有关更多信息,请查看私有数据。
本教程将指导您完成以下步骤,以练习在Fabric中定义,配置和使用私有数据:
- Asset transfer private data sample use case
- Build a collection definition JSON file
- Read and Write private data using chaincode APIs
- Deploy the private data smart contract to the channel
- Register identities
- Create an asset in private data
- Query the private data as an authorized peer
- Query the private data as an unauthorized peer
- Transfer the Asset
- Purge Private Data
- Using indexes with private data
- Additional resources
资产转移私人数据样本用例
此示例演示如何使用三个私有数据集合,assetCollection,Org1MSPPrivateCollection和Org2MSPPrivateCollection在Org1和Org2之间转移资产,并使用以下用例:
Org1的成员创建一个新资产,此后称为所有者。资产的公共详细信息(包括所有者的身份)存储在名为assetCollection的私有数据集合中。资产也由所有者提供的评估值创建。每个参与者都使用评估值来同意资产转移,并且仅存储在所有者组织的集合中。在我们的案例中,所有者同意的初始评估值存储在Org1MSPPrivateCollection中。
要购买资产,购买者需要同意与资产所有者相同的评估价值。在此步骤中,买方(Org2的成员)使用智能合约功能“ AgreeToTransfer”创建交易协议并同意评估价值。此值存储在Org2MSPPrivateCollection集合中。现在,资产所有者可以使用智能合约功能“ TransferAsset”将资产转让给买方。 “ TransferAsset”功能使用信道账本上的哈希值来确认所有者和买方在转让资产之前已经同意了相同的评估价值。
在进行传输方案之前,我们将讨论组织如何在Fabric中使用私有数据集合。
用JSON文件创建集合定义
在一组组织可以使用私有数据进行交易之前,通道上的所有组织都需要构建一个集合定义文件,该文件定义与每个链码相关联的私有数据集合。 存储在私有数据集合中的数据仅分发给确定组织的peer,而不分发给通道的所有成员。 集合定义文件描述了组织可以从链码读取和写入的所有私有数据集合。
每个集合由以下属性定义:
- name:集合的名称。
- police:定义允许持久保存收集数据的组织peer。
- requiredPeerCount:传播私有数据所需的peer数目,以作为对链码的认可的条件。
- maxPeerCount:出于数据冗余的目的,当前背书中peer尝试向其他peer分发数据的的数目。如果一个背书中的节点发生故障,则如果有请求拉取私有数据的请求,则这些其他peer在提交时可用。
- blockToLive:对于价格或个人信息等非常敏感的信息,此值表示数据以块为单位驻留在专用数据库上的时间。数据将在专用数据库上保留该指定数量的块,然后将被清除,从而使该数据从网络中删除。要无限期保留私有数据,即从不清除私有数据,请将blockToLive属性设置为0。
- memberOnlyRead:值为true表示peer自动强制仅允许属于集合成员组织之一的客户端读取对私有数据的访问权限数据。
- memberOnlyWrite:值为true表示peer自动强制仅允许属于集合成员组织之一的客户端对私有数据进行写访问。
- endorsementPolicy:定义为了写入私有数据集合需要满足的背书策略。集合级别背书策略将覆盖链码级别策略。有关构建策略定义的更多信息,请参阅“背书策略”主题。
即使组织不属于任何集合,使用链码的所有组织也需要部署相同的集合定义文件。 除了在集合文件中显式定义的集合之外,每个组织都可以访问其peer上的隐式集合,这些隐式集合只能由其组织读取。 有关使用隐式数据收集的示例,请参见Fabric中的有担保资产转移。
资产转移私有数据示例包含一个collections_config.json文件,该文件定义了三个私有数据集合定义:assetCollection,Org1MSPPrivateCollection和Org2MSPPrivateCollection。
使用链码API读和写私有数据
理解如何对通道上的数据进行私有化的下一步是在链码中建立数据定义。 资产转移私有数据示例根据如何访问数据将私有数据分为三个单独的数据定义。
// Peers in Org1 and Org2 will have this private data in a side database
type Asset struct {
Type string `json:"objectType"` //Type is used to distinguish the various types of objects in state database
ID string `json:"assetID"`
Color string `json:"color"`
Size int `json:"size"`
Owner string `json:"owner"`
}
// AssetPrivateDetails describes details that are private to owners
// Only peers in Org1 will have this private data in a side database
type AssetPrivateDetails struct {
ID string `json:"assetID"`
AppraisedValue int `json:"appraisedValue"`
}
// Only peers in Org2 will have this private data in a side database
type AssetPrivateDetails struct {
ID string `json:"assetID"`
AppraisedValue int `json:"appraisedValue"`
}
具体来说,对私有数据的访问将受到如下限制:
- objectType,color,size和owner存储在assetCollection中,因此根据集合策略(Org1和Org2)中的定义,通道成员将可以看到它们。
- 资产的AppraisedValue存储在集合Org1MSPPrivateCollection或Org2MSPPrivateCollection中,具体取决于资产的所有者。只有属于可以存储集合的组织的用户才能访问该值。
资产转移私有数据样本智能合约创建的所有数据都存储在PDC中。智能合约使用Fabric Chaincode API使用GetPrivateData()和PutPrivateData()函数将私有数据读取和写入私有数据集合。您可以在此处找到有关这些功能的更多信息。此私有数据存储在peer的私有状态db中(与公共状态db分开),并通过gossip协议在授权peer之间分发。
下图说明了私有数据样本使用的私有数据模型。请注意,Org3仅在图中显示,以说明如果通道上还有其他任何组织,则它们将无权访问配置中定义的任何私有数据集合。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H2IkdNRQ-1634268920962)(file://C:/Users/62483/AppData/Roaming/Typora/typora-user-images/image-20210514151755476.png?lastModify=1621323966)]
读私有数据
智能合约使用链码API GetPrivateData()查询数据库中的私有数据。 GetPrivateData()具有两个参数,集合名称和数据键。 回想一下集合assetCollection允许Org1和Org2的peer将私有数据存储在边数据库中,而集合Org1MSPPrivateCollection仅允许Org1的peer存储在边数据库中,而Org2MSPPrivateCollection允许Org2的peer存储在边数据库中 边数据库。 有关实现的详细信息,请参考以下两个资产转移专用数据功能:
- ReadAsset,用于查询资产ID,color,size和owner属性的值。
- ReadAssetPrivateDetails用于查询appraisedValue属性的值。
在本教程后面的内容中使用peer命令发出数据库查询时,我们将调用这两个函数。
写私有数据
智能合约使用链码API PutPrivateData()将私有数据存储到私有数据库中。 该API还需要集合的名称。 请注意,资产转移私有数据样本包括三个不同的私有数据集合,但是在链码中被两次调用(在这种情况下,它充当Org1)。
- 使用名为assetCollection的集合编写私有数据assetID,颜色,大小和所有者。
- 使用名为Org1MSPPrivateCollection的集合写私有数据appraisedValue。
如果我们充当Org2,则将Org1MSPPrivateCollection替换为``Org2MSPPrivateCollection。 例如,在下面的CreateAsset函数片段中,两次调用PutPrivateData(),对于每组私有数据一次。
// CreateAsset creates a new asset by placing the main asset details in the assetCollection
// that can be read by both organizations. The appraisal value is stored in the owners org specific collection.
func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface) error {
// Get new asset from transient map
transientMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("error getting transient: %v", err)
}
// Asset properties are private, therefore they get passed in transient field, instead of func args
transientAssetJSON, ok := transientMap["asset_properties"]
if !ok {
//log error to stdout
return fmt.Errorf("asset not found in the transient map input")
}
type assetTransientInput struct {
Type string `json:"objectType"` //Type is used to distinguish the various types of objects in state database
ID string `json:"assetID"`
Color string `json:"color"`
Size int `json:"size"`
AppraisedValue int `json:"appraisedValue"`
}
var assetInput assetTransientInput
err = json.Unmarshal(transientAssetJSON, &assetInput)
if err != nil {
return fmt.Errorf("failed to unmarshal JSON: %v", err)
}
if len(assetInput.Type) == 0 {
return fmt.Errorf("objectType field must be a non-empty string")
}
if len(assetInput.ID) == 0 {
return fmt.Errorf("assetID field must be a non-empty string")
}
if len(assetInput.Color) == 0 {
return fmt.Errorf("color field must be a non-empty string")
}
if assetInput.Size <= 0 {
return fmt.Errorf("size field must be a positive integer")
}
if assetInput.AppraisedValue <= 0 {
return fmt.Errorf("appraisedValue field must be a positive integer")
}
// Check if asset already exists
assetAsBytes, err := ctx.GetStub().GetPrivateData(assetCollection, assetInput.ID)
if err != nil {
return fmt.Errorf("failed to get asset: %v", err)
} else if assetAsBytes != nil {
fmt.Println("Asset already exists: " + assetInput.ID)
return fmt.Errorf("this asset already exists: " + assetInput.ID)
}
// Get ID of submitting client identity
clientID, err := submittingClientIdentity(ctx)
if err != nil {
return err
}
// Verify that the client is submitting request to peer in their organization
// This is to ensure that a client from another org doesn't attempt to read or
// write private data from this peer.
err = verifyClientOrgMatchesPeerOrg(ctx)
if err != nil {
return fmt.Errorf("CreateAsset cannot be performed: Error %v", err)
}
// Make submitting client the owner
asset := Asset{
Type: assetInput.Type,
ID: assetInput.ID,
Color: assetInput.Color,
Size: assetInput.Size,
Owner: clientID,
}
assetJSONasBytes, err := json.Marshal(asset)
if err != nil {
return fmt.Errorf("failed to marshal asset into JSON: %v", err)
}
// Save asset to private data collection
// Typical logger, logs to stdout/file in the fabric managed docker container, running this chaincode
// Look for container name like dev-peer0.org1.example.com-{chaincodename_version}-xyz
log.Printf("CreateAsset Put: collection %v, ID %v, owner %v", assetCollection, assetInput.ID, clientID)
err = ctx.GetStub().PutPrivateData(assetCollection, assetInput.ID, assetJSONasBytes)
if err != nil {
return fmt.Errorf("failed to put asset into private data collecton: %v", err)
}
// Save asset details to collection visible to owning organization
assetPrivateDetails := AssetPrivateDetails{
ID: assetInput.ID,
AppraisedValue: assetInput.AppraisedValue,
}
assetPrivateDetailsAsBytes, err := json.Marshal(assetPrivateDetails) // marshal asset details to JSON
if err != nil {
return fmt.Errorf("failed to marshal into JSON: %v", err)
}
// Get collection name for this organization.
orgCollection, err := getCollectionName(ctx)
if err != nil {
return fmt.Errorf("failed to infer private collection name for the org: %v", err)
}
// Put asset appraised value into owners org specific private data collection
log.Printf("Put: collection %v, ID %v", orgCollection, assetInput.ID)
err = ctx.GetStub().PutPrivateData(orgCollection, assetInput.ID, assetPrivateDetailsAsBytes)
if err != nil {
return fmt.Errorf("failed to put asset private details: %v", err)
}
return nil
}
综上所述,以上我们collections_config.json的策略定义允许Org1和Org2中的所有peer实体使用其私有数据库中的资产传输私有数据assetID,颜色,大小,所有者来存储和进行交易。 但是,只有Org1中的peer可以与Org1集合Org1MSPPrivateCollection中的appraisedValue密钥数据一起存储和进行交易,而只有Org2中的peer可以与Org2集合Org2MSPPrivateCollection中的appraisedValue密钥数据进行存储和事交易。
作为附加的数据隐私优势,由于使用了集合,因此只有私有数据散列会通过order,而不是私有数据本身,从而使私有数据对order保持机密。
启动网络
现在,我们准备逐步执行一些命令,这些命令演示了如何使用私有数据。
在安装,定义和使用私有数据智能合约之前,我们需要启动Fabric测试网络。 出于本教程的考虑,我们希望在已知的初始状态下进行操作。 以下命令将杀死所有活动或陈旧的Docker容器,并删除先前生成的工件。 因此,让我们运行以下命令来清理以前的所有环境:
cd fabric-samples/test-network
./network.sh down
在test-network目录中,可以使用以下命令通过证书颁发机构和CouchDB启动Fabric测试网络:
./network.sh up createChannel -ca -s couchdb
该命令将部署一个Fabric网络,该网络由一个名为mychannel的通道组成,该通道具有两个组织(每个组织维护一个peer节点),证书颁发机构和排序服务,同时使用CouchDB作为状态数据库。 LevelDB或CouchDB都可以与集合一起使用。 选择CouchDB来演示如何对私有数据使用索引。
注意:
为了使集合正常工作,正确配置跨组织gossip很重要。 请参阅我们关于gossip数据分发协议的文档,尤其要注意“锚点peer”部分。 考虑到gossip已在测试网络中进行了配置,我们的教程不再关注gossip,但是在配置通道时,gossip锚点peer对于配置集合以使其正常工作至关重要。
在信道上部署私有数据智能合约
在test network目录下运行一下命令进行智能合约部署
./network.sh deployCC -ccn private -ccp ../asset-transfer-private-data/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg ../asset-transfer-private-data/chaincode-go/collections_config.json
请注意,我们需要将私有数据集合定义文件的路径传递给命令。 作为将链码部署到信道的一部分,信道上的两个组织都必须传递相同的私有数据集合定义,作为Fabric链码生命周期的一部分。 我们还将部署具有链码级别认可策略“ OR(‘Org1MSP.peer’,‘Org2MSP.peer’)”的智能合约。 这使Org1和Org2可以创建资产而无需获得其他组织的认可。 发出上述命令后,您可以查看部署打印在日志中的链码所需的步骤。
当两个组织都使用peer生命周期chaincode approveformyorg命令批准链码定义时,链码定义将使用–collections-config标志包括私有数据收集定义的路径。 您可以在终端上看到以下approveformyorg命令:
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name private --version 1.0 --collections-config ../asset-transfer-private-data/chaincode-go/collections_config.json --signature-policy "OR('Org1MSP.member','Org2MSP.member')" --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA
在通道成员同意私有数据收集作为链码定义的一部分之后,使用peer生命周期chaincode commit命令将数据收集提交给通道。 如果在日志中查找commit命令,则可以看到它使用相同的–collections-config标志来提供集合定义的路径。
peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --channelID mychannel --name private --version 1.0 --sequence 1 --collections-config ../asset-transfer-private-data/chaincode-go/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的成员,而买方将属于Org2。 为了突出显示GetClientIdentity().GetID()API和用户证书中的信息之间的联系,我们将使用Org1和Org2证书颁发机构(CA)注册两个新的身份,然后使用CA生成每个身份的证书和私钥。
首先,我们需要设置以下环境变量以使用Fabric CA客户端:
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
我们将使用Org1 CA创建身份资产所有者。 将Fabric CA客户端设置为Org1 CA管理员的MSP(此身份由测试网络脚本生成):
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/
您可以使用fabric-ca-client工具注册新的所有者客户端身份:
fabric-ca-client register --caname ca-org1 --id.name owner --id.secret ownerpw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
现在,您可以通过向enroll命令提供注册名称和密码来生成身份证书和MSP文件夹:
fabric-ca-client enroll -u https://owner:ownerpw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
运行以下命令,将节点OU配置文件复制到所有者身份MSP文件夹中。
cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp/config.yaml
现在,我们可以使用Org2 CA创建买家身份。 将Fabric CA客户端设置为Org2 CA管理员:
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/
您可以使用fabric-ca-client工具注册新的所有者客户端身份:
fabric-ca-client register --caname ca-org2 --id.name buyer --id.secret buyerpw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
现在,我们可以注册生成身份MSP文件夹:
fabric-ca-client enroll -u https://buyer:buyerpw@localhost:8054 --caname ca-org2 -M ${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
运行以下命令,将节点OU配置文件复制到买家身份MSP文件夹中。
cp ${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp/config.yaml
在私人数据中创建资产
现在,我们已经创建了资产所有者的身份,我们可以调用私有数据智能合约来创建新资产。 将以下命令集复制并粘贴到测试网络目录中的终端中:
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/owner@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
我们将使用CreateAsset函数创建存储在私有数据中的资产—assetID asset1,颜色为green,大小为20,appraisedValue为100。回想一下,私有数据appraisedValue将与私有数据资产ID,color,size分开存储。 因此,CreateAsset函数两次调用PutPrivateData()API来保存私有数据,每个集合一次。 另请注意,私有数据是使用–transient标志传递的。 作为瞬态数据传递的输入将不会保留在交易中,以保持数据的私密性。 瞬态数据作为二进制数据传递,因此在使用终端时,它必须是base64编码的。 我们使用环境变量来捕获base64编码的值,并使用tr命令去除linux base64命令添加的有问题的换行符。
运行一下命令来创建账户:
export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset1\",\"color\":\"green\",\"size\":20,\"appraisedValue\":100}" | 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 private -c '{"function":"CreateAsset","Args":[]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"
运行结果如下:
[chaincodeCmd] chaincodeInvokeOrQuery->INFO 001 Chaincode invoke successful. result: status:200
请注意,上面的命令仅针对Org1peer。 CreateAsset交易将写入两个集合,assetCollection和Org1MSPPrivateCollection。 为了写入集合,Org1MSPPrivateCollection需要Org1peer的认可,而assetCollection继承链码“ OR(‘Org1MSP.peer’,‘Org2MSP.peer’)”的认可策略。 来自Org1peer的背书可以同时满足两种背书策略,并且无需Org2背书就可以创建资产。
作为授权peer查询私有数据
我们的集合定义允许Org1和Org2的所有peer在其辅助数据库中拥有assetID,颜色,大小和所有者私有数据,但是只有Org1中的peer可以在其辅助数据库中拥有对Org1的appraisedValue私有数据的意见。 作为Org1中的授权peer,我们将查询两组私有数据。
第一个查询命令调用ReadAsset函数,该函数将assetCollection作为参数传递。
// ReadAsset reads the information from collection
func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, assetID string) (*Asset, error) {
log.Printf("ReadAsset: collection %v, ID %v", assetCollection, assetID)
assetJSON, err := ctx.GetStub().GetPrivateData(assetCollection, assetID) //get the asset from chaincode state
if err != nil {
return nil, fmt.Errorf("failed to read asset: %v", err)
}
//No Asset found, return empty response
if assetJSON == nil {
log.Printf("%v does not exist in collection %v", assetID, assetCollection)
return nil, nil
}
var asset *Asset
err = json.Unmarshal(assetJSON, &asset)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
}
return asset, nil
}
第二条查询命令调用ReadAssetPrivateDetails函数,该函数将Org1MSPPrivateDetails作为参数传递。
// ReadAssetPrivateDetails reads the asset private details in organization specific collection
func (s *SmartContract) ReadAssetPrivateDetails(ctx contractapi.TransactionContextInterface, collection string, assetID string) (*AssetPrivateDetails, error) {
log.Printf("ReadAssetPrivateDetails: collection %v, ID %v", collection, assetID)
assetDetailsJSON, err := ctx.GetStub().GetPrivateData(collection, assetID) // Get the asset from chaincode state
if err != nil {
return nil, fmt.Errorf("failed to read asset details: %v", err)
}
if assetDetailsJSON == nil {
log.Printf("AssetPrivateDetails for %v does not exist in collection %v", assetID, collection)
return nil, nil
}
var assetDetails *AssetPrivateDetails
err = json.Unmarshal(assetDetailsJSON, &assetDetails)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON: %v", err)
}
return assetDetails, nil
}
我们可以读取通过使用ReadAsset函数以Org1形式查询assetCollection集合而创建的资产的主要详细信息:
peer chaincode query -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}'
成功后,该命令将返回以下结果:
{"objectType":"asset","assetID":"asset1","color":"green","size":20,"owner":"x509::CN=appUser1,OU=admin,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"}
资产的“所有者”是通过调用智能合约创建资产的身份。 私有数据智能合约使用GetClientIdentity()。GetID()API读取身份证书的名称和颁发者。 您可以在owner属性中查看身份证书的名称和颁发者。
查询作为Org1成员的资产1的“评估值”私有数据。
peer chaincode query -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'
看到如下结果:
{"assetID":"asset1","appraisedValue":100}
作为非授权节点查看私有数据
现在,我们将操作Org2中的用户。 Org2在根据assetCollection策略定义的其侧数据库中具有资产转移专用数据资产ID,颜色,大小,所有者,但不存储Org1的资产评估价值数据。 我们将查询两组私人数据。
跳转到Org2的一个peer
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/buyer@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051
查询私有数据Org2被授权执行
Org2中的peer应在其侧数据库中拥有第一组资产转移私有数据(assetID,颜色,大小和所有者),并且可以使用通过AssetAsset参数调用的ReadAsset()函数进行访问。
peer chaincode query -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}'
成功后,应该会看到类似于以下结果的内容:
{"objectType":"asset","assetID":"asset1","color":"green","size":20,
"owner":"x509::CN=appUser1,OU=admin,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US" }
查询私有数据Org2没被授权执行
由于资产是由Org1创建的,因此与asset1关联的appraisedValue存储在Org1MSPPrivateCollection集合中。 该值未由peer存储在Org2中。 运行以下命令以证明资产的appraisedValue未存储在Org2peer的Org2MSPPrivateCollection中:
peer chaincode query -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 private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'
空响应表示在买方(Org2)私有集合中不存在asset1私有详细信息。
来自Org2的用户也无法读取Org1私有数据集合:
peer chaincode query -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'
通过在集合配置文件中设置“ memberOnlyRead”:true,我们指定只有Org1中的客户端才能读取集合中的数据。 尝试读取集合的Org2客户端只会收到以下响应:
Error: endorsement failure during query. response: status:500 message:"failed to
read asset details: GET_STATE failed: transaction ID: d23e4bc0538c3abfb7a6bd4323fd5f52306e2723be56460fc6da0e5acaee6b23: tx
creator does not have read access permission on privatedata in chaincodeName:private collectionName: Org1MSPPrivateCollection"
来自Org2的用户将只能看到私有数据的公共哈希。
转移资产
让我们看看将资产1转移到组织2所需的时间。 在这种情况下,Org2需要同意从Org1购买资产,并且需要就appraisedValue达成协议。 您可能想知道,如果Org1将他们对appraisedValue的意见保留在他们的私有数据库中,他们将如何达成一致。 对于这个问题的答案,让我们继续。
使用我们的peerCLI切换回终端。
要转移资产,买方(接收者)需要通过调用链码函数AgreeToTransfer来同意与资产所有者相同的appraisedValue。 约定的值将存储在Org2peer的Org2MSPDetailsCollection集合中。 运行以下命令,以将Org2的评估值为100:
export ASSET_VALUE=$(echo -n "{\"assetID\":\"asset1\",\"appraisedValue\":100}" | 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 private -c '{"function":"AgreeToTransfer","Args":[]}' --transient "{\"asset_value\":\"$ASSET_VALUE\"}"
买方现在可以查询他们在Org2私有数据集合中同意的价值:
peer chaincode query -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 private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'
调用将返回以下值:
{"assetID":"asset1","appraisedValue":100}
现在,买方已同意以评估后的价格购买资产,所有者可以将资产转让给Org2。 资产需要通过拥有资产的身份进行转移,因此让我们充当Org1:
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051
来自Org1的所有者可以读取AgreeToTransfer交易添加的数据,以查看买方身份:
peer chaincode query -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 private -c '{"function":"ReadTransferAgreement","Args":["asset1"]}'
{"assetID":"asset1","buyerID":"eDUwOTo6Q049YnV5ZXIsT1U9Y2xpZW50LE89SHlwZXJsZWRnZXIsU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUzo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"}
现在,我们拥有转移资产所需的一切。 智能合约使用GetPrivateDataHash()函数检查Org1MSPPrivateCollection中资产评估值的哈希值是否与Org2MSPPrivateCollection中资产评估值的哈希值匹配。 如果哈希相同,则确认所有者和感兴趣的购买者已同意相同的资产价值。 如果满足条件,则转移功能将从转移协议中获得买方的客户ID,并使买方成为资产的新所有者。 转移功能还将从前所有者的集合中删除资产评估价值,并从assetCollection中删除转移协议。
运行以下命令以转移资产。 所有者需要向转移交易提供买方的资产ID和组织的MSP ID:
export ASSET_OWNER=$(echo -n "{\"assetID\":\"asset1\",\"buyerMSP\":\"Org2MSP\"}" | 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 private -c '{"function":"TransferAsset","Args":[]}' --transient "{\"asset_owner\":\"$ASSET_OWNER\"}" --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
您可以查询asset1以查看传输结果:
peer chaincode query -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 private -c '{"function":"ReadAsset","Args":["asset1"]}'
结果将显示买方身份现在拥有资产:
{"objectType":"asset","assetID":"asset1","color":"green","size":20,"owner":"x509::CN=appUser2, OU=client + OU=org2 + OU=department1::CN=ca.org2.example.com, O=org2.example.com, L=Hursley, ST=Hampshire, C=UK"}
资产的“所有者”现在具有买方身份。
您还可以确认转移已从Org1集合中删除了私人详细信息:
peer chaincode query -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 private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'
您的查询将返回空结果,因为资产专用数据已从Org1专用数据集合中删除。
清除私人数据
对于只需要保留短时间的私有数据的用例,可以在一定数量的块之后“清除”数据,而仅留下数据的散列,这些散列可作为数据的不可变证据。交易。如果数据包含另一个交易使用但不再需要的敏感信息,或者数据正在复制到链下数据库中,则组织可以决定清除私有数据。
在我们的示例中,appraisedValue数据包含一个私人协议,该协议可能会让组织在一段时间后过期。因此,它的寿命有限,并且可以使用集合定义中的blockToLive属性在指定数量的块上在区块链上未更改存在后清除。
Org2MSPPrivateCollection定义的blockToLive属性值为3,这意味着该数据将在边数据库中保留三个块,然后将其清除。如果我们在通道上创建其他块,则Org2同意的appraisedValue最终将被清除。我们可以创建3个新块来演示:
在终端中运行以下命令以切换回以Org2成员的身份运行,并以Org2peer对象为目标:
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/buyer@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051
我们仍然可以在Org2MSPPrivateCollection中查询appraisedValue:
peer chaincode query -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 private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'
log如下:
{"assetID":"asset1","appraisedValue":100}
由于我们需要跟踪清除私有数据之前要添加的块数,因此打开一个新的终端窗口并运行以下命令以查看Org2peer的私有数据日志。 注意最高的块号。
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
现在返回到我们作为Org2成员的终端,并运行以下命令来创建三个新资产。 每个命令将创建一个新块。
export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset2\",\"color\":\"blue\",\"size\":30,\"appraisedValue\":100}" | 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 private -c '{"function":"CreateAsset","Args":[]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"
export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset3\",\"color\":\"red\",\"size\":25,\"appraisedValue\":100}" | 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 private -c '{"function":"CreateAsset","Args":[]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"
export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset4\",\"color\":\"orange\",\"size\":15,\"appraisedValue\":100}" | 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 private -c '{"function":"CreateAsset","Args":[]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"
返回另一个终端并运行以下命令,以确认新资产导致创建了三个新块:
docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'
现在,已从Org2MSPDetailsCollection私有数据集合中清除了appraisedValue。 从Org2终端再次发出查询,以查看响应为空。
peer chaincode query -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 private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'
对私有数据使用索引
通过将索引包装在链代码旁边的META-INF / statedb / couchdb / collections / <collection_name> / indexes目录中,索引也可以应用于私有数据集合。 此处提供示例索引。
为了将链码部署到生产环境,建议在链码旁边定义任何索引,以便一旦链码已安装在peer端并在通道上实例化后,链码和支持索引将作为一个单元自动部署。 当指定–collections-config标志指向集合JSON文件的位置时,关联索引将在通道上链代码实例化时自动部署。