作为区块链应用开发人员,智能合约的开发是必不可少的,在Hyperledger Fabric中,智能合约又称为链玛(chiancode)。
链码的概念
链码又称之为链上代码,一般由开发人员使用Golang(java或nodejs)编写,提供分布式账本的状态处理逻辑。链码被部署在Hyperledger Fabric的网络节点上,能够独立运行在安全且受保护的Docker容器中,以gRPC协议与相应的Peer节点进行通信,操作分布式账本中的数据。在Fabric中,链码分为系统链码和用户链码。
系统链码
系统链码负责Fabric节点自身的处理逻辑,包括系统配置、背书、校验等工作。目前,系统链码只支持Golang语言。Peer节点启动的同时,也会注册和部署相应系统链码。这里,我们开发人员不需要关心太多有关系统链码的编写。
用户链码
与系统链码不同,用户链码是由开发人员根据不同场景需求以及成员制定的相关规则,可以使用Golang、java或nodejs来编写。
用户链码是Fabric应用程序中占据着重要地位。它向下可以对账本数据进行操作,向上可以给企业级应用程序提供api接口。所以,一个没有链码的企业级应用程序,不能称为区块链应用程序。
链码的生命周期管理
链码在编写完成之后,还需要经过一系列的操作才能应用在Fabric网络中,这一系列操作由以下5个命令来进行:
- install: 将已编写的链码安装在指定的peer节点中。
- instantiate: 对已安装的链码进行实例化。
- upgrade: 对已有的链码进行升级。链代码可以在安装后根据具体需求的变化进行升级。
- package: 对指定的链码进行打包
- singnpackage: 对已打包的文件进行签名。
install、instantiate、upgrade这3个操作只适用于用户链码,不适用于系统链码。链码在经过安装和初始化之后便于运行了起来,之后才可以调用链码。
链码的编写
开放基于Hyperledger Fabric的分布式账本应用程序,编写链码是一重要环节。Hyperledger Fabric提供了有关链码编写的SDK,
- NodeJs SDK: https://github.com/hyperledger/fabric-sdk-node
- Java SDK: https://github.com/hyperledger/fabric-sdk-java
- Python SDK: https://github.com/hyperledger/fabric-sdk-py
- Go SDK: https://github.com/hyperledger/fabric-sdk-go
这里我们将使用golang的SDK进行了链码的开发,在编写之前,需要将与Hyperledger Fabric Go相关的API下载至本地系统中。
下载命令如下:
$ go get -u github.com/hyperledger/fabric/core/chaincode/shim
$ go get -u github.com/hyperledger/fabric/protos/peer
还可以将整个fabric的SDK下载到本地,这种情况需要将git分支切换到release-1.2版本,master分支不包含shim包
链码接口
启动链码需要调用shim包的Start函数,Start函数调用时需要传递一个类型为Chaincode的参数,Chaincode是一个接口类型,该接口包含两个未实现方法:Init与Invoke。
type Chaincode interface{
Init(stub ChaincodeStubInterface) peer.Response
Invoke(stub ChaincodeStubInterface) peer.Response
}
编写链码其实就是实现Chaincode中的Init和Invoke方法。在链码被初始化或者升级(instantiate或upgrade)时会调用Init方法,在被调用或者查询(invoke或query)时会调用Invoke方法
链码结构
shim包为链码提供了用来访问/操作数据状态、事物上下文和调用其他链代码的相关API;peer包提供了链码执行后的响应信息。开发链码需要引入shim包和peer包:
package main
import (
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
//结构体
type SimpleChaincode struct{}
//结构体实现Chaincode的Init方法
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response{
//实现链码初始化或者升级时的处理逻辑
}
//结构体实现Chaincode的Invoke方法
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response{
//实现链码运行时被调用或者查询时的处理逻辑
}
//主函数,每个链码都需要有个主函数,固定写法
func main(){
err := shim.Start(new(SimpleChaincode))
if err != nil{
fmt.Println("启动SimpleChaincode时发生错误")
}
}
需要注意的是:
- 因为链码是一个可独立运行的应用程序,所以必须声明在一个main包中并提供相应的main函数作为应用入口。
- 链码可以是一个工程(多个go文件,多个包),这个go工程需要有一个main函数入口。
这里不再赘述shim包下的API了,有兴趣的读者可以查阅官方文档。