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\'))
此背書政策有兩種方法使它生效:
- 組織Org1MSP中的某個使用者對交易進行驗證
- 組織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 <輸入參數>