Hyperledger Fabric的Private Data功能实战

阅读本文章前建议先了解《使用VS Code开发智能合约》

Private Data功能想要解决的问题

在区块链中,上链的数据可说是“永久的”、“公开的”的在各个参与方之间共享。但是现实应用场景中,很多数据出于隐私保护或者安全性的要求,希望数据不是“公开的”存在区块链账本里;另外也会出于存储成本等要求,希望数据不是“永久的”存在区块链账本里。这种场景中,我们最常见的解决方法是hash上链,也就是数据本身不上链,数据在链下根据也许需求在各方之间流转,数据的hash值存储到链上,使得各方都可以通过区块链技术来验证数据的完整性。但是这种方案最大的问题在于数据的存储、共享传输以及处理逻辑都和区块链脱钩了,用户需要单独开发这些系统,其中数据处理逻辑和智能合约的脱钩,更是一个非常棘手的问题。Fabric提供了Private Data相关的功能,直接在区块链中融入了解决上述问题的技术。

Private Data功能技术架构简述

Fabric官方的Private Data说明文档:https://hyperledger-fabric.readthedocs.io/en/release-1.4/private-data-arch.html

基于collection的数据隔离

在智能合约的部署、升级过程中,用户可以定义一系列的collections。collections长这样:

[
{
   "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
}
]

对于每一个collection,peer节点们都会开一个“独立的账本”用来存储这部分数据,智能合约也需要使用专门的“PrivateData”系列API来访问这些账本。大家可以对比一下普通账本的接口和private data的接口:

// GetState returns the value of the specified `key` from the
    // ledger. Note that GetState doesn't read data from the writeset, which
    // has not been committed to the ledger. In other words, GetState doesn't
    // consider data modified by PutState that has not been committed.
    // If the key does not exist in the state database, (nil, nil) is returned.
    GetState(key string) ([]byte, error)
    // PutState puts the specified `key` and `value` into the transaction's
    // writeset as a data-write proposal. PutState doesn't effect the ledger
    // until the transaction is validated and successfully committed.
    // Simple keys must not be an empty string and must not start with null
    // character (0x00), in order to avoid range query collisions with
    // composite keys, which internally get prefixed with 0x00 as composite
    // key namespace.
    PutState(key string, value []byte) error
    // DelState records the specified `key` to be deleted in the writeset of
    // the transaction proposal. The `key` and its value will be deleted from
    // the ledger when the transaction is validated and successfully committed.
    DelState(key string) error
    // GetPrivateData returns the value of the specified `key` from the specified
    // `collection`. Note that GetPrivateData doesn't read data from the
    // private writeset, which has not been committed to the `collection`. In
    // other words, GetPrivateData doesn't consider data modified by PutPrivateData
    // that has not been committed.
    GetPrivateData(collection, key string) ([]byte, error)
    // PutPrivateData puts the specified `key` and `value` into the transaction's
    // private writeset. Note that only hash of the private writeset goes into the
    // transaction proposal response (which is sent to the client who issued the
    // transaction) and the actual private writeset gets temporarily stored in a
    // transient store. PutPrivateData doesn't effect the `collection` until the
    // transaction is validated and successfully committed. Simple keys must not be
    // an empty string and must not start with null character (0x00), in order to
    // avoid range query collisions with composite keys, which internally get
    // prefixed with 0x00 as composite key namespace.
    PutPrivateData(collection string, key string, value []byte) error
    // DelState records the specified `key` to be deleted in the private writeset of
    // the transaction. Note that only hash of the private writeset goes into the
    // transaction proposal response (which is sent to the client who issued the
    // transaction) and the actual private writeset gets temporarily stored in a
    // transient store. The `key` and its value will be deleted from the collection
    // when the transaction is validated and successfully committed.
    DelPrivateData(collection, key string) error

Private Data的流转流程

  • client通过transient字段传递Private Data到智能合约

    • 普通的交易的参数(Args)会完整的记录在交易里,也就意味着会完整的记录在区块中。而通过transient字段传递的参数不会出现在交易里,智能合约使用完后就销毁了。
    • 智能合约通过如下接口从交易中获取transient字段
// GetTransient returns the `ChaincodeProposalPayload.Transient` field.
    // It is a map that contains data (e.g. cryptographic material)
    // that might be used to implement some form of application-level
    // confidentiality. The contents of this field, as prescribed by
    // `ChaincodeProposalPayload`, are supposed to always
    // be omitted from the transaction and excluded from the ledger.
    GetTransient() (map[string][]byte, error)
  • 智能合约通过Private Data系列接口读、写collention。在此过程中,Fabric会负责隐私数据的分发。

    由此产生的交易中的写集不会包含数据本身,而是数据hash

    • 数据本身Fabric会遵循collection中定义的policy,通过p2p网络发送给符合policy要求的peer节点
    • requiredPeerCount、maxPeerCount是collection中定义的用来保护数据可用性的,具体来说

      • requiredPeerCount决定了peer在背书签名前,至少要把隐私数据分发给多少个其他peer
      • maxPeerCount决定了peer在交易处理阶段,会主动推送隐私数据给多少个其他peer
    • memberOnlyRead是一个推荐开启的访问控制策略。在绝大多数的Private Data使用场景中,我们不希望隐私数据流出到policy定义以外的组织中,自然也不希望那些组织的用户通过智能合约的接口来读取数据,开启这个访问控制策略,系统会自动拒绝policy定义以外的组织读取隐私数据。
  • 交易提交orderer、并被peer节点commit

    • 符合policy定义的peer在收到隐私交易后,会向其他peer拉取数据
    • 同时peer会根据blockToLive的设定,清理过期的数据

在Visual Studio Code (VS Code) 中开发Private Data功能的合约

其实开发和调试Private Data合约和普通合约开发没有太大的区别。以下合约代码和collection-config.json可以供大家参考:

/*
 * SPDX-License-Identifier: Apache-2.0
 */

package main

import (
    "fmt"

    "github.com/hyperledger/fabric/core/chaincode/shim"
    sc "github.com/hyperledger/fabric/protos/peer"
)

// Chaincode is the definition of the chaincode structure.
type Chaincode struct {
}

// Init is called when the chaincode is instantiated by the blockchain network.
func (cc *Chaincode) Init(stub shim.ChaincodeStubInterface) sc.Response {
    fcn, params := stub.GetFunctionAndParameters()
    fmt.Println("Init()", fcn, params)
    return shim.Success(nil)
}

// Invoke is called as a result of an application request to run the chaincode.
func (cc *Chaincode) Invoke(stub shim.ChaincodeStubInterface) sc.Response {
    fcn, params := stub.GetFunctionAndParameters()
    fmt.Println("Invoke()", fcn, params)
    ts, err := stub.GetTransient()
    if err != nil {
        return shim.Error("GetTransient error!")
    }
    if fcn == "get" {
        pd, err := stub.GetPrivateData("col1", "key")
        if err != nil {
            return shim.Error("GetPrivateData failed!")
        }
        return shim.Success(pd)
    } else if fcn == "put" {
        value, ok := ts["value"]
        if !ok {
            return shim.Error("Get value failed!")
        }
        err := stub.PutPrivateData("col1", "key", value)
        if err != nil {
            return shim.Error("PutPrivateData failed!")
        }
        return shim.Success(nil)
    } else {
        return shim.Error("Unknown function!")
    }
}
[
    {
        "name": "col1",
        "policy": {
            "identities": [
                {
                    "role": {
                        "name": "member",
                        "mspId": "Org1MSP"
                    }
                }
            ],
            "policy": {
                "1-of": [
                    {
                        "signed-by": 0
                    }
                ]
            }
        },
        "requiredPeerCount": 0,
        "maxPeerCount": 0,
        "blockToLive": 10000000,
        "memberOnlyRead": true
    }
]

其中policy是需要大家注意的以上policy是OR('Org1MSP.member')解析之后的样子。因为VS Code插件使用了fabric-nodejs-sdk,在nodejs sdk中对policy的定义是基于底层数据结构的。而Fabric的peer命令行会把"OR('Org1MSP.member')"之类的抽象规则解析为policy的底层定义后再提交,所以如果我们用peer chaincode instantiate之类命令来指定背书策略或者collection-config里的策略时,需要提供的是一个抽象的描述字符串。

在阿里云BaaS中使用private data功能

我们可以使用阿里云BaaS提供的VSCode 配套插件来把我们带private data功能的合约在云上部署、并实例化。要使用private data功能,我们需要提前准备好一个collections-config文件,这个文件中policy的定义和fabric官方文档保持一致,是一个描述字符串。

[
    {
        "name": "col1",
        "policy": "OR('org1MSP.member')",
        "requiredPeerCount": 1,
        "maxPeerCount": 3,
        "blockToLive": 10000000,
        "memberOnlyRead": true
    }
]

在activate chaincode这一步操作中,会提示我们是否需要设置collections。我们输入并选择collections-config文件。

memberOnlyRead策略验证

Hyperledger Fabric的Private Data功能实战

上图是一个两个组织的通道中,部署链码后,我们依次向org1的peer节点发起3次交易的情况

  1. 用org1用户put
  2. 用org1用户get
  3. 用org2用户get(这是一个使用org2的user向org1的gateway发起交易的例子。直接连接gateway只能使用本组织的身份。因此需要一些小技巧。操作步骤如下:在插件中断开gateway的连接,直接使用shift+command+p,使用evaluate transaction命令,此时由于未选择任何gateway和wallet,系统会列出所有选项)

前两个交易成功,而最后一个失败,可以看到memberOnlyRead策略阻止了org2的用户的get请求。

留一些课后作业给大家思考和尝试:

  1. org2用户无法读取,那是否可以写入呢?
  2. 读写请求发送到org2的peer上会发生什么?
  3. memberOnlyRead设置为false后进行上述测试,又会发生什么?

联系我们

欢迎感兴趣的同学加入钉钉群(钉钉群号: 23181816)。
Hyperledger Fabric的Private Data功能实战

上一篇:spring boot json忽略字段,spring boot json忽略部分字段


下一篇:《软件定义网络:基于OpenFlow的SDN》一一2.1 OpenFlow参考交换机