鏈碼常用API


鏈碼基礎教程(關於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)
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM