全部產品
Search
文件中心

:智能合約Go開發指南

更新時間:Jul 06, 2024

本節介紹如何使用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)
}