链码常用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)
}

链码常用API

上一篇:TokuDB && InnoDB insert压力测试对比


下一篇:数据库层面的一些基本理论