Paoの技術力を磨くブログ

機械学習やブロックチェーン等の技術を身に付けていくブログです。

【Ethereum】ブロックチェーンのじゃんけんdAppゲームを作ってみた

ブロックチェーンを使ったジャンケンゲーム(デモ)のdAppを作りました。
ゲーム自体はジャンケンなので大して面白くないですが、 ジャンケンというゲームがブロックチェーンの特性理解およびdappsの勉強になるいい題材と思ったため ジャンケンのゲームを作りました。

ゲームの紹介

ゲームのページ

Ethereum ジャンケンゲーム

※PC用サイトです。
※ジャンプする前にMetaMaskでログインしてください。
※ゲームの具体的な手順はリンク先の下の方に記載されています。

ゲームのポイント

ブロックチェーンのパブリックな特性ゆえに、処理フローを工夫しています。
これをしないと後出しジャンケンが可能になってしまうためです。
詳しくは↓の「ブロックチェーンならではのゲームの特徴」に書いています。

作ったゲームの概要

  • 2人対戦用のジャンケンゲーム
  • HostPlayerが先にゲームを作り、GuestPlayerがそのゲームに参加する
  • HostPlayerが賭け金(Ether)を設定でき、敗者から勝者に賭け金が渡される
    • Hostがゲームを作る時点で賭け金を設定
    • それぞれゲームに参加する時点でコントラクトに賭け金をデポジット
    • 勝負後、コントラクトは勝者のアドレスにデポジットした金額を送金する
    • あいこの場合、それぞれのプレイヤーに賭け金を返す

ゲームの前提

  • HostとGuestを同じユーザがプレイするデモゲーム
  • RopstenNet(イーサリアムのテストネット)上に存在
  • MetaMaskのインストールが必要
    • PCのみ利用可能
  • MetaMaskにRopstenのEtherを持ったアカウントが2つ必要

MetaMaskのインストール及びRopstenのEtherの貰い方は下記サイトを参考にすればと思います。

ブロックチェーンならではのゲームの特徴

課題:シンプルにやると後出しジャンケンが可能になってしまう

dappsでは一般的にユーザの操作内容やデータをブロックチェーン上に記録します。
ジャンケンの場合、ユーザが出した手(グー、チョキ、パー)はEthereumのブロックチェーン上に記録されます。
その結果、先に出したプレーヤー(ここでいうHost)の手をGuestがブロックチェーンから見た上で勝てる手を出すという、後出しジャンケンが可能になってしまいます。

解決策:ランダム文字列と組み合わせたハッシュ化を活用

後出しジャンケンをさせないために、ランダムな文字列とハッシュ化を使っています。
ハッシュ化とは、元のデータからハッシュ値と呼ばれる規則性のない値を計算することで、 ハッシュ値から元のデータを逆算するのは、ほぼ不可能と言われています。

Hostは、ジャンケンの手とランダム文字列を最初のMakeGameで提出しますが、 それらを結合して事前にハッシュ化したデータのみをブロックチェーン上に記録します。
こうすることで、GuestPlayerはHostが何の手を出したのか分からず、後出しができなくなります。
そしてGuestPlayerが手を提出した後に、Hostハッシュ化する前の手とランダム文字列を提出し、 それをEVM側でハッシュ化し、最初のハッシュ値と同じかどうかをチェックし、改竄していないことを確認します。

実装に関して

以下は開発する人向けの細かい話です。

利用したライブラリ、ツール等

  • Web3js:フロントエンドにほぼ必須ですね。
  • Truffle:テストやデバッグがかなり楽になりますね。
  • Remix:デバッグに使いました。EVM上での処理の流れとかも見れるの便利すぎるとようやく気付きました。
  • MetaMask:PCでの利用として現時点では必須ですね。今後はOperaのようにスマホブラウザでもアクセスできる流れでしょう。

その他実装した機能

賭け金のデポジットおよび移動

賭けれないと面白くないので、HostとGuestが同じ額だけBetして、敗者から勝者に賭け金が移動するようにしました。
よくある方法でゲームに参加する時の関数をpayableにしていて、EtherをBetできるようにしておき、 コントラクトがお金を一時的に預かり、決着が決まったら勝者もしくは引き分けの者に送金するようにしています。

コントラクトのデプロイ

デフォルトで指定しているコントラクトアドレスに複数人が同時アクセスすると、ゲームのStateがぐちゃぐちゃになるので、 各ユーザが自分用のコントラクトをデプロイできるようにしています。
Web3.jsでデプロイ済みのコントラクトオブジェクトと、デプロイ時のbytecode、gasを指定すれば下記のようにデプロイできます。
本当はゲーム管理用のコントラクトと組み合わせた方が良さそうですが。

イベントのウォッチ、トランザクション状況の表示

ユーザの操作によりトランザクション発行するだけでは、ブロックチェーン上で正しく受理されたかどうかは分かりません。
そこで、Solidity側でイベントを定義して、それをフロントエンド(Web3js)側でウォッチしておき、その状況をユーザに伝えることが望ましいです。
今回のジャンケンでは、下記のような対応をしました。

ハッシュ値の確認

上記の通り、ハッシュ値を確認することで手の改ざんなどがないかをチェックしています。
具体的には、「ローカルでハッシュ化してコントラクトに保存されたもの」と「コントラクトに送られた手とランダム文字列からハッシュ化したもの」について 比較しています。 それぞれのハッシュ値を確認できるような機能をページに用意しました。

残っている課題・改善点

別技術との組み合わせによる改善

今回は純粋にパブリックチェーンを使った場合のジャンケンとして実装しましたが、 Hostが一度ハッシュを提出した上で、再度手を提出するとか、ただのジャンケンにしては面倒すぎます。

まだ勉強不足ですが、「zk-snarks」や「Enigma」といった暗号化関連技術や、「state-channel」を使うなどして、 この面倒なフローを改善できるかもしれません。
それでこそブロックチェーンの可能性も広がると思うので、この辺りを追求していきたいです。  

適切なゲームサイクル等の管理

デモプレイ用に作っているため、細かい条件チェックなどは実装していません。 例えば、Hostがゲームを作った後に、Guestが来なかったりHostが再度手の提出をしなかった場合、 最初にベットしたEtherはコントラクトに残ります。
本来であれば、一定時間経ったら資金を返す、再提出しなければペナルティとして没収するなどを実装すべきだと思います。

コントラクトの設計

今回は一つのコントラクトに全てのFunctionやState変数をまとめて実装していますが、 本格的にやるならば、ゲーム毎や、複数ゲームを管理するコントラクト等複数に分けた方が良いかと思います。

セキュリティ対策強化

Consensysのセキュリティプラクティスは読みましたが、 このコントラクトでそれを網羅して、しっかりと試験をしたわけではありません。
メインネットにデプロイするなら、ここはしっかりやりたいですね。
OpenZeppelinの導入もしたいところです。

Gas節約のための最適化

Solidityの書き方によってユーザがゲームするときの手数料GASが変わってきます。
無駄のないコードを書くことでこのGASの節約ができますが、今回はそこまで考えていません。

別ユーザ同士の対戦

別にページを二つに分ければ出来るのですが、 誰かがサイトにアクセスしてくれても、同時にサイトにアクセスしているユーザがいないと遊べないので、 やめました。
あくまでデモであり、過疎ったオンラインゲームを作りたいわけではないので。。

フロントエンド側の改良

Javascript初心者過ぎてフルスクラッチで書きましたが、 よほど腕に自信ない限り、React+Reduxといったフレームワークを活用したほうが分かりやすく質の良いソースになるはず。
Web3+React+Reduxといった組み合わせが主流になりそうな感じもあるので、勉強がてら変えていきたいですね。。 あとデザインも残念な感じですが、そこメインでないので目をつぶる予定です。

(メインネットにデプロイする場合)法律調査など

今回のdAppsはEtherを賭けるゲームです。
メインネットにデプロイすると「賭博罪」や「資金決済法における仮想通貨交換業としての規制」に触れる可能性あります。
メインネットにあげるならそのあたりの調査をした上でのデプロイになります。
(何となくダメな気がしますが。。)

作ってみての感想

作ってみて初めてわかることが沢山

どんなことでもそうですが。。。
Ethereumの基本知識を本とかサイトで勉強して、CryptoZombiesでもやれば、 簡単なdAppsであれば作ることはできるとは思います。
ただ実際作ってみると、MetaMaskを通じてのトランザクションやら、どういうView Functionを用意すべきか、変数の型は何が適切か・・・ とか悩むことはいっぱいあって、細かいところで沢山つまづきました。 でもそうして、作ってみて初めて悩むことやわかることによる学びが沢山あります。
ということで作ってよかったです。

サーバサイドが不要だと個人開発もやりやすい

サーバサイドの開発も必要だと専用のサーバを用意したりとか、サーバ構成、DB設計などやらないといけないことが増えていきます。
デプロイ後の保守運用も大変。。
でもSolidityで記述したEthereum上のコントラクトがサーバのような役目を果たしてくれて、 基本シンプルな内容を書くだけなので、非常に楽です。
個人開発のハードルを下げてくれます。
※もちろんオフチェーン部分をサーバ使ってやるタイプのdAppもあると思うので一概には言えないですが。

非中央集権でやる意味を改めて考えさせられる

上にも書いた通り、パブリックチェーンで完結させたジャンケンは正直ジャンケンにしては面倒です。
手順の複雑さを踏まえても、本当にブロックチェーンで非中央集権的にやる必要があるのでしょうか。
ジャンケンくらいであれば正直、どこかのゲーム会社のサーバに置く中央集権的な方法でも良い気もします。
ただこれがもっと大規模なもの(eスポーツの国際大会とかオリンピックとか?)であれば、サーバ保有者への信用問題が発生しうるので 非中央集権的に実現できるブロックチェーンは魅力的だったりもします。

どういう場面で、オンチェーン上の非中央集権とすべきで、どこは中央集権化すべきなのか改めて考え続けていき 世の中の流れもつかんでいく必要があると思いました。

ソースコード

github.com

もっといい方法があるといったご意見があれば、ぜひお待ちしております。