Hyperledger Fabric私有数据
1.使用场景
Fabric区块链网络一个channel即一个记账本, 在很多业务场景,一个记账本的数据自身组织可以读写,也可以提供给其它组织只读,部分读或部分写。数据隔离使用channel是粗粒度的,private data私有数据是fabric 1.2引入, 是为了在更细的粒度上控制数据访问。
2.如何使用私有数据?
以fabric-sample/chaincode/marble02_private弹珠游戏为例.
(1)文件collections_config.json
policy定义谁可以持久化(写)数据(符合policy的); requiredPeerCount定义私有数据传播到给多少个peer节点; blockToLive定义私有数据以区块的形式会被持久化多久,如果不想被清理, 配置为0; memberOnlyRead定义true时则强制指定组织(根据policy确定)的客户端才能读取数据(设置为false会咋样?都不能读?还是所有都能读?)。
[
{
"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的成员具有读写权限。
私有数据设计思路是拆分数据, 公有的数据提取出来设置什么人可读写, 私有的提取出来让部分的人可读写。 跟我们平常说设置文件权限, 谁可读谁可写有些不一样。
policy首先是保证持久化的写权限, 数据要同步对应peer的记账本才行, peer节点没这些数据就谈不上读了。
(2)chaincode如何读写私有数据
marble的名称,颜色,大小和归属人对于org1和org2都开放 // 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"`
}
但marble的价格Org1才可以采访
// 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"`
}
Org1节点数据的分布, 实际用到是side database边库来实现私有数据, 共有的状态还是一个库, marble基本属性一个私有库, 价格一个私有库。
Org2只能采访公共库,marble基本属性库, 没有价格私有库数据
直接上链码好了.
先调用initMarble初始化数据,链码初始化时设置的背书策略应该是Org1MSP或者Org2MSP
// export MARBLE=$(echo -n "{\"name\":\"marble1\",\"color\":\"blue\",\"size\":35,\"owner\":\"tom\",\"price\":99}" | base64)
// peer chaincode invoke -C mychannel -n marblesp -c '{"Args":["initMarble"]}' --transient "{\"marble\":\"$MARBLE\"}"
transientMap可以传入json数据, 也算是一种args, 这里应该需要用Org1MSP的节点, 因为分拆保存为两种私有数据。
// ============================================================
// initMarble - create a new marble, store into chaincode state
// ============================================================
func (t *SimpleChaincode) initMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response {
var err error
type marbleTransientInput struct {
Name string `json:"name"` //the fieldtags are needed to keep case from bouncing around
Color string `json:"color"`
Size int `json:"size"`
Owner string `json:"owner"`
Price int `json:"price"`
}
// ==== Input sanitation ====
fmt.Println("- start init marble")
if len(args) != 0 {
return shim.Error("Incorrect number of arguments. Private marble data must be passed in transient map.")
}
transMap, err := stub.GetTransient()
if err != nil {
return shim.Error("Error getting transient: " + err.Error())
}
if _, ok := transMap["marble"]; !ok {
return shim.Error("marble must be a key in the transient map")
}
if len(transMap["marble"]) == 0 {
return shim.Error("marble value in the transient map must be a non-empty JSON string")
}
var marbleInput marbleTransientInput
err = json.Unmarshal(transMap["marble"], &marbleInput)
if err != nil {
return shim.Error("Failed to decode JSON of: " + string(transMap["marble"]))
}
if len(marbleInput.Name) == 0 {
return shim.Error("name field must be a non-empty string")
}
if len(marbleInput.Color) == 0 {
return shim.Error("color field must be a non-empty string")
}
if marbleInput.Size <= 0 {
return shim.Error("size field must be a positive integer")
}
if len(marbleInput.Owner) == 0 {
return shim.Error("owner field must be a non-empty string")
}
if marbleInput.Price <= 0 {
return shim.Error("price field must be a positive integer")
}
// ==== Check if marble already exists ====
marbleAsBytes, err := stub.GetPrivateData("collectionMarbles", marbleInput.Name)
if err != nil {
return shim.Error("Failed to get marble: " + err.Error())
} else if marbleAsBytes != nil {
fmt.Println("This marble already exists: " + marbleInput.Name)
return shim.Error("This marble already exists: " + marbleInput.Name)
}
// ==== 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())
}
// ==== Index the marble to enable color-based range queries, e.g. return all blue marbles ====
// An 'index' is a normal key/value entry in state.
// The key is a composite key, with the elements that you want to range query on listed first.
// In our case, the composite key is based on indexName~color~name.
// This will enable very efficient state range queries based on composite keys matching indexName~color~*
indexName := "color~name"
colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marble.Color, marble.Name})
if err != nil {
return shim.Error(err.Error())
}
// Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble.
// Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value
value := []byte{0x00}
stub.PutPrivateData("collectionMarbles", colorNameIndexKey, value)
// ==== Marble saved and indexed. Return success ====
fmt.Println("- end init marble")
return shim.Success(nil)
}
读取私有数据, readMarblePrivateDetails应该要Org2MSP才能读取
// ===============================================
// 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)
}
// ===============================================
// readMarblereadMarblePrivateDetails - 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)
}
更多细节参考官方文档 https://hyperledger-fabric.readthedocs.io/en/release-1.4/private_data_tutorial.html
- 原文作者:Zealot
- 原文链接:https://www.51discuss.com/posts/fabric-private-data/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。