Edited at

分散型台帳ソフトウェアScalar DLのSandbox環境を試してみた


はじめに

Hyperledger fabric, Ethereumなどのように、ブロックチェーンベースの分散型台帳ソフトウェアであるScalar DLのSandboxが一般利用出来るようになったとのことなので、登録して軽く遊んでみました。

この記事はある程度ブロックチェーン、スマートコントラクトなどの知識がある人向けで、記事内での詳細な用語についての説明はしません。


Scalar DLとは

Scalar社の開発している、ブロックチェーンをベースとする分散型台帳(DL)ソフトウェア。

以下公式サイトの説明。


Scalar DLは、Scalar社が開発する分散型台帳ソフトウェアです。Scalar DLは、電子署名が付与されたスマートコントラクトを分散トランザクションの形式で実行し、その実行結果を複数の独立したコンポーネントで連鎖的に管理することで高い耐改ざん性を実現します。また、従来のブロックチェーンでは実現が困難であった高いスケーラビリティ、強い一貫性、確定性を実現しており、本格的なエンタープライズシステムにおいて求められる要件を満たします。Scalar DLは、現在オンプレミスおよびAmazon Web Service上での利用が可能であり、今後は多様なクラウド環境での利用が可能となる予定です。


従来のブロックチェーン技術に比べて、よりビジネスユーズを意識したソフトウェアらしいです。


Scalar DL Sandboxとは

Scalar DLを誰でも試すことができるように同社がホスティングしている最小環境。


環境


  • Oracle JDK 8以上

  • GitHubアカウント(利用登録に必要)


利用登録

https://scalar-labs.com/sandbox/

上記のURLからsandboxを利用するためのアクセス権をリクエストします。

リクエスト後しばらくすると登録完了メールが届き、利用に必要な鍵ペア、証明書、設定ファイルなどが配布されます(自分は数十分で届きました)。


セットアップ

リポジトリをクローン後、先ほどダウンロードした鍵ペア、設定ファイルをリポジトリ直下に置きます。

$ git clone https://github.com/scalar-labs/scalardl-client-sdk.git

$ cd scalardl-client-sdk
$ ls (...) <username>-key.pem <username>.pem client.properties


サンプルコントラクトの実行

基本的に公式チュートリアルの通りでうまくいきましたが、現状日本語の文献が無いためメモ書き程度に残します。


証明書の登録

Scalar DL Networkに参加できるように先ほどダウンロードした証明書を登録。

$ client/bin/register-cert -properties client.properties

※僕はここでio.grpc.StatusRuntimeExceptionが出て詰まりました。原因はmacOSにプリインストールされていたJavaバージョンのようで、こちらからOracle JDKを入れ直したら正しく動きました。


コントラクトの登録

cloneしたリポジトリ内にStateUpdaterとStateReaderというサンプルコントラクトのsourceファイルがあるのでコンパイルします。

$ ./gradlew assemble

StateUpdaterとStateReaderというサンプルコントラクトのclassファイルが生成されるので、それをネットワークに登録します。

$ client/bin/register-contract -properties client.properties -contract-id <username>-StateUpdater -contract-binary-name com.org1.contract.StateUpdater -contract-class-file build/classes/java/main/com/org1/contract/StateUpdater.class

$ client/bin/register-contract -properties client.properties -contract-id <username>-StateReader -contract-binary-name com.org1.contract.Reader -contract-class-file build/classes/java/main/com/org1/contract/StateReader.class

<username>の部分は各自変更してください(sandbox内は共有namespaceなので一意なcontract-idでないといけないようです)。以下同様。


コントラクトの実行

今登録したStateUpdaterというコントラクトを実行してみます。


$ client/bin/execute-contract -properties client.properties -contract-id <username>-StateUpdater -contract-argument '{"asset_id": "<username>-myasset", "state": 3}'

status: 200

というメッセージが返ってきたら成功です。

次に正しくstateが更新されているか確認するためにStateReaderコントラクトを実行します。

$ client/bin/execute-contract -properties client.properties -contract-id <username>-StateReader -contract-argument '{"asset_id": "<username>-myasset"}'


output

{

"age": 0,
"input": {},
"output": {
"state": 3
},
"contract_id": "shu22203-StateUpdater",
"argument": {
"asset_id": "shu22203-myasset",
"state": 3,
"nonce": "8e106ff3-e740-471b-a1b9-bf01370b09cd"
},
"signature": "MEUCIFFQbtNpt3WuRSq+kiK+Q5r7vroxFQekL3ZdpglbwAF3AiEAqEfF9B/YwqacvWwYoFqd7NZRyO2w+irGhKW+6WN2s2s=",
"hash": "QQ2t28Bva2i9rdJM7gdZHxdyv5Ne7MTf4ei5lpvRl7c=",
"prev_hash": ""
}

結果はJSON形式で出力されます。(可読性のために整形しています)

outputに正しくstateが登録されていることが分かります

contract_id、argumentは実行したコントラクトID、引数などの情報もあります。

また、まだ1度も登録されたことのないasset_idなので、prev_hashはありません(BitcoinなどにおけるGenesis Blockのようなもの)

今度は同じasset_idに対して異なる値をセットしてみます。

$ client/bin/execute-contract -properties client.properties -contract-id <username>-StateUpdater -contract-argument '{"asset_id": "<username>-myasset", "state": 100}'


output

{

"age": 1,
"input": {
"shu22203-myasset": {
"age": 0,
"data": {
"state": 3
}
}
},
"output": {
"state": 100
},
"contract_id": "shu22203-StateUpdater",
"argument": {
"asset_id": "shu22203-myasset",
"state": 100,
"nonce": "3a7cbc8c-a0cf-41dc-bff7-e56a2e3e2972"
},
"signature": "MEYCIQCv3/AtMh0r2RxBzwbT+fXJwmqSfsXZDs4ulTeyZOBBbAIhAMHL2/isig+uwcjLgLDNCeQsh4BRTu/ewkLIwHtvMzTb",
"hash": "+E6U159bVDDUKvtAyPFxKxf6c+ipzenNp14UZziySnU=",
"prev_hash": "QQ2t28Bva2i9rdJM7gdZHxdyv5Ne7MTf4ei5lpvRl7c="
}

今度はinputの値に前回(age=0)の状態が入っており、stateも正しく更新されています。

また、prev_hashにage=0のブロックのhash値が入り、自身にはまた別の異なるhash値が入っておりQQ2t28Bva2i9rdJM7gdZHxdyv5Ne7MTf4ei5lpvRl7c=+E6U159bVDDUKvtAyPFxKxf6c+ipzenNp14UZziySnU=とハッシュチェーンが形成されているのが分かります。


自作コントラクトの作成・実行

次は自分でコントラクトを作り、登録、実行していきます。cloneしたリポジトリの src/main/java/com.org1.contract 内にStateReaderとStateUpdaterのソースがあるのでそれを見ながら作ります。

今回はあるアセットからあるアセットへと送金を行うコントラクトを作ってみます。


StateTransfer.java

package com.org1.contract;

import com.scalar.ledger.asset.Asset;
import com.scalar.ledger.contract.Contract;
import com.scalar.ledger.ledger.Ledger;

import javax.json.Json;
import javax.json.JsonObject;
import java.util.Optional;

public class StateTransfer extends Contract {

@Override
public JsonObject invoke(Ledger ledger, JsonObject argument, Optional<JsonObject> properties) {
String fromAssetId = argument.getString("from");
String toAssetId = argument.getString("to");
Integer state = argument.getInt("state");
if (fromAssetId == null || toAssetId == null || state == null) {
throw new ContractContextException("please set from, to, state in the argument");
}

Optional<Asset> fromAsset = ledger.get(fromAssetId);
Optional<Asset> toAsset = ledger.get(toAssetId);

if (!fromAsset.isPresent()) throw new ContractContextException("Could not find from asset");
if (fromAsset.get().data().getInt("state") - state < 0) throw new ContractContextException("From asset doesn't have enough balance");

int fromAfterState = fromAsset.get().data().getInt("state") - state;
int toAfterState = toAsset.map(asset -> asset.data().getInt("state") + state).orElse(state);

ledger.put(fromAssetId, Json.createObjectBuilder().add("state", fromAfterState).build());
ledger.put(toAssetId, Json.createObjectBuilder().add("state", toAfterState).build());

return null;
}
}


ここではargumentとしてfrom, to, stateを取り、fromで指定したアセットからtoへとstate分だけ数値を移動させるようなコードを書きました。お試しなのでエラーハンドリングなどは適当です。

※追記(2019/02/21)

開発者の方からのコメントにより、エラーハンドリングを一部修正しました。


コントラクトの登録

今作ったコントラクトを登録します。

$ gradle assemble // classファイルへのビルド

$ client/bin/register-contract -properties client.properties -contract-id <username>-StateTransfer -contract-binary-name com.org1.contract.StateTransfer -contract-class-file build/classes/java/main/com/org1/contract/StateTransfer.class


A→Bへの送金コントラクトの実行

正しく実行されたら、A→Bへと送金してみます。


A(state=1000)のアセットを作成

送り元のアセットを作ります。

$ client/bin/execute-contract -properties client.properties -contract-id <username>-StateUpdater -contract-argument '{"asset_id": "shu22203-A", "state": 1000}'


コントラクトの実行

A→Bへと100円送金します。登録されていないasset_idのアセットについては自動的に作成されるので初期化の必要はありません(実際の送金で送り先がundefinedなことは無いと思いますが)。

$ client/bin/execute-contract -properties client.properties -contract-id <username>-StateTransfer -contract-argument '{"from": "shu22203-A", "to": "shu22203-B", "state": 100}'


実行結果

本当に送れたか見てみます。(コマンドは省略)


Aのoutput

{

"age": 1,
"input": {
"shu22203-A": {
"age": 0,
"data": {
"state": 1000
}
}
},
"output": {
"state": 900
},
"contract_id": "shu22203-StateTransfer",
"argument": {
"from": "shu22203-A",
"to": "shu22203-B",
"state": 100,
"nonce": "c51103b5-56ac-456e-aaba-49909f2d5be6"
},
"signature": "MEYCIQCc06eb1lBBgdr4OKUmuadINFj+NX6lu7zHV99HKKuOZQIhAK56EjLx6s2jWb0Fq88XSn3vnTSe3lns/fPeqci7Lwkf",
"hash": "lvhiG0jxiGgnvo9x2j5CL0db+cBhiT8Ow/77nO7NTQ4=",
"prev_hash": "BjXbQHW1qDNJG2foATFK2QXHFochtGUBJUqyNFTNWjs="
}


Bのoutput

{

"age": 0,
"input": {
"shu22203-A": {
"age": 0,
"data": {
"state": 1000
}
}
},
"output": {
"state": 100
},
"contract_id": "shu22203-StateTransfer",
"argument": {
"from": "shu22203-A",
"to": "shu22203-B",
"state": 100,
"nonce": "c51103b5-56ac-456e-aaba-49909f2d5be6"
},
"signature": "MEYCIQCc06eb1lBBgdr4OKUmuadINFj+NX6lu7zHV99HKKuOZQIhAK56EjLx6s2jWb0Fq88XSn3vnTSe3lns/fPeqci7Lwkf",
"hash": "EvG2ztRuMQWPB+vdCdhQ7McYj+zLkGcD1csw8h0kuEs=",
"prev_hash": ""
}

正しくそれぞれのstateが900と100になりました。nonce, signatureを見ると、同じトランザクションによって値が書き換えられたということが分かります。また、Aについては先ほどstate = 1000で初期化したトランザクションのhash値がprev_hashに登録されています。


B→Aへの送金コントラクトの実行

今度はBからAへ50円返金してみます。

$ client/bin/execute-contract -properties client.properties -contract-id shu22203-StateTransfer -contract-argument '{"from": "shu22203-B","to": "shu22203-A", "state": 50}'


Aのoutput

{

"age": 2,
"input": {
"shu22203-A": {
"age": 1,
"data": {
"state": 900
}
},
"shu22203-B": {
"age": 0,
"data": {
"state": 100
}
}
},
"output": {
"state": 950
},
"contract_id": "shu22203-StateTransfer",
"argument": {
"from": "shu22203-B",
"to": "shu22203-A",
"state": 50,
"nonce": "5289721d-3daa-4d69-9965-ce6a4229876b"
},
"signature": "MEUCIQCYVb5ypfdJ7k+DtKWd6gElq8jJqGmg/uTytZ8KmGfMcQIgEPY5F+fABuFv3iaIMSjC61IUyyKW+JS8t4GL3ASW+t0=",
"hash": "vOaYUCgcMj1rWJ0ZfXrIZe31Y7tm5x4aK57H88BQHOM=",
"prev_hash": "lvhiG0jxiGgnvo9x2j5CL0db+cBhiT8Ow/77nO7NTQ4="
}


Bのoutput

{

"age": 1,
"input": {
"shu22203-A": {
"age": 1,
"data": {
"state": 900
}
},
"shu22203-B": {
"age": 0,
"data": {
"state": 100
}
}
},
"output": {
"state": 50
},
"contract_id": "shu22203-StateTransfer",
"argument": {
"from": "shu22203-B",
"to": "shu22203-A",
"state": 50,
"nonce": "5289721d-3daa-4d69-9965-ce6a4229876b"
},
"signature": "MEUCIQCYVb5ypfdJ7k+DtKWd6gElq8jJqGmg/uTytZ8KmGfMcQIgEPY5F+fABuFv3iaIMSjC61IUyyKW+JS8t4GL3ASW+t0=",
"hash": "Nc/yY3fgcyVeN5MJb3FSZQXS2LyzJ5lDgx/RoJhXGto=",
"prev_hash": "EvG2ztRuMQWPB+vdCdhQ7McYj+zLkGcD1csw8h0kuEs="
}

無事にAのstateが950円に、Bのstateが50円となり、正しく送金が完了しました。

先ほどはinputがアセットAのみでしたが、今回はアセットBも増えています。


おわりに

今回は分散型台帳ソフトウェアであるScalar DLのSandboxを軽く触ってみました。

EthereumやHyperledger fabricは環境を作るのがなかなか大変(バージョンが頻繁に変わるため既存の文献でうまくいかなかったりすることも多い)で気軽にスマートコントラクトの体験ができない分、Sandboxという形で手軽に試すことが出来るのは良かったです。

ただ、Scalar DLの特徴である耐改ざん性、スケーラビリティなどについては、このSandboxだけで実感するのは難しいと思います。

途中詰まったところや疑問に思ったところが数ヶ所あり公式のgoogleフォーラムで質問したのですが、すぐに返信がもらえたのでその点は好印象でした。

今回は軽く触ってみただけなので、次回は簡単なアプリケーションを作ったり、Ethereum, Hyperledger fabricとの比較なども行えたらと思います。


参考