イーサリアムノードにおいて、なにがデプロイされ、コントラクトは何をしたのでしょうか? Ethereum のコントラクトは、EVM(Ethereum Virtual Machine)というプログラム実行の仮想環境を持っています。このEVMが実行できるのは専用のバイトコードとなります。
その環境で、実際に操作をするためのものがopecodeとなります。
オペコード (operation code, opcode) とは、機械語の1個の命令の部分で、実行する操作 (operation) の種類を指定する部分のこと、およびそのコード(符号)のことである。 数式における演算子に相当する。 命令のもうひとつの主要部分は、操作される対象を指定するオペランド(被演算子)である。
オペコード - Wikipedia
https://ja.wikipedia.org/wiki/オペコード
今回もopecodeを知るために、以下のhelloworldコントラクトを使用します。
pragma solidity ^0.4.18;
contract helloworld {
string message;
function helloworld() public {
message = "Hello World!";
}
function setHelloworld(string _message) public {
message = _message;
}
function getHelloworld() constant returns (string) {
return message;
}
}
helloworldTruffleスマートコントラクトの3つ目のトランザクションの詳細を見ていきます。
準備
testrpcを起動して、
$ export PATH=$PATH:/usr/local/Cellar/node/8.9.1/bin/
$ testrpc
truflleでコントラクトをプロジェクトフォルダの直下で、マイグレーションしてください。
$ export PATH=$PATH:/usr/local/Cellar/node/8.9.1/bin/
$ truffle migrate
testrpcのコンソール上では、以下のようなログが見れるはずです。
Transaction: 0x73151f02e403fc97d10d4778335035e7901e72270753ca7e5575b925386d204e
Contract created: 0xd86bb74bf418f2ab83a5bdaeea5e533afd504754
Gas usage: 269607
Block Number: 1
Block Time: Mon Nov 13 2017 12:45:50 GMT+0900 (JST)
eth_newBlockFilter
eth_getFilterChanges
eth_getTransactionReceipt
eth_getCode
eth_uninstallFilter
eth_sendTransaction
Transaction: 0x01923735dcda279a5dc973ec76cf8e27378322746763700114a7e7db1c69b5af
Gas usage: 41981
Block Number: 2
Block Time: Mon Nov 13 2017 12:45:50 GMT+0900 (JST)
eth_getTransactionReceipt
eth_accounts
net_version
net_version
eth_sendTransaction
Transaction: 0xd59f0a4c159aa2a2058bd7d8dc4b759d1d6474d3f4b85606e3b4a3d00ff73e10
Contract created: 0x0de31a5558126027cf2843faf3c0a29bd421eceb
Gas usage: 284220
Block Number: 3
Block Time: Mon Nov 13 2017 12:45:50 GMT+0900 (JST)
eth_newBlockFilter
eth_getFilterChanges
eth_getTransactionReceipt
eth_getCode
eth_uninstallFilter
eth_sendTransaction
Transaction: 0x0aa46ef52b05541b4cf5b4e7e0b52c687ac7b5fc70666382d27136b8899a108a
Gas usage: 26981
Block Number: 4
Block Time: Mon Nov 13 2017 12:45:50 GMT+0900 (JST)
eth_getTransactionReceipt
OPECODEの検証
gethコンソールから、トランザクションを取り出します。
コンソールから、(get console attach http://localhost:8545) で、geth コンソールに入ってください。
getTransaction()関数で、実施されたトランザクションの詳細を取り出します。
> eth.getTransaction('0xd59f0a4c159aa2a2058bd7d8dc4b759d1d6474d3f4b85606e3b4a3d00ff73e10')
{
blockHash: "0xc57824e2ea76d97c530056031b9a54f3175e1d68f636ecd29fc7e23876e60bca",
blockNumber: 3,
from: "0xa47964decea42227d060f564802b8f7acd075585",
gas: 4600000,
gasPrice: 100000000000,
hash: "0xd59f0a4c159aa2a2058bd7d8dc4b759d1d6474d3f4b85606e3b4a3d00ff73e10",
input: "0x6060604052341561000f57600080fd5b6040805190810160405280600c81526020017f48656c6c6f20576f726c642100000000000000000000000000000000000000008152506000908051906020019061005a929190610060565b50610105565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a157805160ff19168380011785556100cf565b828001600101855582156100cf579182015b828111156100ce5782518255916020019190600101906100b3565b5b5090506100dc91906100e0565b5090565b61010291905b808211156100fe5760008160009055506001016100e6565b5090565b90565b6102e3806101146000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063d005bb8a14610051578063eaebeb50146100ae575b600080fd5b341561005c57600080fd5b6100ac600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061013c565b005b34156100b957600080fd5b6100c1610156565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101015780820151818401526020810190506100e6565b50505050905090810190601f16801561012e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101529291906101fe565b5050565b61015e61027e565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101f45780601f106101c9576101008083540402835291602001916101f4565b820191906000526020600020905b8154815290600101906020018083116101d757829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061023f57805160ff191683800117855561026d565b8280016001018555821561026d579182015b8281111561026c578251825591602001919060010190610251565b5b50905061027a9190610292565b5090565b602060405190810160405280600081525090565b6102b491905b808211156102b0576000816000905550600101610298565b5090565b905600a165627a7a72305820f40c0f74a210ee4da7bf7bc70cef39624ba757a318631509bf1312dbeaa4f6430029",
nonce: 2,
to: "0x0",
transactionIndex: 0,
value: 0
}
opecodeを考えるとき、重要な要素は、input要素です。これは、先程、solidityで記述されたhelloworld.solをコンパイルした後のバイトコードです。
このバイナリのバイトコードが、コンパイルされ、EVM上にデプロイされ、操作されます。これは、truffleコンソールからも確認出来ます。helloworlコントラクトと、先程のトランザクションのバイトコードが一致しています。
truffleから、以下のコードを打ち込むと、バイトコードが帰ってきます。このバイナリとInputのバイナリは、同一となっています。
> truffle(development)> helloworld.binary
'0x6060604052341561000f57600080fd5b6040805190810160405280600c81526020017f48656c6c6f20576f726c642100000000000000000000000000000000000000008152506000908051906020019061005a929190610060565b50610105565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100a157805160ff19168380011785556100cf565b828001600101855582156100cf579182015b828111156100ce5782518255916020019190600101906100b3565b5b5090506100dc91906100e0565b5090565b61010291905b808211156100fe5760008160009055506001016100e6565b5090565b90565b6102e3806101146000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063d005bb8a14610051578063eaebeb50146100ae575b600080fd5b341561005c57600080fd5b6100ac600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061013c565b005b34156100b957600080fd5b6100c1610156565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101015780820151818401526020810190506100e6565b50505050905090810190601f16801561012e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101529291906101fe565b5050565b61015e61027e565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101f45780601f106101c9576101008083540402835291602001916101f4565b820191906000526020600020905b8154815290600101906020018083116101d757829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061023f57805160ff191683800117855561026d565b8280016001018555821561026d579182015b8281111561026c578251825591602001919060010190610251565b5b50905061027a9190610292565b5090565b602060405190810160405280600081525090565b6102b491905b808211156102b0576000816000905550600101610298565b5090565b905600a165627a7a72305820f40c0f74a210ee4da7bf7bc70cef39624ba757a318631509bf1312dbeaa4f6430029'
このバイトコードは、EVMネットワーク上の全てのノードに配置され、実行されます。linuxでもwindowsでもmacでも環境に依存せずに起動することが出来ます。
しかし、EVMは、このバイトコードをそのまま実行するわけではなく、一旦、EVMが理解できる形で変換されますが、それをopecodeといいます。
このopecodeは、アセンブリ言語となります。
このバイトコードをopecodeに変換してくれるサービスがあります。
● Solidity バイナリコード - Opecode 変換ツール
使い方は、このhelloworldのbinaryデータである 0x60606040...0029
をコピー&ペーストして、変換するだけです。以下のようなアセンブリが出力されますが、このコマンドの一つ一つがopecodeです。
[1] PUSH1 0x60
[3] PUSH1 0x40
[4] MSTORE
[5] CALLVALUE
[6] ISZERO
[9] PUSH2 0x000f
[10] JUMPI
[12] PUSH1 0x00
[13] DUP1
[14] 'fd'(Unknown Opcode)
[15] JUMPDEST
[17] PUSH1 0x40
[18] DUP1
[19] MLOAD
[20] SWAP1
:
:
EVM上では、この変換後のopecodeがEVM上で実行されます。
これは、helloworld.sol オブジェクトをopecodeにしたものです。一昔前のエンジニアたちは、このアセンブリ言語で、プログラムを記述したと言われています。また、Visual C++などを扱うと、エラーを起こした際にこのようなアセンブリが現れることがあります。
イーサリアムOpecodeの詳細に関しては、イーサリアム イエローペーパーに記載されています。
● イーサリアム イエローペーパー
Gasの計算
一般的に、PUSH1 や MSTORE などのコマンドをopecodeといいます。それぞれのコマンドにアクションが、決められていて、PUSH1 0x60 の0x60などは、EVM上のアドレスを表しています。上から順に実行していくのですが、メモリ空間をopecodeの操作により、移動することで、複雑なプログラムも実行できます。イーサリアムでは、このEVM上で動作するopecodeとCPUパワーを上手く結びつけることで、GASを算出しています。
実際のGASによるコストは、このイエローペーパー上で詳細に定義されいて、opecodeと関連していることが確認出来ます。
例えば、CALLVALUE というopecodeであれば、
0x34 CALLVALUE 0 1 Get deposited value by the instruction/transaction responsible for this execution.
µ0s[0] ≡ Iv
と記載されています。
訳すと、「この実行に責任のあるインストラクションまたはトランザクションによって、保存された値を得る」とあります。
このとき、「µ0s[0] ≡ Iv」のµ0s[0]は、Gasを計算する際の変数として使用されています。
例にあげたopecodeは、ほんの一部ですが、全てのopecodeに対して、計算手法が定義されていて、このopecodeによる操作により、GASの総量が決定されます。
このイーサリアムイエローペーパーの詳細はかなり難解ですが、それぞれのopecodeにGasを見積もるための計算手法があり、この総和として、GASが決定されると理解しておけば当面問題ないと思います。
また、複雑なコントラクトを記述してしまうと、opecodeによるEVMへの操作が多くなり、GASが高く付いてしまうので、完結で必要最低限のコントラクトとする必要があるのがわかります。