链上代码,简称链码,又称智能合约。一般是指由开发人员使用Go语言(也支持Java等语言)编写的应用程序代码,提供分布式账本的状态处理逻辑。链码被部署在Fabric的网络节点中,能够独立运行在具有安全特性的受保护的Docker 容器中,以 gRPC 协议与相应的 peer 节点进行通信,以操作(初始化或管理)分布式账本中的数据。可以根据不同的需求开发出不同的复杂的应用。
在 Hyperledger Fabric 中,链码一般分为:
链码开发编写完成后,并不能立刻使用,而是必须经过一系列的操作之后才能应用在 Hyperledger Fabric 网络中进而处理客户端提交的交易。这一系列的操作是由链码的生命周期来负责管理。主要需要熟悉的三个操作如下:
需要提供 go 1.13或以上版本,以下所有教程均在Mac下运行,如果是使用Windows的用户,请大家注意进行类比操作
mkdir first_cc && cd first_cc
touch first_cc.go
package main
import (
"github.com/hyperledger/fabric/core/chaincode/shim"
pb "github.com/hyperledger/fabric/protos/peer"
)
// 自定义结构体,名称可随意设置
type FirstChainCode struct {
}
// 实现 Chaincode接口的Init()方法
func (t *FirstChainCode) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}
// 实现 Chaincode接口的Invoke()方法
func (t *FirstChainCode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}
// 函数入口
func main() {
// 启动链码:将继承了Chaincode接口的FirstChainCode结构体作为入参,此处使用了多态
err := shim.Start(new(FirstChainCode))
if err != nil {
fmt.Printf("Error starting Simple chaincode: %s", err)
}
}
# 使用go mod init进行初始化项目
go mod init first_cc
# 此时会生成go.mod文件
# 编辑go.mod文件,在末尾添加以下内容
require (
github.com/hyperledger/fabric v1.4.2
)
# 拉取fabric依赖包
go mod tidy
如果大家点击main方法执行,会发现执行失败,这是因为chaincode没有找到peer与它进行通信。我们可以使用MockStub的方式,进行链码方法的测试。
以下内容均写入first_cc.go文件中
type User struct {
Name string `json:"name"`
Coin int `json:"coin"`
}
// 用户注册
func UserRegister(stub shim.ChaincodeStubInterface, args []string) pb.Response {
// 判断参数个数是否为1
if len(args) != 2 {
return shim.Error("args must be two")
}
// 获取参数:name、coin,并把coin转成int类型
name := args[0]
coinStr := args[1]
coin, err := strconv.Atoi(coinStr)
if err != nil {
return shim.Error("string to int failed: " + err.Error())
}
// 构建用户数据,填充name、coin字段
var user = User{
Name: name,
Coin: coin,
}
// 将user数据序列化成[]byte
data, err := json.Marshal(user)
if err != nil {
return shim.Error("data marshal failed: " + err.Error())
}
// 根据name作为唯一字段,将数据存入状态数据库中
err = stub.PutState(name, data)
if err != nil {
return shim.Error("data put failed: " + err.Error())
}
// 若以上操作都成功,返回Success状态,nil表示不返回数据
return shim.Success(nil)
}
// 用户查询
func UserQuery(stub shim.ChaincodeStubInterface, args []string) pb.Response {
// 判断参数个数是否为1
if len(args) != 1 {
return shim.Error("args must be one")
}
// 传入的参数arg[0]即默认为用户的name字段,从状态数据库中获取数据
data, err := stub.GetState(args[0])
if err != nil {
return shim.Error("data get failed: " + err.Error())
}
fmt.Println(string(data)) // 此处在链码中打印数据,可选
// 返回数据给链码调用者
return shim.Success(data)
}
// 修改之前实现的Invoke()方法
func (t *FirstChainCode) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
/*
获取参数,分解为调用方法名,以及传入的参数列表
然后根据具体的function,调用对应的之前写过的函数
*/
function, args := stub.GetFunctionAndParameters()
switch function {
case "UserRegister":
return UserRegister(stub, args)
case "UserQuery":
return UserQuery(stub, args)
default:
return shim.Error("no this function name.")
}
}
touch first_cc_test.go
package main
import (
"testing"
)
func Test(t *testing.T) {
}
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/stretchr/testify/assert"
"testing"
)
// 用于初始化链码
func checkInit(t *testing.T, stub *shim.MockStub, args [][]byte) {
res := stub.MockInit("1", args)
assert.Equal(t, int32(shim.OK), res.Status, "Init failed: %v", string(res.Message))
}
// 用于调用链码
func checkInvoke(t *testing.T, stub *shim.MockStub, args [][]byte) (data []byte) {
res := stub.MockInvoke("1", args)
if assert.Equal(t, int32(
shim.OK), res.Status, "Invoke failed: %s", string(res.Message)) {
fmt.Println(string(res.Payload))
}
return res.Payload
}
func Test(t *testing.T) {
}
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/stretchr/testify/assert"
"testing"
)
// 用于初始化链码
func checkInit(t *testing.T, stub *shim.MockStub, args [][]byte) {
res := stub.MockInit("1", args)
assert.Equal(t, int32(shim.OK), res.Status, "Init failed: %v", string(res.Message))
}
// 用于调用链码
func checkInvoke(t *testing.T, stub *shim.MockStub, args [][]byte) (data []byte) {
res := stub.MockInvoke("1", args)
if assert.Equal(t, int32(
shim.OK), res.Status, "Invoke failed: %s", string(res.Message)) {
fmt.Println(string(res.Payload))
}
return res.Payload
}
func Test(t *testing.T) {
cc := new(FirstChainCode)
stub := shim.NewMockStub("FirstChainCode", cc)
checkInit(t, stub, [][]byte{
[]byte("init")})
var args [][]byte
args = append(args, []byte("UserRegister"), []byte("a"), []byte("100"))
checkInvoke(t, stub, args)
args = [][]byte{
}
args = append(args, []byte("UserQuery"), []byte("a"))
data := checkInvoke(t, stub,args)
fmt.Println(string(data))
return
}
可以得到如下结果:
{
"name":"a","coin":100}
{
"name":"a","coin":100}
{
"name":"a","coin":100}