Fabric中的Transient Data与Private Data
在Hyperledger Fabric中有兩個相關的概念:私有數據(Private Data)和暫態數據(Transient Data)。本文提供四個示例程序,分別對應私有數據和暫態數據的四種組合使用方式,并通過觀察賬本的交易以及世界狀態數據庫,理解為什么在使用私有數據時應當采用暫態數據作為輸入。
從技術上講,私有數據和暫態數據是兩個不同的概念。私有數據是考慮如何在通道的部分機構之間共享數據,而暫態數據則是使用私有數據時的一種輸入方法。有趣的是,這兩者并沒有直接的關系,雖然在現實中,當我們需要安全的使用私有數據時,通常都應當使用暫態數據作為輸入。
Hyperledger Fabric區塊鏈開發教程:?Fabric Node.js開發詳解?|?Fabric Java開發詳解?|?Fabric Golang開發詳解
1、基本概念
先讓我們重溫一下在演示程序中將要用到的一些核心概念。
賬本:在Hyperledger Fabric中,當一個peer節點加入通道后,就會維護一個賬本的副本。賬本包含一個用于保存區塊的區塊鏈數據結構,以及一個用于保存最新狀態的世界狀態數據庫。當peer節點從排序服務收到一個新的區塊并且驗證成功后,peer節點就將區塊提交進賬本,并根據區塊中每個交易的
RWSet來更新相應的世界狀態。
基于共識機制,在一個通道中,不同的peer節點上的賬本的大部分都是一致的。不過有一個例外,Fabric支持在部分通道之間存在的私有數據。
私有數據:在一個通道內有時會需要僅在部分機構之間共享數據。Hyperledger Fabric引入私有數據的目的就是滿足這一需求。通過定義數據集,我們可以聲明私有數據實現的機構子集,同時所有節點(包括在機構子集之外的其他節點)都會保存私有數據哈希的記錄以便作為數據存在的證據或者用于審計目的。
可以利用鏈碼API來使用私有數據。我們在示例代碼中使用PutPrivateData和GetPrivateData這兩個API。作為對比,我們使用PutState和GetState完成對公共狀態的讀寫。
在Fabric 2.0中,會為每個機構準備一個隱含的數據集。本教程將利用這個隱含的數據集,因此我們不需要單獨的數據集定義文件。
Fabric的賬本結構以及私有數據的位置如下圖所示,在后面的演示代碼中,我們將查看交易以及世界狀態數據庫的內容:
暫態數據:許多鏈碼函數在被調用時需要額外的輸入數據。在大多數情況下我們會在調用函數時傳入一組參數,而鏈碼參數,包括函數名和函數參數,都會作為有效交易的一部分保存在區塊內,因此將永久性的存在于賬本中。如果出于某種原因我們不希望在鏈上永久保存參數列表,我們就可以使用暫態數據。暫態數據是一種可以向鏈碼函數傳參但不需要將其保存在交易記錄中的輸入方法。當使用暫態數據時,需要一個特殊的鏈碼API即GetRansient方法來讀取暫態數據。我們接下來將在演示代碼中看到。
因此,鏈碼的設計需要根據業務需求設計鏈碼,決定哪些數據應當作為正常參數輸入并記錄在交易中,哪些數據應當作為暫態數據輸入而不必記錄在鏈上。
私有數據與暫態數據的關系:私有數據與暫態數據并不是直接相關的,我們可以只使用私有數據而不利用暫態數據作為輸入,也可以在非私有數據中使用暫態數據。因此我們可以得到示例中的四種應用場景并觀察每種場景下的結果。
私有數據與暫態數據的關系如下圖所示:
輸入方法的選擇以及是否是否私有數據依賴于具體的業務需求,因為鏈碼函數反應了真實的業務交易。我們可以選擇普通的參數列表、暫態數據或者同時使用兩種方式的輸入,也可以向公共狀態或者私有數據集寫入數據。我們需要的是正確地選擇需要使用的鏈碼API。
2、應用場景概述
如前所述,我們將私有數據和暫態數據進行2X2的組合,概述如下:
場景1:不使用私有數據,不輸入暫態數據
在這種場景下,數據被寫入賬本中的公開狀態部分,所有的peer節點將保存相同的賬本數據。在鏈碼中使用PutState和GetState來訪問這部分數據。當我們調用鏈碼時,使用普通的參數列表指定輸入數據。
當通道中的所有機構都需要相同的數據時,適合采用這種方式。
場景2:使用私有數據,不輸入暫態數據
在這種場景下,數據被寫入賬本中的私有數據部分,并且只有在私有數據集定義的機構的peer節點上會保存數據。在鏈碼中我們使用PutPrivateData和GetPrivateData來訪問私有數據集。當我們調用鏈碼時,使用普通的參數列表指定輸入數據。
當應用中存在對部分數據的隱私需求,而對于輸入數據不敏感時,可以采用這種方式。
場景3:使用私有數據,輸入暫態數據
類似于場景2,數據被寫入賬本中的私有數據部分,只有在私有數據集定義中的那些peer節點會保存這部分私有數據。在鏈碼中,我們使用PutPrivateData和GetPrivateData來訪問數據集。當我們調用鏈碼時,采用暫態數據作為輸入,因此在鏈碼中我們需要使用GetTransient來處理輸入數據。
場景4:不使用私有數據,輸入暫態數據
這是一個想象的場景,只是用來表明在不使用私有數據時,也可以使用暫態數據作為輸入。我們在鏈碼中使用PutState和GetState來將數據保存到賬本的公共狀態,而采用暫態數據作為鏈碼調用的輸入參數。和之前一樣,我們在鏈碼中使用GetTransient方法來處理輸入數據。
3、演示環境搭建
在這些演示中我們使用Fabric 2.0,使用First Network作為Fabric網絡。我們采用CouchDB選項啟動網絡,以便于查看世界狀態數據庫的內容。我們將重點關注peer0.org1.example.com (couchdb port 5984) 和 peer0.org2.example.com (couchdb port 7984) 以便查看兩個機構中的節點的行為。
在私有數據部分,我們使用Org1內置的隱含私有數據集(_implicit_org_Org1MSP)。只有Org1中的peer節點可以保存私有數據,而Org1和Org2中的節點都可以保存數據哈希。
我們修改了fabric-samples中的SACC鏈碼。SACC鏈碼有兩個函數set和get。為了展示私有數據和暫態數據,我們創建以下函數:
- setPrivate:使用相同的參數列表,數據保存在Org1隱含的私有數據集
- setPrivateTransient:使用暫態數據輸入,數據保存在Org1隱含的私有數據集
- setTransient:使用暫態數據輸入,數據保存在公共狀態
- getPrivate:提取保存在Org1隱含的私有數據集中的數據
修改后的SACC鏈碼如下:
/** Copyright IBM Corp All Rights Reserved** SPDX-License-Identifier: Apache-2.0*/package mainimport ("encoding/json""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 proposalargs := 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 ledgererr := 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 proposalfn, args := stub.GetFunctionAndParameters()var result stringvar err errorif fn == "set" {result, err = set(stub, args)} else if fn == "setPrivate" {result, err = setPrivate(stub, args)} else if fn == "setTransient" {result, err = setTransient(stub, args)} else if fn == "setPrivateTransient" {result, err = setPrivateTransient(stub, args)} else if fn == "getPrivate" {result, err = getPrivate(stub, args)} else { // assume 'get' even if fn is nilresult, err = get(stub, args)}if err != nil {return shim.Error(err.Error())}// Return the result as success payloadreturn 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 setPrivate(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 }func setTransient(stub shim.ChaincodeStubInterface, args []string) (string, error) {type keyValueTransientInput struct {Key string `json:"key"`Value string `json:"value"`}if len(args) != 0 {return "", fmt.Errorf("Incorrect arguments. Expecting no data when using transient")}transMap, err := stub.GetTransient()if err != nil {return "", fmt.Errorf("Failed to get transient")}// assuming only "name" is processedkeyValueAsBytes, ok := transMap["keyvalue"]if !ok {return "", fmt.Errorf("key must be keyvalue")}var keyValueInput keyValueTransientInputerr = json.Unmarshal(keyValueAsBytes, &keyValueInput)if err != nil {return "", fmt.Errorf("Failed to decode JSON")}err = stub.PutState(keyValueInput.Key, []byte(keyValueInput.Value))if err != nil {return "", fmt.Errorf("Failed to set asset")}return keyValueInput.Value, nil }func setPrivateTransient(stub shim.ChaincodeStubInterface, args []string) (string, error) {type keyValueTransientInput struct {Key string `json:"key"`Value string `json:"value"`}if len(args) != 0 {return "", fmt.Errorf("Incorrect arguments. Expecting no data when using transient")}transMap, err := stub.GetTransient()if err != nil {return "", fmt.Errorf("Failed to get transient")}// assuming only "name" is processedkeyValueAsBytes, ok := transMap["keyvalue"]if !ok {return "", fmt.Errorf("key must be keyvalue")}var keyValueInput keyValueTransientInputerr = json.Unmarshal(keyValueAsBytes, &keyValueInput)if err != nil {return "", fmt.Errorf("Failed to decode JSON")}err = stub.PutPrivateData("_implicit_org_Org1MSP", keyValueInput.Key, []byte(keyValueInput.Value))if err != nil {return "", fmt.Errorf("Failed to set asset")}return keyValueInput.Value, 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 getPrivate(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)} }4、Fabric私有數據和暫態數據演示
首先啟動First Network,不要部署默認鏈碼,啟用CouchDB選項:
cd fabric-samples/first-network ./byfn.sh up -n -s couchdb當看到所有容器(5個排序節點,4個peer節點,4個couchdb,一個CLI)啟動后:
創建一個新的鏈碼目錄:
cd fabric-samples/chaincode cp -r sacc sacc_privatetransientdemo cd sacc_privatetransientdemo然后使用上面的鏈碼替換sacc.go。
在第一次運行之前我們需要先加載依賴的模塊:
GO111MODULE=on go mod vendor最后我們使用lifecycle chaincode命令部署這個鏈碼。
5、場景1演示:不使用Fabric私有數據和暫態數據輸入
場景1時最常用的一種:使用普通的參數列表作為鏈碼方法的輸入,然后將其保存在公共狀態中,所有peer節點持有完全相同的數據。我們調用鏈碼的set和get方法。
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":["set","name","alice"]}'docker exec cli peer chaincode query -C mychannel -n mycc -c '{"Args":["get","name"]}'結果如下:
我們首先查看世界狀態。這個數據同時保存在peer0.org1.example.com 和peer0.org2.example.com的公共狀態中(mychannel_mycc)。
當查看區塊鏈中的交易記錄時,我么看到WriteSet中的鍵/值對是:name/alice,采用base64編碼。
我們也可以看到調用鏈碼時的參數列表,3個base64編碼的參數分別是:set、name和alice。
和預期一樣,RWSet更新了公開狀態,輸入參數被記錄在交易中。
6、場景2演示:使用私有數據,不適用暫態數據輸入
在場景2中,鏈碼調用還是采用普通的參數列表,數據則保存在Org1的私有數據集。我們使用setPrivate和getPrivate訪問鏈碼
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":["setPrivate","name","bob"]}'docker exec cli peer chaincode query -C mychannel -n mycc -c '{"Args":["getPrivate","name"]}'我們首先查看世界狀態。在peer0.org1.example.com中我們可以看到數據保存為私有數據,
創建了兩個數據庫:一個用于實際數據,一個用于數據哈希。在peer0.org2.example.com
上,我們看到只有哈希文件。
內容的哈希在兩個機構的節點上都是一樣的。此外,在peer0.org1.example.com中我們可以
看到調用鏈碼時輸入的數據。
當查看區塊鏈中的交易記錄時,我們看到沒有RWSet。相反我們看到數據被應用于
Org1隱含的數據集,它指向已經保存在peer中的數據,通過hash得以保護這部分數據的隱私。
我們可以看到調用鏈碼時的參數列表。3個base64編碼的參數分別是:setPrivate、name、bob。
如果我們關系數據隱私,這可能存在問題。一方面數據保存在私有數據集中,這樣只有限定的機構節點可以保存。另一方面,這部分隱私數據的鏈碼輸入卻還是公開可見的,并且永久保存在所有peer節點的區塊鏈中。如果這不是你期望的,那么我們還需要將輸入數據隱藏掉。這就是使用暫態數據輸入的原因。
6、場景3演示:使用私有數據和暫態數據輸入
如果你希望確保數據輸入不會保存在鏈上,那么場景3是推薦的方式。在這種場景下,采用暫態數據輸入,并且數據保存在Org1的私有數據集。我們使用setPrivateTransient和getPrivate方法訪問鏈碼:
在我們的鏈碼中,我們實現函數時將暫態數據編碼為特定的JSON格式?{“key”:”some key”, “value”: “some value”}?(鏈碼134–137行)。我們也要求暫態數據包含一個keyvalue鍵(鏈碼149行)。為了在命令行調用
中使用暫態數據,我們需要首先將其進行base64編碼。
結果如下:
同樣,我們首先查看世界狀態。這類似于我們在場景2中看到的內容。實際的數據僅在peer0.org1.example.com保存,而哈希則在兩個peer節點中都有保存。注意修訂版本的值目前是2,而在場景2中的第一次修訂的值是1。是鏈碼調用促成了對數據的修訂。
類似于場景2,在區塊鏈上的交易記錄中,我們可以看到沒有Write Set。
我們也可以看到沒有調用鏈碼的參數列表。唯一的參數是鏈碼函數名,setPrivateTransient。
具體的調用數據{“key”:”name”, “value”:”charlie”}沒有出現在區塊鏈上。
我們看到私有數據和隱私數據的組合提供了某種程度的數據隱私。
7、場景4演示:不適用私有數據,使用暫態數據輸入
最后我們看一下想象的這個場景的演示。在場景3中,采用暫態數據作為數據,
然后保存在賬本的公開狀態。我們使用setTransient和get訪問鏈碼:
結果如下:
可以看到公開狀態被更新,兩個節點目前有同樣的數據。注意修訂版本更新為2.
我們看到Write Set中的鍵/值對是name和david,base64編碼。
我們沒有看到參數中的輸入數據,只看到調用的方法名setTransient。
原文鏈接:Hyperledger Fabric私有數據與暫態數據 — 匯智網
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的Fabric中的Transient Data与Private Data的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 阿里云正式推出内容平台“云栖号”:全面助
- 下一篇: 数据爆发式增长下,CIO不可不知的“数据