Hyperledger fabric 鏈碼篇GO(四)
阿新 • • 發佈:2020-12-25
# Hyperledger fabric 鏈碼篇GO(四)
fabric中的鏈碼也就是我們區塊鏈所認知的智慧合約,fabric中可由nodejs,java,go編寫,本篇只針對GO語言編寫鏈碼。將詳細介紹鏈碼編寫所必須的函式以及相關功能函式。
## 1、常識
- 鏈碼的包名指定
```go
// xxx.go
package main
```
- 必須要引入的包
```go
import(
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
//pb為別名的意思
)
```
- 鏈碼的書寫要求
```go
//自定義結構體,類,基於這個類實現介面函式
type Test struct{
//空著即可
}
```
- 鏈碼API查詢
```html
//go語言的鏈碼包shim
https://godoc.org/github.com/hyperledger/fabric-chaincode-go/shim
```
**注意事項**
> 要注意put寫入的資料狀態不會立刻得到獲取,因為put只是執行鏈碼的模擬交易,並不會真正將狀態儲存在賬本中,必須經過orderer達成共識之後,將資料狀態儲存在區塊中,然後儲存在各peer節點的賬本中
## 2、函式及鏈碼基本結構
- success
```go
// Success ... status:200
//鏈碼執行後返回成功資訊
func Success(payload []byte) pb.Response {
return pb.Response{
Status: OK,
Payload: payload,
}
}
```
- error
```go
// Error ...status:500
//鏈碼執行後返回錯誤資訊
func Error(msg string) pb.Response {
return pb.Response{
Status: ERROR,
Message: msg,
}
}
```
### 2.1、基本結構
下面是編寫一個鏈碼的基本框架,由空結構體,Init函式和Invoke函式,主函式組成
```go
package main
//引入必要的包
import (
"fmt"
"strconv"
"github.com/hyperledger/fabric/core/chaincode/shim"
//別名pb
pb "github.com/hyperledger/fabric/protos/peer"
)
// 宣告一個結構體,必備,為空
type SimpleChaincode struct {
}
//新增Init函式
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
//實現鏈碼初始化或升級時的處理邏輯
//編寫時可靈活使用stub中的API
}
//新增Invoke函式
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
//實現鏈碼執行中被呼叫或查詢時的處理邏輯
//編寫時可靈活使用stub中的API
}
//主函式,需要呼叫shim.Start()方法啟動鏈碼
func main() {
err := shim.Start(new(SimpleChaincode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}
```
### 2.2、Init函式
> Init方法是系統初始化方法,當執行命令`peer chaincode instantiate`例項化chaincode時會呼叫該方法,同時命令中`-c`選項後面內容為會作為引數傳入`Init`方法中。
```shell
#shell 命令
$ peer chaincode instantiate -o orderer.example.com:7050 -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a","100", "b", "100"]}'
```
Args中共有5個引數,第一個為固定值,後面4個為引數,如果傳入資料太多,可以採用json等資料格式
```go
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
_ ,args := stub.GetFunctionAndParameters()
return shim.Success ([]byte("success init!!!"))
}
```
### 2.3、Invoke函式
> Invoke方法的主要作用為寫入資料,比如發起交易等,在執行命令 `peer chaincode invoke` 時系統會呼叫該方法,並把`-c`後的引數傳入invoke方法中。
引數原理和init類似
## 3、賬本操作API
## [ChaincodeStubInterface](https://godoc.org/github.com/hyperledger/fabric-chaincode-go/shim#ChaincodeStubInterface)
### 3.1、引數解析API
> 含義:呼叫鏈碼時需要給被呼叫的目標函式/方法傳遞引數,與引數解析相關的API 提供了獲取這些引數(包括被呼叫的目標函式/方法名稱)的方法
#### GetFunctionAndParameters
- 獲取客戶端傳入的引數`GetFunctionAndParameters()`
```go
//原始碼
func (s *ChaincodeStub) GetFunctionAndParameters() (function string, params []string) {
allargs := s.GetStringArgs()
function = ""
params = []string{}
if len(allargs) >= 1 {
//返回時第一個為函式名,後面為其他引數
function = allargs[0]
params = allargs[1:]
}
return
}
//執行命令
peer chaincode invoke -o orderer.example.com:7050 -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["invoke","a", "b", "10"]}'
//示例程式碼
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
_ ,args := stub.GetFunctionAndParameters()
//獲取執行命令中的引數a,b,10
var a_parm = args[0]
var b_parm = args[1]
var c_parm = args[2]
return shim.Success ([]byte("success invoke!!!"))
}
```
### 3.2、賬本資料狀態操作API
> 含義:該型別API提供了對賬本資料狀態進行操作的方法,包括對狀態資料的查詢以及事務處理
#### GetState
- GetState(key string) ([]byte, error) :根據指定的key查詢相應的資料狀態
```go
func (s *ChaincodeStub) GetState(key string) ([]byte, error) {
// Access public data by setting the collection to empty string
collection := ""
return s.handler.handleGetState(collection, key, s.ChannelID, s.TxID)
}
//示例程式碼
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
keyvalue, err := stub.GetState("user1")
return shim.Success(keyvalue)
}
```
#### PutState
- PutState(key string, value []byte) error:根據指定的key,將相應的value儲存在分類賬本中
```go
func (s *ChaincodeStub) PutState(key string, value []byte) error {
if key == "" {
return errors.New("key must not be an empty string")
}
// Access public data by setting the collection to empty string
collection := ""
return s.handler.handlePutState(collection, key, value, s.ChannelID, s.TxID)
}
//示例程式碼
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
stub.PutState("user1",[]byte("putvalue"))
return shim.Success([]byte("sucess invoke putstate"))
}
```
#### DelState
- DelState(key string) error:根據指定的key將對應的資料狀態刪除
```go
func (s *ChaincodeStub) DelState(key string) error {
// Access public data by setting the collection to empty string
collection := ""
return s.handler.handleDelState(collection, key, s.ChannelID, s.TxID)
}
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
err :=stub.DelState("user1")
return shim.Success("delete success")
}
```
#### GetStateByRange
- GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error):根據指定的開始key和結束key,查詢範圍內的所有資料狀態,**注意,結束key對應的資料狀態不包含在返回的結果集中**
```go
func (s *ChaincodeStub) GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) {
if startKey == "" {
startKey = emptyKeySubstitute
}
if err := validateSimpleKeys(startKey, endKey); err != nil {
return nil, err
}
collection := ""
// ignore QueryResponseMetadata as it is not applicable for a range query without pagination
iterator, _, err := s.handleGetStateByRange(collection, startKey, endKey, nil)
return iterator, err
}
//示例程式碼
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
startKey = "startKey"
endKey = "endKey"
//根據範圍查詢,得到StateQueryIteratorInterface迭代器介面
keyIter , err := stub.getStateByRange(startKey,endKey)
//關閉迭代器介面
defer keyIter.close()
var keys []string
for keyIter.HasNext(){ //如果有下一個節點
//得到下一個鍵值對
response, iterErr := keysIter.Next()
if iterErr != nil{
return shim.Error(fmt.Sprintf("find an error %s",iterErr))
}
keys = append(keys,response.Key) //儲存鍵值到陣列中
}
//遍歷key陣列
for key, value := range keys{
fmt.Printf("key %d contains %s",key ,value)
}
//編碼keys陣列為json格式
jsonKeys ,err = json.Marshal(keys)
if err != nil{
return shim.Error(fmt.Sprintf("data marshal json error: %s",err))
}
//將編碼之後的json字串傳遞給客戶端
return shim.Success(jsonKeys)
}
```
#### GetHistoryForKey
- GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error):根據指定的key查詢所有的歷史記錄資訊
```go
func (s *ChaincodeStub) GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error) {
response, err := s.handler.handleGetHistoryForKey(key, s.ChannelID, s.TxID)
if err != nil {
return nil, err
}
return &HistoryQueryIterator{CommonIterator: &CommonIterator{s.handler, s.ChannelID, s.TxID, response, 0}}, nil
}
//示例程式碼
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
keyIter , err := stub.GetHistoryForKey("user1")
if err != nil{
return shim.Error(fmt.Sprintf("GetHistoryForKey error: %s",err))
}
//關閉迭代器介面
defer keyIter.close()
var keys []string
for keyIter.HasNext(){ //如果有下一個節點
//得到下一個鍵值對
response, iterErr := keysIter.Next()
if iterErr != nil{
return shim.Error(fmt.Sprintf("find an error %s",iterErr))
}
//交易編號
txid := response.TxId
//交易的值
txvalue = response.Value
//當前交易的狀態
txstatus = response.IsDelete
//交易發生的時間戳
txtimestamp = response.Timestamp
keys = append(keys,txid) //儲存鍵值到陣列中
}
//編碼keys陣列為json格式
jsonKeys ,err = json.Marshal(keys)
if err != nil{
return shim.Error(fmt.Sprintf("data marshal json error: %s",err))
}
//將編碼之後的json字串傳遞給客戶端
return shim.Success(jsonKeys)
}
```
#### CreateCompositeKey
- CreateCompositeKey(objectType string, attributes []string) (string, error):建立一個複合鍵
```go
func (s *ChaincodeStub) CreateCompositeKey(objectType string, attributes []string) (string, error) {
return CreateCompositeKey(objectType, attributes)
}
func CreateCompositeKey(objectType string, attributes []string) (string, error) {
if err := validateCompositeKeyAttribute(objectType); err != nil {
return "", err
}
ck := compositeKeyNamespace + objectType + string(minUnicodeRuneValue)
for _, att := range attributes {
if err := validateCompositeKeyAttribute(att); err != nil {
return "", err
}
ck += att + string(minUnicodeRuneValue)
}
return ck, nil
}
```
#### SplitCompositeKey
- SplitCompositeKey(compositeKey string) (string, []string, error):對指定的複合鍵進行分割
```go
func (s *ChaincodeStub) SplitCompositeKey(compositeKey string) (string, []string, error) {
return splitCompositeKey(compositeKey)
}
func splitCompositeKey(compositeKey string) (string, []string, error) {
componentIndex := 1
components := []string{}
for i := 1; i < len(compositeKey); i++ {
if compositeKey[i] == minUnicodeRuneValue {
components = append(components, compositeKey[componentIndex:i])
componentIndex = i + 1
}
}
return components[0], components[1:], nil
}
```
#### GetQueryResult
- GetQueryResult(query string) (StateQueryIteratorInterface, error) :對狀態資料庫進行富查詢,目前支援富查詢的只有CouchDB
```go
func (s *ChaincodeStub) GetQueryResult(query string) (StateQueryIteratorInterface, error) {
// Access public data by setting the collection to empty string
collection := ""
// ignore QueryResponseMetadata as it is not applicable for a rich query without pagination
iterator, _, err := s.handleGetQueryResult(collection, query, nil)
return iterator, err
}
```
#### InvokeChaincode
- InvokeChaincode(chaincodeName string, args [][]byte, channel string):呼叫其他鏈碼
```go
//鏈碼名,傳遞引數,通道名
func (s *ChaincodeStub) InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response {
// Internally we handle chaincode name as a composite name
if channel != "" {
chaincodeName = chaincodeName + "/" + channel
}
return s.handler.handleInvokeChaincode(chaincodeName, args, s.ChannelID, s.TxID)
}
//示例程式碼
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
//設定引數,a向b轉賬11
trans := [][]byte{[]byte("invoke"),[]byte("a"),[]byte("b"),[]byte("11")}
//呼叫chaincode
response := stub.InvokeChaincode("mycc",trans,"mychannel")
//判斷是否操作成功
if response.Status != shim.OK {
errStr := fmt.Sprintf("Invoke failed ,error : %s ", response.Payload)
return shim.Error(errStr)
}
return shim.Success([]byte"轉賬成功")
}
```
### 3.3、交易資訊API
> 含義:獲取提交的交易資訊的相關API
#### GetTxID
- GetTxID() string :返回交易提案中指定的交易ID
```go
func (s *ChaincodeStub) GetTxID() string {
return s.TxID
}
```
#### GetChannelID
- GetChannelID() string:返回交易提案中指定通道的ID
```go
func (s *ChaincodeStub) GetChannelID() string {
return s.ChannelID
}
```
#### GetTxTimestamp
- GetTxTimestamp() (*timestamp.Timestamp, error):返回交易建立的時間戳,這個時間戳時peer接收到交易的具體時間
```go
func (s *ChaincodeStub) GetTxTimestamp() (*timestamp.Timestamp, error) {
hdr := &common.Header{}
if err := proto.Unmarshal(s.proposal.Header, hdr); err != nil {
return nil, fmt.Errorf("error unmarshaling Header: %s", err)
}
chdr := &common.ChannelHeader{}
if err := proto.Unmarshal(hdr.ChannelHeader, chdr); err != nil {
return nil, fmt.Errorf("error unmarshaling ChannelHeader: %s", err)
}
return chdr.GetTimestamp(), nil
}
```
#### GetBinding
- GetBinding() ([]byte, error):返回交易的繫結資訊,如一些臨時性資訊,以避免重複性攻擊
```go
func (s *ChaincodeStub) GetBinding() ([]byte, error) {
return s.binding, nil
}
```
#### GetSignedProposal
- GetSignedProposal() (*pb.SignedProposal, error):返回與交易提案相關的簽名身份資訊
```go
func (s *ChaincodeStub) GetSignedProposal() (*pb.SignedProposal, error) {
return s.signedProposal, nil
}
```
GetCreator
- GetCreator() ([]byte, error) :返回該交易提交者的身份資訊
```go
func (s *ChaincodeStub) GetCreator() ([]byte, error) {
return s.creator, nil
}
```
#### GetTransient
- GetTransient() (map[string][]byte, error):返回交易中不會被寫至賬本中的一些臨時性資訊
```go
func (s *ChaincodeStub) GetTransient() (map[string][]byte, error) {
return s.transient, nil
}
```
### 3.4、事件處理API
> 含義:與事件處理相關的API
#### SetEvent
- SetEvent(name string, payload []byte) error:設定事件,包括事件名稱和內容
```go
// SetEvent documentation can be found in interfaces.go
func (s *ChaincodeStub) SetEvent(name string, payload []byte) error {
if name == "" {
return errors.New("event name can not be empty string")
}
s.chaincodeEvent = &pb.ChaincodeEvent{EventName: name, Payload: payload}
return nil
}
```
### 3.5、對PrivateData操作的API
> 含義:hyperledger fabric在1.2.0版本中新增的對私有資料操作的相關API
```go
//根據指定key,從指定的私有資料集中查詢對應的私有資料
func (s *ChaincodeStub) GetPrivateData(collection string, key string) ([]byte, error)
//根據給定的部分組合鍵的集合,查詢給定的私有狀態
func (s *ChaincodeStub) GetPrivateDataByPartialCompositeKey(collection, objectType string, attributes []string) (StateQueryIteratorInterface, error)
//根據指定的開始key和結束key查詢範圍內的私有資料(不包括結束key)
func (s *ChaincodeStub) GetPrivateDataByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error)
//根據指定的查詢字串執行富查詢
func (s *ChaincodeStub) GetPrivateDataQueryResult(collection, query string) (StateQueryIteratorInterface, error)
//將指定的key與value儲存到私有資料集中
func (s *ChaincodeStub) PutPrivateData(collection string, key string, value []byte) error
//根據key刪除相應資料
func (s *ChaincodeStub) DelPrivateData(collection string, key string) error
```
## 4、背書策略
> 背書的過程就是一筆交易被確認的過程,而背書策略就是用來指示相關的參與方如何對交易進行確認。背書策略的設定是通過部署鏈碼例項化時`instantiate`命令中的`-P`引數來設定的
```shell
peer chaincode instantiate -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 -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a", "100", "b","200"]}' -P "AND ('Org1MSP.member','Org2MSP.member')"
```
> 此命令表示需要組織1和組織2中任意一個使用者共同來參與交易的確認並且同意,這樣的交易才能生效並被記錄到去區塊鏈中
```shell
#也可以指定背書節點,使用peerAddresses來指定背書節點
peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C $CHANNEL_NAME -n mycc --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 '{"Args":["invoke","a","b","10"]}'
```
- **背書規則示例1**
```shell
#需要組織中任意使用者共同參與背書
"AND ('Org1MSP.member', 'Org2MSP.member')"
```
- **背書規則示例2**
```shell
#只要組織中任意一個使用者驗證即可
"OR ('Org1MSP.member', 'Org2MSP.member')"
```
- **背書規則示例3**
```shell
#兩種方法讓交易生效
# 1、組織1中有成員驗證成功
# 2、組織2和組織3中有成員共同參與驗證成功
"OR ('Org1MSP.member', AND ('Org2MSP.member', 'Org3MSP.member'))"
```
**注意:背書規則只針對寫入資料的操作進行校驗,對於查詢類的背書不操作**
## 5、應用示例
設計了一個鏈碼,實現了資產管理,轉賬交易的功能,主要實現了以下幾個函式
> - init函式
>
> 初始化鏈碼,設定賬戶名稱及資產額度
>
> - invoke函式
>
> 實現函式的識別,並轉到不同的函式功能
>
> - del函式
>
> 刪除賬戶
>
> - find函式
>
> 檢視賬戶額度
>
> - set函式
>
> 根據賬戶名稱設定要存入的金額
>
> - get函式
>
> 根據賬戶名稱設定要取出的金額
```go
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
"strconv"
)
type AssetChaincode struct {
//empty
}
//Init function
func (t *AssetChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
_, args := stub.GetFunctionAndParameters()
if len(args) != 4 {
return shim.Error("Must four initialization parameters, representing name and money ,respectively")
}
var a = args[0]
var acount = args[1]
var b = args[2]
var bcount = args[3]
if len(a) < 2 {
return shim.Error("the length of name must not less than 2")
}
if len(b) < 2 {
return shim.Error("the length of name must not less than 2")
}
_, err := strconv.Atoi(acount)
if err != nil {
return shim.Error(a + " account is false")
}
_, err = strconv.Atoi(bcount)
if err != nil {
return shim.Error(b + " account is false")
}
err = stub.PutState(a, []byte(acount))
if err != nil {
return shim.Error(a + " Occuring error when saving the data")
}
err = stub.PutState(b, []byte(bcount))
if err != nil {
return shim.Error(b + " Occuring error when saving the data")
}
return shim.Success([]byte("Init success"))
}
//Invoke function
func (t *AssetChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
fun, args := stub.GetFunctionAndParameters()
if fun == "set" {
return set(stub, args)
} else if fun == "get" {
return get(stub, args)
} else if fun == "payment" {
return payment(stub, args)
} else if fun == "del" {
return del(stub, args)
} else if fun == "find" {
return find(stub, args)
}
return shim.Error("not have this ability")
}
//find function
func find(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 1 {
return shim.Error("the number of parameters must one")
}
result, err := stub.GetState(args[0])
if err != nil {
return shim.Error("occor error when reading the data")
}
if result == nil {
return shim.Error("no data by the key")
}
return shim.Success(result)
}
//payment function
func payment(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 3 {
return shim.Error("payment format error")
}
var source, target string
source = args[0]
target = args[1]
asset, err := strconv.Atoi(args[2])
if err != nil {
return shim.Error("transfer amount atoi failed")
}
sourceS, err := stub.GetState(source)
if err != nil {
return shim.Error("query source asset failed")
}
targetS, err := stub.GetState(target)
if err != nil {
return shim.Error("query target asset failed")
}
sourceasset, err := strconv.Atoi(string(sourceS))
if err != nil {
return shim.Error("source asset transform int failed")
}
targetasset, err := strconv.Atoi(string(targetS))
if err != nil {
return shim.Error("target asset transform int failed")
}
if sourceasset < asset {
return shim.Error("source asset don't have enough balance")
}
sourceasset -= asset
targetasset += asset
err = stub.PutState(source, []byte(strconv.Itoa(sourceasset)))
if err != nil {
return shim.Error("after save payment soure asset failed")
}
err = stub.PutState(target, []byte(strconv.Itoa(targetasset)))
if err != nil {
return shim.Error("after save payment target asset failed")
}
return shim.Success([]byte("payment success"))
}
//delete function
func del(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args)!=1{
return shim.Error("elete account format error")
}
err := stub.DelState(args[0])
if err!= nil{
return shim.Error("delete account error")
}
return shim.Success([]byte("delete account success"+args[0]))
}
//set function
func set(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 2 {
return shim.Error("set account asset format error")
}
result, err := stub.GetState(args[0])
if err != nil {
return shim.Error("occor error when reading the data")
}
if result == nil {
return shim.Error("no data by the key")
}
asset,err := strconv.Atoi(string(result))
if err!= nil{
return shim.Error("transfrom account balance error")
}
val,err := strconv.Atoi(string(args[1]))
if err!= nil{
return shim.Error("transfrom set balance error")
}
val += asset
err = stub.PutState(args[0],[]byte(strconv.Itoa(val)))
if err != nil {
return shim.Error("save balance error")
}
return shim.Success([]byte("set asset success!"))
}
//get function
func get(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 2 {
return shim.Error("t account asset format error")
}
result, err := stub.GetState(args[0])
if err != nil {
return shim.Error("occor error when reading the data")
}
if result == nil {
return shim.Error("no data by the key")
}
asset,err := strconv.Atoi(string(result))
if err!= nil{
return shim.Error("transfrom account balance error")
}
val,err := strconv.Atoi(string(args[1]))
if err!= nil{
return shim.Error("transfrom set balance error")
}