前回までの記事で 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 で実装したらどうなるか気になってきませんか?
というわけで次回は逆パターンを試してみたいと思います。