このトピックでは、Java 言語を使用してスマートコントラクトを開発する方法について説明します。
チェーンコード構造
Java 言語のチェーンコードは、主に以下のメソッドで構成されています。
/**
* すべてのチェーンコードが実装する必要があるメソッドを定義します。
*/
public interface Chaincode {
/**
* コンテナーが確立された後にインスタンス化トランザクション中に呼び出され、チェーンコードが内部データを初期化できるようにします。
*/
public Response init(ChaincodeStub stub);
/**
* すべての Invoke トランザクションに対して呼び出されます。チェーンコードはその状態変数を変更できます。
*/
public Response invoke(ChaincodeStub stub);
}
init:チェーンコードは、初期化およびアップグレード中にこのインターフェイスを呼び出して、関連データを初期化します。
invoke:このメソッドは、主にチェーンコードの内部ビジネスロジックを実装するために使用されます。このメソッドでは、関連するビジネスを実装できます。
上記のメソッドの実装中に、ユーザーは ChaincodeStubImpl API インターフェイス を呼び出して、チェーンと対話できます。
チェーンコードの例
Hyperledger Fabric は、公式のチェーンコードの多くの例を提供しています。詳細については、「公式チェーンコードの例」をご参照ください。 Hyperledger Fabric によって提供される example02 サンプル を使用して、チェーンコード開発仕様について説明します。
簡単な例
まず、空のチェーンコード構造のサンプルコードを見てみましょう。
import java.util.List;
import com.google.protobuf.ByteString;
import io.netty.handler.ssl.OpenSsl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperledger.fabric.shim.ChaincodeBase;
import org.hyperledger.fabric.shim.ChaincodeStub;
import static java.nio.charset.StandardCharsets.UTF_8;
/*
* アセットを管理するためのシンプルなチェーンコード
*/
public class SimpleAssetDemo extends ChaincodeBase {
/*
* チェーンコードのインスタンス化期間中に Init を呼び出してデータを初期化します。
*/
@Override
public Response init(ChaincodeStub stub) {
}
/*
* Invoke は、各比較トランザクション中に呼び出されます。このメソッドには、対応するキーと値を作成および取得するための set と get が含まれている必要があります。
*/
@Override
public Response invoke(ChaincodeStub stub) {
}
public static void main(String[] args) {
new SimpleAssetDemo().start(args);
}
}
init の例
Init 関数は、チェーンコードがインスタンス化およびアップグレードされるときに呼び出されます。 Init 関数の実装プロセスでは、Java バージョンのコントラクト API リスト を使用して、パラメーターと分散 ledger に対する操作を実行できます。
@Override
public Response init(ChaincodeStub stub) {
try {
_logger.info("Java のシンプルなチェーンコードを初期化します");
// getFunction メソッドを呼び出して、現在呼び出されている関数を取得します。
String func = stub.getFunction();
if (!func.equals("init")) {
return newErrorResponse("init 以外の関数はサポートされていません");
}
// API getParameters を呼び出して、呼び出しのパラメーターを取得します。
List<String> args = stub.getParameters();
if (args.size() != 4) {
return newErrorResponse("引数の数が正しくありません。4 つ必要です");
}
// 関連データを初期化します
String account1Key = args.get(0);
int account1Value = Integer.parseInt(args.get(1));
String account2Key = args.get(2);
int account2Value = Integer.parseInt(args.get(3));
_logger.info(String.format("account %s, value = %s; account %s, value %s", account1Key, account1Value, account2Key, account2Value));
// putStringState メソッドを呼び出して、データを ledger に書き込みます。
stub.putStringState(account1Key, args.get(1));
stub.putStringState(account2Key, args.get(3));
return newSuccessResponse();
} catch (Throwable e) {
return newErrorResponse(e);
}
}
この例では、ユーザーがパラメーター KEY1_NAME
、VALUE1
、KEY2_NAME
、および VALUE2
を入力し、2 つのキーと値のペアを初期化し、putStringState を呼び出してデータを分散 ledger に書き込む必要があります。
invoke の例
invoke 関数は、ユーザー固有のビジネスロジックの実装です。 invoke、delete、query などのさまざまなビジネス処理関数を、さまざまなビジネス処理ロジックに基づいて呼び出すことができます。
// invoke は、ユーザーによって呼び出された関数を invoke、delete、query などのいくつかのサブ関数に細分化します。
@Override
public Response invoke(ChaincodeStub stub) {
try {
_logger.info("Java のシンプルなチェーンコードを呼び出します");
String func = stub.getFunction();
List<String> params = stub.getParameters();
if (func.equals("invoke")) {
return invoke(stub, params);
}
if (func.equals("delete")) {
return delete(stub, params);
}
if (func.equals("query")) {
return query(stub, params);
}
return newErrorResponse("無効な invoke 関数名です。[\"invoke\"、\"delete\"、\"query\"] のいずれかを想定しています");
} catch (Throwable e) {
return newErrorResponse(e);
}
}
invoke 関数
ビジネスロジック invoke 関数は、ビジネスロジックのアセットを accountTo に転送します。
// invoke は、2 つのキー間の値の転送を実装します。入力パラメーターは KEY1_NAME、KEY2_NAME、および VALUE です。
private Response invoke(ChaincodeStub stub, List<String> args) {
if (args.size() != 3) {
return newErrorResponse("引数の数が正しくありません。3 つ必要です");
}
String accountFromKey = args.get(0);
String accountToKey = args.get(1);
// getStringState API を使用して、accountFromKey に対応する現在のアセット情報を取得します。
String accountFromValueStr = stub.getStringState(accountFromKey);
if (accountFromValueStr == null) {
return newErrorResponse(String.format("エンティティ %s が見つかりません", accountFromKey));
}
int accountFromValue = Integer.parseInt(accountFromValueStr);
// accountToKey の現在のアセット情報を取得します。
String accountToValueStr = stub.getStringState(accountToKey);
if (accountToValueStr == null) {
return newErrorResponse(String.format("エンティティ %s が見つかりません", accountToKey));
}
int accountToValue = Integer.parseInt(accountToValueStr);
int amount = Integer.parseInt(args.get(2));
if (amount > accountFromValue) {
return newErrorResponse(String.format("アカウント %s に十分なお金がありません", accountFromKey));
}
// ビジネスロジック:アセットの転送を実装します。
accountFromValue -= amount;
accountToValue += amount;
_logger.info(String.format("A の新しい値:%s", accountFromValue));
_logger.info(String.format("B の新しい値:%s", accountToValue));
// 更新されたアセットをアカウントブックに更新します。
stub.putStringState(accountFromKey, Integer.toString(accountFromValue));
stub.putStringState(accountToKey, Integer.toString(accountToValue));
_logger.info("転送完了");
return newSuccessResponse("invoke が正常に完了しました", ByteString.copyFrom(accountFromKey + ": " + accountFromValue + " " + accountToKey + ": " + accountToValue, UTF_8).toByteArray());
}
getStringState API を使用して、KEY_NAME に対応する合計アセット値を取得します。
ビジネスロジックを呼び出して、アセットユニットの数を転送します。
putStringState API を呼び出して、更新されたアセットステータスをアカウントブックに書き込みます。
上記の実装は、転送に似た単純なロジックですが、転送量がゼロより大きい、残高が負ではないなどのパラメーターの有効性を検証しません。
delete 関数
ビジネスロジック delete 関数は、ビジネスロジックのアカウント削除関数を実装します。
// 状態からエンティティを削除します
private Response delete(ChaincodeStub stub, List<String> args) {
if (args.size() != 1) {
return newErrorResponse("引数の数が正しくありません。1 つ必要です");
}
String key = args.get(0);
// ledger の状態からキーを削除します
stub.delState(key);
return newSuccessResponse();
}
query 関数
ビジネスロジック query 関数は、ビジネスロジックのアカウントクエリ関数を実装します。 GetState API を呼び出して、対応するアカウントのアセットをクエリできます。
// チェーンコードのクエリを表すクエリコールバック
private Response query(ChaincodeStub stub, List<String> args) {
if (args.size() != 1) {
return newErrorResponse("引数の数が正しくありません。クエリ対象の人の名前が必要です");
}
String key = args.get(0);
//byte[] stateBytes
String val = stub.getStringState(key);
if (val == null) {
return newErrorResponse(String.format("エラー:%s の状態が null です", key));
}
_logger.info(String.format("クエリ応答:\n名前:%s、金額:%s\n", key, val));
return newSuccessResponse(val, ByteString.copyFrom(val, UTF_8).toByteArray());
}