このトピックでは、Go 言語を使用してスマートコントラクトを開発する方法について説明します。
チェーンコードの構造
Go チェーンコードは、以下のメソッドで構成されています。
// Chaincode インターフェースは、すべてのチェーンコードによって実装される必要があります。Fabric は、
// 指定されたとおりにこれらの関数を呼び出すことによって、トランザクションを実行します。
type Chaincode interface {
// Init は、チェーンコードコンテナーが初めて確立された後に、Instantiate トランザクション中に呼び出され、チェーンコードが
// 内部データを初期化できるようにします。
Init(stub ChaincodeStubInterface) peer.Response
// Invoke は、提案トランザクションで台帳を更新またはクエリするために呼び出されます。
// 更新された状態変数は、トランザクションがコミットされるまで台帳に追加コミットされません。
Invoke(stub ChaincodeStubInterface) peer.Response
}
Init:チェーンコードは、初期化およびアップグレード中にこのインターフェースを呼び出して、関連データを初期化します。
Invoke:主にチェーンコードの内部ビジネスロジックを実装するために使用されます。このメソッドでは、関連するビジネスを実装できます。
上記のメソッドの実装中に、ユーザーは ChaincodeStubInterface API インターフェース を呼び出して、チェーンと対話できます。
チェーンコードの例
Hyperledger 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 // エンティティ
var Aval, Bval int // アセット保有額
var err error
if len(args) != 4 {
return shim.Error("引数の数が正しくありません。4 つ必要です。")
}
// 関連データを初期化します
A = args[0]
Aval, err = strconv.Atoi(args[1])
if err != nil {
return shim.Error("アセット保有額には整数値が必要です。")
}
B = args[2]
Bval, err = strconv.Atoi(args[3])
if err != nil {
return shim.Error("アセット保有額には整数値が必要です。")
}
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 は、ユーザーによって呼び出される関数を 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("無効な invoke 関数名です。「invoke」「delete」「query」が必要です。")
}
invoke 関数
ビジネスロジック invoke 関数は、ビジネスロジックでアセット転送を実装し、A から B に X 単位のアセットを転送します。
// A のアセットの X 単位を B に転送するトランザクション。
// invoke 関数は、2 つのキー間の値の転送を実装します。入力パラメーターは KEY1_NAME、KEY2_NAME、および VALUE です。
func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) peer.Response {
var A, B string // エンティティ
var Aval, Bval int // アセット保有額
var X int // トランザクション値
var err error
if len(args) != 3 {
return shim.Error("引数の数が正しくありません。3 つ必要です。")
}
A = args[0]
B = args[1]
// A と B の現在のアセット情報を取得します。
Avalbytes, err := stub.GetState(A)
if err != nil {
return shim.Error("状態の取得に失敗しました。")
}
if Avalbytes == nil {
return shim.Error("エンティティが見つかりません。")
}
Aval, _ = strconv.Atoi(string(Avalbytes))
Bvalbytes, err := stub.GetState(B)
if err != nil {
return shim.Error("状態の取得に失敗しました。")
}
if Bvalbytes == nil {
return shim.Error("エンティティが見つかりません。")
}
Bval, _ = strconv.Atoi(string(Bvalbytes))
// 実行します
X, err = strconv.Atoi(args[2])
if err != nil {
return shim.Error("無効なトランザクション金額です。整数値が必要です。")
}
// ビジネスロジック:アセットの転送を実装します。
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)
}
GetState API を使用して、KEY_NAME に対応する合計アセット値を取得します。
ビジネスロジックを呼び出して、X アセット単位の転送を実現します。
PutState API を呼び出して、更新されたアセットステータスをアカウントブックに書き込みます。
上記の実装は、転送に類似した単純なロジックですが、転送量がゼロより大きい、残高が負ではないなどのパラメーターの有効性を検証しません。
delete 関数
ビジネスロジック delete 関数は、ビジネスロジックでアカウント削除機能を実装します。
// 状態からエンティティを削除します
func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) peer.Response {
if len(args) != 1 {
return shim.Error("引数の数が正しくありません。1 つ必要です。")
}
A := args[0]
// 台帳の状態からキーを削除します
err := stub.DelState(A)
if err != nil {
return shim.Error("状態の削除に失敗しました。")
}
return shim.Success(nil)
}
query 関数
ビジネスロジック query 関数は、ビジネスロジックでアカウントクエリ機能を実装します。 GetState API を呼び出して、対応するアカウントのアセットをクエリできます。
// チェーンコードクエリのクエリコールバックを示します。
func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) peer.Response {
var A string // エンティティ
var err error
if len(args) != 1 {
return shim.Error("引数の数が正しくありません。クエリ対象の人の名前が必要です。")
}
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)
}