本节介绍如何使用Go语言进行智能合约的开发。
链码结构
Go 语言的链码主要由以下方法组成:
// Chaincode interface must be implemented by all chaincodes. The fabric runs
// the transactions by calling these functions as specified.
type Chaincode interface {
// Init is called during Instantiate transaction after the chaincode container
// has been established for the first time, allowing the chaincode to
// initialize its internal data
Init(stub ChaincodeStubInterface) peer.Response
// Invoke is called to update or query the ledger in a proposal transaction.
// Updated state variables are not committed to the ledger until the
// transaction is committed.
Invoke(stub ChaincodeStubInterface) peer.Response
}
Init: 链码在初始化和升级时调用此接口,初始化相关的数据。
Invoke:主要用于实现链码的内部业务逻辑,您可以在该方法中实现相关的业务。
在上述方法实现过程中,用户可以调用 ChaincodeStubInterface 的 API 接口和链上进行交互。
链码示例
Hyperledger Fabric 提供了很多官方链码样例,具体请参考fabric 官方示例。 我们以 Hyperledger Fabric 官方提供的 example02 样例为例,为大家介绍链码的开发规范。
简单示例
首先,我们看一个空链码结构的示例代码
package main
import (
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-protos-go/peer"
"fmt"
)
// 一个管理资产的简单链码
type SimpleChaincode struct {
}
//在链码实例化期时调用Init初始化数据
func (t *SimpleChaincode)Init(stub shim.ChaincodeStubInterface) peer.Response {
return shim.Success(nil)
}
// Invoke在每一比交易时都会被调用
func (t *SimpleChaincode)Invoke(stub shim.ChaincodeStubInterface) peer.Response {
return shim.Success(nil)
}
func main() {
if err := shim.Start(new(SimpleChaincode)); nil != err {
fmt.Printf("启动链码失败,err := %n", err.Error())
}
}
Init 示例
Init 函数在链码实例化以及升级的时候会被调用。在实现 Init 函数的过程中,可使用 Go 语言版本的合约 API 列表来对参数和分布式账本进行操作。
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
fmt.Println("ex02 Init")
// 调用 GetFunctionAndParameters 方法对参数进行解析
_, args := stub.GetFunctionAndParameters()
var A, B string // Entities
var Aval, Bval int // Asset holdings
var err error
if len(args) != 4 {
return shim.Error("Incorrect number of arguments. Expecting 4")
}
// 初始化相关数据
A = args[0]
Aval, err = strconv.Atoi(args[1])
if err != nil {
return shim.Error("Expecting integer value for asset holding")
}
B = args[2]
Bval, err = strconv.Atoi(args[3])
if err != nil {
return shim.Error("Expecting integer value for asset holding")
}
fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)
// 调用 PutState 方法将数据写入账本中
err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
本示例要求用户输入的参数为KEY1_NAME, VALUE1, KEY2_NAME, VALUE2
,并初始化2个键值对,调用 PutState
将数据写入分布式账本中。
Invoke 示例
Invoke 函数是对用户具体业务逻辑的实现,用户可以根据不同的业务处理逻辑,调用不用的业务函数,如invoke
,delete
和 query
函数。
// Invoke把用户调用的function细分到几个子function, 包含invoke,delete和query
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
fmt.Println("ex02 Invoke")
// 调用 GetFunctionAndParameters 方法对参数进行解析
function, args := stub.GetFunctionAndParameters()
if function == "invoke" {
// 将A的资产转移X个单位给B
return t.invoke(stub, args)
} else if function == "delete" {
// 从状态中删除实体
return t.delete(stub, args)
} else if function == "query" {
// 老的“Query”功能可以在调用中实现
return t.query(stub, args)
}
return shim.Error("Invalid invoke function name. Expecting \"invoke\" \"delete\" \"query\"")
}
invoke 函数
业务逻辑 invoke 函数实现了业务逻辑中的资产转移,将 A 的资产转移 X 个单位给 B。
// 将A的资产转移X个单位给B的交易
// invoke实现了两个键之间的value转移,输入参数为KEY1_NAME, KEY2_NAME,VALUE
func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) peer.Response {
var A, B string // Entities
var Aval, Bval int // Asset holdings
var X int // Transaction value
var err error
if len(args) != 3 {
return shim.Error("Incorrect number of arguments. Expecting 3")
}
A = args[0]
B = args[1]
// 获取A、B的当前资产情况
Avalbytes, err := stub.GetState(A)
if err != nil {
return shim.Error("Failed to get state")
}
if Avalbytes == nil {
return shim.Error("Entity not found")
}
Aval, _ = strconv.Atoi(string(Avalbytes))
Bvalbytes, err := stub.GetState(B)
if err != nil {
return shim.Error("Failed to get state")
}
if Bvalbytes == nil {
return shim.Error("Entity not found")
}
Bval, _ = strconv.Atoi(string(Bvalbytes))
// 执行
X, err = strconv.Atoi(args[2])
if err != nil {
return shim.Error("Invalid transaction amount, expecting a integer value")
}
// 业务逻辑:实现资产的转移
Aval = Aval - X
Bval = Bval + X
fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)
// 将更新后的资产更新到账本中
err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
使用 API GetState 获取到 KEY_NAME 对应的资产总值
调用业务逻辑实现 X 个资产单位的转移
调用 API PutState 将更新后的资产情况写入到账本中
注:上述实现的是一个类似转账的简单逻辑,但并未对参数的合法性诸如转账金额大于零、余额不为负等进行校验。
delete 函数
业务逻辑 delete 函数实现了业务逻辑中的账户删除功能。
// 从状态中删除实体
func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
A := args[0]
// 从分类帐中的状态删除密钥
err := stub.DelState(A)
if err != nil {
return shim.Error("Failed to delete state")
}
return shim.Success(nil)
}
query 函数
业务逻辑 query 函数实现了业务逻辑中的账户查询功能,通过调用 API GetState 查询对应账户的资产。
// 代表链码查询的查询回调
func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) peer.Response {
var A string // 实体
var err error
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting name of the person to query")
}
A = args[0]
// 从分类帐中获取状态
Avalbytes, err := stub.GetState(A)
if err != nil {
jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}"
return shim.Error(jsonResp)
}
if Avalbytes == nil {
jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}"
return shim.Error(jsonResp)
}
jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}"
fmt.Printf("Query Response:%s\n", jsonResp)
return shim.Success(Avalbytes)
}