HyperLedger Fabric Chaincode(鏈碼)介紹和使用
title: HyperLedger Fabric Chaincode(鏈碼)介紹和使用
tags: 區塊鏈,HyperLedger Fabric,Chaincode
鏈碼是什麼?
鏈碼也稱為智慧合約,實質上是控制區塊鏈網路中的不同實體或相關方如何相互互動或交易的業務邏輯。
鏈碼是獨立可執行的應用程式,執行在基於Docker的安全容器中,在啟動的時候和背書節點建立gRPC連線, ,
可以抽象為以下幾條:
- chaincode是Fabric介面的實現程式碼
- chaincode需要部署在Fabric區塊鏈⽹絡結點上
- 與Fabric區塊鏈互動的唯⼀渠道
- chaincode是⽣成Transaction的唯⼀來源 Ledger <- Blocks <- TransacGons
- chaincode是智慧合約在Fabric上的實現⽅式
Hyperledger 支援使用 Golang Java 、Node.js 等語言編寫鏈碼,鏈碼最終在一個 Docker 容器內執行,本文將採用go編寫鏈碼。
相關概念
-
Blockchain(區塊鏈):由一系列區塊組成
- 每個區塊包含許多交易。每個區塊包含 World State 的雜湊值,並被連結到前一個區塊。
- 區塊鏈採用僅附加模式 (append-only)
-
Transaction(交易) : 一次chaincode函式的執行
- 目前有五類,其中一個是 Undefined,其餘均與 chaincode的執行有關。
- Transaction儲存 chaincode執行的相關資訊,比如 chaincodelD、函式名稱、引數等,並不包含操作的資料。 -
Word State(全域性狀態) :Fabric區塊鏈系統中所有變數的值的集合
- Transaction實際操作的是資料,交易商品的資訊。每個 chaincode都有自己的資料。
- Fabric使用Rocksdb儲存資料,一個 key-value資料庫。
- Fabric將每一對 key-va|ue叫做一個 state,而所有的 chaincode的 state的合集就是Word State。
- 可用於儲存序列化 JSON 結構
運⾏原理
Fabric結點有兩種運⾏模式:
- ⼀般模式(預設模式)
- Chaincode運⾏在docker容器中
- 開發除錯過程⾮常繁雜
部署 -> 除錯 -> 修改 -> 建立docker映象 -> 部署 -> …
⼀般模式,Chaincode是執行在docker容器中,如果需要修改、除錯會很麻煩,每次都需要部署docker映象,開發效率較低。
- 開發模式: --peer-chaincodedev
- Chaincode運⾏在本地
- 開發除錯相對容易
部署 -> 除錯 -> 修改 -> 部署 -> …
執行過程
-
Peer 收到來自鏈碼容器的
ChaincodeMessage_REGISTER
訊息,將其註冊到本地的一個 Handler 結構,返回ChaincodeMessage_REGISTERED
訊息發給鏈碼容器。之後更新狀態為established
,併發送ChaincodeMessage_READY
訊息給鏈碼側,更新狀態為ready
。 -
鏈碼側收到
ChaincodeMessage_REGISTERED
訊息後,不進行任何操作,註冊成功。更新狀態為established
。收到ChaincodeMessage_READY
訊息後更新狀態為ready
。 -
Peer 側發出
ChaincodeMessage_INIT
訊息給鏈碼容器,準備觸發鏈碼側初始化操作。 -
鏈碼容器收到
ChaincodeMessage_INIT
訊息,通過Handler.handleInit()
方法進行進行初始化。初始化成功後,返回ChaincodeMessage_COMPLETED
訊息給 Peer。此時,鏈碼容器進入可被呼叫(Invoke
)狀態。 -
鏈碼被呼叫時,Peer 發出
ChaincodeMessage_TRANSACTION
訊息給鏈碼。 -
鏈碼收到
ChaincodeMessage_TRANSACTION
訊息,會呼叫Invoke()
方法,根據 Invoke 方法中使用者實現的邏輯,訊息給 Peer 側。Peer 側收到這些訊息,進行相應的處理,並回復ChaincodeMessage_RESPONSE
訊息。最後,鏈碼側會回覆呼叫完成的訊息ChaincodeMessage_COMPLETE
給 Peer 側。 -
在上述過程中,Peer 和鏈碼側還會定期的傳送
ChaincodeMessage_KEEPALIVE
訊息給對方,以確保彼此線上。
使用鏈碼
示例程式碼
在學習鏈碼編寫之前,我們來看一段go語言實現的一個簡單例子
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
)
type SampleChaincode struct {
}
func (t *SampleChaincode) Init(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
return nil, nil
}
func (t *SampleChaincode) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
return nil, nil
}
func (t *SampleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
return nil, nil
}
func main() {
err := shim.Start(new(SampleChaincode))
if err != nil {
fmt.Println("Could not start SampleChaincode")
} else {
fmt.Println("SampleChaincode successfully started")
}
}
程式碼分析
我們首先為我們的chaincode引入必要的依賴。我們將在此引入chaincode shim package和peer protobuf package。
import (
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
必須要存在這樣的一個結構體,用來呼叫只能合約函式,結構體的名字沒有要求,符合go語言的命名規則即可。
type SampleChaincode struct {
}
- Init 方法
在鏈程式碼首次部署到區塊鏈網路時呼叫,將由部署自己的鏈程式碼例項的每個對等節點執行。此方法可用於任何與初始化、引導或設定相關的任務, ⼀般情況下僅被調⽤⼀次.
注意:值得留意的是chaincode升級同樣會呼叫該函式。當我們編寫的chaincode會升級現有chaincode時,需要確保適當修正Init函式
-
Query 方法
只要在區塊鏈狀態上執行任何讀取/獲取/查詢操作,就會呼叫 Query 方法。根據鏈程式碼的複雜性,此方法含有你的讀取/獲取/查詢邏輯,或者它可以外包給可從 Query 方法內呼叫的不同方法。
Query 方法不會更改底層鏈程式碼的狀態,因此它不會在交易上下文內執行。如果嘗試在 Query 方法內修改區塊鏈的狀態,將出現一個錯誤顯示缺少交易上下文。另外,因為此方法僅用於讀取區塊鏈的狀態,所以對它的呼叫不會記錄在區塊鏈上。 -
Invoke 方法
只要修改區塊鏈的狀態,就會呼叫 Invoke 方法。簡言之,所有建立、更新和刪除操作都應封裝在 Invoke 方法內。因為此方法將修改區塊鏈的狀態,所以區塊鏈 Fabric 程式碼會自動建立一個交易上下文,以便此方法在其中執行。對此方法的所有呼叫都會在區塊鏈上記錄為交易,這些交易最終被寫入區塊中。 -
使用shim.Start 啟動鏈碼
在main函式中shim.Start(new(SampleChaincode)) 啟動了鏈碼並向對等節點註冊它
shim常用方法
這裡只給出一些常用的方法說明,其他的方法和具體使用可以到官方文件檢視。
https://godoc.org/github.com/hyperledger/fabric/core/chaincode/shim
方法的使用說明,請移步 :https://www.cnblogs.com/studyzy/p/7360733.html
1.獲得呼叫的引數
Fabric提供了不同的引數傳遞函式
- GetArgs() [][]byte 以byte陣列的陣列的形式獲得傳入的引數列表
- GetStringArgs() []string 以字串陣列的形式獲得傳入的引數列表
- GetFunctionAndParameters() (string, []string) 將字串陣列的引數分為兩部分,陣列第一個字是Function,剩下的都是Parameter
- GetArgsSlice() ([]byte, error) 以byte切片的形式獲得引數列表
2.增刪改查State DB
對於ChainCode來說,核心的操作就是對State Database的增刪改查,對此Fabric介面提供了3個對State DB的操作方法
- PutState(key string, value []byte) error 增加和修改資料
- DelState(key string) error 刪除資料
- GetState(key string) ([]byte, error) 查詢資料
注意 :不能在一個ChainCode函式中PutState後又馬上GetState,這個時候GetState是沒有最新值的,因為在這時Transaction並沒有完成,還沒有提交到StateDB裡面
3.複合鍵的處理
- CreateCompositeKey(objectType string, attributes []string) (string, error) 生成複合鍵
- SplitCompositeKey(compositeKey string) (string, []string, error) 拆分複合鍵
- GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error) 部分複合鍵的查詢
- 獲取當前使用者
- GetCreator() ([]byte, error)
- 高階查詢
前面提到的GetState只是最基本的根據Key查詢值的操作,但是對於很多時候,我們需要查詢返回的是一個集合,比如我要知道某個區間的Key對於所有物件,或者我們需要對Value物件內部的屬性進行查詢。
- GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) Key區間查詢
- GetQueryResult(query string) (StateQueryIteratorInterface, error) 富查詢
- GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error) 歷史資料查詢
- GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error) 部分複合鍵查詢
6.呼叫另外的鏈上程式碼
這個比較好理解,就是在我們的鏈上程式碼中呼叫別人已經部署好的鏈上程式碼。
- InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response
7.獲得提案物件Proposal屬性
- GetSignedProposal() (*pb.SignedProposal, error) 獲得簽名的提案
- GetTransient() (map[string][]byte, error) 獲得Transient物件
- GetTxTimestamp() (*timestamp.Timestamp, error) 獲得交易時間戳
- GetBinding() ([]byte, error) 獲得Binding物件
8.事件設定
- SetEvent(name string, payload []byte) error