Ethereumはどのように動いているのか
最近では主要なメディアEthereumが出るようになってきました。Ethereumを聞いたことがあってもよくわからんという人が多いと思います。
また、AmazonがEthereumの名前のついたドメイン名を取得したというニュースも、なぜかBitcoinのイラストが表紙になってしまうのが現状です。理由は、EthereumよりBitcoinのほうが大衆の関心を掴みやすいからでしょう。
そんなEhereumですが、2018年は大手メディアが解説をし始めたり、解説本が出たりとする年になるでしょう。2017年のビットコインと同じように時代を飾ると予想しています。そんな時期にEthereumについて知っていたほうがいい!ということでこのポストを書きました。
基本はこちらのブログポストとEthereumのイエローペーパーを翻訳・解説しながら仕組みにせまっていきます。図も同ブログポストとホワイトペーパーから引用しています。
テクニカルレベルでEthereumがどう機能するのかを説明します。興味のない人も、技術的なことが少し分かる、ことになることを望んでいます。
途中の構造のところで難しいと感じる部分があるかもしれませんが、雰囲気で読み進めて、ガスやトランザクションなど馴染みのあるところまで読んで全体像を掴んでみましょう。
- ブロックチェーンの定義
- Ethereum ブロックチェーンの枠組みについて
- アカウント
- Gasと支払いについて
- トランザクションとメッセージ
- ブロック
- トランザクション実行
- 実行モデル(Execution model)
- proof of workについて
- 最後に
ブロックチェーンの定義
イーサリアムにかぎらず、一般的にブロックチェーンとは、”状態がシェアされた、暗号によってセキュアである、トランザクションのシングルトンマシン” のことです。
だいぶ冗長なので、分解します。
・状態がシェアされる
マシンが持っている状態はシェアされ、すべての人に開示されます。
・暗号によってセキュアである
デジタル通貨の作成は、複雑な数学的アルゴリズムによって安全性を保っています。
・トランザクションのシングルトンマシン
1つの基準となる場合(インスタンス)があり、すべての取引はこれを基準とします。言い換えると、全員(全ノード)が信じるような1つのグローバルな解が存在します。
Ethereumはこのブロックチェーンの枠組を実装しています。
Ethereum ブロックチェーンの枠組みについて
Ethereumのブロックチェーンは、トランザクションに基づいた”状態機械”といえます。コンピュータ・サイエンスでは、「インプットを読み取りそれによって次の状態が決まる」枠組みのことを状態機械と言います。次の図のような感じです。
一方Ethereumの状態機械は、”Genesis State”(一番最初の状態)から始まります。そしてトランザクションが実行されるとこのジェネシス状態から次の状態へと移行していきます。そして現在のEthereumの状態まで続いています。
下の図のような感じです。
このEthereumの状態において、数百万のトランザクション情報を持ちます。これらのトランザクションがグループとして ”ブロック” になります。
ブロックは連続するトランザクションを保持しており、それぞれのブロックはチェーンのように前のブロックと繋がっていきます。
下の図のような感じです。
この状態遷移を引き起こすためには、トランザクションが ”有効である”必要があります。そしてトランザクションが有効であるためには、有効化するプロセスが必要になり、これがマイニングと呼ばれます。
マイニングは、いくつかのノード(コンピュータ)が、トランザクションが有効であるブロックを生成するために計算力を使うことを言います。
すべてのノードがこのプロセスに参加することができます。
そして多くのマイナーが同時に次のブロックを生成しようとするので、競いになります。
その争いに勝ったマイナーは、ブロックチェーンに新しいブロックを繋げる際に、数学的証明をし、これが保証として働きます。
以後この数学的証明があるものを有効なブロックとして認識されるようになります。
メインブロックチェーンにブロックを追加するには、競合のノードより速くその証明をしなければなりません。マイナーが数学的証明を提供することによって各ブロックを有効化するプロセスは、「proof of work」と一般的に言われています。
新しいブロックを有効にするマイナーは、この作業の見返りに一定の報酬が渡されます。それがEthereumブロックチェーンでは、「Ether」という固有のデジタルトークンになっています。マイナーがブロックを証明するたびに、新しいEtherトークンが生成され、報酬として渡されます。
「ブロックが繋がった1つのチェーンが正しい」と、どう保証できるのだろうと疑問に思うかもしれません。
ここで上で書いた、ブロックチェーンの定義「共有状態のトランザクション型シングルトンマシン」を考えると、現在の状態は誰もが受け入れる必要がある単一の状態であると理解できます。
複数の状態(またはチェーン)を持つことは、どの状態が正しい状態であるかに同意することは不可能であるため、システム全体を破壊することになります。
もしチェーンがばらばらになっている場合は、あるチェーンに10コイン、別のチェーンに20コイン、別のチェーンに40コインを所有している可能性があります。このシナリオでは、どのチェーンが最も有効かを判断する方法はありません。
複数のパスが生成されるたびに、「フォーク」が発生します。フォークはシステムを混乱させ、チェーンを選択させるという状況になるため、一般的には避けたいことです。
どちらのチェーンが有効であるかを決め、チェーンが複数になるを防ぐために、EthereumではGHOSTプロトコル呼ばれるメカニズムを使っています。
“GHOST” = Greedy Heaviest Observed Subtree
簡単にいうと、よりコンピューティングパワーがかけられた方を選ぶような仕組みになっています。1つの方法は、ブロックの合計数を使います。ブロック数が多いチェーンは、長いチェーンつまりマイニングにかけられたリソースが大きいことを意味するので、そちらを基準となるチェーン(Canonical Blockchain)として採用することになります。
以上がざっくりとしてブロックチェーンの概要です。
次はEthereumシステムの構成要素を見てみましょう。
構成要素としては以下があります。
アカウント
Ethereum全体として共有される状態は、小さなオブジェクト(アカウント)によって構成されており、メッセージ送信でアカウント間でやり取りができるようになっています。
それぞれのアカウントはそれぞれの”状態”と、20byteのアドレスを持ちます。このアドレスはアカウントを認識するための160bitの識別子としての役割があります。
このアカウントには2種類あり、
- 外部アカウント(Externally owned account)
秘密鍵によって制御され、コードを持っていない。 - コントラクトアカウント(Contraxt account)
コントラクトコードによって制御され、コードを持っている。
という2種類です。
外部アカウント vs コントラクトアカウント
外部アカウントはメッセージを、外部アカウントもしくはコントラクトアカウントに送ることができます。方法は、秘密鍵でトランザクションに署名して行われます。
外部アカウント同士のメッセージは単なる価値のやり取りで、送金とイメージしていただければOKです。
一方外部アカウントからコントラクトアカウントに送った場合は、コントラクトアカウントが持っているコード(プラグラム)を実行させます。その結果、トークンを送金したり新しいトークンを発行したりと様々なことを自動で実行させることができます。
一方コントラクトアカウントのほうは、自らトランザクションを最初に始めることはできません。その代わり、コントラクトアカウントは受け取ったトランザクションによって、トランザクションを発行することができます。
(外部アカウント→コントラクトアカウント→という具合に)
このあとトランザクションとメッセージのセクションでさらに説明します。
したがって、すべてのEthereumのブロックチェーンは、外部アカウントによって開始されます。
Account state (アカウントの状態)
アカウントの状態は4つの要素からなります。外部アカウント、コントラクトアカウント両方とも同様です。以下がその4つです。
- nonce:日本語で「ノンス」と呼ばれます。アカウントが外部アカウントであれば、ここはアカウントのアドレスから送られたトランザクションの数を表します。アカウントがコントラクトアカウントであれば、このnonceはアカウントによって作られたコントラクトの数を表します。
- balance:アカウントが保有するWeiの数です。Weiとは単位のことで、1Wei=0.000000000000000001 ETHとなります。Ethereumの最小単位です。
- storageRoot:Merkle Patricia treeのルートノードのハッシュ値を示します。(Merkle Patricia treeについては後ほど説明します)
この木構造ではアカウントのコンテンツのハッシュ値をエンコードします。デフォルトでは空っぽです。 - codeHash:EVM(Ethereum Virtual Machin)コードのハッシュ値です。EVMについては後ほど説明します。コントラクトアカウントの場合、ここはcodeHashとして、ハッシュされて保持されるためのコードになります。外部アカウントにとっては、このフィールドは空の文字のハッシュ値になります。
World state (全体としての状態)
Ethereumの全体としての状態は、アカウントのアドレスとそのアカウントの状態の紐付け(マッピング)で構成されます。このマッピングはMerkle Patricia treeと言われるデータ構造に保存されます。
Merkle treeとは以下のノードで構成された二分木(binary tree)の一種です。
- リーフノード。treeに対して葉っぱとなりデータを保持する。画像の一番した。
- 中間ノード。2つの子ノードのハッシュである。画像の真ん中の層。
- ルートノード。これも2つの子ノードのハッシュから作られる。画像の一番上。
この図において一番したの層は、データを分割することでつくられます。データのハッシュ値をとり、繰り返すと1つのルートハッシュにたどり着きます。
この木構造は、価値を管理するための鍵を持っていることが必須となっています。
鍵があることによって、ルートノードから始まり、どこの子ノードを見ればよいかがわかり、最終的に価値を保持するリーフノードのところまで辿りつくことができます。Ethereumのケースの場合、アドレスとそのアカウント(上で書いた、balance, nonce, codeHash, storageRoot)で下図のように鍵と価値のマッピングがされています。
※ホワイトペーパーより
この同じ木構造はトランザクションをうけとり、それを保存するときにも同様に使われています。具体的にいうと、各ブロックは、ヘッダーをもっており、そのヘッダーには、3つの「Merkle木構造のルートノード」のハッシュ値を持ちます。
3つのルートノードとは、
- State tree (trie) 状態木
- Transactions tree トランザクション木
- Receipts tree 受信木
このすべての情報を効率的に保存する技術は、イーサリアムのライトクライアントや、ライトノードにおいて非常に効果的で、その理由を説明します。
ブロックチェーンはたくさんのノードによって維持されていると上で書きましたが、より広く書くと、2つに分けられます。それがフルノードと、ライドノードです。
フルノードは、genesis block(はじめのブロック)から現在のブロックまで、ブロックチェーンすべてをダウンロードして同期しているノードのことです。一般的に、マイナーはマイニングのためにフルノードを持ちます。(マイニングプロセスがそうするよう要求しているためです)
すべてのトランザクションを実行せずにダウンロードが可能ですが、とにかくチェーンをすべて情報として持つのがフルノードです。
しかしすべてのチェーンを情報として持つ必要は必ずしもありません。ここでライトノードというコンセプトがでてきます。
すべてのチェーンをダウンロードする代わりに、ライトクライアントではヘッダーのチェーンのみをダウンロードします。ヘッダーにアクセスすることで3つの木のハッシュ値から、簡単にトランザクション、イベント、残高を参照できるようになります。
こういうことができる理由は、Merkle木のハッシュによる遷移が図でいうと上向きになっているためです。もし悪意のあるユーザが、ニセのトランザクションを作ったとしてMerkle木の下のデータをニセものにしたとしましょう、するとその上のノードのハッシュ値は、もともとと違うものとなり、また別の上のノードを参照しにいきます。これが続いていくと結局ルートノードが変わることになります。つまりエラーになるような作りになっています。
ノードがデータを確かめたいときは、Merkle proofというものを使います。これは以下3つによって構成されます。
1.確認をされるデータの塊
2.木構造のルートハッシュ
3.木の幹に相当する部分(つまり画像の下からの上までの道筋:リーフノードからルードノードまでの道筋)
このMekcle proofを読むと、枝のハッシュが、その木構造に矛盾がないことを確認できます。したがって、あるチャンクが木構造のそのポジションにあると確かめることができます。
まとめると、Merkle Patricia treeの利点は、ルートノードがデータに(暗号的に)依存していて、それによってルートノードのハッシュが、そのデータを確かめるための安全なアイデンティティとして使える、ということです。
ブロックのヘッダーが、上で書いた
1.State tree 状態木
2.Transactions tree トランザクション木
3.Receipts tree 受信木
を含むので、どのノードも、すべての状態を保存することなくEthereumの状態を有効にすることができるのです。
Gasと支払いについて
Ethereumにおいて、大切なコンセプトは、手数料の概念です。Ethereumネットワークで起こるすべてのこと(トランザクションによって引き起こされますが)はすべて手数料を必要とします。この手数料は、Ethereumでは、”Gas”(ガス)と呼ばれて支払われます。
Gas は手数料の大きさを図るために使われます。Gas priceとは、自分で設定する、払ってもいいよというEtherの量のことです。
通常”gwei”という単位で図られれます。(giga wei)
10の18乗 Weiが1Etherとイコールになります。gweiは1,000,000,000Weiのことです。
キロは10の3乗、メガは10の6乗、ギガは10の9乗なので、giga weiは、wei×10の9乗というわけです。
つまりweiとはものすごい小さな量のEtherと考えればいいでしょう。
すべてのトランザクションにおいて、送信者は Gas limit(ガスリミット)とGas price(ガスプライス)を設定します。この2つの掛け算が、最大で払ってもいいWeiの量になります。
例えば、gas limit が50,000で、gas priceが 20gwei だとします。この場合、送信者は、最大で 50,000 * 20 wei = 1,000,000,000,000,000Wei = 0.001 Etherをトランザクションを行うときの手数料として支払ってもよいと宣言していると同義になります。
ガスリミットは送信者が払ってもいいと思っている最大のガス量です。なので必要以上に多めに設定する場合もあります。その場合、余った分がアカウントに戻ってくる仕組みになっています。
もし送信者が必要なガスをもっていない場合は、そのトランザクションはガス切れで無効になります。そして途中で無効となった場合は、トランザクションの前のEthereumの状態へと戻ります。
さらに、失敗したトランザクションは、どこでガス切れになり無効になったか、などが記録されます。
ガス切れになる途中でガスを使うことにはなるので、この場合、送信者にガスは戻ってきません。
ではこの途中で使われたガスの分はどこへいくでしょうか。すべてのガスとして支払われたお金は、beneficiary (日本語でいうと受益者)のアドレスへ送られます。
これがマイナーのアドレスというわけです。
マイナーはコンピューティングパワーを使って計算をしトランザクションを有効化することによって、ガスの分のお金を報酬として得ることができます。
一般的に、ガスプライスが高く設定すると、マイナーはそのトランザクションからより高い価値を得られることを意味します。したがって、マイナーはガスプライスが高く設定されたトランザクションを選びがちになります。
マイナーは、有効化したい、もしくは無視したいトランザクションを選べるからです。
送信者にセットするガスプライスを教えるために、マイナーは、自分らが有効化するトランザクションのミニマムガスプライス(最低限のガスプライス)を告知することが可能です。マシンが「これ以下の価格だと割に合わないからやらないよ」と言い張り、実際そのように挙動する、というイメージです。
ストレージの手数料
ガスは計算に使われるだけでなく、ストレージの用途にも使われます。ストレージのための合計の手数料は、32バイトの積の最小値に比例します。
ストレージのための手数料は、特別な意味合いを持ちます。
例えば、増加したストレージがEthereumのすべてのノードのデータベース(ブロックチェーンの部分)の容量を肥大化させるので、データを小さくするようインセンティブ設計がされているのです。
この理由のため、トランザクションがストレージをきれいにするステップがあるトランザクションは、手数料の要求は免除されます。ストレージスペースを広げるためにリファンドすら与えられます。
手数料の目的は?
Ethereumを動くための大切な要素としては、ネットワーク上のすべての出来事は、同時にすべてのフルノードに影響を与えます。手数料という概念を加えることで、ユーザが過度にネットワークに負担を与えることを防ぐ効果があります。
Ethereumはいわゆるチューリング完全の言語です。(かんたんに言うと、チューリング完全なマシンとは、どのコンピュータアルゴリズムでもシミュレートすることが可能なマシンのことを言います。)
これによって、ループすることができ、halting problemという問題があります。このhalting problemとは、プログラムを無限に実行し続けるか、そうじゃないかを決めることができないという問題です。
もし手数料がなければ、ここで悪意のある人が、トランザクションで無限ループを作り出すことができネットワークを崩壊させることができます。つまり手数料によって、Ethereumネットワークは、故意的な攻撃から守られているのです。
ストレージのための手数料も、計算プロセスのところで説明したことと同様で、Ethereumネットワーク上へのデータのストレージはコストであり、ネットワークによっては負荷なのです。したがって手数料によって、制限されない限りは、崩壊をされるすきを与えてしまうことになります。
だから手数料が、Ethereumにおいて重要な役目を果たします。(ブロックチェーン業界一般に言えることですが。)
トランザクションとメッセージ
Ethereumはトランザクションに基づいた状態機械と、上で書きましたが、言い換えると「2つのアカウント間でのトランザクションが、Ethereum全体の状態に影響を与えるようなネットワーク」とも言えます。
基本的には、トランザクションは暗号的に署名されている(AからBに送るなどの)指示の一部であり、外部アカウントから生成されます。そしてそれが続いて、ブロックチェーンに組み込まれます。
ここで2つのトランザクションの種類をします。
それが “Message call”と “Contract creation” です。
両方のトランザクションに共通して以下を含んでいます。
- nonce:送信者から送られたトランザクションの数になります。
- gasPrice:送信者がそのトランザクションで払ってもいいと設定したWeiの価格です。
- gasLimit:送信者がそのトランザクションで払ってもいいと設定したgasの量です。
- to:トランザクションを受ける側のアドレスが入ります。contract creationのトランザクションにおいては、コントラクトアカウントを作るためのトランザクションのため(当然作る時点ではまだアドレスは存在しないため)このフィールドは空になります。
- value:送信者から受信者へ送るWei(=ETH)の量になります。いくら送るかを設定するフィールドですね。
- v,r,s:送信者のトランザクションを特定する署名を作るために使われます。
ここはcontract creationのトランザクションにしか含まれない部分です。 - init:コントラクトアカウントを作るために使われるEVMのコードの一部が入っています。このinitのコードが走りだすと、アカウントコードのbody部分が返り値として受けることができ、それが生成されるコントラクトアカウントの一部として今後使われ続けることになります。
ここは反対にmessage callのトランザクションにしか含まれない部分です。 - data:パラメーターなどのデータが入ります。例えば、スマートコントラクトがドメイン登録としての機能で動いていたとして、そこにリクエストするためのドメインやIPアドレスなんかをデータとして入れる、などの使い方のときに使います。
アカウントのパートで、トランザクションは、常に外部アカウントから開始されブロックチェーンに追加されると書きました。なので、今書いた2つのトランザクションはどちらも外部アカウントから開始されます。外部アカウントによってEthereum全体の状態が決まります。
しかしコントラクトアカウントが他のコントラクトアカウントにやり取りができないわけではありません。コントラクトアカウントは、同じ範囲内のコントラクトに対して、メッセージ、もしくは内部トランザクションを使ってやり取りができます。これらは外部アカウントによって生成されるわけではない点が、普通のトランザクションと異なり、コントラクトアカウントによって生成されます。しかしこれらは仮想のオブジェクトであり、Ethereumの実行環境でしか存在しません。
コントラクトが内部トランザクションを他のコントラクトに送ったとき、受け取った側は、関連するプログラムを実行します。
ここで気をつけることは、内部トランザクションは、gasLimitを含まない点です。ガスリミットは最初にトランザクションを生成した外部アカウントが決めるためです。最後までトランザクションを完了させるためには、最初に生成するトランザクションにおいて、コントラクトが内部トランザクションで使うガスの分も含めて、ガスを十分な値に設定する必要があります。
もしトランザクションのチェーンの途中でガス切れになった場合、メッセージの実行は元に戻ります。しかし、メッセージを起こすための最初のトランザクションは必ずしも状態を戻す必要はありません。
ブロック
トランザクションはグループになりブロックに追加されます。ブロックチェーンはこのブロックが連なって鎖のようになっていることからそう呼ばれます。
Ethereumでは、ブロックは以下で構成されます。
- ブロックヘッダー
- ブロックに含まれるトランザクションの情報
- 他のブロックのヘッダー(現在のブロックのommerのため)
Ommerの説明
いきなりommerが出てきましたがここで説明をします。
ommerとはあるブロックのことで、その親は、現在のブロックの親の親です。
何に使われて、なぜブロックがこのommerのためのブロックヘッダーを含むかを見ていきましょう。
Ethereumの仕組みとして、ブロックタイムは15秒前後と他のブロックチェーンよりも短くなっています。(Bitcoinは10分前後)
これによって速いトランザクションプロセスが可能です。
しかし、ブロックタイムが短いことの欠点として、マイナーが競合するブロックを見つける可能性が高くなります。これらはorphaned blockと呼ばれています。(orphaneとは、孤児の意味がありますが、ここではマイニングされたブロックだけどメインのチェーンに組み込まれたなかったブロックを意味します。)
ommerの目的は、このようなorphaned blockを見つけたマイナーにも報酬を与えるようにすることです。
マイナーが取り込んだommerは現在のブロックから6番目前のブロックまでは、有効となり、報酬が与えられます。
6番目以降の子ブロックの場合、古いorphaned blockはもう参照されなくなります。
ommerのブロックはフルブロックよりは小さな報酬ですが、(orphanedブロックでもいいからマイニングをしようというインセンティブになり)ネットワークを維持する設計となっています。
ブロックヘッダー
先程のブロックを構成する3つの要素に話を戻します。すべてのブロックは、ブロックヘッダーがあると書きましたが、それは何でしょうか。
ブロックヘッダーはブロックの一部であり以下で構成されます。
・parentHash:親ブロックのヘッダーのハッシュ
・ommersHash:現在のブロックのommerのリストのハッシュ
・beneficiary:このブロックをマイニングするのにあたり手数料を受け取るアカウントのアドレス
・stateRoot:状態木のルートノードのハッシュ(状態木はヘッダーに保存され、ライトクライアントが状態を有効化するのを簡単にすると上で書きましたが、その部分です。)
・transactionsRoot:このブロックに入っているトランザクションを含んだ木構造のルートノードのハッシュ
・receiptsRoot:このブロックに入っている受信者を含んだ木構造のルートノードのハッシュ
・logsBloom:Bloomフィルターというデータ構造のことで、ログ情報を持ちます。
・difficulty:ブロックの難易度
・number:現在のブロックの番号です。(genesisブロックが0としてブロックが追加されるごとに1ずつ増えていきます。)
・gasLimit:ブロックごとの現在のガスリミット
・gasUsed:このブロック内のすべてのトランザクションに使われたガスの合計値
・timestamp:このブロックが生成されたunixタイムスタンプ(時間のこと)
・extraData:このブロックに関連するextraデータ
・mixHash:あるハッシュのことをいい、nonceと組み合わせることで、このブロックが十分な算出が実行できたことを認証します。
・nonce:あるハッシュのことをいい、mixHashと組み合わせることで、このブロックが十分な算出が実行できたことを認証します。
上の図のようにブロックヘッダーは3つの木構造を持っています。
- state(stateRoot)
- transactions(transactionsRoot)
- receipts(receiptsRoot)
これらの木構造は、上で説明したMerkle Patricia treeです。
加えて、上フィールドをもう少し説明します。
Logs
Ethereumはトランザクションや内部トランザクション(メッセージ)をログとしてトラッキングできるようになっています。
コントラクトも、記録したいevents (イベント)を定義することでログを生成することができます。
このログには以下が含まれます。
・ログを取っているのアカウントアドレス
・トランザクションによって実行されるイベントを表すトピック
・それらのイベントに関連するデータ
そしてログは上で書いたBloomフィルターに保存され続けます。
Transacton receipt
ヘッダーに保存されたログは、トランザクションレシートに含まれるログ情報から情報をもってきています。現実世界で何か買ったらレシートを受け取るのと同じように、Ethereumではすべてのトランザクションでレシートが生成されます。想像通りレシートには、トランザクションの各種情報が盛り込まれており、
- ブロックナンバー
- ブロックハッシュ
- トランザクションハッシュ
- 該当のトランザクションで使われたgas
- 該当のトランザクションを実行したあとにブロック内で使われたガス
- 該当のトランザクションを生成したときに作られたログ
などです。
Block difficulty
difficulty(以下ディフィカルティ)はブロック有効化・生成時間を一定に保つために導入された概念です。genesisブロックでは、131,072というディフィカルティ(難易度)でした。特別な計算が毎回行われ、ディフィカルティが決まります。
もしあるブロックが前のよりも早く生成された場合、Ethereumプロトコルはディフィカルティを上げて、時間を一定にしようと調整します。
このディフィカルティはnonceに影響を与えます。nonceはマイニングする際に計算されるハッシュのことです。(proof-of-wookにおいては)
このディフィカルティとnonceの関係は、以下のように公式化できます。
Hdとはディフィカルティのことです。
nonceを見つける唯一の方法は、すべての可能性を片っ端に計算していくproof-of-workのアルゴリズムです。なので、難易度があがると、それに伴ってブロック生成までの時間も長くなっていきます。
(むずかしくなるとnonceを見つけるのが難しくなって、ブロックを生成とするプロセスに時間がかかるため)
だからプロトコルとして、ディフィカルティを調整することで、ブロック生成時間を調整することができるのです。
一方で、もしタイム生成までの時間がかかりすぎると、プロトコルは難易度を下げるように調整をかけます。
トランザクション実行
ここでEthereumプロトコルの中で最も複雑なパートになります。トランザクションの実行です。
あなたが誰かに送信したとすると、Ethereumの状態に何か起こるのでしょうか。
まずはじめに、すべてのトランザクションは、以下の条件を満たす必要があります。
●トランザクションがフォーマットにしたがったRLPであること、RLPとは Recursice Lengh Prefix の略で暗号に使うデータ・フォーマットです。
●有効なトランザクション署名
●有効なトランザクションnonce
nonceはアカウントによって送られたトランザクションの回数と上で書いていますが、有効にするには送信者のアカウントのnonceとトランザクションのnonceがイコールである必要があります。
●トランザクションのガスリミットが、トランザクションで使われるガス(intrinsic gas)を上回っている必要があります。
※ここでintrinsic gasとは、
1. 事前に定められた21,000gasのコスト
2. トランザクションと一緒に送るデータのためのgas手数料(送るデータもしくはコードが1byte増えるごとに4gas増えていきます。それに加えて、最低限68gasが必要になります)
3. もしトランザクションがコントラクトを生成するためのトランザクション(上でいうcontract creationトランザクション)の場合はさらに32,000gasがかかります。
●送信者のアカウント残高は “upfront” のガスコストを上回ってる必要があります。
このupfrontのガスコストの計算はシンプルで、まずトランザクションのガスリミットとガスプライスをかけ合わせてガスコストの最大値を求めます。そのあと送信者がガスとは別に、誰かに送りたいEtherの価格を足します。
もし上のこれらの条件をすべて満たしたら、トランザクションは次のステップに移ります。
まず送信者の残高から上で算出したupfrontの額(ガス+送る額)を引きます。そして送信者のnonceの数を1プラスします。(nonceはトランザクションを生成した回数と上で書きましたね。)
ここで、送信者が設定したガスの最大値であるGas Limitから、実際にかかったガス分(insrinsic gas)を差し引きすると、ガスの残り(Gas remaining)が求められます。
次に、トランザクションの実行が始まります。この実行の間、Ethereumは、”substate”を保持し続けます。このsubstateは、トランザクションが終わったあとにすぐ必要になる情報を記録するためのものです。具体的にはsubstateには以下が含まれます。
- Self-destruct set:トランザクション後に捨てられるアカウントがもしあれば、そのアカウントの情報
- Log series:仮想マシンのコード実行度合いを図るためのチェックポイント
- Refund balance:トランザクション後に返金として送信者に返される額。「Ethereumネットワークのストレージはコストなので、きれいにしてくれたら代わりにリファンドされる」と上で書きましたが、その分の情報がこのフィールド入ります。トランザクションの初めは当然リファンド分は0ですが、コントラクトがストレージ内のデータを何か消すたびに「リファンド分」が増えていきます。
さて次ですが、トランザクションのプロセスが進むにつれて様々な計算が要求されていきます。
すべてのステップが無効ではなく無事終わると、使ってないガスが送信者に戻され、状態が決定します。また使ってないガスだけでなく上で書いたRefund balance分も返金がされます。
その返金を受け取ると、
- マイナーにガス分のEtherが送られます。
- トランザクションで使ったガスはブロックのgas counterというフィールドに追加されます。(すべてのトランザクションの合計ガス量を計算するのに使われ、ブロック生成に役立ちます。)
- self-destructに入っているアカウントは消されます。
そして最後に、新しい状態となり、トランザクションによって作られたログが記録されます。
以上がトランザクション実行の基本になります。
次に、コントラクト生成のためのトランザクションContract creationと、Message callの違いについて見てみましょう。(トランザクションにはこの2種類あると上で書きました。)
Contract creation(コントラクト生成)
Ethereumでは、外部アカウントとコントラクトアカウントの2種類のアカウントがあると書きました。コントラクト生成というと、新しくコントラクトアカウントを作るという意味になります。
コントラクトアカウントを作るために、まず特別な公式を使ってアドレスを作る必要があります。そのあと以下をしてアカウントを作ります。
- nonceを0に設定します
- もし送信者がEtherも同時に送っていれば、コントラクトアドレスの最初の残高は、その送られた分になります。
- この分のEtherは送信者の残高から引かれます
- ストレージを空にします
- 空の文字列のハッシュをコントラクトのcodeHashとして設定します
そしてトランザクション内のinit codeを使ってアカウントが作成されます。(init codeは上のTransaction and messagesのセクションで書いています。)
この init codeにより行われることは、コントラクトを作った人の意向によります。例えば、アカウントのストレージをアップデートする、他のコントラクトアカウントを作る、メッセージコールを行うなど、init codeにより様々です。
このコードが実行されるとガスを使います。トランザクションは残っているガスとりも多くのガスを使うことができません。使おうとすると、out of gas exception (ガス切れエラー)を返し、処理を終了させます。もしトランザクションがガス切れエラーで終了すると、状態はトランザクション前の状態に戻ります。そして使われたガスは戻ってきません。
しかし、もし送信者が、トランザクション内でEtherを送ろうとしていた場合は、そのEtherは戻ってきます。手数料だけ使われるということですね。
もしinit codeの実行が成功すると、コントラクト生成に必要なコストが支払われます。これはストレージコストといって、コントラクトコードのサイズに伴って大きくなります。もしこれらを支払う十分なガスがない場合は、またout of gas exceptionエラーとなり、処理が中止されます。
もしここで十分なガスがあった場合は、余ったガスが送信者に返金され、やっとトランザクションは成功し、アカウントは次の状態へ遷移することができます。
Message calls
message callのトランザクションの実行はコントラクト生成のそれと似ています。
message callの場合、アカウントが作られるわけではないので、init codeを含みません。
その代わり、もし送信者が “データ”を設定すると、input データとしてトランザクションに含まれることになります。
そして実行されるとさらに outputデータを持つようになり、場合によっては以降のトランザクションでこのoutputデータが使われます。(つまり、このデータを必要とするコントラクトに送ったときなどに参照されます。)
コントラクト生成のときと同様に、もしmessage callの実行がガス切れによって終了となった場合、もしくはスタックオーバーフローなどで無効となった場合は、使われたガスは戻ってきません。使われなかったガスは戻り、残高など状態もトランザクション前に戻ります。
2017.10月で最近のアップデートまでは、すべてのガスを使うまでトランザクションを止めたり変更する方法はありませんでした。例えば、権限がないとき、エラーを投げるコントラクトを作ったとします。前バージョンであれば、残ったガスは使われ、あまりはなくなるので、ガスは戻ってきませんでした。
しかし、Byzantiumにアップデートし revert code(返送コード)が実装されたことにより、コントラクトはトランザクションを終了することができ、ガスを使いきることなく、ただ失敗したという理由で前の状態に戻ることができます。
このrevertによってトランザクションが終了したら、使われなかったガスは戻ってきます。
実行モデル(Execution model)
これまでで、トランザクションにおいて開始から終了まで起きていることを学びました。
次は実際にVM(virtual machine=仮想マシン)の中でトランザクションがどう実行されるかを見ていきましょう。
このトランザクションを司るプロトコルのパートは、Ethereumのvirtual machineということでEVM(Ethereum Virtual Machine)と呼ばれています。
EVMは、チューリング完全の仮想マシンです。普通のチューリング完全のマシンと違い、EVMは実質ガスが必要という制限はあります。
したがって、合計の計算量は供給されるガスの総量によるということになります。
さらにEVMは、スタックベースの構造です。スタックマシンとは、last in, first out (LIFO)のルールで変数を保持するコンピュータのことです。
(inputされた変数は列の最後に組み込まれ、列の最初がoutputとして出力されるルールです。当然First in First out (FIFO)などもあります。)
EVM内のそれぞれのスタックアイテムのサイズは256bitであり、スタックの最大サイズは1024bitです。
EVMは文字列が保存されるメモリを持ち、メモリは永続的に残るわけではなくて揮発性です。
EVMはさらにストレージも持ちます。メモリと違って、ストレージは不揮発性で、システム状態の一部として維持され続けます。
またEVMは、特別な手法でないとアクセスできない仮想ROMの中に、プログラムのコードを別で保持します。コードをメモリかストレージに格納する典型的なvon Neumann architectureとはこの点が異なります。
さらにEVMは “EVM bytecode” という自身の言語を持ちます。
プログラマがスマートコントラクトを書くとき、一般的にはSolidityなどの高級言語を書きます。それをEVMが理解できるようにコンパイルして、EVM bytecodeにします。
これで実行の話になります。
計算の実行の前にプロセッサが以下の情報が有効であるかを確かめます。
- System state
- 計算のためのガスの残り(Remaining gas)
- 実行しているコードを持つアカウントのアドレス
- この実行をしたトランザクション送信者のアドレス
- コードを実行させたアカウントのアドレス(最初の送信者とは異なる場合があるので)
- この実行をしたトランザクションのガスプライス
- この実行のインプットデータ
- 現在の実行の一部としてアカウントに送られる価値(単位はWei)
- 実行されるマシンコード
- 現在のブロックのヘッダー
- message callもしくはコントラクト生成のスタックのDepth
実行のはじめは、メモリとスタックは空であり、プログラムカウンターは0になっています。
EVMはそこからトランザクションを再帰的に実行し、そのループの間、システム状態とマシン状態を計算していきます。このシステム状態とは単純にEthereum全体の状態のことです。マシン状態とは以下で構成されるものです。
・gas available(利用できるガス)
・programカウンタ
・メモリの中身
・メモリ内のアクティブな文字数
・スタックの中身
スタックアイテムは一番左の部分から追加されたり削除されたりします。
それぞれのサイクルで、適切なガス量が、残っているガス量から引かれ、programカウンタが1ずつ増えていきます。
それぞれのループの終わりで以下、3つの可能性があります。
1.マシンが例外状態になり、停止する。
2.上のサイクルが次のループまで進み続ける。
3.実行プロセスが終わり、完了する。
実行が例外エラーにならず完了した場合、マシンは結果の状態、残ったガス、substate、そしてアウトプットを生成します。
これがEthereumの最も複雑なパートでした。すべてを完璧に理解しなくても大丈夫です。正直この複雑な実行については、かなり深くまで開発する人以外は、理解する必要はありません。
どのようにブロックはファイナライズ(最終決定)されるか
最後に、たくさんのトランザクションが入ったブロックがどう最終決定されるかを見ていきます。これを英語そのままファイナライズとよく言うそうです。
ファイナライズというと、2つ意味があって、ブロックが新しいものか、既存のものかによって異なってきます。もし新しいブロックに対して使うのであれば、そのブロックをマイニングするためのプロセスのことを意味します。
既存ブロックに対して使う場合、ブロックを有効化(英語ではvalidate)するプロセスを意味します。
どちらの場合もブロックがファイナライズされるためには以下4つの必須条件があります。
1.ommerを有効化する (マイニングであればommerを決定する)
ブロックヘッダーの中の、それぞれのommerブロックは有効なヘッダーであり、現在のブロックから6つ前以内のブロックである必要があります。
2.トランザクションを有効化する(マイニングであればトランザクションを決定する)
gasUsedのフィールドの数は、ブロック内トランザクションによって使われたガスの合計と一致する必要があります。
3.リワード(報酬)を適用する ※マイニング場合のみ
ブロックをマイニングしたアドレスに対して5Etherが報酬として与えられます。
---------
ちなみにEIP(Ethereum improvement proposalの略でその名の通り、改善案のこと)の中の「EIP-649」では、「5ETHではなく3ETHに下げよう」という提案がされています。
---------
さらにそれぞれのommerに対して、現在のブロックの受益者は、現在の報酬の1/32が追加で与えられます。
最後に、ommerブロックの受益者は、あるの額の報酬を受け取ります。(額の算定には公式があります。)
4.状態とnonceを検証する
すべてのトランザクションとその結果の状態遷移を確認し、リワードを適用した後に、新しいブロックが生成されます。
proof of workについて
ブロックのセクションんで、ブロックの難易度について触れました。ブロック難易度(ディフィカルティ)に意味を持つアルゴリズムは、Proof of Work(PoW)と言われます。
EthereumのProof of Workのアルゴリズムは Ethashと呼ばれます。
公式がすると以下のようになります。
ここでmはmixHash, nがnonce, Hn/が新しいブロックヘッダー, Hnはブロックヘッダーのnonce, dがDAG(でかいデータ群のこと)になっています。
ブロックセクションで、ブロックヘッダーに存在する幾つかの要素について話しました。これらのうち2つは、mixHashとnonceというのがありましたが、おさらいで書きます。
・mixHash:あるハッシュのことをいい、nonceと組み合わせることで、このブロックが十分な算出が実行できたことを認証します。
・nonce:あるハッシュのことをいい、mixHashと組み合わせることで、このブロックが十分な算出が実行できたことを認証します。
PoW機能は、この2つの要素の評価に使われます。
これらの計算は複雑なので、また記事で説明をしようと思いますが、ざっくりと以下のような感じになります。
ある”シード”といわれるものが各々のブロックで計算されます。このシードは”epoch”によって異なり、epochは常に30,000のブロックの長さになります。
最初のepochでは、シードは0の32byteの羅列のハッシュとなり、それ以降のepochではシードは、前回のシードハッシュのさらにハッシュとなります。このシードを使って、ノードは、擬似ランダムの”キャッシュ”を計算することができます。
このキャッシュは、ライトノードのためには欠かせないものです。ライトノードの目的は、すべてのブロックチェーンを同期することなしにトランザクションを確認することです。
キャッシュは確認したいブロックを参照できるので、ライトノードはトランザクションの有効性をキャッシュだけに基づいて確認することができます。
キャッシュを使うことで、ノードはDAGと呼ばれるデータ郡を作り出すことができ、このデータの要素は、キャッシュから擬似ランダムに選ばれた要素によります。マイナーになるためには、このデータ群をすべて生成する必要があります。当然このデータは時間とともに大きくなっていきます。
マイナーは、ランダムにデータ群のどこかをとってきて、あるハッシュ関数で計算をしmixHashにそのデータを格納します。さらにマイナーはアウトプットがnonceの条件に合うまでmixHashを作り続けます。アウトプットが条件を満たしたら、nonceは有効とされ、ブロックが新たにチェーンにつながれます。
セキュリティメカニズムとしてのマイニング
結局Powの目的は、暗号的にセキュアと証明することです。これは、片っ端から計算していく総当りのやり方以外に、nonceを見つける方法がないためです。
このハッシュ関数を何度も試す方法だと、難易度が高くなるにつれて正解までの時間が長くなっていきます。だからPOWにとって難易度(ディフィカルティ)は非常に大切な役割を果たし、現在のブロックチェーンセキュリティには欠かせないのです。
ブロックチェーンセキュリティとはなんのことでしょうか?これはシンプルで、みんなが信頼できるブロックチェーンを作ることです。
多くのチェーンがあったとき、信頼をなくします
どのチェーンが有効化わからなくなるからです。
ユーザが使うためには、1つのcanonicalなブロックチェーン(絶対的な基準となるチェーン)が必要です。
そこでPoWが役目を果たします。攻撃者が新しいブロックを作って記録を改ざんしたりすることが
難しく、絶対的な基準となるチェーンとなり続けることができるからです。
ブロックを有効化するには
攻撃者は、ネットワークの他のマイナーよりも速くブロックをnonceを見つけてブロックを有効化する必要があります。しかしこれは攻撃者がネットワークのマイニングの計算力の半分以上を持っていない限りは不可能です。そのような可能性を51%アタックと呼びます。
富の配分メカニズムとしてのマイニング
PoWはセキュアなブロックチェーンを提供する以外にも、このセキュリティを提供するために計算力を使ってくれた人に富を分配する手段でもあります。マイナーが上で書いたように、以下の報酬を受取ることができます:
・有効となるブロック生成の報酬5ETH(3ETHにすぐに変更される)
・ブロックに含まれるトランザクションによってブロック内で消費されたガスのコスト分
・ommerなどの追加報酬
Ethereumは、セキュリティと資産配分のためにPoWコンセンサスメカニズムをこれからの維持し続けるために、以下2つの特性を定着されるよう努力している。
1.できるだけ多くの人がアクセスできるようにする。
言い換えれば、アルゴリズムを実行するために特殊化されたハードウェアや珍しいハードウェアを必要としないようにしている。
この目的は、富の分配モデルとして、可能な限りオープンで誰にでもEtherを報酬として与えられるようにするためです。
2.単一のノード(またはセット)が不均衡な利益を生む可能性を減らす。不均衡な利益を生み出すことができるノードは、そのノードがブロックチェーンの決定に大きな影響を及ぼすことを意味します。ネットワークのセキュリティが低下するため、これはあまりいいことではありません。
Bitcoinブロックチェーンネットワークでは、PoWアルゴリズムがSHA256ハッシュ関数であるため、専用ハードウェア(ASICとも呼ばれます)で効率的に計算ができ、上の課題をうまく解決できていません。
一方EthereumはPoWアルゴリズム(Ethhash)をsequentially memory hardにすることにしました。つまり、nonceの計算に多くのメモリと帯域幅が必要となるようにアルゴリズムが設計されています。
メモリが大量に必要となるため、コンピュータが「メモリを並列に使用してnonceをいくつも見つける」ということが難しくなり、
広帯域が必要なため、超高速のコンピュータでも「nonceを複数、同時に見つける」ということが難しくなります。
これにより、集中化のリスクが軽減され、業者がやっているようなノード以外でも入る余地ができるのです。
EthereumはPoWのコンセンサスメカニズムから「Proof of stake」と呼ばれるものに移行を検討していますがこれはまた別の機会に。
最後に
長かったですが、いかがでしたでしょうか。難解ですが、英語の翻訳やホワイトペーパー/イェローペーパーの概念をかなり分かりやすく書いたつもりです。
自分はTheDAOのICOなど参加しながら、コントラクトアドレスに送るトランザクションをガス設定して実際に作り、とにかく手を動かして遊んでいました。そうした経験をしながら、今回のように体系的に考えると非常に感銘を受けます。(まさに天才がつくったものです。)
これからも実技と理論の"両輪"で勉強していきましょう。
ご一読ありがとうございました。