智能合约Go开发指南

本节介绍如何使用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 函数是对用户具体业务逻辑的实现,用户可以根据不同的业务处理逻辑,调用不用的业务函数,如invokedeletequery 函数。

// 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)
}