こんにちは、えたろうです。
「少しだけ易しいMastering Bitcoin」を一通り読んだ方は、Bitcoinの仕組みはだいたい理解できたかと思います。
Bitcoinでブロックチェーンの基本を理解した後、次のステップはやはりEthereumの理解ですよね。
- 「Bitcoinの仕組みはだいたい分かった!ブロックチェーンすげぇ!」という方
- 「Ethereumでスマートコントラクトが実現する云々」「Ethereum上で Dappsを作る云々」というのはよく聞くけど、実際Ethereumってどう動いてるの?という方
を対象にして懇切丁寧に書いて行きます。
第2回では次回以降で Ethereumを学んでいくために「Ethereum特有の様々な概念を理解する」ことを目標にします。
EVMという概念
第1回の終わりで、Ethereumが新規トランザクションをブロックに取り込む大まかな流れを見ました。
ここでBitcoinと大きく違うのはEthereumではブロックを生成したマイナー、ブロックを検証するノードが、トランザクションを実行しなくてはならない点です。
Bitcoinのトランザクションは単なる送金データでしたので、それが正しいことを各ノードが検証した後は、ブロックに取り込めばいいだけでした。
一方、Ethereumの場合は、そのトランザクションが実行された結果の各値、すなわちプログラムの実行結果が正しいかどうかを検証しなければなりません。
このように各ノード(恐らく全てのフルノード)が、Ethereumに関連するプログラムを実行するのですが、それを行う仮想マシンをEVM(Ethereum Virtual Machine)と言います。
各ノードが自分のコンピュータ上でEthereumのプログラムを実行するソフトを動かしていてそれがEVMと捉えれば良いと思います。
また、Ethereumでは各ノードが集って形成しているネットワークを一つのコンピュータと見なして The World Computer と呼んでおり、これがEVM (Ethereum Virtual Machine)と表されることも多いです。
コントラクトアカウントのプログラムコードは、このEVM(World Computer)に向けて記述します。
EVMは “EVM bytecode” という独自の低レベル言語を持っていますが、これは人間にとってはとても読みにくいものです。
なので、開発者がプログラムを書くときにはSolidityやVyperなどの高級言語(人間にとって可読性のある言語)で書き、そのプログラムコードを実行する際には、EVMが理解できるEVM bytecodeにコンパイルしてから実行するのです。
マークルパトリシアツリー
マークルパトリシアツリーは、Ethereumのあらゆるところで使われているデータ要約の手法です。
Bitcoinで使用されていたマークルツリー(Merkle tree)に、ツリーの中でのデータの探索が容易かつ高速になるパトリシアツリーを組み合わせたものと考えれば分かりやすいと思います。
役割としては「データのRoot Hashへの要約」なのですが、Ethereumでは頻繁にツリーの中を探索する必要があるためパトリシアツリーを組み合わせたという感じです。
詳しい解説は以下のサイトが分かりやすすぎたので、一読しておくと良いと思います。
イーサリアム(Ethereum)のデータ構造~マークルパトリシアツリー
アカウントの構造
第1回で、Ethereumには
- 外部アカウント(Externally owned account : EOA) = 人間
- コントラクトアカウント(Contract account) = プログラム
の2種類のアカウントがあることを学びました。
これらアカウントのデータ構造について詳しく見て行きます。
外部アカウント(EOA)は、ユーザーを表したアカウントであり、「20byteのアドレス」と「state」を持ちます。
ユーザーが管理している秘密鍵でコントロールされています。
外部アカウントのアドレスは、BitcoinにおけるBitcoinアドレスと同じ立ち位置です。
ユーザー(ウォレット)が持つ、秘密鍵から公開鍵が生成され、その公開鍵からこの外部アカウントアドレス(いわゆるEthereumアドレス)が生成されます。
外部アカウントはstate(パラメータ)として、NonceとBalanceのみを持ちます。
外部アカウントのNonceはアカウントが行ったトランザクションの回数を記録しているもので、二重支払いを防ぐ役割をしています。
また、トランザクションにもこのNonceが含まれるので、もしトランザクションが作成された順番と逆の順にあるノードに伝搬したとしても、nonceの小さい方から順にブロックに詰められるようになっています。
またethereumでは、外部アカウントがBalanceを持っていることによって、Bitcoinのように残高照会の際にUTXOをかき集める必要はなく、自分の外部アカウントのBalanceの値を取ってこれば良いので残高参照が早いです。
しかし、一方でUTXOで表されているBitcoinはトランザクションが独立しているため、「トランザクションの並列処理」ができたのですが、アカウントに残高情報のあるEthereumでは、並列処理をすると「1つしかない値に対して同時に変更を加える」場合などが生じ、期待した処理が実現しないことがあります。
Contractアカウント(CA)は、スマートコントラクトと呼ばれるプログラムを表したアカウントであり、「20byteのアドレス」と「state」を持ちます。
自身が持つプログラムコードに管理されます。
コントラクトアカウントのアドレスは、「このCAの作成者(外部アカウント or Contractアカウント)のアドレス」と「Nonce」つまり「このCAは、作成者が最初から何番目に作ったCAか」を合わせてハッシュ化したものです。
CAはstate(状態)として、Nonce, Balanceの他にも、自身の持つプログラムコードのハッシュであるCodeHashとStorage rootというデータを持っています。
CAはプログラムであるので、自身のプログラムコードを実行した結果の配列や文字列などを保持しておかなければなりません。
しかし、全てのプログラム実行結果を自身に保持していては容量を圧迫してしまいます。
そのために、それらのプログラム実行結果をマークルパトリシアツリーで要約したハッシュ値だけをStorage rootとして持ち、実際のマークルパトリシアツリーはブロックの外に保持されるようになっているのです。(詳しくは次回)
Ethereumの内部通貨
Bitcoinは「管理者無し(非中央集権)で送金を実行できるデジタル通貨」のために構築されたものでした。
そこでやり取りされる通貨は他でもないBitcoinであり、1 Bitcoin = 10^8 satoshi = 1億 satoshi である、satoshiという単位も存在していました。
Ethereumは、「誰もがあらゆるプログラムをブロックチェーン上で実行できるプラットフォーム」であり、Bitcoinとは大きく目的が異なりますが、内部通貨は持っています。
Ethereumの内部通貨はether(イーサ)と呼ばれるもので、Bitcoinのような通貨としての送金にも、トランザクション手数料の支払いにも使用されます。
また1 ether = 10^18 wei である、 weiという最小単位が存在します。
その他にもたくさんの単位が存在しますが、Dapps開発をするレベルでも ether と wei だけ覚えておけば良いでしょう。
トランザクション手数料Gasの概念
Bitcoinのトランザクション手数料は、そのトランザクションの「Inputの額とOutputの額の差額として表され、マイナーは基本的にはそのトランザクション手数料が高いトランザクションから順にブロックに取り込んでいく」というものでした。
Bitcoinでは、トランザクションをブロックに取り込むマイナーの目線で考えると、マイナーはPoWを発見した後は「トランザクションデータをブロックに取り込む」という処理を行えば良いだけであり、その処理で彼らに負担がかかる可能性のある点は「トランザクションのデータサイズがめちゃめちゃデカイ」場合のみです。
そのため、トランザクションデータサイズに比例してトランザクション手数料がかかるのがBitcoinでした。
一方でEthereumは、「第1回Bitcoinを通じて理解するEthereum ⑶世界の"状態"が記録されていく一連の流れ」でも見た通り、マイナーが「トランザクションを実行する」必要があり、この時「"あらゆるプログラムを実行"してその実行結果に従って値の書き換え」を行わなければなりません。
つまり、EVMがチューリング完全でどんな処理でも書き込める以上、マイナー目線で見るとPoWを解いてブロックの生成権を得た後、「様々なプログラムの実行」をする際に「そのプログラムの処理がめちゃめちゃ大変で負担がかかる」可能性が常にあるのです。
そのためEthereumでは、「トランザクションを実行する時の計算量」に比例して手数料がかかるようになっています。
このEthereumでのトランザクション手数料はGasと呼ばれており、"電力"だとイメージすると分かりやすいと思います。
Gasの額は以下の計算式で求められ、Etherで支払われます。
トランザクション手数料(Gas)総量 = GasPrice × GasUsed
トランザクション手数料Gasの額を決定する3つの要素について以下で見ていきます。
- Gas Used
Gas Usedは、「そのトランザクションの実行処理にかかったGasの総量」です。
電力で例えると、プログラムを処理するのにかかった電力量だとイメージできます。
トランザクション実行にかかるGasは、もちろん使ったマシーンの計算量などによって決まるため、複雑なプログラムを実行するトランザクションほどGas Usedが高くなります。
- Gas Price
Gas Price は、「Gas1単位の処理に対してトランザクション生成者からマイナーに支払われる手数料(ether)」です。
「電力 1kW/h かかる処理をした時のマイナーの給料」みたいなものです。
これはトランザクションの処理を依頼する側であるトランザクション生成者がが任意に決定できます。
手数料がマイナーのインセンティブになっているので、このGas Priceが高ければトランザクションがマイニングしてもらいやすくなり、低ければブロックに取り込まれるまでにより時間がかかるか、最悪の場合ブロックに取り込んでもらえないこともあるでしょう。
- Gas Limit
以上見てきたGas UsedとGas Priceだけでトランザクション手数料Gasが決まるとすると問題があります。
もし、「ユーザーが作成したトランザクションが実行するプログラムにバグがあり、永遠にループしてしまった」場合に、トランザクション生成者は無限にGasを支払うことになってしまうのです。
このようにあらゆるプログラムを実行する以上、事前にそのトランザクションにかかるGas Usedを見積もることは難しいです。
そこでGas Limit という「このトランザクションが使っても良いGasの上限」をトランザクション生成者が決めることができるようになっています。
つまりトランザクション生成者は実質、トランザクション生成の際に「Gas Price × Gas Limit」分のetherをトランザクションの中に入れ込むことになります。
もし、トランザクションが消費するGas Usedが Gas Limitに達してしまった場合には、強制的にトランザクションの実行が終了します。
ここでポイントなのは、「Gas Used が Gas Limitに達してトランザクションの処理が停止した場合は、それまでに消費したGasはトランザクション生成者には返ってこない」という点です。
逆に、トランザクションの処理を完遂してもGas Usedが Gas Limitに達しなかった場合は、余った分のGas × Gas Price 分のetherがトランザクション生成者に返却されることになります。
新規トランザクションの処理をマイナーに依頼する例
最後に、トランザクション手数料計算の例を見ていきます。
トランザクション生成者は、トランザクションの 「Gas Price, Gas Limit」にそれぞれ「20Gwei (0.000 000 02 ether) , 50,000」を入れ、トランザクションを作成し、Ethereumネットワーク(EVM)に伝搬したとします。
ここでトランザクション生成者がこのトランザクション実行に対して支払っても良い最大の手数料は、50,000 × 20Gwei = 0.001 etherということになります。
(K[キロ]は10の3乗、M[メガ]は10の6乗、G[ギガ]は10の9乗なので、Gweiは 10^9 weiです。ちなみに 1ether は10^18weiでした。)
ブロックの生成権を得たマイナーが、未実行トランザクションの中からそのトランザクションを選んで実行します。
このトランザクションの実行処理に 45,000 のGas がかかったとします。
この場合は、トランザクションが完遂された上で
50,000 - 45,000 = 5,000 のGas が余り、
5,000 × 20Gwei = 0.000 1 ether
がトランザクション生成者に返却されます。
一方で、トランザクション実行処理に55,000 のGasがかかったとすると
トランザクションはガス切れでブロックに取り込まれず、
50,000 × 20Gwei = 0.001 ether
がトランザクション生成者の残高から失われることになるのです。
ちなみに失敗したトランザクションもどこでGasが切れたか、などがログとして記録されます。
Gas の存在意義
EVMはチューリング完全のマシンであり、あらゆるプログラムが実行できます。
そのため、もし手数料であるGasがなかったら、悪意のある攻撃者がめちゃめちゃに計算量が多いプログラムをEthereuネットワーク上に放ち、大きな負担をかけて攻撃することが可能になってしまうのです。
Uncle ブロック
Uncle ブロックは、forkが起こった際の「親のブロックと同じ親を持つブロック」のことです。
上記の画像でいうと、現在のブロックである「ブロックA2」のUncleブロックは「ブロックA2の親であるブロックA1の親(ブロックn)を親にもつ他のブロック」なので「ブロックB1」になります。
Uncleは叔父という意味なので、「親ブロックの兄弟」と覚えておいてもいいかもしれません。
また、Uncleではなくommerと呼ばれることもあります。
Bitcoinではブロック生成間隔は10分でしたが、Ethereumではトランザクションの処理能力を上げるためにブロック生成間隔が15秒になっています。
しかし、Bitcoinについて学んだ際にも言及しましたが、ブロック生成速度が早くなると必然的にforkの頻度が高くなります。
Ethereumは、この「forkが頻繁に起こってしまう」という問題をGHOSTプロトコルという上手いインセンティブ設計で解決しようとしています。
そのインセンティブ設計については第4回で解説しますが、ここではその鍵となるUncleブロックの定義だけ覚えておいてください。
Remote Client ※2018/11に追記
Bitcoinのノードには「フルブロックチェーンデータベースを保持」するFull Nodeと、「ブロックヘッダだけを保持してSPV検証を行う」Light Clientの2種類が存在しました。
一方で、Ethereumのクライアントは、 Full nodeとRemote clientの2種類です。
full nodeはBitcoinのフルノードと同じで、ブロックチェーンの全データを保持し、独立してトランザクション・ブロックの検証作業を行うことができるものです。
しかし、Ethereumのフルノードを立てるためには、80–100 GB(2018年9月時点、時間が経つにつれて容量増加) のデータをローカルストレージに保存しなくてはなりません。
そのため、スマートフォンやIoTデバイスを始めとしたそのような大容量ローカルストレージを持たないデバイスは、Remote clientとして動作することになります。
ここで注意しておくべき点は、EthereumのRemote clientは、Bitcoinのlight clientとは異なるという点です。Bitcoinのlight client は、ブロックチェーンのブロックヘッダデータのみを保持し、Merkle proofによって受け取った「トランザクションがそのブロックに含まれているかどうか」の検証は独立してすることができるのでした。
しかし、これとは対照的にEthereumのremote clientは、ブロックやトランザクションの検証は行わず、完全に自身が通信しているfull nodeを信頼するようになっています。
Remote Clientは以下の機能を持ちます。
- ウォレット内の秘密鍵とアドレスを管理する
- トランザクションを作成・署名・伝搬する
- スマートコントラクトと通信する
- Dappsと通信してブラウザとして機能する
- Block Explorerなどの外部サービスへのリンクを提供する
- 外部から為替レートの情報などを取得する
- web3インスタンスをJavaScriptオブジェクトとしてブラウザに入れ込む
- 他のEthereumノードのRPC APIにアクセスする
walletは基本的にはトランザクションを処理する機能(上記でいうと上から2つ)のみを持つものを指すので、remote clientは、walletとも意味が異なります。
もちろんセキュリティ上、軽量クライアントはRemote ClientよりもBitcoinのLight Clientのような機能を持っていた方がいいので、EthereumでもLight Clientは開発中だそうです。
第2回では、Ethereumを理解するのに必要な前提知識である「Ethereum特有の概念」について書きました。
次回からはEthereumの詳しい仕組みに入っていきたいと思います。
続き↓
0コメント