Hyperledger Fabric的Private Data功能实战

简介: 阅读本文章前建议先了解《使用VS Code开发智能合约》 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策略验证

3

上图是一个两个组织的通道中,部署链码后,我们依次向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)。
钉钉群

目录
相关文章
|
6月前
|
开发框架 .NET 区块链
Hyperledger fabric部署链码(五)初始化与链码升级
fabric部署chaincode-go(智能合约)系列之五
|
2月前
|
前端开发 JavaScript 开发者
Canvas库 fabric.js可以实现哪些功能? 动图介绍
fabric.js是一个canvas库,今天整理了一下fabric.js可以实现的功能,用动图的形式分享给大家,方便快速了解fabric.js。
Canvas库 fabric.js可以实现哪些功能? 动图介绍
|
4月前
|
存储 SQL 数据管理
Data Fabric:一站式数据管理与服务
在2023年11月01日云栖大会D区D2-1进行了主题为阿里云瑶池数据库如何助力企业数字化转型与升级的演讲,本文带大家一起了解阿里云数据库生态工具产品部负责人周文超的精彩演讲,内容为《Date Fabric:一站式数据管理与服务》。
97563 5
|
6月前
|
存储 JSON 安全
Hyperledger fabric智能合约编写(一)
本篇文章主要对链码编写的主要思路和部分API进行梳理。
|
6月前
|
Go API 区块链
Hyperledger Fabric相关概念介绍
在学习Hyperledger Fabric的过程中,初步对相关概念的了解。
Hyperledger Fabric相关概念介绍
|
6月前
|
JSON 区块链 数据格式
Hyperledger fabric部署链码(四)提交链码定义到channel
fabric部署chaincode-go(智能合约)系列之四
|
6月前
|
测试技术 API 区块链
Hyperledger fabric部署链码(三)批准链码定义
fabric部署chaincode-go(智能合约)系列之三
|
6月前
|
JavaScript 测试技术 Go
Hyperledger fabric部署链码(一)打包链码
fabric部署chaincode-go(智能合约)系列之一
|
6月前
|
测试技术 Go 区块链
Hyperledger fabric 测试环境部署
Hyperledger fabric 测试环境部署及相关问题解答
109 3
|
6月前
|
区块链
Hyperledger fabric部署链码(二)安装链码到fabric
fabric部署chaincode-go(智能合约)系列之二