4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

分散型台帳 Scalar DLT のサンプルアプリケーションを Ethereum で実装して比べてみた

Last updated at Posted at 2019-09-10

前回までの記事で Scalar DLT でのスマートコントラクトの書き方、アプリとの連携の仕方などをご紹介しました。
ここら辺で他のブロックチェーン基盤との比較をしてみたいと思います。
今回は Ethereum を使ってみます。

Ethereum のスマートコントラクトに関して

Ethereum のスマートコントラクトについては様々な書籍・サイトで解説されているので詳細は割愛させていただきます。
詳しく知りたい方は以下が参考になるかと思います。

スマートコントラクトを書いてみる

比較ということで、前回までに使った資産管理アプリケーションを実装してみましょう。
Ethereum のスマートコントラクトの開発には Truffle を使っていきます。
開発言語は Solidity になります。
セットアップは以下を参考にしました。

nvm、Node.js、npm、Truffle をインストールしてください。
今回使ったバージョンは以下の通りです。

$ node --version
v8.16.0

$ truffle version
Truffle v4.1.15 (core: 4.1.15)
Solidity v0.4.25 (solc-js)

早速ですがコードは以下になります。
と言いたい所ですが、結構長くなってしまったので以下から参照下さい。

各機能一覧は基本的に同じですが、以下の通りです。

メソッド名 機能
AddType 資産の種類の登録を行う
ListType 資産の種類の一覧を取得する
AddAsset 資産の登録を行う
List 資産の一覧を取得する
StatusChange 資産の状態を変更する
AssetHistory 資産の利用履歴を取得する

コード解説

全体を説明すると長くなってしまうので、特徴的な部分をかいつまんで行いたいと思います。

イベント

Ethereum ではスマートコントラクトの実行がマイニングに行われます。
つまり、非同期なのでスマートコントラクトの実行命令を出してもすぐには結果がわかりません。
そこで、イベントというものを使います。

    /*** Event ***/
    event TypeAdded (
        address _sender,
        string  _name
    );

    event AssetAdded (
        address _sender,
        string  _type,
        string  _name
    );

    event StatusChanged (
        address _sender,
        string  _id,
        string  _status
    );

イベントを起こすことで、処理の終了を伝える仕組みですね。

文字列の長さ

Solidity では正直言って文字列の扱いがしづらいです。
例えば必須な文字列のチェックなどは以下の感じでチェックします。

    function AddType(string memory name) public {
        // bytes のデータを取得
        bytes memory b = bytes(name);

        // name が空の場合はエラー
        if (b.length == 0) {
            revert("wrong argument");
        }
        ...
    }

いったん bytes に変換してから length をチェックする感じですね。
そして null も無く、空の場合は空文字 "" がセットされているというのも特徴です。

トランザクションの送信者

Ethereum のスマートコントラクトでは、トランザクションの送信者が誰なのかが msg.sender で取得出来ます。
ScalarDLT では実行時に指定していましたが、スマートコントラクト側で取得できるのは便利ですね。
送信者の情報はアドレス型で取得できるので、 mapping (連想配列)のキーにも使用出来ます。

        // アドレスごとにインデックスを保存
        i = types.length - 1;
        typeIndex[msg.sender].push(i);

こうすることで、どの資産タイプを誰が登録したのかがわかるようになっています。

存在チェック

Solidity では配列の値の検索が出来ないので、ループしてチェックする必要があります。
データ型によっては mapping (連想配列)のキーに検索対象データを突っ込むことで存在チェックも可能ですが、文字列はキーに指定できません。
というわけで以下の様にしました。

        // 重複確認
        uint256 count = typeIndex[msg.sender].length;
        uint256 i;
        for (i = 0; i < count; i++) {
            if (keccak256(types[typeIndex[msg.sender][i]]) == keccak256(name)) {
                revert("already registered");
            }
        }

ちなみに、文字列の比較も単純には出来ないので、一度ハッシュ値を取得して比較しています。

値の取得

値を変更しない=値を取得するだけのメソッドの場合は、同期的に呼び出すことが出来ます。
が、多次元配列は返すことが出来ません。
そして Solidity の内部では string では配列扱いとなっている為、 string の配列も返すことが出来ません。
というわけで、今回は文字列をスラッシュ等のセパレータで繋げて返すような形にしています。

        uint256 count = assetHistory[assetId].length;
        uint256 total = 0;
        string memory sep = "/";
        bytes memory s = bytes(sep);
        string memory colon = ":";
        bytes memory c = bytes(colon);
        bytes memory b;
        uint256 i;

        // 全体の長さを取得
        total = total + s.length;
        for (i = 0; i < count; i++) {
            // timestamp
            b = bytes(assetHistory[assetId][i].timestamp);
            total = total + b.length + c.length;
            // status
            b = bytes(assetHistory[assetId][i].status);
            total = total + b.length + s.length;
        }

        ret = new bytes(total);

        uint256 index = 0;
        uint256 j;
        for (j = 0; j < s.length; j++) {
            ret[index] = s[j];
            index++;
        }
        for (i = 0; i < count; i++) {
            // timestamp
            b = bytes(assetHistory[assetId][i].timestamp);
            for (j = 0; j < b.length; j++) {
                ret[index] = b[j];
                index++;
            }
            for (j = 0; j < c.length; j++) {
                ret[index] = c[j];
                index++;
            }
            // status
            b = bytes(assetHistory[assetId][i].status);
            for (j = 0; j < b.length; j++) {
                ret[index] = b[j];
                index++;
            }
            for (j = 0; j < s.length; j++) {
                ret[index] = s[j];
                index++;
            }
        }
        return ret;

文字列の連結も出来ないので、いったん bytes に変換し、トータルのサイズで bytes 変数を初期化し、連結していっています。
これもなかなか面倒ですね・・・

ローカルで動かしてみる

色々説明してきましたが、ほぼほぼ同じ感じで動作するようにはなっているので動作確認してみます。
今回は truffle でローカル環境で動作確認してみましょう。

$ truffle compile
$ truffle develop

compile するとソースにエラーが有ると教えてくれるのでまずやっておくのがいいでしょう。
そして develop を指定すると、ローカル環境が立ち上がります。

truffle(develop)> migrate

ローカル環境が立ち上がったら migrate と打つことでスマートコントラクトがデプロイされます。
では AssetManagement コントラクトを使っていきましょう。

truffle(develop)> am = AssetManagement.at(AssetManagement.address)

ずらずらとコードが出てきたかと思います。
これで am という変数にコントラクトが読み込まれました。
それでは、まずは資産タイプの登録からです。

truffle(develop)> am.AddType("Tablet")
{ tx: '0xe96500911c9f8f06ce5da9bc96d85ab945d3f4c2b7cb9c09b4d9e1c054fe8b51',
 receipt:
  { transactionHash: '0xe96500911c9f8f06ce5da9bc96d85ab945d3f4c2b7cb9c09b4d9e1c054fe8b51',
    transactionIndex: 0,
    blockHash: '0x9981b669a4ca4a1c0e966d6bff47e51790a87f2850be1b88a96bbd153def3bbd',
    blockNumber: 7,
    gasUsed: 91735,
    cumulativeGasUsed: 91735,
    contractAddress: null,
    logs: [ [Object] ],
    status: '0x1',
    logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000100000000000000000010000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000000000' },
 logs:
  [ { logIndex: 0,
      transactionIndex: 0,
      transactionHash: '0xe96500911c9f8f06ce5da9bc96d85ab945d3f4c2b7cb9c09b4d9e1c054fe8b51',
      blockHash: '0x9981b669a4ca4a1c0e966d6bff47e51790a87f2850be1b88a96bbd153def3bbd',
      blockNumber: 7,
      address: '0x9fbda871d559710256a2502a2517b794b482db40',
      type: 'mined',
      event: 'TypeAdded',
      args: [Object] } ] }

こちらもずらずらと出てきましたね。
前半部分がスマートコントラクトを叩いた時のトランザクションの情報です。
後半部分がスマートコントラクトから発火したイベントの情報です。
先ほど非同期と言いましたが、truffle のローカル環境はスマートコントラクトのメソッドを実行したタイミングでマイニングしてくれるので、こういった表示が可能となっています。
これは便利ですね。

念のためもう一つ資産タイプを登録していきましょう。

truffle(develop)> am.AddType("SmartPhone")

では資産タイプの一覧を取得します。

truffle(develop)> am.ListType()
'/Tablet/SmartPhone/'

登録されているのが確認出来ましたね。
先述の通り、配列が返せないのでスラッシュ区切りにしてみました。
これはやろうと思えば JSON 形式にも出来なくはないのですが・・・気が向いた方はやってみてください(笑)

続いて資産を登録します。

truffle(develop)> am.AddAsset("Tablet", "iPad", "20190901", "1001")

リストで確認してみましょう。

truffle(develop)> am.List("Tablet")
'/1001:iPad:20190901:in-stock/'

少し見難いですが、登録されているのが確認できましたね。
では借用・返却も試していきます。

truffle(develop)> am.StatusChange("1001","on-loan","20190902")
{ tx: '0x0519c59886c6f8bbd6a760e7e466ad1e5804ebf1f7b494f6caee7443d96d49fd',
...
      event: 'StatusChanged',
...

truffle(develop)> am.StatusChange("1001","in-stock","20190903")
{ tx: '0x8074827af9e9532d4ca73f3cffe19a93ae6f962efc83c4934d316760384ded5e',
...
      event: 'StatusChanged',
...

出力結果は一部省いていますが、借用・返却が出来ているの確認出来るかと思います。
では最後に貸し出しの履歴も確認しましょう。

truffle(develop)> am.AssetHistory("1001")
'/20190901:in-stock/20190902:on-loan/20190903:in-stock/'

こちらも見難いですが、timestamp 順にステータスが出力されているのがわかるかと思います。

まとめ

というわけで Solidity で ScalarDLT のスマートコントラクトを記述してみましたが、色々違いが見えてきたのでまとめてみました。

項目 ScalarDLT Ethereum
ローカル実行 エミュレータで実行 truffle ならローカル実行が可能
言語仕様 Javaに準ずる JavaScriptライクだが無い命令・出来ないことがある
文字列の扱い Javaに準ずる 非常に面倒
コントラクトの実行 コントラクトを登録したユーザに限る 一度デプロイすれば誰でも使える
実行ユーザごとの挙動制御 コントラクトを登録したユーザごとに制御される msg.sender を用いて制御可能

なんにせよ Solidity では文字列の扱いが面倒です。
なのでほとんどのプロダクトでID等に権限等を紐付けたデータのみを保持することが多いです。
それに比べて、ScalarDLT では Java で書けるので文字列も扱いやすく、バックエンドが Apache Cassandra という事もありデータの保存もあまり気にせず行えました。

そもそも、プライベート(またはコンソーシアム)とパブリックという違いが有るので、一概にどちらが良いとは言えません。
利用用途・目的に応じて基盤を選定する必要が有ると思います。

ここで、逆に Ethereum のトークン(ERC 20, ERC 721)を ScalarDLT で実装したらどうなるか気になってきませんか?
というわけで次回は逆パターンを試してみたいと思います。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?