天天看點

Hyperledger fabric入門 day2

Hyperledger fabric入門 day2

Fabric鍊碼規範

/* 
	simplechaincode可以自己進行命名
	chaincode結構體是chaincode的主體結構。chaincode結構體需要實作Fabric提供	的接口"github.com/hyperledger/fabric/protos/peer",其中必須實作以下的	Init和Invoke方法
*/
type simplechiancode struct {
}

/*
系統初始化方法,在部署chaincode的過程中當執行指令的時候會調用該方法
peer chaincode instantiate -o orderer.example.com:7050 -C <通道名> -n <鍊碼名稱> -v <版本号> -c <傳入參數> -P <政策>
*/
func (t *simplechaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
    // 擷取參數
    _, args := stub.GetFunctionAndParameters()
}

/*
主業務邏輯,在執行指令的時候會調用該方法并傳入相關的參數,注意“invoke”之後的參數是需要傳入的參數
peer chaincode invoke -o 192.168.23.212:7050 -C <通道名> -n <鍊碼名稱> -c <傳入參數>
*/
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    
}

func main() {
    err := shim.Start(new(simplechiancode))
    if err != nil {
        fmt.Printf("error: %s", err)
    }
}
           

鍊碼的安裝和調用

在Chaincode安裝之前首先要確定已經成功編譯安裝了Fabric的相關子子產品,并且要保證已經正确啟動Orderer節點和Peer節點

在部署前檢視目前Peer節點已經加入了哪些Channel

export set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/[email protected]/msp

peer channel list
           

部署

export set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/[email protected]/msp

peer chaincode install -n <鍊碼名稱> -v <版本号> -p <鍊碼所在目錄>
           

執行個體化

export set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/[email protected]/msp

peer chaincode instantiate -o orderer.example.com:7050 -C <通道名稱> -n <鍊碼名稱> -v <版本号> -c <輸入參數> -P <背書政策>
           

Chaincode執行個體化成功之後運作Chaincode的Docker容器會被啟動,這個時候執行指令docker ps,會發現一個包含chaincode名字的程序以及被運作。Chaincode如果被成功執行個體化,在目前的Peer節點被重新啟動之後,已經被執行個體化的Chaincode不會自動重新啟動,這個時候如果用戶端對Chaincode發起請求(比如請求query方法),系統會自動運作Chaincode的Docker程序。

還有一點需要注意,如果Chaincode某個方法發生異常導緻Docker容器關閉,若此時用戶端重新對Chaincode發起通路請求,隻要請求的方法沒有異常,系統會自動啟動Chaincode的容器。

關于Chaincode的示例還有一點需要注意,如果在一個channel中已經加入了多個peer節點,并且這些peer節點需要安裝相同的chaincode。這個時候隻需要在第一個部署Chaincode的Peer節點中執行peer chaincode instantiate,其餘的Peer節點部署Chaincode的時候隻需要執行peer chaincode install指令,然後執行invoke或者query指令,系統會自動啟動Chaincode相關的Docker鏡像。當需要在多個Peer節點部署同一個Chaincode的時候instantiate指令隻需要執行一次。

調用

export set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/[email protected]/msp

peer chaincode invoke -o orderer.example.com:7050 -C <通道名> -n <鍊碼名> -c <輸入參數>
           

shim包的核心方法

shim包主要負責和用戶端進行通信。shim提供了一組核心方法和用戶端進行互動,這些方法如下所示

Success

Success方法負責将正确的消息傳回給調用Chaincode的用戶端,Success方法的定義和調用示例如下:

// 方法定義
func Success(payload []byte) peer.Response
// 代碼示例
func (t *simplechaincode) Invoke(stub shim.ChiancodeStubInterface) peer.Response {
    return shim.Success([]byte("success invoke"))
}
           

Error

Error方法負責将錯誤的資訊傳回給調用Chaincode的用戶端,Error方法的定義和調用示例如下:

// 方法定義
func Error(msg string) peer.Response
// 代碼示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    shim.Error(error_str)
}
           

LogLevel

LogLevel方法負責修改Chaincode中運作日志的級别,LogLevel方法的定義個調用示例如下:

// 方法定義
func LogLevel(levelString string) (LoggingLevel, error)
// 代碼示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    loglevel, _ := shim.Loglevel("debug")
    shim.SetLoggingLevel(loglevel)
    return shim.Success([]byte("success invoke"))
}
           

shim包中ChaincodeStubInterface接口的核心方法

ChaincodeStubInterface接口的核心方法大概可以分四個大類:系統管理、存儲管理、交易管理、調用外部chaincode。

ChaincodeStubInterface接口的系統管理相關方法

GetFunctionAndParameters:該方法負責接收調用Chaincode的用戶端傳遞過來的參數,它的定義和調用示例:

// 方法定義
GetFunctionAndParameters() (string, []string)
// 代碼示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    _, args := stub.GetFunctionAndParameters()
    var a_parm = args[0]
    var b_parm = args[1]
    var c_parm = args[2]
}
           

-c \'{"Args":["invoke", "set", "abc", "def"]}\'

根據Invoke子指令的格式定義,Args不是參數而是格式關鍵字,後面的參數數組中第一個是方法名,後面三個是參數

ChaincodeStubInterface接口的存儲管理相關方法

PutState:PutState方法負責把用戶端傳遞過來的資料儲存到Fabric中,它的定義和調用示例如下:

// 方法定義
PutState(key string, value []byte) error
// 代碼示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    stub.PutState(b_parm, []byte("putvalue"))
    return shim.Success([]byte("Success invoke"))
}
           

GetState:GetState方法負責從Fabric中取出資料,然後把這些資料交給Chiancode處理,它的定義和調用示例如下:

// 方法定義
GetState(key string) ([]byte, error)
// 代碼示例
func (t *simplechaincode) Invoke(stub *shim.ChaincodeStubInterface) peer.Response {
    var keyvalue []byte
    var err error
    keyvalue, err = stub.GetState("getkey")
    if (err != nil) {
        return shim.Error("error")
    }
    return shim.Success(keyvalue)
}
           

GetStateByRange:GetStateByRange方法根據key的範圍來查詢相關的資料。它的定義和調用示例如下:

// 方法定義
GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error)
// 代碼示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    startkey := "startkey"
    endkey := "endkey"
    keysIter, err := stub.GetStateByRange(startkey, endkey)
    if err != nil {
        return shim.Error(fmt.Sprintf("GetStateByRange find err: %S", err))
    }
    defer keyIter.Close()
    var keys []string
    for keysIter.HasNext(){
        response, iterErr := keyIter.Next()
        if iterErr != nil {
            return shim.Error(fmt.Sprintf("find an error %s", iterErr))
        }
        keys = append(keys, response.Key)
    }
    for key, value := range keys {
        fmt.Printf("key %d contains %s\n", key, value)
    }
    jsonKeys, err := json.Marshal(keys)
    if err != nil {
        return shim.Error(fmt.Sprintf("find error on Marshaling JSON: %S", err))
    }
    return shim.Success(jsonKeys)
}
           

GetHistoryForKey:GetHistoryForKey方法負責查詢某個鍵的曆史記錄,它的定義和調用示例如下:

// 方法定義
GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error)
// 代碼示例
func (t *simplechiancode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    KeysIter, err := stub.GetHistoryForKey(b_parm)
    if err != nil {
        return shim.Error(fmt.Sprintf("GetHistoryForKey failed. Error accessing state: %s", err))
    }
    defer keysIter.Close()
    var keys []string
    for keysIter.HasNext() {
        response, iterErr := keysIter.Next()
        if iterErr != nil {
            return shim.Error(fmt.Sprintf("GetHsitoryForKey operation failed. Error accessing state: %s", err))
        }
        // 交易編号
        txid := response.TxId
        // 交易的值
        txvalue := response.Value
        // 目前交易的狀态
        txstatus := response.IsDelete
        // 交易發生的時間戳
        txtimestamp := response.Timestamp
        tm := time.Unix(txtimestamp.Seconds, 0)
        datestr := tm.Format("2006-01-02 03:04:05 PM")
        fmt.Printf("Tx info - txid : %s  value : %s if delete: %t  datatime: %s\n", txid, string(txvalue), txstatus, datestr)
        keys = qppend(keys, rxid)
    }
    jsonKeys, err := json.Marshal(keys)
    if err != nil {
        return shim.Error(fmt.Sprintf("Query operation failed. Error marshaling JSON: %s", err))
    }
    return shim.Success(jsonKeys)
}
           

DelState:DelState方法用來删除一個key,它的定義和調用示例如下:

// 方法定義
DelState(key string) error
// 代碼示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    err := DelState("deletekey")
    if err != nil {
        return shim.Error("delete error")
    }
    return shim.Success([]byte("delete success"))
}
           

CreateCompositeKey:CreateCompositeKey方法負責建立一個組合鍵,它的定義和調用示例如下:

// 方法定義
CreateCompositeKey(objectType string, attributes []string) (string, error)
// 代碼示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    parms := []string{"c_1", "d_1", "e_1", "f_1", "g_1", "h_1"}
    ckey, _ := stub.CreateCompositeKey("testkey", parms)
    err := stub.PutState(ckey, []byte(c_parm))
    if err := nil {
        fmt.Println("find error %s", err)
    }
    return shim.Success([]byte(ckey))
}
           

GetStateByPartialCompositeKey:GetStateByPartialCompositeKey方法用來查詢複合鍵的值,它的定義和調用示例如下:

// 方法定義
GetStateByPartialCompositeKey(objectType string, key []string) (StateQueryIteratorInterface, error)
//代碼示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    parms := []string{"c_1"}
    rs, err := stub.GetStateByPartialCompositeKey("testkey", parms)
    if err := nil {
        error_str := fmt.Sprintf("find error: %s", err)
        return shim.Error(error_str)
    }
    defer rs.Close()
    var i int
    var tlist []string
    for i = 0; rs.HasNext(); i++ {
        responseRange, err := rs.Next()
        if err != nil {
            error_str := fmt.Sprintf("find error: %s", err)
            fmt.Printfln(error_str)
            return shim.Error(error_str)
        }
        value1, compositeKeyParts, _ := stub.SplitCompositeKey(responseRange.Key)
        value2 := compositeKeyParts[0]
        value3 := compositeKeyParts[1]
        fmt.Printf("find value v1:%s v2:%s v3:%s\n", value1, value2, value3)
    }
    return shim.Success("success")
}
           

SplitCompositeKey:SplitCompositeKey方法用來拆分複合鍵的屬性,它的定義示例如下:

// 方法定義
SplitCompositeKey(compositeKey string) (string, []string, error)
           

ChaincodeStubInterface接口中交易管理相關的方法

GetTxTimestamp:該方法負責擷取目前用戶端發送的交易時間戳,它的定義和調用示例如下:

// 方法定義
GetTxTimestamp() (*timestamp.Timestamp, error)
// 代碼示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    txtime, err := stub.GetTxTimestamp()
    if err != nil {
        fmt.Printf("Error getting transaction timestamp: %s", err)
        return shim.Error(fmt.Sprintf("Error getting transaction timsstamp:%s", err))
    }
    tm := time.Unix(txtime.Seconds, 0)
    fmt.Printf("Transaction Time: %v\n", tm.Format(2006-01-02 03:04:05 PM))
    return shim.Success([]byte(fmt.Sprint("time is: %s", tm.Format("2006-01-02 15:04:05"))))
}
           

調用其他Chaincode的方法

InvokeChaincode

InvokeChaincode方法的定義和調用示例如下:

// 方法定義
InvokeChaincode(chaincodeName string, args [][]byte, channel string) peer.Response
// 代碼示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    // 設定參數
    parms1 := []string{"query", "a"}
    queryArgs := make([][]byte, len(parms1))
    for i, arg := range parms1 {
        queryArgs[i] = []byte(arg)
    }
    // 調用Chaincode
    response := stub.InvokeChaincode("cc_endfinlshed", queryArgs, "roberttestchannel12")
    if response.Status != shim.OK {
        errStr := fmt.Sprintf("Failed to query chaincode. Got error: %s", reponse.Payload)
        fmt.Printf(errStr)
        return shim.Error(errStr)
    }
    result := string(response.Payload)
    fmt.Printf("invoke chaincode %s", result)
    return shim.Success([]byte("Success InvokeChaincode and Not opter" + result))
}
           
GetTxID

該方法可以擷取用戶端發送的交易的編号,GetTxID方法的定義和調用示例如下:

// 方法定義
GetTxID() string
// 代碼示例
func (t *simplechaincode) Invoke(stub shim.ChaincodeStubInterface) peer.response {
    txid := stub.GetTxID()
    fmt.Println("===== GetTxID ===== %s", txid)
    return shim.Success([]byte(txid))
}
           

如何通過Chaincode進行交易的endorse

在Fabric中有一個非常重要的概念稱為Endorsement,中文名為背書。背書的過程是一筆交易被确認的過程。而背書政策被用來訓示對相關的參與方如何對交易進行确認。當一個節點接收到一個交易請求的時候,會調用VSCC(系統Chaincode,專門負責處理背書相關的操作)與交易的Chaincode共同來驗證交易的合法性。在VSCC和交易的Chaincode共同對交易的确認中,通常會做以下的校驗

  • 所有的背書是否有效(參與的背書的簽名是否有效)。
  • 參與背書的數量是否滿足要求。
  • 所有背書參與方是否滿足要求。

背書政策是指定第二和第三點的一種方式。

背書政策的設定通過Chaincode部署時instantiate指令中-P參數來設定的。

peer chiancode instantiate -o oerderer.example.com:7050 -C <鍊碼名稱> -n <鍊碼名> -v <版本号> -c <輸入參數> -P "AND (\'Org1MSP.member\', \'Org2MSP.member\')"
           

"AND (\'Org1MSP.member\', \'Org2MSP.member\')"

這個參數包說明的是目前Chaincode發起的交易,需要組織編号為Org1MSP群組織編号為Org2MSP的組織中的任何一個使用者共同參與交易的确認并且同意,這樣交易才能生效并被記錄到區塊中。

在cryptogen的配置檔案中有一個節點Orgnizations->ID,該節點的值就是該組織的編号,也是在配置背書政策時需要用到的組織的編号。

OR(\'Org1MSP.member\', AND(\'Org2MSP.member\',\'Org3MSP.member\'))

此背書政策有兩種方法使它生效:

  1. 組織Org1MSP中的某個使用者對交易進行驗證
  2. 組織Org2MSP和Org3MSP中的成員共同對交易進行驗證

有一點需要注意的是,背書規則隻針對Chiancode中寫入資料的操作進行校驗(PutState、DelState),對于查詢類的操作不需要背書

Fabric中的背書是發生在用戶端的,需要進行相關的代碼的編寫才能完成整個背書的操作。

Chaincode的調試方式

Chaincode在Docker容器之外的運作

注冊需要調試的chaincode

如果需要在Docker容器之外運作Chaincode,首先需要把需要運作的Chaincode注冊到Fabric中,注冊完成後就可以借用已注冊成功的chaincode的名字和版本号在Docker容器之外運作Chaincode。有一點需要強調,在注冊的時候隻需要向Fabric送出Chaincode的名字和版本号,實際注冊的Chaincode的代碼并不重要

設定Peer節點的運作模式

可以通過修改配置檔案或者添加環境變量的方式修改Peer節點的啟動模式。配置檔案可以通過修改core.yaml中的chaincode節點的mode子節點的值,将mode的值修改為net,修改後如下:

chaincode:
mode: net
           

或者通過環境變量修改Peer運作模式

export set CORE_CHAINCODE_MODE=net
           
注冊Chaincode
export set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/[email protected]/msp

peer chaincode install -n <鍊碼名稱> -v <版本号> -p <鍊碼所在目錄>
peer chaincode instantiate -o orderer.example.com:7050 -C <通道名稱> -n <鍊碼名稱> -v <版本号> -c <輸入參數> -P <背書政策>
           

注冊成功之後一定要記住install指令執行時參數-n和-v的值,這一點非常重要,後面的配置中需要用到這兩個參數的值。

将Peer節點設定為調試模式

要想在Docker外面運作Chaincode,首先需要調整Peer節點的運作模式。可以通過修改配置檔案或者添加環境變量的方式修改Peer節點的啟動模式。配置檔案可以通過修改配置檔案core.yaml中的chaincode節點的mode子節點的值,将mode的值修改為dev,修改後如下所示:

chiancode:
mode: dev
           

mode子節點有兩個屬性dev和net,net表示chaincode運作在Docker容器中,dev表示Chaincode運作在容器之外,通常用于開發模式。

設定環境變量的方式讓Chaincode可以運作在Docker容器之外:

export set CORE_CHAINCODE_MODE=dev
           

如果設定的dev模式,那麼目前Peer子產品不能執行peer chaincode instantiate指令

編譯chaincode

進傳入連結碼所在目錄進行

go build

運作Chaincode

要運作Chaincode需要設定相應的環境變量和系統參數

export set CORE_PEER_ADDRESS=192.168.23.212:7051
export set CORE_CHAINCODE_ID_NAME=mycc:1.1
export set CORE_CHIANCODE_LOGGING_LEVEL=debug
export set CORE_CHAINCODE_LOGGING_SHIM=debug
./simplechaincode -peer.address=192.168.23.212:7052
           

上述的環境變量和指令選項作用如下:

  • CORE_PEER_ADDRESS:Peer節點的IP位址
  • CORE_CHAINCODE_ID_NAME:chaincode的名字和版本号,冒号前面的是chaincode的名字,後面是chaincode的版本。這兩個值必須和第一步install指令中-n和-v的參數完全一緻,否則Chaincode無法執行
  • CORE_CHAINCODE_LOGGING_LEVEL:chaincode系統的日志級别
  • CORE_CHAINCODE_LOGGING_SHIM:shim的日志級别
  • -peer.address=:Peer節點中的Chiancode的監聽端口,該端口在core.yaml檔案中的peer節點下面的chaincodeListenAddress子節點
調動Chaincode

上面啟動的Chaincode通過下面的指令調用:

export set FABRIC_CFG_PATH=/opt/hyperledger/peer
export set CORE_PEER_LOCALMSPID=Org1MSP
export set CORE_PEER_ADDRESS=peer0.org1.example.com:7051
export set CORE_PEER_MSPCONFIGPATH=/opt/hyperledger/.../peerOrgnizations/org1.example.com/users/[email protected]/msp

peer chaincode invoke -o 192.168.23.212:7050 -C <通道名> -n <鍊碼名> -c <輸入參數>