Hyperledger Fabric v2.0 中的隐式数据集合【区块链系列...】
-
私有数据是Hyperledger Fabric中的重要功能,允许某些数据仅存储在选定的组织内部。这样可以在通道内提供数据保密性,如果在数据级别需要保密性,则可以减少所需的通道数。在Fabric v2.0中,为每个组织引入了一个隐式集合,并且应用程序链码可以在不显式定义集合的情况下使用它。本文旨在探讨隐式集合,并了解如何在生命周期链代码和应用程序链代码中使用它。
-
在典型设置中,组织加入通道后,其对等方将维护分类帐的本地副本,包括世界状态数据库和交易区块链。每个分类账的世界状态都是相同的,在整个区块链平台上维护着真理的来源。这意味着存储在分类帐中的任何数据都存储在所有对等方中。
-
在某些情况下,需要某些数据需要一定隐私。某些数据只能由组织的子组访问。在这种情况下,该子组之外的组织不应拥有该数据的副本。自1.2版开始引入的私有数据解决了这一问题。
-
私有数据被实现为数据收集。集合中有两种类型的信息:实际的私有数据及其数据哈希。收集定义了一个策略,该策略显示了组织的子组(哪些组织可以拥有实际数据)。虽然私有数据仅存储在此子组中,但是所有对等方(包括子组外的对等方)都保留数据散列,以作为交易验证的证据。
-
这是快速说明每个peer的世界状态数据库中存储的各种数据。
- 集合是在集合定义中定义的,集合定义是一个JSON文件,显示所有集合的属性。在Fabric v2.0中批准或提交链码定义时(或在以前的版本中实例化)将指定此文件。以下是集合定义的示例,该示例取自fabrics-samples中的marbles02_private示例。
[
{
"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
}
]
- 在此示例中,collectionMarbles是Org1和Org2都将保留的集合,而collectionMarblePrivateDetails是仅用于Org1的集合。
**链码与集合进行交互的方式是通过两个API:PutPrivateData和GetPrivateData。就像PutState和GetState一样工作。 **
-
我们需要指定分别用于存储私有数据或从中检索私有数据的集合。
-
在v2.0之前,我们必须先定义集合,然后才能使用它。由于每个组织都有一个数据收集非常普遍,因此v2.0引入了隐式收集。该名称告诉我们含义:它是每个peer中预定义的集合,对应于组织的私有数据集合。在使用它之前,我们不需要在集合定义中明确定义一个。
-
集合名称是_implicit_org_
。如果正在使用它,则在PutPrivateData或GetPrivateData中指定此集合。当在链码中使用时,它以通道名称和链码名称为前缀。 -
足够有趣的是,生命周期链代码也使用此隐式集合,生命周期链代码是用于在结构网络中部署应用程序链代码的系统链代码。在操作期间,某些信息被写入并保存在隐式集合中。
-
在本文中,我们主要关注隐式集合,并观察生命周期链代码和应用程序链代码如何使用它。我们不会详细介绍生命周期链代码。
演示设置
-
我们的演示基于fabric-samples的第一网络。这将建立一个具有基于raft-based orderer的fabric网络,该群集具有两个peer组织,每个peers组织都有两个peers(总共四个peers)。
-
我们正在使用经过修改的SACC链码。SACC是一个示例链代码,用于将key/value对存储到分类帐中。我们包括两个函数:setPrivateOrg1和getPrivateOrg1。这个名称是不言自明的,出于演示目的,我们使用它来处理Org1的隐式集合。
修改后的链码
/*
* Copyright IBM Corp All Rights Reserved
*
* SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"fmt"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-protos-go/peer"
)
// SimpleAsset implements a simple chaincode to manage an asset
type SimpleAsset struct {
}
// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data.
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
// Get the args from the transaction proposal
args := stub.GetStringArgs()
if len(args) != 2 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}
// Set up any variables or assets here by calling stub.PutState()
// We store the key and the value on the ledger
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
}
return shim.Success(nil)
}
// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// Extract the function and args from the transaction proposal
fn, args := stub.GetFunctionAndParameters()
var result string
var err error
if fn == "set" {
result, err = set(stub, args)
} else if fn == "setPrivateOrg1" {
result, err = setPrivateOrg1(stub, args)
} else if fn == "getPrivateOrg1" {
result, err = getPrivateOrg1(stub, args)
} else { // assume 'get' even if fn is nil
result, err = get(stub, args)
}
if err != nil {
return shim.Error(err.Error())
}
// Return the result as success payload
return shim.Success([]byte(result))
}
// Set stores the asset (both key and value) on the ledger. If the key exists,
// it will override the value with the new one
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 2 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
}
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return "", fmt.Errorf("Failed to set asset: %s", args[0])
}
return args[1], nil
}
func setPrivateOrg1(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 2 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
}
err := stub.PutPrivateData("_implicit_org_Org1MSP", args[0], []byte(args[1]))
if err != nil {
return "", fmt.Errorf("Failed to set asset: %s", args[0])
}
return args[1], nil
}
// Get returns the value of the specified asset key
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 1 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key")
}
value, err := stub.GetState(args[0])
if err != nil {
return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
}
if value == nil {
return "", fmt.Errorf("Asset not found: %s", args[0])
}
return string(value), nil
}
// Get returns the value of the specified asset key
func getPrivateOrg1(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 1 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key")
}
value, err := stub.GetPrivateData("_implicit_org_Org1MSP", args[0])
if err != nil {
return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
}
if value == nil {
return "", fmt.Errorf("Asset not found: %s", args[0])
}
return string(value), nil
}
// main function starts up the chaincode in the container during instantiate
func main() {
if err := shim.Start(new(SimpleAsset)); err != nil {
fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
}
}
-
如您所见,为这两个新功能添加了第51–54、80–90和109–122行。并且您可以看到在第85行中使用了PutPrivateData,在第114行中使用了GetPrivateData。两者都引用了Org1的隐式集合。
-
实例
整个演示分为三个部分。首先我们启动First Network环境,并准备应用程序链码sacc_private。然后,我们使用生命周期链代码将链代码部署到该结构网络。我们的重点主要是在chaincode操作期间使用隐式集合。最后我们将与应用程序链代码进行交互,并了解如何引用隐式集合。
环境配置
- 步骤1:调出所有组件并设置mychannel
cd fabric-samples/first-network
./byfn.sh up -n -s couchdb
- 当脚本完全执行后,我们会看到这一点。
-
步骤2:准备浏览器以显示每个peer的CouchDB
-
我们需要四个浏览器,每个浏览器一个
peer0.org1.example.com:http://<host_ip>:5984/_utils/ peer1.org1.example.com:http://<host_ip>:6984/_utils/ peer0.org2.example.com:http://<host_ip>:7984/_utils/ peer1.org2.example.com:http://<host_ip>:8984/_utils/
为方便起见,它们按顺序排列在选项卡中。从URL的端口中,我们知道我们正在检查哪个peer。
-
步骤3:创建一个新的chaincode目录并放置新的chaincode
-
为简单起见,只需将sacc目录复制到一个新的sacc_private。
cd fabric-samples/chaincode
cp -r sacc sacc_private
cd sacc_private
然后用提供的代码替换sacc.go(或仅插入添加的部分)。
- 步骤4:第一次加载模块
GO111MODULE=on go mod vendor
在First Network中部署应用程序链码
我们经历了完整的生命周期过程,以部署我们的应用程序链代码。
- 步骤5:生命周期阶段1:打包链码
请注意,chaincode目录已正确映射到CLI容器。因此,我们可以打包这个新创建的链码。
docker exec cli peer lifecycle chaincode package
sacc.tar.gz --path github.com/hyperledger/fabric-samples/chaincode
/sacc_private/ --label sacc_1
docker exec cli ls
- 步骤6:生命周期阶段2:将chaincode程序包安装到peer0.org1和peer0.org2
# peer0.org1
docker exec cli peer lifecycle chaincode install sacc.tar.gz
# peer0.org2
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/
hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/
users/Admin@org2.example.com/msp -e
CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e
CORE_PEER_LOCALMSPID="Org2MSP" -e
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/
fabric/peer/crypto/peerOrganizations/org2.example.com/peers/
peer0.org2.example.com/tls/ca.crt cli peer lifecycle chaincode
install sacc.tar.gz
注意程序包ID,因为我们在批准链码定义时将使用它。
步骤7:观察peers状态
所有peers的状态都没有改变。以此为基准,我们可以比较接下来的步骤中添加的内容。
- 步骤8:批准Org1的链码定义
我们首先有Org1批准链码定义。
docker exec cli peer lifecycle chaincode approveformyorg --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 --channelID mychannel
--name mycc --version 1 --sequence 1 --waitForEvent
--package-id sacc_1:669c48a3aa18f629e86cbe99fd4fccae2575b400c0f7da
8741f68517ae4a6579
- 步骤9:观察peer状态
首先,我们在peer0.org1中进行观察。
在peer1.org1中也可以看到相同的内容。
- 然后,我们在peer0.org2中进行观察。
- 在peer1.org2中也可以看到相同的内容。
总而言之,我们可以看到在所有peers中都创建了新数据库。但是组织之间存在差异。当我们批准Org1的链码时,我们看到
这是一个私有数据收集设置,此收集是特定于通道mychannel和chaincode生命周期中使用的Org1的隐式收集。具有$$p的数据库是实际数据,而具有$$h的数据库是实际数据的哈希。我们看到,对于Org1的隐式收集,实际数据保留在Org1的peer中,而数据哈希保留在通道内的所有peer中(也包括在Org1和Org2中)。
- 步骤10:批准Org2的链码定义
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com
/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com
/users/Admin@org2.example.com/msp -e
CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e
CORE_PEER_LOCALMSPID="Org2MSP" -e
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger
/fabric/peer/crypto/peerOrganizations/org2.example.com/peers
/peer0.org2.example.com/tls/ca.crt cli peer lifecycle chaincode
approveformyorg --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 --channelID mychannel --name mycc
--version 1 --sequence 1 --waitForEvent --package-id
sacc_1:669c48a3aa18f629e86cbe99fd4fccae2575b400c0f7da8741f68517ae4a6579
- 步骤11:观察peer状态
首先,我们观察peer0.org1(在peer1.org1中也可以找到)
- 然后,我们观察到peer0.org2(在peer1.org2中也找到了相同的对象)
我们看到,所有数据库中的新数据库都以类似的方式创建。当我们批准Org2的链码时,我们看到
结果是生命周期链代码正在使用另一个特定于Org2的隐式集合。
- 步骤12:提交Chaincode
在获得两个组织的批准后,我们现在提交链码,现在可以使用应用程序链码了。
docker exec cli peer lifecycle chaincode commit -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 --peerAddresses
peer0.org1.example.com:7051 --tlsRootCertFiles
/opt/gopath/src/github.com/hyperledger/fabric/peer
/crypto/peerOrganizations/org1.example.com/peers
/peer0.org1.example.com/tls/ca.crt
--peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles
/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto
/peerOrganizations/org2.example.com/peers/peer0.org2.example.com
/tls/ca.crt --channelID mychannel --name mycc --version 1 --sequence 1
如果您查看状态数据库,则隐式集合没有更新。提交链码后的更新部分在mychannel__lifecycle上,这在本文范围之外。
提交完成后,即可使用应用程序链码。
使用应用程序链码
我们使用调用和查询链码与已部署的链码进行交互。作为比较,我们首先使用原始set和get作为参考,以比较我们用于数据收集的新功能。
- 步骤13:使用get调用设置并从两个组织中的peers查询
我们首先调用set并从peer0.org1和peer0.org2进行查询。
cker exec cli 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 --peerAddresses
peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src
/github.com/hyperledger/fabric/peer/crypto/peerOrganizations
/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
--peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles
/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto
/peerOrganizations/org2.example.com/peers/peer0.org2.example.com
/tls/ca.crt -C mychannel -n mycc -c '{"Args":["set","name","kc"]}'
# query from peer0.org1
docker exec cli peer chaincode query -C mychannel -n mycc
-c '{"Args":["get","name"]}'
# query from peer0.org2
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com
/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com
/users/Admin@org2.example.com/msp -e
CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e
CORE_PEER_LOCALMSPID="Org2MSP" -e
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com
/hyperledger/fabric/peer/crypto/peerOrganizations
/org2.example.com/peers/peer0.org2.example.com/tls
/ca.crt cli peer chaincode query -C mychannel -n mycc
-c '{"Args":["get","name"]}'
数据在peer0.org1和peer0.org2中均可用。
- 步骤14:观察peer状态
当我们使用PutState将数据写入分类帐时,数据将保存在mychannel__mycc中,这在加入该通道的所有peers中都会发生。
- 步骤15:调用setPrivateOrg1并使用getPrivateOrg1从两个组织中的peers查询
docker exec cli 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 --peerAddresses
peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src
/github.com/hyperledger/fabric/peer/crypto/peerOrganizations
/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
--peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles
/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto
/peerOrganizations/org2.example.com/peers/peer0.org2.example.com
/tls/ca.crt -C mychannel -n mycc -c '{"Args":[
"setPrivateOrg1","name","peter"]}'
# query from peer0.org1
docker exec cli peer chaincode query -C mychannel -n mycc -c
'{"Args":["getPrivateOrg1","name"]}'
# query from peer0.org2
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com
/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com
/users/Admin@org2.example.com/msp -e
CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e
CORE_PEER_LOCALMSPID="Org2MSP" -e
CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger
/fabric/peer/crypto/peerOrganizations/org2.example.com/peers
/peer0.org2.example.com/tls/ca.crt cli peer chaincode query
-C mychannel -n mycc -c '{"Args":["getPrivateOrg1","name"]}'
- 数据仅在peer0.org1中可用,而在peer0.org2中不可用。出现错误消息,因为peer0.org2无法获取资产。这是因为此数据写在Org1的隐式集合上。
当我们观察状态数据库时,情况更加清楚。
- 步骤16:观察peers状态
当我们使用PutPrivateData并指定隐式集合_implicit_org_Org1MSP将数据写入分类账时,数据将保存在mychannel_mycc $$ p_implicit_org_ $ org1 $ m $ s $ p中,并且还将创建相应的数据哈希文件 这适用于Org1的两个peers节点(peer0.org1和peer1.org1)
如果我们在Org2中观察peer(peer0.org2和peer1.org2),则数据部分为空,但有一个数据哈希。这是预期的,因为数据收集是Org1的隐式收集。
在这里,我们看到虽然隐式收集是针对特定组织的,但它是一个由通道名称和链码名称指定的数据库。正如我们在状态中看到的,有两个数据库:一个用于mychannel-lifecycle,另一个用于mychannel-mycc。它们是独立的数据库,用于不同的通道链代码组合。
在本文中,我们演示并观察了隐式集合的外观和使用方式。
隐式集合为我们提供了针对一个组织的预定义数据集合。每个通道链代码组合都会创建一个单独的集合。除了在我们的应用程序链代码中使用之外,在部署应用程序链代码时,生命周期链代码还将使用此隐式集合。