概要
最近、仮想通貨の市場も落ち着いてきて、技術的な話がしやすい環境が整ってきたように思う。最近、改めて「ブロックチェーンとは何か?」という疑問を持ち、技術的な側面を探究したくなった。
仮想通貨といえば、ビットコインが一番有名だが、イーサリアムのほうが後発だけあって、技術的な整理がされていて、ブロックチェーンの技術的な側面を学ぶには便利に感じた。イーサリアムでは、スマートコントラクトと言って、一定のプログラムをマイニングの過程で実行できる機能がある。そのプログラムは安全性を確保するため、EVM(Ethereum Virtual Machine)という仮想マシン上で実行される。
通常は、Solidity という専用の高級言語でコントラクト上のプログラムは記述されることが多いのだが、最終的には、EVM上のバイトコードにコンパイルされて実行される。
これらのプログラムは、マイニングの過程で実行され、ブロックチェーンに記録されるという特殊な要件があるために、おそらく厳しい制約が課されることが想像される。より低水準の動きを知りたくなったので、EVMのバイトコードそのものを少し調べてみた。
調査結果
EVMのバイトコードの仕様は、yellow paperと呼ばれる論文に規定されている。だが厳密すぎて読みにくい。私の場合、ざっくり仕様がつかめれば十分だったので、Notes on the EVMがとても役にたった。
準備
Ethereum では Go 言語の実装が有名なようである。これを利用したシステムをインストールする。
私は Go 言語には慣れていないのでちょっと手こずった。私が実際に行った手順を下にしるす。
- Go 言語のインストール(説明省略)
- GOPATH 環境変数の設定
- 例 .bashrc 等に記述
- export GOPATH=~/go
- export PATH=$PATH:$GOPATH/bin
- source ~/.bashrc などして反映
- 例 .bashrc 等に記述
- go get github.com/Masterminds/glide
- Go 言語のパッケージ管理ツールである glide がインストールされる
- $GOPATH/src で
- git clone https://github.com/CoinCulture/evm-tools.git
- cd evm-tools
- glide install # もろもろのソースコードがダウンロードされる
- make # $GOPATH/bin に disasm, evm, evm-deploy の各コマンドがインストールされる
バイトコードについて
Notes on the EVMの解説がたいへんわかりやすいので、読んで欲しい。基本的にスタックマシーンである。
1から10の合計を計算する
アセンブリ言語
push 10
push 0
push 0x0
MSTORE
JUMPDEST
DUP1
push 0x0
MLOAD
ADD
push 0x0
MSTORE
push 1
SWAP1
SUB
DUP1
push 7
JUMPI
という内容を sum.esam という名前で保存。
本当は push 1
ではなく PUSH1 1
と書きたいのだが、evm compile
コマンドは受け付けてくれない。オペランドの数字の大きさで自動的に push1
, push2
... 等にアセンブル仕分けているようだ。
evm compile
コマンドは大文字小文字の区別をするらしく、大文字で書くと yellow paperのオリジナルのオペコードとして取り扱われ、小文字で書くと、このコマンドの独自拡張となり振る舞いが変わるようである。
evm compile
がアセンブルする対象のソースコードの仕様を見つけることができない。だれがご存じの方がいれば教えて下さい。
アセンブル
$ evm compile sum.easm
600a60006000525b80600051016000526001900380600757
バイトコードがそっけない16進数で表示される。
ディスアセンブル
正しくアセンブルされたか確認する。
$ evm compile sum.easm | disasm
600a60006000525b80600051016000526001900380600757
0 PUSH1 => 0a
2 PUSH1 => 00
4 PUSH1 => 00
6 MSTORE
7 JUMPDEST
8 DUP1
9 PUSH1 => 00
11 MLOAD
12 ADD
13 PUSH1 => 00
15 MSTORE
16 PUSH1 => 01
18 SWAP1
19 SUB
20 DUP1
21 PUSH1 => 07
23 JUMPI
大丈夫そうだ。
実行
実行といっても当然イーサリアムの本番環境で行われるわけではなく、ローカルPCの EVM で実行されるだけ。
$ evm --debug --code $(evm compile sum.easm) run
#### TRACE ####
PUSH1 pc=00000000 gas=10000000000 cost=3
PUSH1 pc=00000002 gas=9999999997 cost=3
Stack:
00000000 000000000000000000000000000000000000000000000000000000000000000a
PUSH1 pc=00000004 gas=9999999994 cost=3
Stack:
00000000 0000000000000000000000000000000000000000000000000000000000000000
00000001 000000000000000000000000000000000000000000000000000000000000000a
MSTORE pc=00000006 gas=9999999991 cost=6
Stack:
00000000 0000000000000000000000000000000000000000000000000000000000000000
00000001 0000000000000000000000000000000000000000000000000000000000000000
00000002 000000000000000000000000000000000000000000000000000000000000000a
Memory:
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
(中略)
STOP pc=00000024 gas=9999999545 cost=0
Stack:
00000000 0000000000000000000000000000000000000000000000000000000000000000
Memory:
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 37 |...............7|
#### LOGS ####
Notes on the EVMとは出力が少し異なっている。バージョンの違いだろうか。また、コマンドの最後に run
を付けること。これもチュートリアルとは異なっている部分。
いちばん出力の最後に 37 (0x37 = 55) という部分が見えるだろう。これが1から10までを足しあわせた計算結果であり、正しく計算されていることがわかる。