目次


Go 開発者のためのチェーンコード、第 1 回

Hyperledger Fabric v0.6 用のブロックチェーン・チェーンコードを Go 言語で作成する

ブロックチェーン・ネットワークのチェーンコードを作成する方法

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: Go 開発者のためのチェーンコード、第 1 回

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:Go 開発者のためのチェーンコード、第 1 回

このシリーズの続きに乞うご期待。

Go 言語を使用して、Hyperledger Fabric v0.6 をベースとしたブロックチェーン・ネットワークのチェーンコードを開発する方法を学んでください。このチュートリアルでは、Fabric とやりとりするための API をはじめとする基本をおさえると同時に、データ・モデリング、アクセス制御、イベントなどの高度なトピックを取り上げます。また、ブロックチェーン上のプロセスの一例として、サンプル・コードをふんだんに用いて住宅ローンと購入契約のプロセスを説明します (完全なサンプル・チェーンコードをダウンロードするには、このチュートリアルの末尾にある「ダウンロード可能なリソース」セクションを参照してください)。

このチュートリアルは、シリーズの第 1 回です。続く第 2 回で、チェーンコードの単体テストを行う方法、そしてデプロイしたチェーンコードを呼び出すことのできるクライアント・アプリケーションを開発する方法を説明します。

チェーンコードとは何か

「スマート・コントラクト」とも呼ばれるチェーンコードは、本質的に、ブロックチェーン・ネットワーク内の異なる複数のエンティティーや関係者が互いにどのようにして対話または取り引きするのかを統制するビジネス・ロジックです。簡単に言うと、チェーンコードにはビジネス・ネットワークのトランザクションがカプセル化されます。これらのトランザクションを 1 つにまとめたチェーンコードを呼び出すことで、レジャーまたはワールド・ステートの設定および取得処理が行われます。

このチュートリアルを公開する時点で、Hyperledger では Go 言語または Java 言語でチェーンコードを作成し、最終的に Docker コンテナー内でチェーンコードを実行できるようになっています。Java 向けのチェーンコードのサポートはまだベータ版なので、このチュートリアルでは Go を使用したチェーンコードの作成方法に焦点を絞ります。

開発環境のセットアップ

このリンク先の IBM Bluemix の資料で、「Setting up the development environment」というセクションから「Set up your development pipeline」というセクションまでの手順に従ってください。これで、Go 言語でチェーンコードを開発する準備が整います。

チェーンコードの構造

チェーンコードの構造を詳しく見ていきましょう。前述のとおり、リスト 1 とこのチュートリアル全体を通して記載するサンプル・チェーンコードとチュートリアルで説明するアーキテクチャーは、Hyperledger Fabric の v0.6 preview に厳密に従っています。

リスト 1 の 4 行目で、shim パッケージをチェーンコードにインポートします。チェーンコードはこの shim パッケージに用意されている API を使用して、基礎となるブロックチェーン・ネットワークとやりとりし、状態変数、トランザクション・コンテキスト、呼び出し側の証明書と属性にアクセスしたり、他のチェーンコードを呼び出したりするなどの処理を行います。

リスト 1. サンプル・チェーンコード
package main

import "fmt"
import "github.com/hyperledger/fabric/core/chaincode/shim"

type SampleChaincode struct {
}

func (t *SampleChaincode) Init(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	return nil, nil
}

func (t *SampleChaincode) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	return nil, nil
}

func (t *SampleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	return nil, nil
}

func main() {
	err := shim.Start(new(SampleChaincode))
	if err != nil {
		fmt.Println("Could not start SampleChaincode")
	} else {
		fmt.Println("SampleChaincode successfully started")
	}

}

main 関数

Go プログラムでは例外なく、main 関数が出発点となります。したがって、チェーンコードをブートストラップ/起動するには main 関数を使用します。ピアがチェーンコードのインスタンスをデプロイするときにも、main 関数が実行されます。

リスト 2 の2 行目に示されているように、shim.Start(new(SampleChaincode)) の行でチェーンコードを起動し、そのチェーンコードをピアに登録します。この動作をローカルで確認するには、開発環境内でコードを実行してください。すると、ピアのアドレスが構成されていないためにピアに接続できないことを通知する、[shim] CRIT : peer.address not configured, can't connect to peer というエラーが生成されるはずです。

リスト 2. main()
func main() {
	err := shim.Start(new(SampleChaincode))
	if err != nil {
		fmt.Println("Could not start SampleChaincode")
	} else {
		fmt.Println("SampleChaincode successfully started")
	}

}

SampleChaincode は、shim.Chaincode を実装するために必要な構造体です。この構造体には、Init、Query、Invoke という 3 つのメソッドがあります。この 3 つが揃っていることにより、shim パッケージは構造体を有効な Chaincode タイプであると見なします。この 3 つのメソッドのそれぞれについて見ていきましょう。

Init メソッド

Init メソッドは、ブロックチェーン・ネットワークに初めてチェーンコードをデプロイする際に呼び出すメソッドです。各ピアも、このメソッドを実行してチェーンコードの独自のインスタンスをデプロイします。初期化、ブートストラップ、またはセットアップに関連するタスクであれば、どのタスクにも Init メソッドを使用できます。

リスト 3. Init()
func (t *SampleChaincode) Init(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	return nil, nil
}

Query メソッド

ブロックチェーンの状態に対して読み取り/取得/クエリー処理を実行する必要がある場合は、必ず Query メソッドを呼び出します。チェーンコードの複雑さによっては、このメソッド自体に読み取り/取得/クエリーのロジックを保持することも、あるいは Query メソッド内から呼び出すことのできる別のメソッドにそのロジックをアウトソーシングすることもできます。

Query メソッドは、基礎となるブロックチェーンの状態を変更するようには意図されていないため、トランザクション・コンテキスト内で実行されることはありません。Query メソッド内でブロックチェーンの状態を変更しようとすると、トランザクション・コンテキストが欠落していると通知するエラーが発生します。また、このメソッドはブロックチェーンの状態を読み取ることだけを目的としているため、ブロックチェーンには、このメソッドの呼び出しは記録されません。

リスト 4. Query()
func (t *SampleChaincode) Query(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	return nil, nil
}

Invoke メソッド

ブロックチェーンの状態を変更する場合は、必ず Invoke メソッドを呼び出します。端的に言えば、作成処理、更新処理、および削除処理はすべて、Invoke メソッド内にカプセル化してください。このメソッドはブロックチェーンの状態を変更するためのものなので、ブロックチェーンの Fabric コードはこのメソッドを実行するトランザクション・コンテキストを自動的に作成します。このメソッドの呼び出しはいずれもブロックチェーンにトランザクションとして記録され、最終的にはブロックに書き込まれます。

リスト 5. Invoke()
func (t *SampleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	return nil, nil
}

チェーンコード内でのデータ・モデル

Hyperledger レジャーは以下の 2 つの部分で構成されています。

  1. ワールド・ステート。キー・バリュー型ストアに保管されます。このストアは RocksDB によって駆動されるキー・バリュー型ストアであり、直列化された JSON 構造を格納するために使用できるバイト配列を値として取ります。基本的に、このキー・バリュー型ストアを使用すれば、スマート・コントラクトを機能させるために必要なあらゆるカスタム・データ・モデル/スキーマを格納できます。
  2. ブロックチェーン。ブロックチェーンは、それぞれに多数のトランザクションを含む一連のブロックからなります。それぞれのブロックにはワールド・ステートのハッシュが格納され、各ブロックが先行するブロックにリンクされます。ブロックチェーンでは追加のみが可能です。

リスト 6 に、カスタム・データ・モデル/スキーマを作成するコードを記載します。このコードでは、住宅ローンの申請に必要となる複数のデータ・モデルを定義しています。主要なモデルは LoanApplication という名前のモデルです。このモデルにはプリミティブ・データ型と併せて、複合データ型 (つまり、Personal Info と Financial Info の 2 つのデータ・モデル) が含まれています。

このキー・バリュー型ストアではデータが JSON として保管されるため、最終的にはこれらのデータ・モデルを JSON 文字列に変換しなければなりません。各フィールドのアノテーション (例えば、json:"id") は、マーシャル/アンマーシャル API に対してメタデータのような役割を果たします。API はこれらのアノテーションを使用して、各フィールドをそれと同等の JSON 文字列の表現にマッピングします。

リスト 6. カスタム・データ・モデル/スキーマを作成するコード
//custom data models
type PersonalInfo struct {
	Firstname string `json:"firstname"`
	Lastname  string `json:"lastname"`
	DOB       string `json:"DOB"`
	Email     string `json:"email"`
	Mobile    string `json:"mobile"`
}

type FinancialInfo struct {
	MonthlySalary      int `json:"monthlySalary"`
	MonthlyRent        int `json:"monthlyRent"`
	OtherExpenditure   int `json:"otherExpenditure"`
	MonthlyLoanPayment int `json:"monthlyLoanPayment"`
}

type LoanApplication struct {
	ID                     string        `json:"id"`
	PropertyId             string        `json:"propertyId"`
	LandId                 string        `json:"landId"`
	PermitId               string        `json:"permitId"`
	BuyerId                string        `json:"buyerId"`
	SalesContractId        string        `json:"salesContractId"`
	PersonalInfo           PersonalInfo  `json:"personalInfo"`
	FinancialInfo          FinancialInfo `json:"financialInfo"`
	Status                 string        `json:"status"`
	RequestedAmount        int           `json:"requestedAmount"`
	FairMarketValue        int           `json:"fairMarketValue"`
	ApprovedAmount         int           `json:"approvedAmount"`
	ReviewerId             string        `json:"reviewerId"`
	LastModifiedDate       string        `json:"lastModifiedDate"`
}

データの保管と取得

リスト 7 とリスト 8 に、データをレジャーに保管する方法、レジャーからデータをフェッチする方法をそれぞれ説明するコードを記載します。

データをレジャーに保管する

リスト 7 の 1 行目に示されている CreateLoanApplication メソッドは、2 つの引数を取ります。最初の引数は、ChaincodeStubInterface です。このインターフェースの API を利用して、ブロックチェーンのレジャー、トランザクション・コンテキスト、呼び出し側の証明書などとやりとりすることができます。2 番目の引数は文字列配列で、このメソッドの呼び出し側は、この配列を使用して必須の引数を渡すことができます。

2 行目から 8 行目で、入力された引数のロギングと検証を処理します。

9 行目で、ローン申請 ID 値を取得します。この値をキーとして使用して、実際のローン申請オブジェクトを保管することになります。

10 行目で、実際のローン申請内容を JSON 文字列の形で取得します。例: ‘{"propertyId":"prop1","landId":"land1","permitId":"permit1","buyerId":"vojha24","personalInfo":{"firstname":"Varun","lastname":"Ojha","dob":"dob","email":"varun@gmail.com","mobile":"99999999"},"financialInfo":{"monthlySalary":10000,"otherExpenditure":0,"monthlyRent":1000,"monthlyLoanPayment":1000},"status":"Submitted","requestedAmount":40000,"fairMarketValue":58000,"approvedAmount":40000,"reviewedBy":"abc1","lastModifiedDate":"21/09/2016 2:30pm"}’

12 行目では stub.PutState メソッドを呼び出し、ローン申請 ID と JSON 形式の実際のローン申請内容を、キーと値のペアとしてブロックチェーン・レジャーに保管します。注意する点として、キー・バリュー型ストアに保管される値は、常にバイト配列でなければなりません。したがって、ローン申請 JSON 文字列はレジャーに保管される前に、バイト配列に変換されるようになっています。

リスト 7. データをレジャーに保管するコード
func CreateLoanApplication(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
	fmt.Println("Entering CreateLoanApplication")

	if len(args) < 2 {
		fmt.Println("Invalid number of args")
		return nil, errors.New("Expected at least two arguments for loan application creation")
	}

	var loanApplicationId = args[0]
	var loanApplicationInput = args[1]

	err := stub.PutState(loanApplicationId, []byte(loanApplicationInput))
	if err != nil {
		fmt.Println("Could not save loan application to ledger", err)
		return nil, err
	}

	fmt.Println("Successfully saved loan application")
	return nil, nil
}

レジャーからデータを取得する

リスト 8 では、1 行目GetLoanApplication メソッドが 2 つの引数を取ります。最初の引数は、ChaincodeStubInterface です。このインターフェースの API を利用して、ブロックチェーンのレジャー、トランザクション・コンテキスト、呼び出し側の証明書などとやりとりすることができます。2 番目の引数は文字列配列で、このメソッドの呼び出し側は、この配列を使用して必須の引数を渡すことができます。

2 行目から7 行目で、入力された引数のロギングと検証を処理します。

9 行目で、ローン申請 ID 値を取得します。この値をキーとして使用して、実際のローン申請オブジェクトをレジャーから取得することになります。

10 行目で、stub.GetState メソッドを呼び出し、loanApplicationId キーを渡して、JSON 形式のローン申請内容をバイト配列の形で取得します。注意する点として、キー・バリュー型ストアに保管される値は、常にバイト配列でなければなりません。したがって、ローン申請 JSON 文字列はレジャーに保管される前に、バイト配列に変換されるようになっています。

リスト 8. レジャーからデータを取得するコード
func GetLoanApplication(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
	fmt.Println("Entering GetLoanApplication")

	if len(args) < 1 {
		fmt.Println("Invalid number of arguments")
		return nil, errors.New("Missing loan application ID")
	}

	var loanApplicationId = args[0]
	bytes, err := stub.GetState(loanApplicationId)
	if err != nil {
		fmt.Println("Could not fetch loan application with id "+loanApplicationId+" from ledger", err)
		return nil, err
	}
	return bytes, nil
}

注: 従来型のリレーショナル・データ・モデルを使用してデータを処理するという方法もあります。例えば、ChaincodeStubInterface にはテーブルを作成して行と列を処理するための手段がありますが、ここで説明しているのは論理的な抽象化でしかないため、データをキーと値のペアとして RocksDB に保管します。このチュートリアルを書いている時点で開発が進められている Hyperledger Fabric バージョン 1.0 では、テーブル・データ構造が非推奨となっています。したがって、このチュートリアルでは、チェーンコード開発者が v0.6 から v1.0 に移行するために必要な変更を最小限にするために、従来型のリレーショナル・データ・モデルについては取り上げません。

golang 構造体と JSON 文字列の間でのマーシャリングとアンマーシャリング

リスト 7 とリスト 8 から明らかなように、stub.PutState メソッドと stub.GetState メソッドはバイト配列しか扱いません。そのため、チェーンコード内で使用される通常の構造体オブジェクトと JSON 文字列との間で双方向の変換を可能にすることが重要です。

リスト 9 に、構造体を JSON 文字列のバイト配列にマーシャリングしてレジャーに保管できるようにする方法を示します。このコードでは 2 行目で、PersonalInfo オブジェクトのインスタンスを作成します。3 行目で、json パッケージを使用してオブジェクトを JSON 文字列にマーシャリングし、同じオブジェクトをバイト配列として返します。

json パッケージをインポートするには、先頭の import ブロックに「encoding/json」を含めます。マーシャリング後のバイト配列は、stub.PutState メソッド (リスト 7 を参照) を使用してレジャーに保管できます。

リスト 9. 構造体を JSON 文字列のバイト配列にマーシャリングするコード
var personalInfo PersonalInfo
 personalInfo = PersonalInfo{"Varun", "Ojha", "dob", "varun@gmail.com", "9999999999"}
 bytes, err := json.Marshal (&personalInfo)
 if err != nil {
        fmt.Println("Could not marshal personal info object", err)
		return nil, err
 }
 err = stub.PutState("key", bytes)

リスト 10 に、バイト配列の構造体を、データが取り込まれた状態の構造体にアンマーシャリングする方法を示します。このコードでは、1 行目PersonalInfo JSON 文字列バイトを、それに関連付けられたキーを使用してレジャーから取得します。3 行目で、1 行目で取得したバイトを、personalInfo 変数によって参照されている PersonalInfo オブジェクトにアンマーシャリングします。

これで、4 行目に示されているようにドット表記を使用して、personalInfo オブジェクトにアクセスして変更できるようになります。

リスト 10. バイト配列の構造体を、データが取り込まれた状態の構造体にアンマーシャリングするコード
piBytes, err := stub.GetState("la1")	
 var personalInfo PersonalInfo
 err = json.Unmarshal(piBytes, &personalInfo)
 fmt.Println(personalInfo.Firstname)

アクセス制御および許可の実装

Hyperledger を他のブロックチェーン・ファブリックとは差別化する大きな違いは、Hyperledger ではセキュアな許可制レジャーを使用するという点です。このようなレジャーは、エンタープライズ・グレードのソリューションを実装するにはまさにうってつけです。

メンバーシップ・サービス

Hyperledger 内のメンバーシップ・サービス・コンポーネントは、ブロックチェーン・ネットワーク内のセキュリティーとアクセス許可を有効にする上で極めて重要な役割を果たします。メンバーシップ・サービス・コンポーネントの役割は、登録 (Enrollment) 証明書と取引 (Transaction) 証明書を発行して、ユーザーの参加と登録に対処することです。

  • 登録証明書: メンバーシップ・サービスの認証局は、ブロックチェーン上での取引を希望するユーザーに対し、ユーザーの身分証明書として登録証明書を発行します。
  • 取引証明書: 取引証明書は、1 回限りのトークンとして使用されるよう意図されており、呼び出し元アプリケーションはチェーンコードの呼び出し要求ごとに、その要求と併せて取引証明書を渡します。取引証明書はその親となる登録証明書から数学的な計算によって生成されるため、理論上、1 つの親登録証明書から無限の数の取引証明書を生成できます。トランザクション・データをブロックチェーンに保管する際は、取引証明書を使用してデータに署名および暗号化を適用することができます。これにより、ブロックチェーン内に書き込まれたトランザクションの内容を実際に見ることができるのは、取引証明書を持つユーザーまたは親証明書を持つ取締官/監査員などに制限されます。
  • 属性: 各取引証明書には、いくつものユーザー定義の属性を持たせることができます。このような属性は、クライアント・アプリケーションから認証局への登録要求の一部として使用できます。属性については、後続のチュートリアルでクライアント・アプリケーションの開発という観点から詳しく説明します。

リスト 11 に、呼び出し側の取引証明書から属性を取得する方法を示します。前述のとおり、ユーザーまたはクライアント・アプリケーションはそのユーザーの証明書をチェーンコードの呼び出し要求の一部として渡し、ブロックチェーン・ネットワーク上のターゲット・ピアによって認証できるようにする必要があります。HFC SDK はこのタスクを引き受け、自動的に要求の一部として証明書を渡します。

リスト 11 の 11 行目に示されている Invoke 関数は、入力された関数名を調べて、その呼び出しを適切なハンドラー関数に委任するように変更されています。さらに、Invoke 関数は CreateLoanApplication 呼び出し要求を送信した呼び出し側のアクセス権およびロールも検証します。呼び出し側の取引証明書から特定の属性を取得するために Invoke 関数が呼び出すのは、カスタム GetCertAttribute 関数です。

GetCertAttribute 関数は、3 行目で属性名を渡して、属性値をフェッチします。

15 行目では、呼び出し側のロールが銀行のローン管理者であるかどうかをチェックします。ローン管理者であれば、CreateLoanApplication 関数を呼び出す権限があります。呼び出し側にこの必須のロールが割り当てられていない場合、それに応じたエラーが返されます。このようにして、属性ベースのアクセス制御をチェーンコードに実装することができます。

ChaincodeStubInterface には、属性を処理するユーティリティー関数がいくつかあります (ReadCertAttributeVerifyAttributeVerifyAttributes など)。これらのメソッドはいずれも、属性を処理する AttributeHandlerImpl を作成して使用するために、github.com/hyperledger/fabric/core/chaincode/shim/crypto/attr パッケージに依存します。

現在開発中の Hyperledger Fabric バージョン v1.0 では、これらのユーティリティー関数は ChaincodeStubInterface から削除されています。そのため v1.0 では、チェーンコード開発者が直接 AttributeHandlerImpl を使用して属性を処理する必要があります。

リスト 11. 呼び出し側の取引証明書から属性を取得するコード
func GetCertAttribute(stub shim.ChaincodeStubInterface, attributeName string) (string, error) {
	fmt.Println("Entering GetCertAttribute")
	attr, err := stub.ReadCertAttribute(attributeName)
	if err != nil {
		return "", errors.New("Couldn't get attribute " + attributeName + ". Error: " + err.Error())
	}
	attrString := string(attr)
	return attrString, nil
}

func (t *SampleChaincode) Invoke(stub shim.ChaincodeStubInterface, function string, args []string) ([]byte, error) {
	if function == "CreateLoanApplication" {
		username, _ := GetCertAttribute(stub, "username")
		role, _ := GetCertAttribute(stub, "role")
		if role == "Bank_Home_Loan_Admin" {
			return CreateLoanApplication(stub, args)
		} else {
			return nil, errors.New(username + " with role " + role + " does not have access to create a loan application")
		}

	}
	return nil, nil
}

カスタム・イベントの作成と送信

Hyperledger にはイベント・フレームワークが含まれています。これを使用することで、事前定義されたイベントやカスタム・イベントをパブリッシュ/サブスクライブできます。カスタム・イベントをチェーンコード内で作成および送信するにあたって制約はありません。例えば、ブロックチェーンの状態が変更された場合に常にイベントが生成されるようにすることも可能です。これらのイベントをクライアント・アプリケーションがサブスクライブして、イベントを取り込めるようにするには、イベント・アダプターをブロックチェーン上のイベント・ハブに登録する必要があります。このシリーズの後続のチュートリアルで、HFC SDK を使用してチェーンコードから生成されるイベントをクライアント・アプリケーションがサブスクライブし、取り込めるようにするにはどのようにするのかを詳しく紹介します。

カスタム・イベントとは別に、Hyperledger に含まれている定義済み内部イベントも使用できます。例えば、以下の内部イベントがあります。

  • ブロック・イベント
  • チェーンコード・イベント
  • 拒否イベント
  • 登録イベント

リスト 12 のコードに、カスタム・イベントの使用方法を示します。

1 行目から 4 行目で、Type フィールドおよび Description フィールドを含むカスタム・イベント・オブジェクトを定義します。

6 行目から始まる CreateLoanApplication 関数は、ローン申請の作成が成功した時点でイベントを作成するように変更されています。

23 行目で、customEvent オブジェクトのインスタンスを作成し、そのインスタンスに該当するイベントの詳細を取り込みます。

24 行目で、このイベント・オブジェクトを、前に説明したように JSON 文字列のバイト配列にマーシャリングします。

28 行目で、カスタム・イベントを設定します。この stub.SetEvent メソッドが取る引数は、イベントペイロードの 2 つです。クライアント・アプリケーションがこの同じイベント名/トピックをサブスクライブすると、チェーンコードによってイベントが生成された時点で、イベントを受信できるようになります。

リスト 12. カスタム・イベントを作成してパブリッシュするコード
type customEvent struct {
	Type        string `json:"type"`
	Description string `json:"description"`
}

func CreateLoanApplication(stub shim.ChaincodeStubInterface, args []string) ([]byte, error) {
	fmt.Println("Entering CreateLoanApplication")

	if len(args) < 2 {
		fmt.Println("Invalid number of args")
		return nil, errors.New("Expected at least two arguments for loan application creation")
	}

	var loanApplicationId = args[0]
	var loanApplicationInput = args[1]

	err := stub.PutState(loanApplicationId, []byte(loanApplicationInput))
	if err != nil {
		fmt.Println("Could not save loan application to ledger", err)
		return nil, err
	}

	var event = customEvent{"createLoanApplication", "Successfully created loan application with ID " + loanApplicationId}
    eventBytes, err := json.Marshal(&event)
    if err != nil {
            return nil, err
    }
	err = stub.SetEvent("evtSender", eventBytes)
	if err != nil {
		fmt.Println("Could not set event for loan application creation", err)
	}

	fmt.Println("Successfully saved loan application")
	return nil, nil

}

ロギングの処理

チェーンコード内でロギングを処理するには、標準の fmt パッケージと print ステートメントを使用する方法、または shim パッケージに含まれる ChaincodeLogger タイプを使用する方法のいずれかを選べます。

ChaincodeLogger では、以下のログ・レベルがサポートされています。

  • CRITICAL
  • ERROR
  • WARNING
  • NOTICE
  • INFO
  • DEBUG

ロギング・レベルを設定するには、以下の 3 つの方法があります。

  • shim.SetChaincodeLoggingLevel(): このメソッドは、core.yaml ファイル内で指定されている CORE_LOGGING_CHAINCODE セットからロギング・レベルを選びます。core.yaml ファイルには、ブロックチェーン・ネットワークをセットアップしてデプロイするために必要なすべての情報が含まれています。
  • shim.SetLoggingLevel(level LoggingLevel): このメソッドは、ロギング・レベルを shim レベルで設定します。
  • ChaincodeLogger.SetLevel(level LoggingLevel): このメソッドは、ロギング・レベルを個々のロガー・インスタンス・レベルで設定します。

リスト 13 に、ChaincodeLogger を作成、構成、使用する方法を示します。

リスト 13. ChaincodeLogger を作成、構成、使用するコード
func SampleLogging() {
		//Different Logging Levels
		criticalLevel, _ := shim.LogLevel("CRITICAL")
		errorLevel, _ := shim.LogLevel("ERROR")
		warningLevel, _ := shim.LogLevel("WARNING")
		noticeLevel, _ := shim.LogLevel("NOTICE")
		infoLevel, _ := shim.LogLevel("INFO")
		debugLevel, _ := shim.LogLevel("DEBUG")

		//Logging level at the shim level
		shim.SetLoggingLevel(infoLevel)

		//Create a logger instance
		myLogger := shim.NewLogger("SampleChaincodeLogger")

		//Set logging level on logger instance
		myLogger.SetLevel(infoLevel)

		//Check logging level
		fmt.Println(myLogger.IsEnabledFor(infoLevel))

		//Log statements
		myLogger.Info("Info Message")
		myLogger.Critical("Critical Message")
		myLogger.Warning("Warning Message")
		myLogger.Error("Error Message")
		myLogger.Notice("Notice Message")
		myLogger.Debug("Debug Message")

	}

FAQ とベスト・プラクティス

顧客と協力してブロックチェーン・アプリケーションを開発する際によく尋ねられる質問を紹介します。

ブロックチェーンにファイル (画像、音声、ビデオ、PDF など) を保管するにはどうすればよいですか

現行の Hyperledger Fabric (v0.6) では、以下の手法を両方とも使用できます。

  • すべてのファイル/オブジェクトを base64 でエンコードした文字列として保管します。クライアント・アプリケーションがファイル/オブジェクトを base64 でエンコードした文字列に変換し、それを入力パラメーターとしてチェーンコード関数に渡します。これにより、チェーンコードがファイル/オブジェクトをバイト配列としてキー・バリュー型ストアに保管できるようになります。
  • 実際のファイル/オブジェクトの内容を、ブロックチェーン外部 (例えば、IBM Bluemix Object Storage サービス) に保管します。ブロックチェーン上には、ファイル/オブジェクトのリンク/参照/ID をファイル/オブジェクトのハッシュと併せて保管します。ハッシュを保管することで、ブロックチェーン外部でファイル/オブジェクトが改ざんされたとしても、関係者/関係機関がそれを検出できるようになります。

ネットワーク内のすべてのピアに対して、機密性の高いビジネス・ロジック/契約の詳細が開示されないようにするには、どのような措置を取れますか

この質問は、サプライ・チェーンのシナリオで、機密性の高いビジネス・ロジック/契約の詳細 (ベンダーによって異なる交渉額など) をスマート・コントラクトで共有することに懸念を感じていたブロックチェーン・ソリューションのエンド・ユーザーから挙がってきました。ユーザーが懸念を感じていたわけは、スマート・コントラクトはすべてのピアに可視になるためです。v0.6 では、外部システムを統合することによって、この状況に対処できます。

ソリューション: ピアが機密にしておく必要のあるビジネス・ロジック/ルール/契約は、外部アプリケーション (サービスなど) 内で一連のビジネス・ルールとして実行できます。チェーンコードには、アウトバウンドの呼び出し機能が備わっています。チェーンコードで、例えばビジネス・ルール/ロジック・サービスに対して REST API 呼び出しを行い、その結果をフェッチすれば、ロジックを実際のチェーンコードから隠すことができます。

ブロックチェーン内部から、ブロックチェーン外部にあるシステムを統合することは可能です。例えば、チェーンコードを使用して外部データベース、API などといったシステムとやりとりすることができます。ただし、これらのシステムとのやりとりによってチェーンコードが非確定的にならないことが重要です。

制約事項と懸念事項

  • ビジネス・ルール・サービスはブロックチェーン外部で実行されるため、他のピアの認識なしに変更される可能性があります。ブロックチェーン・ネットワーク内のさまざまな参加者の間で行われるビジネスのタイプによっては、このことが信用の問題につながる可能性があります。
  • ビジネス・ルール・サービスは、スマート・コントラクト/チェーンコードを実行することになる、ブロックチェーン・ネットワーク内のすべてのピアが使用可能でなければなりません。
  • このソリューションは、チェーンコードを非確定的なものにする可能性があります。チェーンコードは確定的であることが必須です。つまり、複数の関係者がチェーンコード内の同じ関数を同じパラメーターを使用して呼び出した場合、結果が同じでなければなりません。例えば、チェーンコード内で応答に関連付けたタイムスタンプやカウンターを使用した場合、複数のピアがチェーンコードを呼び出すと、それぞれに異なる結果になってしまいます。その場合、ブロックチェーン・ネットワーク内のピアの間で、レジャーの状態に不整合が生じます。

    チェーンコードを呼び出すごとに、コンセンサス・ネットワークに参加しているすべてのピアが、レジャーのローカル・コピーに対してチェーンコードを呼び出すことが重要です。

: 現在開発中の Hyperledger Fabric v1.0 では、アーキテクチャー自体の変更により、この問題が組織的に解決されています。

まとめ

このチュートリアルでは、チェーンコードの基礎から始め、チェーンコード内で重要なタスク (アクセス制御、データ・モデリング、イベント管理など) を実行するために使用できるビルディング・ブロックとさまざまな API を詳細に探っていきました。SampleChaincode.go ファイルに統合されたコード・スニペットをダウンロードしてください。

このシリーズの第 2 回では、テスト駆動型のチェーンコード開発を話題にします。シリーズ最終回として、ブロックチェーン・ネットワークとやりとりできる Node.js クライアント・アプリケーションを開発する方法を説明して、シリーズで取り上げている開発シナリオを締めくくります。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Cloud computing
ArticleID=1046630
ArticleTitle=Go 開発者のためのチェーンコード、第 1 回: Hyperledger Fabric v0.6 用のブロックチェーン・チェーンコードを Go 言語で作成する
publish-date=06152017