Hyperledger Fabric链码开发实践日志
1.链码结构总述
这里,链码的开发用的是Go语言,为此需要先简单学习一下Go语言,这是一门轻量级的语言,有意思的是它自带通道,可以并发,就很适合大型分布式系统的开发。
启动链码必须调用shim包中的Start函数,这个函数的参数是一个Chaincode接口类型,Chaincode这个接口类型中有两个方法分别是Init和Invoke,这是链码开发中极为重要的两个方法:
- Init:在链码实例化或者升级的时候被调用,完成数据初始化;
- Invoke:在更新或查询提案事务中分类帐本数据状态的时候被调用。
在实际开发中,需要定义一个结构体,重写Init和Invoke两个方法完成相关功能。下面具体看看一个链码必要的结构:
package main //所写的包的名称
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)//引入必要的包
type HelloChaincode struct{}//定义一个结构体
func main() {
err := shim.Start(new(HelloChaincode))
if err != nil {
fmt.Printf("链码启动失败: %v", err)
}
}//主函数,调用shim.Start方发启动链码
func (t *HelloChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response{
}
func (t *HelloChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response{ fun, args := stub.GetFunctionAndParameters()
}
2.熟悉链码相关API
主要是shim包提供的API,分为5类:
- 参数解析API:用来获取参数的
- 账本数据状态操作API:对账本数据查询、更新等
- 交易信息获取API:获取提交的交易信息
- 事件处理API:与事件处理相关
- 对私有数据操作的API:专门对私有数据操作
API的数量还是比较多的,大多是获取相关信息的,有时间可以仔细看看,初级阶段主要使用的几个:
- GetFunctionAndParameters()(function string,params []string)返回被调用函数的名称以及参数列表
- GetStringArgs()[]string 直接返回参数列表
- GetState(key string)([]byte,error) 根据指定的key值查询数据状态
- PutState(key string,value []byte)error 根据指定的key,将对应的value保存到帐本中
3.链码实现Hello World
3.1 链码开发
先写个hello world练练手哈。
- 进入chaincode目录下创建一个名为hello的文件夹,然后创建并编辑链码文件:
sudo mkdir hello && cd hello
sudo vim hello.go
- 导入链码依赖包:
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
- 编写主函数:
func main() {
err := shim.Start(new(HelloChaincode))
if err != nil {
fmt.Printf("链码启动失败: %v", err)
}
}
- 自定义结构体:
type HelloChaincode struct{}
- 重写Init方法,它的功能就是初始化数据状态,一个简单的逻辑步骤如下:
- 获取参数并判断参数是否合法;
- 调用PutState函数将状态写入帐本;
- 是否有写入错误;
- 最后调用Success函数返回成功状态。
func (t *HelloChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response{
fmt.Println("开始实例化链码")
_, args := stub.GetFunctionAndParameters()
if len(args) != 2 {
return shim.Error("指定了错误的参数个数")
}
fmt.Println("保存数据")
err := stub.PutState(args[0],[]byte(args[1]))
if err !=nil{
return shim.Error("保存数据时发生错误")
}
fmt.Println("实例化成功")
return shim.Success(nil)
}
可以注意一下其中的GetFunctionAndParameters()函数,这里参数形式是:“Args”:[“init”,“Hello”,“Wzh”],函数获取了被调用的函数"init"和传入的参数"Hello",“Wzh”,因此写成_,args的形式,也可以换成GetStringArgs()函数直接获取后面的参数。
- 重写Invoke方法,它的简单逻辑是:
- 获取参数并判断参数是否合法;
- 利用key值获取状态;
- 完成被调用函数的功能;
- 返回数据状态或者成功状态。
func (t *HelloChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response{ fun, args := stub.GetFunctionAndParameters()
if fun == "query"{
return query(stub,args)
}
return shim.Error("非法操作,指定功能不能实现")
}
func query(stub shim.ChaincodeStubInterface,args []string) peer.Response{
if len(args) !=1{
return shim.Error("指定的参数错误,必须且只能指定相应的Key")
}
result,err :=stub.GetState(args[0])
if err !=nil {
return shim.Error("根据指定的" +args[0] +"查询数据时发生错误")
}
if result ==nil {
return shim.Error("根据指定的" +args[0] +"没有查询到相应的数据")
}
return shim.Success(result)
}
这一段代码非常简单明了,不多赘述了。
3.2 链码测试
又又又又到了链码测试的环节,具体过程在前面的日志中,下面就只附上代码了:
cd chaincode-docker-devmode
sudo docker-compose -f docker-compose-simple.yaml up -d
打开终端2窗口:
sudo docker exec -it chaincode bash
cd hello
go build
CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=hellocc:0 ./hello
打开终端3窗口:
sudo docker exec -it cli bash、
peer chaincode install -p chaincodedev/chaincode/hello -n hellocc -v 0
peer chaincode instantiate -n hellocc -v 0 -c '{"Args":["init","Hello","Wzh"]}' -C myc
peer chaincode query -n hellocc -c '{"Args":["query","Hello"]}' -C myc
最后查询应该能得到Wzh(即Hello键赋值的内容)