鏈碼基礎教程(關於SDK管理接口)
在簡單鏈碼開發中,我們基本只采用以下四種SDK管理接口:
// 提取調用鏈碼交易中的參數,其中第一個參數作為被調用的函數名稱,剩下的參數作為函數的執行參數
// 用於Init與Invoke方法中,負責接收調用者傳過來的參數
GetFunctionAndParameters() (function string, params []string)
// 更新/獲取/刪除 狀態
PutState(key string, value []byte) error
GetState(key string) ([]byte, error)
DelState(key string) error
在本文中,我們將進一步對所有常用接口進行探討研究。
- 注1:以下一些例子寫在Invoke接口中,實際應用是都應該分配單獨的的函數
- 注2:接口位置在
core/chaincode/shim/interfaces.go
GetFunctionAndParameters(系統管理方法)
GetFunctionAndParameters
返回一個對象,其中包含要調用的鏈碼方法名,以及要傳入目標方法的參數對象。
// 定義
GetFunctionAndParameters() (string, []string)
實例:
func (c *AssertsExchangeCC) Init(stub shim.ChaincodeStubInterface) pb.Response {
//第一個參數作為被調用的函數名稱,剩下的參數作為函數的執行參數
funcName, args := stub.GetFunctionAndParameters()
fmt.Println(funcName, args)
return shim.Success(nil)
}
執行及結果:
/*
# -o 要通信的orderer
# -C 通道的名字
# -n 鏈碼的名字
# -l 鏈碼的語言
# -v 鏈碼的版本
# -c 鏈碼的參數
# -P 背書策略
*/
peer chaincode instantiate -o orderer.simple-network.com:7050 -C testchannel -n r_test_cc6 -v 1.0 -c '{"Args":["init", "a", "100", "b", "200"]}' -P "OR ('Org1MSP.member', 'Org2MSP.member')"
// 輸出
init [a 100 b 200]
PutState(存儲/更新狀態)
更新狀態庫中指定的狀態變量鍵。如果變量已經存在,那么覆蓋已有的值。其中:
- key:要更新的狀態鍵,字符串
- value:狀態變量的新值,字節數組或字符串
// 定義
PutState(key string, value []byte) error
此時還未將數據打包成區塊,而是以鍵值對的形式存放在狀態數據庫中。上一篇隨筆,資產交易平台實例中,我們以用戶的ID
為鍵,用戶名,用戶ID,擁有資產
為值。下面我們寫一個簡單學生信息實例幫助理解:
其中:鍵定義為 xiashe;值為學生姓名與年齡,其實鍵可以直接定義為值中參數(構造新參數,如上一篇所寫,此處不再贅述)
type Student struct {
Name string
Age uint8
}
func (c *AssertsExchangeCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
stu := Student{
"xiashe",
10086,
}
// 序列化
bytes, err := json.Marshal(stu)
if err != nil {
return shim.Error(err.Error())
}
// 上傳/更新
stub.PutState("zhangsan", bytes)
return shim.Success(nil)
}
GetState(獲取狀態)
獲取指定 狀態變量鍵 的當前值,即獲取狀態變量鍵的字符串,返回當前值的字節數組。
// 定義
GetState(key string) ([]byte, error)
下面我們寫一個簡單的應用實例幫助理解:
type Student struct {
Name string
Age uint8
}
// 獲取上一例傳入狀態數據庫中的xiahse 鍵內的值
func (c *AssertsExchangeCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
// 獲取
bytes, err := stub.GetState("xiashe")
if err != nil{
return shim.Error(err.Error())
}
// 分配內存
stu := new(Student)
// 反序列化
err = json.Unmarshal(bytes, stu)
if err != nil{
return shim.Error(err.Error())
}
// 打印出 xiashe 鍵 所對應值的數據
fmt.Println(stu)
return shim.Success(nil)
}
DelState(刪除狀態)
從狀態庫中刪除指定的狀態變量鍵。
// 定義
DelState(key string) error
實例:
func (c *AssertsExchangeCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
// 刪除 xiashe 鍵 所對應值的數據
err := stub.DelState("xiashe")
if err != nil{
fmt.Println(err)
}
return shim.Success(nil)
}
GetStateByRange(獲取狀態區間)
查詢指定 狀態鍵 指定范圍的數據(獲取狀態區間):
getStateByRange()
方法返回一個賬本狀態鍵的迭代器,可用來 遍歷在起始鍵和結束鍵之間的所有狀態鍵,返回結果按詞典順序排列。
調用返回的StateQueryIterator
迭代器對象的close()
方法關閉迭代器。根據key的范圍來查詢數據。
注:若起始鍵和結束鍵之間的數量大於節點配置文件core.yaml中定義的totalQueryLimit,那么返回結果數量將受限於totalQueryLimit的約定。
// 定義
GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error)
- startKey:起始鍵
- endKey:結束鍵
- 返回一個
Promise
對象,其解析值為StateQueryIterator
迭代器
// 這個在這里面其實沒用到
type Student struct {
Name string
Age uint8
}
// 更新狀態數據庫
func set(stub shim.ChaincodeStubInterface, args []string) pb.Response {
// 一般情況下是先序列化然后存入狀態數據庫,這里簡單點,直接存進去
stub.PutState("1", []byte("cat"))
stub.PutState("2", []byte("boy"))
stub.PutState("3", []byte("girl"))
stub.PutState("4", []byte("child"))
stub.PutState("5", []byte("odog"))
return shim.Success(nil)
}
// 獲取指定范圍狀態數據
func get(stub shim.ChaincodeStubInterface, args []string) pb.Response {
// 獲得 鍵2 和 鍵5 之間數據
// 一般來說這個是通過args傳入的,這里為了簡單,直接定義
keysIter, err := stub.GetStateByRange("2", "5")
if err != nil{
return shim.Error(err.Error())
}
// 初始化
rsp := make(map[string]string)
for keysIter.HasNext(){
response, interErr := keysIter.Next()
if interErr != nil{
return shim.Error(interErr.Error())
}
// 賦值
rsp[response.Key] = string(response.Value)
// 打印
fmt.Println(response.Key, string(response.Value))
}
// 將獲取的數據序列化
jsonRsp, err := json.Marshal(rsp)
if err != nil{
return shim.Error(err.Error())
}
return shim.Success(jsonRsp)
}
func (c *AssertsExchangeCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
// GetFunctionAndParameters 提取調用鏈碼交易中的參數,其中第一個參數作為被調用的函數名稱,剩下的參數作為函數的執行參數
funcName, args := stub.GetFunctionAndParameters()
// 根據funcName選擇具體要執行的內容
switch funcName {
case "set": // 寫入狀態
return set(stub, args)
case "get":// 獲取指定區間狀態 2-5
return get(stub, args)
default:
return shim.Error(fmt.Sprintf("unsupported function: %s", funcName))
}
}
GetHistoryForKey(查詢狀態歷史)
getHistoryForKey()
方法返回指定狀態鍵的值歷史修改記錄。返回的記錄包括交易的編號、修改的值、當前key的有沒有被刪除,交易發生的時間戳。時間戳取自交易提議頭。
注:該方法需要通過peer節點配置中的如下選項開啟:
core.ledger.history.enableHistoryDatabase = true
// 定義
GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error)
- key:狀態鍵
- 返回一個
Promise
對象,其解析值為HistoryQueryIterator
對象。
type Student struct {
Name string
Age uint8
}
// 更新狀態
func set(stub shim.ChaincodeStubInterface, args []string) pb.Response {
// 初始化
name := args[0]
// 這里同上,直接省略序列化
err := stub.PutState("xiashe", []byte(name))
if err != nil{
return shim.Error(err.Error())
}
return shim.Success(nil)
}
// 查詢狀態鍵修改記錄
func history(stub shim.ChaincodeStubInterface, args []string) pb.Response {
// 查詢鍵為 xiashe 的值修改記錄
keyInter, err := stub.GetHistoryForKey("name")
if err != nil{
return shim.Error(err.Error())
}
for keyInter.HasNext(){
response, interErr := keyInter.Next()
if interErr != nil{
return shim.Error(interErr.Error())
}
txid := response.TxId // 交易編號
txvalue := response.Value // 修改的值
txstatus := response.IsDelete // 當前值有沒有被刪除
txtimestamp := response.Timestamp // 交易發生的時間戳
tm := time.Unix(txtimestamp.Seconds, 0)
timeString := tm.Format("2006-01-02 03:04:05 PM") // 轉換為標准時間格式
fmt.Println(txid, string(txvalue), txstatus, timeString)
}
return shim.Success(nil)
}
func (c *AssertsExchangeCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
// GetFunctionAndParameters 提取調用鏈碼交易中的參數,其中第一個參數作為被調用的函數名稱,剩下的參數作為函數的執行參數
funcName, args := stub.GetFunctionAndParameters()
// 根據funcName選擇具體要執行的內容
switch funcName {
case "set": // 修改狀態值
return set(stub, args)
case "history":// 查詢修改記錄
return history(stub, args)
default:
return shim.Error(fmt.Sprintf("unsupported function: %s", funcName))
}
}
執行命令與結果如下:
// 修改
peer chaincode invoke ... '{"Args":["set", "pigone"]}' // 將狀態值由xiashe改為pigone
// 查詢組合鍵的修改歷史記錄a
// 交易的編號 + 修改的值 + 當前key的有沒有被刪除 + 交易發生的時間戳
de172a29622d41350493d5421d6759c56a38273033a31e3d70e091856e62dc3a pigone false 2020-05-27 06:08:21 PM
CreateCompositeKey(創建組合鍵)
GetStatePyPartialCompositeKey(通過組合鍵取值)
Hyperledger Fabric使用一個簡單的key/value模型(levelDB)來保存鏈碼狀態。在有些場景下, 可能需要跟蹤多個屬性,也可能需要使多種屬性可搜索。組合鍵可用來滿足這些需求。
CreateCompositeKey
通過組合對象類別和給定的屬性創建一個組合鍵。對象類別及屬性都必須是 有效的utf8
字符串,並且不能包含U+0000 (空字節) 和 U+10FFFF (最大未分配代碼點)。 結果組合鍵可以用作pushState()
調用中的參數鍵。
類似於關系數據庫中的組合鍵,你可以認為這里的可搜索屬性就是組合鍵的組成列, 屬性的值稱為鍵的一部分,因此可以使用像getStateByRange()
和getStateByPartialCompositeKey()
這樣的方法進行搜索。
// 定義
// 創建組合鍵
CreateCompositeKey(objectType string, attributes []string) (string, error)
// 查詢組合鍵的值
GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error)
- objectType:組合鍵前綴,string
- attributes:要拼接到組合鍵的各屬性值,string數組
- key:狀態鍵
type Student struct {
Name string
Age uint8
Score uint8
}
// 創建組合鍵,CreateCompositeKey
func set(stub shim.ChaincodeStubInterface, args []string) pb.Response {
// 初始化
stus := []Student{
{"tom", 18, 99},
{"tom", 20, 98},
{"tom", 18, 100},
}
// 遍歷
for i, _ := range(stus) {
stu := stus[i]
// 創建組合鍵:名字,年齡,分數
key, err := stub.CreateCompositeKey(stu.Name, []string{strconv.Itoa(stu.Age), strconv.Itoa(stu.Score)})
if err != nil {
fmt.Println(err)
return shim.Error(err.Error())
}
// 將組合鍵序列化
bytes, err := json.Marshal(stu)
if err != nil {
fmt.Println(err)
return shim.Error(err.Error())
}
// 更新狀態
stub.PutState(key, bytes)
}
return shim.Success(nil)
}
func get(stub shim.ChaincodeStubInterface, args []string) pb.Response {
// 查詢組合鍵內容
rs, err := stub.GetStateByPartialCompositeKey("tom", []string{})
// rs, err := stub.GetStateByPartialCompositeKey("tom", []string{"20"})
if err != nil{
fmt.Println(err)
return shim.Error(err.Error())
}
defer rs.Close()
// 遍歷
for rs.HasNext(){
responseRange, err := rs.Next()
if err != nil{
fmt.Println(err)
}
// 分配內存
stu := new(Student)
// 反序列化
err = json.Unmarshal(responseRange.Value, stu)
if err != nil{
fmt.Println(err)
}
// 打印組合鍵、值
fmt.Println(responseRange.Key, stu)
}
return shim.Success(nil)
}
func (c *AssertsExchangeCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
// GetFunctionAndParameters 提取調用鏈碼交易中的參數,其中第一個參數作為被調用的函數名稱,剩下的參數作為函數的執行參數
funcName, args := stub.GetFunctionAndParameters()
// 根據funcName選擇具體要執行的內容
switch funcName {
case "set": // 修改狀態值
return set(stub, args)
case "get":// 查詢修改記錄get
return get(stub, args)
default:
return shim.Error(fmt.Sprintf("unsupported function: %s", funcName))
}
}
調用set函數,,再調用get查詢,結果:
tom1898 &{tom 18 98}
tom2099 &{tom 20 99}
tom18100 &{tom 18 100}
在get
函數中,由於stub.GetStateByPartialCompositeKey(“lisi”, []string{})
,只指定了一個主鍵,所以把叫lisi
的學生都查詢出來了。
如果修改為rs, err := stub.GetStateByPartialCompositeKey("lisi", []string{"18"})
,則執行get函數后輸出結果為:
lisi1898 &{tom 18 98}
lisi1899 &{tom 18 99}
SplitCompositeKey(拆分組合鍵屬性)
// 方法定義
SplitCompositeKey(compositeKey string) (string, []string, error)
- 組合鍵,由
CreateCompositeKey
創建
實例:
func (c *AssertsExchangeCC) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
// 創建組合鍵
key, _ := stub.CreateCompositeKey("laowang", []string{"20", "100"})
// 拆分組合鍵
key1, keyPart, _:= stub.SplitCompositeKey(key)
// 打印拆分的組合鍵
fmt.Println(key1, keyPart)
return shim.Success(nil)
}
結果:
sili20100
sili [20 100]
GetTxID(獲取交易編號)
該方法可以獲取客戶端發送的交易的編號,交易編號可以在通道范圍內唯一標識一個交易
// 定義
GetTxID() string
實例:
func get(stub shim.ChaincodeStubInterface, args []string) pb.Response {
// 獲取交易編號
id := stub.GetTxID()
// 打印交易編號
fmt.Println(id)
return shim.Success(nil)
}
結果:
42a925816afb2c2067f014a29ad8609ae40a405d5997f79c4c930e4d79c2eb5f
GetTxTimestamp(獲取交易時間)
// 定義
GetTxTimestamp() (*timestamp.Timestamp, error)
getTxTimestamp()
方法返回交易創建時的時間戳,值取自交易的ChannelHeader
部分, 因此它表示的是客戶端的時間戳,並且在所有的背書服務節點上有相同的值。
func get(stub shim.ChaincodeStubInterface, args []string) pb.Response {
// 獲取時間戳
timestamp, _ := stub.GetTxTimestamp()
// 格式化
tm:= time.Unix(timestamp.Seconds, 0)
ts := tm.Format("2006-01-02 03:04:05 PM")
// 打印
fmt.Println(ts)
return shim.Success(nil)
}
結果:
2020-05-29 05:05:21 PM
GetCreator(獲取交易提案的簽名者證書)
// 定義
GetCreator() ([]byte, error)
InvokeChaincode(調用其他鏈碼)
invokeChaincode()
方法在本地使用相同的交易上下文調用指定鏈碼 的invoke()
方法,在鏈碼中調用鏈碼不會產生新的交易消息。
如果被調用的鏈碼在同一個通道,那么它只是簡單地將被調用鏈碼的讀寫集添加到被調用交易中。
如果被調用的鏈碼處於不同的通道,那么只會返回響應結果,在被調用鏈碼中的PutState調用不會影響賬本的狀態。
// 定義
InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response
- chaincodeName:要調用的鏈碼名稱,字符串
- args:調用參數列表,字節數組
- channel:要調用的鏈碼所在通道名稱,字符串
實例:
func set(stub shim.ChaincodeStubInterface, args []string)pb.Response {
// 參數
parms := []string{"get"}
querArgs := make([][]byte, len(parms))
// 將參數轉換為字節數組類型
for i, arg := range parms{
querArgs[i] = []byte(arg)
}
// 調用在channelone通道中鏈碼asset的get函數
response := stub.InvokeChaincode("asset", querArgs, "channelone")
if response.Status != shim.OK{
err := fmt.Sprintln("fail to query chaincode, Got error :%s", response.Payload)
shim.Error(err)
}
return shim.Success(nil)
}
//asset鏈碼中的get函數實現
func get(stub shim.ChaincodeStubInterface, args []string) pb.Response {
fmt.Println("Thank you for calling me")
return shim.Success(nil)
}