Hyperledger Fabric 向け Java チェーンコードは、台帳とのすべてのインタラクションが行われる 2 つの必須インターフェイスメソッド — init および invoke — を実装します。
チェーンコードの構造
すべての Java チェーンコードは、2 つのメソッドを定義する Chaincode インターフェイスを実装します。
public interface Chaincode {
/**
* コンテナが確立された後のインスタンス化およびアップグレードトランザクション中に呼び出されます。
* このメソッドを使用して、台帳上のチェーンコード状態を初期化します。
*/
public Response init(ChaincodeStub stub);
/**
* すべての invoke トランザクションに対して呼び出されます。ここにビジネスロジックを実装します。
* このメソッドでは、チェーンコードの状態変数の読み取りおよび書き込みが可能です。
*/
public Response invoke(ChaincodeStub stub);
}`init`:チェーンコードのインスタンス化およびアップグレード時に実行されます。キーと値のペアおよびその他の台帳状態をここで初期化します。
`invoke`:すべての invoke トランザクションで実行されます。ここからビジネスロジックのサブ関数へルーティングします。
両方のメソッドは ChaincodeStub パラメーターを受け取ります。ChaincodeStubImpl API をこのスタブ上で使用して、分散台帳からの読み取りおよび書き込みを行います。
チェーンコードのサンプル
以下のサンプルは、Hyperledger Fabric サンプルの example02(release-1.4) を基にしています。その他の公式チェーンコードサンプルは、fabric-samples リポジトリ でご確認いただけます。
基本構造
最小限のチェーンコードクラスは ChaincodeBase を継承し、init および invoke をオーバーライドします。
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 {
@Override
public Response init(ChaincodeStub stub) {
// 台帳状態をここで初期化します
}
@Override
public Response invoke(ChaincodeStub stub) {
// ビジネスロジックのサブ関数へルーティングします
}
public static void main(String[] args) {
new SimpleAssetDemo().start(args);
}
}init の実装
init は、チェーンコードのインスタンス化またはアップグレード時に実行されます。以下のサンプルでは、4 つの引数 — KEY1_NAME、VALUE1、KEY2_NAME、VALUE2 — を想定しており、putStringState を使用して 2 つのキーと値のペアを台帳に書き込みます。
@Override
public Response init(ChaincodeStub stub) {
try {
_logger.info("Init java simple chaincode");
// これが init 呼び出しであることを確認します
String func = stub.getFunction();
if (!func.equals("init")) {
return newErrorResponse("function other than init is not supported");
}
// KEY1_NAME、VALUE1、KEY2_NAME、VALUE2 の 4 つの引数を想定します
List<String> args = stub.getParameters();
if (args.size() != 4) {
return newErrorResponse("Incorrect number of arguments. Expecting 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));
// 分散台帳に 2 つのキーと値のペアを書き込みます
stub.putStringState(account1Key, args.get(1));
stub.putStringState(account2Key, args.get(3));
return newSuccessResponse();
} catch (Throwable e) {
return newErrorResponse(e);
}
}invoke の実装
invoke は、すべての invoke トランザクションに対して呼び出されます。これはルーターとして機能し、クライアントから渡された関数名に基づいてサブ関数へディスパッチします。
@Override
public Response invoke(ChaincodeStub stub) {
try {
_logger.info("Invoke java simple chaincode");
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(
"Invalid invoke function name. Expecting one of: [\"invoke\", \"delete\", \"query\"]");
} catch (Throwable e) {
return newErrorResponse(e);
}
}invoke サブ関数 — アセット転送
指定された単位数のアセットを 1 つのアカウントから別のアカウントへ転送します。3 つの引数を受け取ります:KEY1_NAME、KEY2_NAME、VALUE。
状態を変更する前に、この関数は両方のアカウントの現在残高を読み取り、存在することを確認します。台帳エントリに対する操作の前に存在チェックを行うことは、Fabric チェーンコード開発における推奨パターンです。
/**
* アカウント間でアセットを転送します。
*
* @param stub チェーンコードスタブ
* @param args 3 つの引数:送金元アカウントのキー、送金先アカウントのキー、転送金額
* @return 更新済み残高を含む成功応答、またはエラー応答
*/
private Response invoke(ChaincodeStub stub, List<String> args) {
if (args.size() != 3) {
return newErrorResponse("Incorrect number of arguments. Expecting 3");
}
String accountFromKey = args.get(0);
String accountToKey = args.get(1);
// 現在の残高を読み取り — 状態を変更する前に両アカウントが存在することを確認します
String accountFromValueStr = stub.getStringState(accountFromKey);
if (accountFromValueStr == null) {
return newErrorResponse(String.format("Entity %s not found", accountFromKey));
}
int accountFromValue = Integer.parseInt(accountFromValueStr);
String accountToValueStr = stub.getStringState(accountToKey);
if (accountToValueStr == null) {
return newErrorResponse(String.format("Entity %s not found", accountToKey));
}
int accountToValue = Integer.parseInt(accountToValueStr);
int amount = Integer.parseInt(args.get(2));
if (amount > accountFromValue) {
return newErrorResponse(
String.format("not enough money in account %s", accountFromKey));
}
// 残高を更新します
accountFromValue -= amount;
accountToValue += amount;
_logger.info(String.format("new value of A: %s", accountFromValue));
_logger.info(String.format("new value of B: %s", accountToValue));
// 更新済み残高を台帳に書き戻します
stub.putStringState(accountFromKey, Integer.toString(accountFromValue));
stub.putStringState(accountToKey, Integer.toString(accountToValue));
_logger.info("Transfer complete");
return newSuccessResponse("invoke finished successfully",
ByteString.copyFrom(accountFromKey + ": " + accountFromValue + " "
+ accountToKey + ": " + accountToValue, UTF_8).toByteArray());
}このサンプルでは、転送金額がゼロより大きいことや残高がマイナスにならないことなどの本番環境向け入力検証は省略されています。本番環境へのデプロイ前には、これらのチェックを追加してください。
delete サブ関数 — アカウント削除
台帳状態からアカウントキーを削除します。1 つの引数 — アカウントキー — を受け取ります。
/**
* 台帳からアカウントを削除します。
*
* @param stub チェーンコードスタブ
* @param args 1 つの引数:削除対象のアカウントキー
* @return 成功応答、または引数の数が不正な場合のエラー応答
*/
private Response delete(ChaincodeStub stub, List<String> args) {
if (args.size() != 1) {
return newErrorResponse("Incorrect number of arguments. Expecting 1");
}
String key = args.get(0);
// 台帳状態からキーを削除します
stub.delState(key);
return newSuccessResponse();
}query サブ関数 — アカウント残高照会
台帳状態を変更せずにアカウントのアセット価値を読み取ります。1 つの引数 — アカウントキー — を受け取ります。
/**
* アカウントのアセット価値を返します。
*
* @param stub チェーンコードスタブ
* @param args 1 つの引数:照会対象のアカウントキー
* @return アカウント残高を含む成功応答、または該当しない場合のエラー応答
*/
private Response query(ChaincodeStub stub, List<String> args) {
if (args.size() != 1) {
return newErrorResponse(
"Incorrect number of arguments. Expecting name of the person to query");
}
String key = args.get(0);
String val = stub.getStringState(key);
if (val == null) {
return newErrorResponse(String.format("Error: state for %s is null", key));
}
_logger.info(String.format("Query Response:\nName: %s, Amount: %s\n", key, val));
return newSuccessResponse(val,
ByteString.copyFrom(val, UTF_8).toByteArray());
}次のステップ
ChaincodeStubImpl API リファレンス —
ChaincodeStubで利用可能な台帳インタラクションメソッドの完全リスト公式 Hyperledger Fabric チェーンコードサンプル — より複雑なビジネスロジックパターンを含むその他のサンプル