链码基础教程(关于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)
}