簡単な銀行口座申請
このページは英語版のページが機械翻訳されたものです。英語版との間に矛盾または不一致がある場合 は、英語版を正としてください。
概要
これは単純な銀行口座アプリケーションであり、scalardl
リポジトリ にあります。ユーザーが実行できるアクションは、アカウントの作成、アカウント履歴の表示、アカウントへの資金の入金、アカウントからの資金の引き出し、アカウント間の資金の転送です。 アカウントで実行されたすべてのアクションは ScalarDL に記録されます。これは、ブロックチェーンがブロックを記録する方法と同様に、アカウント履歴が改ざん明示的な方法で記録されることを意味します。 これは、アカウント履歴が (意図的かどうかに関わらず) 変更された場合、これを検出することが可能であることを意味します。
ここでは物事を簡単にするために、銀行がすべてのコントラクトを実行するための秘密鍵を保持していると仮定します (これがどのように機能するかについては、以下を参照してください)。 おそらくこれは、この銀行アプリケーションを実際に使用する方法ではありません。 この場合、悪意のあるアカウント マネージャーがユーザーのアカウント履歴を実際に変更する可能性があります。たとえば、単にアカウントを再作成し、偽のデータを入力するだけです。 より意味のある設定は、銀行が口座に入金するための秘密鍵を所有し、各ユーザーが自分の秘密鍵を使用して引き出しと送金のコントラクト を登録することです。 その場合、銀行のみが口座に資金を移動でき、ユーザーのみが口座から資金を移動できます。
このアプリケーションでは、次の 5 つのコントラクトを使用します。
これらのコントラクトは銀行によって登録され、銀行はそれぞれ、口座履歴の表示、口座の作成、口座への入金、口座間の資金移動、口座からの資金の引き出しを行うことができます。
このアプリケーションの全体的なアーキテクチャは次のようになります。 (この使用例は簡素化のためのものであり、実際には少し異なる可能性があることに再度注意してください。)
このサンプルアプリケーションの前提条件
- Eclipse Temurin の OpenJDK LTS バージョン (8, 11, 17, or 21)
- 上記の LTS バージョンの使用をお勧めしますが、他の非 LTS バージョンでも動作する可能性があります。
- また、他の JDK もこのサンプル アプリケーションで動作するはずですが、テストは行っていません。
アプリケーションを試してみる
ScalarDL Client SDKをダウンロードします。 ScalarDL が実行されていることを確認し、必要なコントラクトをすべて実行して登録します。
./gradlew build
cd contract
SCALAR_SDK_HOME=/path/to/scalardl-client-sdk ./register
IntelliJ (または選択した IDE) を使用するか、プロジェクトのホーム ディレクトリで gradle bootRun
を実行して、アプリケーションを実行します。 アプリと対話するために HTTP リクエストを送信できるサーバーを localhost:8080
上に作成する必要があります。 詳細については、API ドキュメント を参照してください。 HTTP リクエストを作成するには、Postman が非常に優れていることがわかりました。
ScalarDL アプリケーションの作成に関する短いチュートリアル
Spring Boot を使用して、コントラクトと対話する Web サービスを作成することにしました。 もちろん、これが唯一の選択肢ではありません。 別の選択肢は、たとえば アセット管理アプリケーション で行われたように、コマンド ライン インターフェイスを作成することです。 そこには、ScalarDL 用のアプリケーションを作成するための非常に優れたチュートリアルもあります。
このチュートリアルでは、Web サービスまたはコマンド ライン インターフェイスのレベルでの詳細については説明せず、代わりにアプリケーションと ScalarDL の間の対話に焦点を当てます。 コントラクトの作成方法、コントラクトの登録方法、そして ScalarDL SDK を使用してアプリケーションからこれらのコントラクトを呼び出す方法について説明します。
コントラクト
コントラクトは、 JacksonBasedContract
クラスを拡張し、 invoke
メソッドをオーバーライドする Java クラスです。 Deposit.java
コントラクトを詳しく見てみましょう。
package com.scalar.application.bankaccount.contract;
import com.fasterxml.jackson.databind.JsonNode;
import com.scalar.dl.ledger.statemachine.Asset;
import com.scalar.dl.ledger.contract.JacksonBasedContract;
import com.scalar.dl.ledger.exception.ContractContextException;
import com.scalar.dl.ledger.statemachine.Ledger;
import java.util.Optional;
import javax.annotation.Nullable;
public class Deposit extends JacksonBasedContract {
@Override
public JsonNode invoke(
Ledger<JsonNode> ledger, JsonNode argument, @Nullable JsonNode properties) {
if (!argument.has("id") || !argument.has("amount")) {
throw new ContractContextException("a required key is missing: id and/or amount");
}
String id = argument.get("id").asText();
long amount = argument.get("amount").asLong();
if (amount < 0) {
throw new ContractContextException("amount is negative");
}
Optional<Asset<JsonNode>> asset = ledger.get(id);
if (!asset.isPresent()) {
throw new ContractContextException("account does not exist");
}
long oldBalance = asset.get().data().get("balance").asLong();
long newBalance = oldBalance + amount;
ledger.put(id, getObjectMapper().createObjectNode().put("balance", newBalance));
return getObjectMapper()
.createObjectNode()
.put("status", "succeeded")
.put("old_balance", oldBalance)
.put("new_balance", newBalance);
}
}
このコントラクトが適切に機能するためには、ユーザーはアカウントの id
と amount
を指定する必要があります。 したがって、最初に行うことは、引数にこれら 2 つのキーが含まれているかどうかを確認し、含まれていない場合は ContractContextException
をスローすることです。
注記: ContractContextException
はコントラクト内で唯一スロー可能な例外であり、回復不可能なエラーが発生した場合は常にスローされる必要があります。
したがって、id
と amount
があると仮定して、amount
に対して簡単な非負のチェックを行い、負の場合は再度 ContractContextException
をスローします。 これで、ledger
を操作する準備が整いました。
ledger
で呼び出すことができるメソッドは 3 つあります: get(String s)
、 put(String s, JsonNode jsonNode)
、および scan(AssetFilterassetFilter)
です。 get(String s)
は Ledger からアセット s
を取得します。 put(String s, JsonNode jsonNode)
は、アセット s
をデータ jsonNode
に関連付け、アセットの年齢を増やします。 scan(AssetFilterassetFilter)
は、AssetFilter
で指定されたアセットの履歴のバージョンを返します。
注記: Ledger ではブラインド書き込みは許可されていません。つまり、特定のアセットに対して put
を実行する前に、まずそのアセットを get
する必要があります。 さらに、 scan
は読み取り専用コントラクトでのみ許可されます。つまり、1 つのコントラクトで scan
と put
の両方を行うことはできません。
コントラクトの残りの部分は単純な方法で進められます。 まず Ledger からアセットを get
し、現在の残高を取得し、それに預金額を追加し、最後に新しい残高でアセットを Ledger に put
。
最後に JsonNode
を返す必要があります。 JsonNode
に何が含まれるかは、コントラクトの設計者次第です。 ここでは、status
メッセージ、old_balance
、および new_balance
を含めることにしました。
必要に応じて、このアプリケーションが使用する他のコントラクトを scalardl
リポジトリ内のこのサンプルの contract
フォルダー で表示できます。
コントラクトを作成したら、それを編集する必要があります。 これは次のようにして実行できます。
./gradlew build
認定資格とコントラクトの登録
これでコントラクトを作成し、まとめたはずです。 ただし、それらを実行する前に、ScalarDL ネットワークに登録する必要があ ります。 ScalarDL Client SDK client/bin
ディレクトリで利用可能なツールを利用して、コントラクトを登録して実行します。 このディレクトリにアクセスできることを確認してください。
ここで、証明書 (例: client.pem
) とそれに対応する秘密鍵 (例: client-key.pem
)、および ScalarDL を起動して実行する必要があります。 構成に合わせて client.properties
(conf
ディレクトリにあります)を編集します。 次のような行が含まれている必要があります。
scalar.dl.client.server.host=localhost
scalar.dl.client.server.port=50051
scalar.dl.client.cert_holder_id=alice
scalar.dl.client.cert_path=conf/client.pem
scalar.dl.client.private_key_path=conf/client-key.pem
すべてが正しく設定されていれば、次のように証明書を ScalarDL ネットワークに登録できるはずです。
cd contract
${SCALAR_SDK_HOME}/client/bin/register-cert --properties ../conf/client.properties
成功すると、ステータス コード 200 が返されます。
コントラクトを登録するには、次の形式を使用して conf
ディレクトリに contracts.toml
ファイルを作成します。
[[contracts]]
contract-id = "create-account"
contract-binary-name = "com.scalar.application.bankaccount.contract.CreateAccount"
contract-class-file = "build/classes/java/main/com/scalar/application/bankaccount/contract/CreateAccount.class"
[[contracts]]
contract-id = "deposit"
contract-binary-name = "com.scalar.application.bankaccount.contract.Deposit"
contract-class-file = "build/classes/java/main/com/scalar/application/bankaccount/contract/Deposit.class"
[[contracts]]
contract-id = "transfer"
contract-binary-name = "com.scalar.application.bankaccount.contract.Transfer"
contract-class-file = "build/classes/java/main/com/scalar/application/bankaccount/contract/Transfer.class"
この例では、 CreateAccount.java
、Deposit.java
、Transfer.java
の 3 つのコントラクトを登録します。 contract-binary-name
と contract-class-file
は決まっていますが、 contract-id
は自由に選択できます。 以下で説明するように、contract-id
は、 ClientService
を使用して特定のコントラクトを参照する方法です。
toml ファイルを作成したら、次のように指定したすべてのコントラクトを登録できます。
${SCALAR_SDK_HOME}/client/bin/register-contracts --properties ../conf/client.properties --contracts-file ../conf/contracts.toml
正常に登録された各コントラクトはステータス コード 200 を返す必要があります。
コントラクトの実行
必要に応じて、登録されているコントラクトを実行できるようになります。 たとえば、登録コントラクトを使用していくつかの口座を作成し、そのうちの 1 つの口座に資金を入金し、これらの資金の一部をもう一方の口座に転送して、口座履歴を確認します。
ID が a111
と b222
の 2 つのアカウントを作成します。 (コントラクト ID には任意の文字列を指定できます。)
${SCALAR_SDK_HOME}/client/bin/execute-contract --properties ../conf/client.properties --contract-id create-account --contract-argument '{"id": "a111"}'
${SCALAR_SDK_HOME}/client/bin/execute-contract --properties ../conf/client.properties --contract-id create-account --contract-argument '{"id": "b222"}'
ここで、アカウント a111
に 100 を入金します。
${SCALAR_SDK_HOME}/client/bin/execute-contract --properties ../conf/client.properties --contract-id deposit --contract-argument '{"id": "a111", "amount": 100}'
最後に、25 を a111
から b222
に転送します。
${SCALAR_SDK_HOME}/client/bin/execute-contract --properties ../conf/client.properties --contract-id transfer --contract-argument '{"from": "a111", "to": "b222", "amount": 100}'
a111
の残高履歴は次のようにして確認できます。
${SCALAR_SDK_HOME}/client/bin/scalardl execute-contract --properties ../conf/client.properties --contract-id account-history --contract-argument '{"id": "a111"}'
次のような結果が表示されます:
Contract result:
{
"status" : "succeeded",
"history" : [ {
"id" : "a111",
"age" : 2,
"data" : {
"balance" : 0
}
}, {
"id" : "a111",
"age" : 1,
"data" : {
"balance" : 100
}
}, {
"id" : "a111",
"age" : 0,
"data" : {
"balance" : 0
}
} ]
}