Hyperledger Fabric学习(十一)智能合约(链码)第一个链码

概述

​      链上代码,简称链码,又称智能合约。一般是指由开发人员使用Go语言(也支持Java等语言)编写的应用程序代码,提供分布式账本的状态处理逻辑。链码被部署在Fabric的网络节点中,能够独立运行在具有安全特性的受保护的Docker 容器中,以 gRPC 协议与相应的 peer 节点进行通信,以操作(初始化或管理)分布式账本中的数据。可以根据不同的需求开发出不同的复杂的应用。

在 Hyperledger Fabric 中,链码一般分为:

  • 系统链码(略)
  • 用户链码: 指由开发人员编写的应用代码(即接下来我们需要掌握的部分

链码的生命周期管理

​      链码开发编写完成后,并不能立刻使用,而是必须经过一系列的操作之后才能应用在 Hyperledger Fabric 网络中进而处理客户端提交的交易。这一系列的操作是由链码的生命周期来负责管理。主要需要熟悉的三个操作如下:

  • install:将已编写完成的链码安装在网络节点中;
  • instantiate:对已安装的链码进行实例化;
  • upgrade:对已有链码进行升级。链代码可以在安装后根据具体需求的变化进行升级;

链码使用方法

编写自己的第一个链码项目

需要提供 go 1.13或以上版本,以下所有教程均在Mac下运行,如果是使用Windows的用户,请大家注意进行类比操作

  • 命令行创建一个目录,名称为first_cc,在该目录下创建一个first_cc.go文件
mkdir first_cc && cd first_cc
touch first_cc.go
  • 打开Goland IDE或者其他类型的编辑器,将如下代码写入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管理
# 使用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文件中

  • 我们先定义一个User结构体,表示用户数据
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方法中使用这两个函数
// 修改之前实现的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.")
	}
}
测试自己的链码
  • 在first_cc目录下创建first_cc_test.go文件
touch first_cc_test.go
  • 编写一个Test方法
package main

import (
  "testing"
)

func Test(t *testing.T) {
     
  
}
  • 添加两个函数checkInit()、checkInvoke()
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) {
     
  
}
  • 接下来我们在Test函数中编写两个测试,分别对应用户注册和用户查询
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}

你可能感兴趣的:(Hyperledger,Fabric)