家蛙树

Hyperledger Fabric私有数据

Zealot
区块链
2019-02-20

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基本属性一个私有库, 价格一个私有库。
t_30291a76135344c5a05c26ba79d9ed05.png
Org2只能采访公共库,marble基本属性库, 没有价格私有库数据
t_41ae00c831f04a1283d05076f4962f95.png

直接上链码好了.

先调用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
t_9f4a284840c74b9ea2cf907848fb5490.png

点赞 0
0条评论
其他心得
1. 问题场景 Fabric peer节点使用文件保存区块, 使用level db或couchdb数据库保存状态, 数据很多state db会膨胀, 我们探讨下一些解决方案。 2. couchdb集群 couchdb2.x支持集群, 分片, 应该能把数据分散到集群的其它节点。先简单过一下如何安装。 2.1 couchdb集群搭建 Fabric用到的couchdb镜像是自己打包的, 1.4对应的是hyperledger/fabric-couchdb:0.4.14, 不过很悲催, 笔者
Zealot · 21天前 
1.简介 Fabric 1.4引入operation service即运维服务接口, orderer,peer节点可提供http服务, 方便外部获取节点的运行指标,管理日志级别,健康检查。 2.如何使用运维服务 以fabirc-sample/first-network为例, ./byfn.sh up 2.1 Orderer节点运维服务 启动后连接到orderer容器 docker exec -it -e LINES=$(tput lines) -e COLUMNS=$(tput co
Zealot · 30天前 
1.使用场景 Fabric区块链网络一个channel即一个记账本, 在很多业务场景,一个记账本的数据自身组织可以读写,也可以提供给其它组织只读,部分读或部分写。数据隔离使用channel是粗粒度的,private data私有数据是fabric 1.2引入, 是为了在更细的粒度上控制数据访问。 2.如何使用私有数据? 以fabric-sample/chaincode/marble02_private弹珠游戏为例. (1)文件collections_config.json
Zealot · 31天前 
1.简介 Fabric CA基于开源项目CFSSL开发, 主要为fabric网络提供PKI证书服务,是MSP生成的基础。可能有人会问, 官方不是有cryptogen工具批量生成MSP吗? cryptogen实际是辅助测试工具,默认不同orderer,org都有不同的CA, 如果一个org要追加个peer或user, cryptogen就不管用了。生产环境我们建议使用fabric ca全面管理证书, 如果想简单来而区块链组织,节点和用户基本不会变, cryptogen也没问题。 2.
1.Kafka排序服务原理 官方文档在google doc上, 参考翻译 https://www.jianshu.com/p/db006359133d 2. kafka 排序服务安装 所有的代码已分享在https://github.com/zealzeng/kafka-orderer-demo 2.1 安装环境 官方文档有一些简单的描述 https://hyperledger-fabric.readthedocs.io/en/release-1.4/kafka.h