誰でも簡単に自分のブロックチェーンとDAppsを作れるようになるTendermintとは
Tendermintとは
最近騒がれているイーサリアムの対抗馬になるかもしれないと言われているコンセンサスエンジンTendermintについてわかりやすく解説していきます。
まずイーサリアムのような既存のブロックチェーンについて軽くおさらいしていきます。
イーサリアムのような通常のブロックチェーンは大別するとネットワーク、コンセンサス、アプリケーションの3つのレイヤーに分けられます。ネットワークレイヤーではトランザクションをノードたちに伝播させる役割を、コンセンサスレイヤーではブロックの生成に伴う合意形成を、アプリケーションではトランザクションを実行し状態を遷移させる役割を担っています。イーサリアムの場合、EVMという特別なバイナリコード実行環境がアプリケーションレイヤーに当たります。普通のブロックチェーンではこれら3つのレイヤーは独立ではありません。例えばイーサリアムチェーンで分散アプリケーションを作ろうと思ったらEVMという実行環境にアプリを載せるしかありません。
昔は自分で分散型アプリケーションを構築するときには上の3層を自分で構築する必要がありましたが、これは大変難しかったため開発者はビットコインなどの既存のブロックチェーンをフォークすることによって独自のチェーンを作成していました。
イーサリアムが登場したことで、開発者は分散型アプリケーションを作る際に、ブロックチェーンを1から作らなくてもイーサリアムという既存のブロックチェーンのアプリケーションであるEVM上に作ったアプリをデプロイするだけでブロックチェーン上で分散型アプリケーションを作れるようになりましたが、ブロックチェーン自体を作ったわけではなく、あくまでイーサリアムのチェーン上にアプリが乗っかっているだけなのでイーサリアムの制約(PoW、ブロック生成間隔、gas、etc)に従う必要がありました。
Tendermintではネットワークレイヤーとコンセンサスレイヤーをパッケージ化して提供することで、開発者たちはTendermintを用いることで下2層を簡単に作ることが簡単に可能になり、アプリケーションレイヤを作るだけで独自のチェーンを作ることができるようになりました。
またアプリケーションレイヤと下2層は独立しているため、開発者は言語に縛られることなく(将来的には)好きな言語でアプリケーションを開発できるようになります。
Tendermintを用いてブロックチェーンを作るメリットは他にもあります。
- パブリックチェーンでもプライベートチェーンでも両方作れるTendermintのコンセンサスアルゴリズムはPoSです。Tendermintではコンセンサスアルゴリズム自体は変更できませんが、バリデータの範囲を開発者が決めることができます。すべてのネットワーク参加者の中から、どれだけトークンをデポジットしているかに基づいてバリデータが決まるように定義すればパブリックなPoSシステムとなります。一方でバリデータが事前に開発者によって許可されたノードからしか選出されないのであれば、それはプライベートなPoAシステムとなります。
- 処理能力が高い
1秒当たり数千トランザクションを扱える - 高速なファイナリティ性
ブロックは生成された時点でファイナリティを得ます。つまり一度生成されたブロックが覆りません。地味ですがこれのおかげでCOSMOSが実現可能になったりとTendermintの重要な特徴です。 - 安全性が高い
悪意を持ったバリデータがいても1/3までなら安全になっています。 - 既存のチェーンのアプリケーションを実装することも可能
イーサリアムのアプリケーション実行環境(EVM)をTendermintで実装することも可能です。(Ethermint)
念のためですがTendermintで作ったブロックチェーンのアプリケーションとしてEVMを実装しているだけでイーサリアムのチェーン本体とは全く関係ありません。またEthermintではTruffleやMetamaskのようなイーサリアムの便利なフレームワークを利用することができます。
なぜTendermintチェーンの上でわざわざEVMを再現するの?と思うかもしれませんが、こうすることで、イーサリアムで作ったdappをスムーズにTendermint側に移植できます。
また分散型アプリケーションは作りたいが、独自のチェーンを作るのはやっぱり面倒だしセキュリティが心配という人はEthermintチェーンを使えばEVM上にアプリをデプロイするだけでよくなります。他にもCOSMOSネットワークを通じてほかのチェーンと価値のやり取りができるようになるというのも大きいです。
なぜアプリ専用のブロックチェーンを作るのか?
今日のDapps開発はイーサリアムのようなプラットフォーム型ブロックチェーン上で作るのが主流となっています。
イーサリアムでは開発者は自分の作ったdappをEVMというイーサリアムチェーンのアプリケーションの上にデプロイすることで自分のdappをイーサリアムチェーン上で使えるようにします。つまりイーサリアムという1つのチェーンに複数のアプリが載っているという関係になっています。
これに対してTendermintでは1つのチェーンに1つのアプリという1対1の関係を作ることができます。
この1つのチェーンに1つのアプリというアプリ専用チェーンの最も有名な例といえば、ビットコインでしょう。ビットコインチェーンは送金サービスをdappとしたアプリ専用チェーンということができるでしょう。
アプリ専用のブロックチェーンは上でも述べたように、ブロックチェーンを1から作るというのは非常に難しいため、今までは作られていませんでしたがTendermintを使うことでアプリ専用チェーンを簡単につくることができます。
さて肝心のdappをイーサリアムのEVM上などではなく、なぜTendermintでわざわざチェーンを作ってまでデプロイするのでしょうか?
- 開発パフォーマンス
開発者はTendermintを使うことでアプリに必要なトランザクションのタイプとトランザクション関数だけ定義すればよくなる上に、Tendermintチェーンの高速な処理能力を簡単に使うことができます。 - セキュリティ
VM上にdappを載せるとVM自体が複雑な仕組みなため、セキュリティの観点で注意すべきところが増えてしまいます。
Tendermintチェーンを使ってアプリ専用チェーンを作ったとき、開発者はアプリ内で生じるセキュリティホールについて考えるだけで済みます。
しかしVM上にdappをデプロイする場合はそれに加えて、dappとVM上の相互作用によって生じるセキュリティホールについても注意しなければいけません。ブロックチェーン上にデプロイしたdappはバグがあっても修復不可能なことが多いので、セキュリティホールが少ないというのは一見、地味に見えますがTendermintチェーンを使う上で重要なメリットと言えるでしょう。 - バリデータ
上で述べたようにTendermintチェーンでは、バリデータとする範囲を自由に選択可能なのでバリデーターとする範囲を自由に設定できます。またアプリ専用チェーンなので当然バリデーターはそのチェーンの専用アプリに関するトランザクションだけを処理することになります。
ここであるdappで問題が起きたときをイーサリアムとTendermintで比較して考えてみましょう。
イーサリアムのようなVM上にアプリが載っている場合では1つのアプリの問題を解決するために、 チェーン全体に影響を及ぼすことになるかもしれないことになり、非常に面倒です。
一方でTendermintを用いて作成したアプリ専用チェーンのアプリに問題が発生した場合は、(dappは独立しているので)他のdappに影響を及ぼすことなく問題に対応することが可能です。 - フレキシブル
チェーンのVM上にアプリをデプロイする場合、開発者はそのブロックチェーンの様々な制約を受けることになります。
例えばイーサリアム上でアプリ開発をする場合、開発言語にはSolidityやSerpentといった非常にわずかな言語しか選択することができません。そもそもSolidityやSerpentはイーサリアムのスマートコントラクトを記述するために生まれた言語なので、開発者はEVM上でdappを作る際は新しく言語を勉強する必要があり面倒です。
また開発者はVM自体からも制約を受けます。例えばイーサリアムのVMであるEVMではEVMからの自動的な状態の更新は行えず、必ず最初は我々ユーザーによってトランザクションを起こさなければ状態の更新を行うことはできません。
一方でTendermintを用いて作ったアプリ専用のブロックチェーンはイーサリアムのブロックチェーンとは違い、好きな言語で開発ができる上に自動的な状態の更新も可能になっています。
Tendermint(正確にはTendermintとdappとで情報のやりとりをするABCI)はBeginBlock関数とEndBlock関数を備えており、これらは文字通り各ブロックの最初と最後に自動的に実行される関数です。この機能はとても役立つ可能性を秘めていますが、重過ぎる処理や無限ループに陥るような処理をこの関数に組み込んでしまうとチェーンが正常に機能しなくなるので注意もまた必要です。
Tendermintを使ってアプリ専用チェーンを開発する利点としては以上のようなものが列挙できます。
もちろん、TendermintチェーンがあればイーサリアムのようなVM型ブロックチェーンで開発するメリットはないのかというようなことではありません。どちらも一長一短でメリットデメリットがあります。VM型ブロックチェーン上でdappを開発するメリットは主に、作ったdappをVM上にデプロイするだけで開発者はいいため手間が最小限になること、バリデータの範囲を決める必要がないことなどがあげられます。
LotionJS
公式ページ: https://lotionjs.com/
Github: https://github.com/keppel/lotion
COSMOSはTendermintチェーンを使ったdapp開発のためのフレームワークをいくつか出しています。
この記事ではその一つであるJavaScriptを用いた開発フレームワークであるLotionJSのインストールと簡単なアプリ開発について説明していきます。
インストール
Ubuntu 18.04 LTS上でのインストールを想定します。
Windows 10だとLoationJSのインストール中にエラーが起きてしまいインストールできないです。(門外漢なのでよくわからないのですがWSL?を使えばWindows上でもインストールできるらしいですが、私はおとなしく仮想マシンのUbuntu上でインストールしました。)
まず筆者はnodejsとnpmを導入していなかったのでnodejsとnpmのインストールから始めました。
sudo apt-get install nodejs
sudo apt-get install npm
これでnodejsのv8.10.0とnpmの3.52.0がインストールされます。
次に
sudo npm install lotion
これでLotionJSのインストールは完了です。nodejsとnpmのバージョンはインストールしたときのv8.10.0と3.52.0のままで大丈夫です。
API解説
LotionJSを用いたアプリ作成
let app = require('lotion')
アプリの初期状態や設定を定義
let app = require('lotion')({
devmode: false,
initialState:{},
keys: '',
genesis: '',
peers:[],
logTendermint: false,
createEmptyBlocks: true,
})
devmode: 開発者モードのオンオフを切り替えられます。オンにすると実行中にチェーンの状態を白紙に戻したりできます。
initialState: チェーンの初期状態を設定できます。
keys: あらかじめアドレス,公開鍵,秘密鍵を書き込んでおいたjsonファイルへのPathを書くことで重複がなければ、jsonファイルの内容をアドレス,公開鍵,秘密鍵とします。
genesis: チェーンの初期状態を記述したjsonファイルへのPathを書くことで重複がなければ、jsonファイルの内容をチェーンの初期状態とします。
peers: ’host’:’p2p_port’の形でリストに書くことで初期のtendermintネットワークのノードを定義できます。
logTendermint: trueにするとTendermintの非表示となっている処理内容をすべて表示します
createEmptyBlocks: falseにすると、Tendermintがトランザクションの入ってない空のチェーンを作らなくなります。チェーンの容量削減につながります。
トランザクションハンドラーの定義
useメソッドを使ってトランザクションが起きたときの処理内容を定義します。
app.use ( function(state,tx,chaininfo) {
//ここにトランザクションで起きる状態遷移の内容を定義
})
- state
アプリの状態を表すオブジェクトです。特に中身は決まっておらず、アプリに応じて開発者は様々なプロパティを設定できます。 - tx
トランザクションを表すオブジェクトです。こちらもアプリに応じて開発者は様々なプロパティを設定できます。 - chaininfo
チェーンの状態を表すオブジェクトです。中身は次のようになっています。
{
height: 42, //ブロック高
validators: {
'<特定のアカウントの公開鍵1>' : 20, //バリデーターの影響力(投票力の分布)
'<特定のアカウントの公開鍵2>' : 140,
...
}
}
自動で起こる状態遷移の定義
「なぜアプリ専用のブロックチェーンを作るのか?」のところでも述べましたがTendermintでは自動で状態遷移を引き起こすことができます。つまり自動でアプリの状態を変更することができます。
useBlockメソッドを使うことで自動で起こる状態遷移の内容を定義できます。
app.useBlock(function(state, chainInfo) {
//ここに自動的な状態遷移の内容を定義
})
作ったLotionアプリを公開するところの指定
app.listen(port)
タイトル通りです。例えばlocalhost:3000に公開するなら引数に3000を渡します。
HTTP API解説
ローカルホスト上のLotionアプリにクエリを送ってアプリの状態を見たり、トランザクションを送ることができます。
GET /state
JSON形式でアプリの最新の状態が返ってきます。
$ curl http://localhost:3000/state
# {"count": 0}:アプリの状態の例
POST /txs
トランザクションを作成し、チェーンのネットワーク(トランザクションを伝播する役割)に送信します。
$ curl http://localhost:3000/txs -d '{}'
# {"state": {"count": 1},"ok": true}:トランザクションが持っている情報の例
GET /info
バリデーターの公開鍵といったネットワークのノードの情報を取得します。
$ curl http://localhost:3000/info
#{"pubKey":"4D9471998DC5A60463B5CF219E4410521112CF578FFAD17C652AEC5D393297C2"}
実際に簡単なアプリを作ってみた
ローカルホスト上で動くジャンケンゲーム(相手の手はランダム)を作ってみました。コードを交えて解説していきます。
let lotion = require('lotion');
let app = lotion({ initialState: { yourPoints: 0, yourLastBattle: "", yourLastHand:"", opponentLastHand:""}});
//stateには適当にプロパティを決めて入れていい
//yourPoints:得点。ジャンケンの勝敗によって変動します。
//yourLastBattle:直近のジャンケンの勝敗
//yourLastHand:直近のあなたの手
//opponentLastHand:直近のCPUの手
let opponentHandList = ["Rock", "Paper", "Scissors"];//グー、パー、チョキfunction playRockPaperScissors(state, tx) {
let opponentHand = opponentHandList[Math.floor(Math.random() * opponentHandList.length)];
//ランダムに手を選ぶ処理です。 if (opponentHand === tx.myHand) {
//あいこのとき
state.yourPoints++;
state.yourLastBattle = "draw"
state.yourLastHand = tx.myHand
state.opponentLastHand = opponentHand } else if ((opponentHand == "Rock" && tx.myHand == "Paper") || (opponentHand == "Paper" && tx.myHand == "Scissors") || (opponentHand == "Scissors" && tx.myHand == "Rock")) {
//こちらの勝ち
state.yourPoints += 2;
state.yourLastBattle = "win"
state.yourLastHand = tx.myHand
state.opponentLastHand = opponentHand } else if ((opponentHand == "Rock" && tx.myHand == "Scissors") || (opponentHand == "Paper" && tx.myHand == "Rock") || (opponentHand == "Scissors" && tx.myHand == "Paper")) {
//こちらの負け
state.yourPoints += 0;
state.yourLastBattle = "lose"
state.yourLastHand = tx.myHand
state.opponentLastHand = opponentHand } else {
//トランザクションに自分が出す手が含まれていないとき
state.yourPoints += 0;
state.yourLastBattle = ""
state.yourLastHand = ""
state.opponentLastHand = ""
}
}app.use(playRockPaperScissors);app.listen(3000);
コード解説
let app = lotion({ initialState: { yourPoints: 0, yourLastBattle: "", yourLastHand:"", opponentLastHand:""}});
アプリの初期状態を定義しています。
今回は
- 自分の得点(初期値0)です。ジャンケンして勝てば+2点、引き分けなら+1点、負けなら得点の可算はありません。
- 最後のジャンケンの勝敗
- 最後のジャンケンであなたが出した手
- 最後のジャンケンでCPUが出した手
の4種類の状態を定義しました。(当初、下3つはジャンケンのたびにconsole.log()を使って逐一表示していたのですが、なぜか2回同じものが表示されるのでアプリの状態に組み込みました)
app.use(playRockPaperScissors);
次にfunction playRockPaperScissorsの中身について
let opponentHand = opponentHandList[Math.floor(Math.random() * opponentHandList.length)];
これでopponentHandList = [“Rock”, “Paper”, “Scissors”]からランダムに1つ要素を取り出しています。
if (opponentHand === tx.myHand) {
state.yourPoints++;
state.yourLastBattle = "draw"
state.yourLastHand = tx.myHand
state.opponentLastHand = opponentHand}
ジャンケンの結果が引き分けだったときの処理です。
state.yourPoints++;でアプリの状態プロパティの1つであるyourPointsを+1してます。
その後も他のアプリの状態プロパティを更新していきます。
残りのelse以下は勝利、負け、トランザクションにあなたの手の情報が入ってなかった時の処理です。
最後にapp.listen(3000);でローカルホスト上にアプリをデプロイしています。
遊んでみた。
まずは初期状態をリクエスト。
$ curl http://localhost:3000/state
>>>{ yourPoints: 0, yourLastBattle: "", yourLastHand:"", opponentLastHand:""}
initial_stateで定義したとおり、初期状態が返ってきました。
トランザクションを送信してジャンケン。こちらの手はパー
$ curl http://localhost:3000/txs -d '{myHand:"Paper"}'
勝敗と自分の得点を確認しましょう。
$ curl http://localhost:3000/state
>>> { yourPoints: 2, yourLastBattle: "win", yourLastHand:"Paper", opponentLastHand:"Rock"}
どうやら勝っていたようです()
まとめ
以上、長くなってしまいましたがTendermintの簡単な解説や既存のブロックチェーンとの比較、そして簡単なアプリの作成について解説しました。
また別記事でTendermintチェーンを主に用いて作るクロスチェーンネットワークであるCOSMOSについても解説しているので、ぜひそちらも読んでください。
イーサリアム研究所(Twitter: @ethereum_tokyo)ではブロックチェーン、特にイーサリアムに対して強い興味を持った学生または社会人のメンバーを募集しています。
発信はMedium上で行い、本格的な開発状況に関する分析、または開発者それぞれの思想をリサーチし、日本語にて発信するなど様々な方向性の記事を作成しています。
EthereumTech Lab.はイーサリアムのモバイルウォレットを開発している”Wei Wallet Team”(*https://github.com/popshootjapan/WeiWallet-iOS) が中心となり、活動を行っています。オフラインでのイベント/ミートアップ実施の機会も多く開催していく予定です。
ご興味のある方は、こちら
EthereumTech Lab | イーサリアム研究所
今後の記事更新のお知らせ・イベント情報・最新ニュースはFacebookグループへの参加がおすすめです。