Fabric实战(10)链码(chaincode)开发-shim包API

本文章所有操作基于的操作系统版本是:ubuntu16.04 64位
本文章基于的Fabric网络环境是《Fabric实战(2)运行一个简单的fabric网络(容器外)》

1 golang版本的chaincode的代码

1.1 chaincode源代码结构

下面看一最简单的chaincode代码以及相关的解释:

//包名
//一个chaincode通常是一个golang源码文件,这个包名必须是main
package main

//导入包
//chaincode需要引入一些Fabric提供的系统包,这些系统包提供了chaincode和Fabirc进行通信的接口。
import (
	"fmt"
	"strconv"

	"github.com/hyperledger/fabric/core/chaincode/shim"
	pb "github.com/hyperledger/fabric/protos/peer"
)

//定义chaincode主结构体
//每个chaincode都需要定义个结构体,结构体的名字可以是任意符合golang命名规范的字符串。chaincode的住结构体必须实现Chaincode接口
// type Chaincode interface {
// Init(stub ChaincodeStubInterface) pb.Response
// Invoke(stub ChaincodeStubInterface) pb.Response
// }
type SimpleChaincode struct {
}

//Init方法
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
	return shim.Success(nil)
}

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
	function, args := stub.GetFunctionAndParameters()
	if function == "invoke" {
		return t.invoke(stub, args)
	}

	return shim.Error("Invalid invoke function name. Expecting \"invoke\"")
}

func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	return shim.Success(nil)
}

//main函数
//调用shim包的Start方法,启动chaincode,如果启动成功,这个函数会一直阻塞在这个地方,不会退出。
func main() {
	err := shim.Start(new(SimpleChaincode))
	if err != nil {
		fmt.Printf("Error starting Simple chaincode: %s", err)
	}
}

1.1.1 Init方法

Init方法是chaincode的初始化方法,当执行peer chaincode instantiate实例化chaincode的时候会调用该方法,同时-c 参数后面内容会作为参数传入Init方法中,以下面的实例化命令为例:

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')"

上面的命令给chaincode传入4个参数"a", “100”, “b” ,“200”。注意命令中Args后面一共有五个参数,其中第一个参数init是固定值,后面才是参数。在Init函数中可以通过下面的方法获取参数:

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
    //获取客户端传入的参数,args是一个字符串切片,存储传入的字符串参数
    _, args := stub.GetFunctionAndParameters()
	return shim.Success(nil)
}

1.1.2 Invoke方法

Invoke方法的主要作用是写入数据。在执行命令peer chaincode invoke的时候会调用这个方法,同时会把命令中-c后面的参数传入Invoke方法中。以下面的invoke命令为例:

peer chaincode invoke -o orderer.simple-network.com:7050 -C testchannel -n r_test_cc6 -v 1.0    -c '{"Args":["invoke", "a", "b", "1"]}'

上面的命令调用chaincode的Invoke方法并传入三个参数, 注意Args后面数组中的第一个值"invoke"是默认的固定参数。

1.2 shim包的方法

shim包常用方法

Success                     #向客户端返回正确消息
Error                       #向客户端返回错误消息
Start                       #启动chaincode
LogLevel                    #将字符串类型转化成LoggingLevel类型
SetLoggingLevel             #设置链码shim包运行日志等级
IsEnabledForLogLevel        #检查合约的是否使能制定的等级

1.2.1 Success方法

Success方法负责将正确的消息返回给调用chaincode的客户端。

//定义
func Success(payload []byte) pb.Response 

//示例
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    return shim.Success([]byte("sucess invoke"))
}

1.2.2 Error方法

Error方法负责将错误的信息返回给调用Chaincode的客户端。

// 定义
func Error(msg string) pb.Response 

//示例
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    return shim.Error([]byte("fail invoke"))
}

1.2.3 SetLoggingLevel方法

设置合约的shim包运行日志等级

// 定义
func SetLoggingLevel(level LoggingLevel) 

//示例
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {

    //将"debug"字符串转换成LoggingLevel类型
    logleve, _ := shim.LogLevel("DEBUG")
    
    //设置日志等级为debug
	shim.SetLoggingLevel(logleve)
	
    return shim.Success([]byte("sucess invoke"))
}

日志等级如下:

"CRITICAL"      0
"ERROR"         1
"WARNING"       2
"NOTICE"        3
"INFO"          4
"DEBUG"         5

1.2.4 IsEnabledForLogLevel方法

检查合约是否使能了日志等级

//定义
func IsEnabledForLogLevel(logLevel string) bool 

//示例
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
    enabled := shim.IsEnabledForLogLevel("DEBUG")
	if enabled{
		fmt.Println("Contract enabled debug level")
	}
	 return shim.Success([]byte("sucess invoke"))
}

注意:只要IsEnabledForLogLevel的参数小于当前的等级,都是返回true。比如当前的等级是DEBUG,IsEnabledForLogLevel传入的参数是ERROR,返回值也是true

1.3 shim包ChaincodeStubInterface接口

Shim包中有一个接口ChaincodeStubInterface,在Invoke方法和Query方法方法中该接口作为参数传入方法中。ChaincodeStubInterface接口提供了一组方法,通过该组方法可以非常方便的操作账本数据。ChaincodeStubInterface接口的核心方法大概可以分为四大类:

  • 系统管理
  • 存储管理
  • 交易管理
  • 调用外部chaincode

方法名列表:

GetFunctionAndParameters        #获取调用者传入的参数
GetArgs                         #获取调用者传入参数,返回二维字节切片
GetStringArgs                   #获取调用者传入参数,返回一维字符串切片
GetArgsSlice                    #获取调用者传入参数,返回字节切片
PutState                        #存储数据到账本
GetState                        #从账本中获取指定的数据 
DelState                        #删除账本中的数据
GetStateByRange                 #查询指定key指定范围的数据
CreateCompositeKey              #创建复合键 
GetStatePyPartialCompositeKey   #通过复合键取值
SplitCompositeKey               #拆分复合键
GetHistoryForKey                #获取制定key的历史记录
GetTxID                         #获取交易的编号
GetTxTimestamp                  #获取交易的时间
GetCreator                      #获取交易的创建者
InvokeChaincode                 #调用其他的chaincode

1.3.1 系统管理接口

1.3.1.1 GetFunctionAndParameters

该方法负责接收调用者传过来的参数。

//方法定义
GetFunctionAndParameters() (string, []string)

//调用
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')"

//chaincode 
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {

	fn, args := stub.GetFunctionAndParameters()
	fmt.Println(fn, args)
	
	return shim.Success(nil)
}

//输出
init [a 100 b 200]

1.3.1.2 GetArgs

获取调用者传入参数,返回二维字节切片

//方法定义
GetArgs() [][]byte

//调用
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')"

//chaincode 
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {

    args :=  stub.GetArgs()
	fmt.Println(args)
	
	return shim.Success(nil)
}

//输出
[[105 110 105 116] [97] [49 48 48] [98] [50 48 48]]

1.3.1.3 GetStringArgs

获取调用者传入参数,返回一维字符串切片

//方法定义
GetStringArgs() []string

//调用
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')"

//chaincode 
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {

    args  := stub.GetStringArgs()
	fmt.Println(args)
	
	return shim.Success(nil)
}

//输出
[init a 100 b 200]

1.3.1.4 GetArgsSlice

获取调用者传入参数,返回字节切片

//方法定义
GetArgsSlice() ([]byte, error)

//调用
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')"

//chaincode 
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {

    args, err := stub.GetArgsSlice()
	if err != nil{
		fmt.Println(err)
	}
	fmt.Println(args )
	
	return shim.Success(nil)
}

//输出
[105 110 105 116 97 49 48 48 98 50 48 48]

1.3.2 存储管理接口

1.3.2.1 PutState

该方法负责把数据保存到fabric账本中。

//方法定义
PutState(key string, value []byte) error

//chaincode代码示例 
type Student struct {
	Name  string
	Age   uint8
	Score uint8
}

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {

	stu := Student{
		"zhangsan",
		25,
		99,
	}
	bytes, err := json.Marshal(stu)
	if err != nil {
		return shim.Error(err.Error())
	}
	stub.PutState("zhangsan", bytes)

	return shim.Success(nil)
}

1.3.2.2 GetState

该方法负责从fabric账本中读取数据

//方法定义
GetState(key string) ([]byte, error)

//chaincode代码示例 
type Student struct {
	Name  string
	Age   uint8
	Score uint8
}

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {


	bytes, err := stub.GetState("zhangsan")
	if err != nil{
		return  shim.Error(err.Error())
	}

	stu := new(Student)
	err = json.Unmarshal(bytes, stu)
	if err != nil{
		return  shim.Error(err.Error())
	}
	fmt.Println(stu)
	return shim.Success(nil)
}

1.3.2.3 DelState

DelState用来删除账本中的key,如果对一个不存在的的key-value进行删除不会返回错误

//方法定义
DelState(key string) error
//chaincode代码示例 
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {

	err := stub.DelState("zhangsan")
	if err != nil{
		fmt.Println(err)
	}
	return shim.Success(nil)
}

1.3.2.4 GetStateByRange

根据key的范围来查询数据

//方法定义
//包含startKey,不包含endKey
//如果startKey和endKey为空,则无限范围查询
GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error)

//chaincode代码示例 
func (t *SimpleChaincode) set(stub shim.ChaincodeStubInterface, args []string)pb.Response {
	stub.PutState("1", []byte("zhangsan"))
	stub.PutState("2", []byte("lisi"))
	stub.PutState("3", []byte("wangwu"))
	stub.PutState("4", []byte("zhaoliu"))
	stub.PutState("5", []byte("sunqi"))
	return shim.Success(nil)
}

func (t *SimpleChaincode) get(stub shim.ChaincodeStubInterface, args []string) pb.Response {

	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))
	}
	//将结果以json字符串返回
	jsonRsp, err := json.Marshal(rsp)
	if err != nil{
		return shim.Error(err.Error())
	}
	return shim.Success(jsonRsp)
}

func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
	function, args := stub.GetFunctionAndParameters()
	if function == "set" {
		return t.set(stub, args)
	} else if function == "get" {
		return t.get(stub, args)
	}
	return shim.Success(nil)
}

链码日志输出:

2 lisi
3 wangwu
4 zhaoliu

1.3.2.5 GetHistoryForKey

查询某个键的历史修改记录。返回的记录包括交易的编号、修改的值、当前key的有没有被删除,交易发生的时间戳,GetHistoryForKey需要peer的配置参数core.ledger.history.enableHistoryDatabase为true。
注意:

1.查询交易所在区块中其他更新操作,本次查询无法查询到。

//方法定义
GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error)
//chaincode代码示例 
func (t *SimpleChaincode) set(stub shim.ChaincodeStubInterface, args []string)pb.Response {
	name := args[0]

	err := stub.PutState("userName", []byte(name))
	if err != nil{
		return shim.Error(err.Error())
	}
	return shim.Success(nil)
}

func (t *SimpleChaincode) history(stub shim.ChaincodeStubInterface, args []string)pb.Response {
	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)
}

链码日志输出:
先调用链码的set方法修改三次,然后在调用history方法获取记录

peer chaincode invoke -o orderer.simple-network.com:7050 -C testchannel -n r_test_cc6 -v 1.0    -c '{"Args":["set", "zhangsan"]}'

peer chaincode invoke -o orderer.simple-network.com:7050 -C testchannel -n r_test_cc6 -v 1.0    -c '{"Args":["set", "lisi"]}'

peer chaincode invoke -o orderer.simple-network.com:7050 -C testchannel -n r_test_cc6 -v 1.0    -c '{"Args":["set", "wangwu"]}'

bdfd3d29622d41350493d5421d6759c56a38273033a31e3d70e091856e62dc69 zhangsan false 2019-03-14 03:28:02 PM
692dba2bf1c7c24ede17248310df58726a974210afe2097b3ff10ce8a3381ab5 lisi false 2019-03-14 03:28:02 PM
5864c768fad78049969891a315838ed1336fa86ed138f2ca87aee4e09900b4bc wangwu false 2019-03-14 03:28:02 PM

1.3.2.4 CreateCompositeKey

创建组合间,用户查询

//方法定义
CreateCompositeKey(objectType string, attributes []string) (string, error)

//chaincode代码示例
func (t *SimpleChaincode) set(stub shim.ChaincodeStubInterface, args []string)pb.Response {
	stus := []Student{
		{"lisi", 20, 99},
		{"lisi",20,98},
		{"lisi",21,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 (t *SimpleChaincode) get(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	rs, err := stub.GetStateByPartialCompositeKey("lisi", []string{})
	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)
}

链码日志输出:

lisi2098 &{lisi 20 98}
lisi2099 &{lisi 20 99}
lisi21100 &{lisi 21 100}

由于stub.GetStateByPartialCompositeKey(“lisi”, []string{}),只指定了一个主键,所以把叫lisi的学生都查询出来了。

如果按修改代码如下:

	rs, err := stub.GetStateByPartialCompositeKey("lisi", []string{"20"})

链码日志输出如下:

lisi2098 &{lisi 20 98}
lisi2099 &{lisi 20 99}

由于21岁的lisi不满足组合键要求,所以没有查询出来

1.3.2.5 GetStatePyPartialCompositeKey

通过组合键进行查询, 相当于模糊查询

//方法定义
GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error)


//chaincode代码示例 
调用示例参考CreateCompositeKey方法的示例

1.3.2.6 SplitCompositeKey

分割组合键, 将创建的组合键分割开来

//方法定义
SplitCompositeKey(compositeKey string) (string, []string, error)
//chaincode代码示例 
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
	key, _ := stub.CreateCompositeKey("sili", []string{"20", "100"})
	key1, keyPart, _:= stub.SplitCompositeKey(key)
	fmt.Println(key1, keyPart)
	return shim.Success(nil)
}

链码日志输出:

sili20100
sili [20 100]

1.3.3 交易管理接口

1.3.3.1 GetTxID

获取当前调用交易的ID号

//方法定义
GetTxID() string

//chaincode代码示例 
func (t *SimpleChaincode) get(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	id := stub.GetTxID()
	fmt.Println(id)
	return shim.Success(nil)
}

链码日志输出:

66a925816afb2c2067f014a29ad8609ae40a405d5997f79c4c930e4d79c2eb5f

1.3.3.2 GetCreator

获得提案的签名者证书


//方法定义
GetTxID() string

//chaincode代码示例 
func (t *SimpleChaincode) get(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	id := stub.GetTxID()
	fmt.Println(id)
	return shim.Success(nil)
}

链码日志输出:

Org1MSP�-----BEGIN -----
MIICLjCCAdWgAwIBAgIQJcxoHNiOAWmIXH7VZ/6fDDAKBggqhkjOPQQDAjCBgTEL
MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBG
cmFuY2lzY28xIDAeBgNVBAoTF29yZzEuc2ltcGxlLW5ldHdvcmsuY29tMSMwIQYD
VQQDExpjYS5vcmcxLnNpbXBsZS1uZXR3b3JrLmNvbTAeFw0xOTAzMTAxNTA5MzBa
Fw0yOTAzMDcxNTA5MzBaMGIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9y
bmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMSYwJAYDVQQDDB1BZG1pbkBvcmcx
LnNpbXBsZS1uZXR3b3JrLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJWM
1LNOMnDGnbpe0XEx7HNFyBZgOZk5E+PDtHer/ZbbFvCIvoFoafzw7qIwlOxXTj99
bOG3kHQX2OPp/+7/nEajTTBLMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAA
MCsGA1UdIwQkMCKAIPXBTNkkfW7yIcRyZvip+yXmvzwPBujCNja1BMv3f+BmMAoG
CCqGSM49BAMCA0cAMEQCIG1Q+qvhCSv/GZtINP51WZLUpUDtABiY8RsmetPqK1Rh
AiBsYoFWSQeVRspiTuEz1dhW+ke2iq05P3k+gFzN1yiUUw==
-----END -----


1.3.3.3 GetTxTimestamp

//方法定义
GetTxTimestamp() (*timestamp.Timestamp, error)


//chaincode代码示例 
func (t *SimpleChaincode) 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)
}

链码日志输出:

2019-03-14 06:12:49 PM

1.3.4 调用外部chaincode

1.3.4.1 InvokeChaincode

在chaincode中可以调用其他的chaincode。

//方法定义
InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response

//chaincode代码示例 
func (t *SimpleChaincode) 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)
	}
	
	response := stub.InvokeChaincode("r_test_cc8", querArgs, "testchannel")
	if response.Status != shim.OK{
		err := fmt.Sprintln("fail to query chaincode, Got error :%s", response.Payload)
		shim.Error(err)

	}
	return shim.Success(nil)
}

//r_test_cc8链码中的get函数实现
func (t *SimpleChaincode) get(stub shim.ChaincodeStubInterface, args []string) pb.Response {
	fmt.Println("Thank you for calling me")
	return shim.Success(nil)
}

r_test_cc8链码日志输出:

Thank you for calling me

你可能感兴趣的:(Fabric)