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