少しお茶を濁してきたGASについて、詳しく見ていきます。
ブロックチェーン上では、あるカウントが送金などのトランザクションを行った際に、その処理を検証した対価として、「賃金」がマイナーに支払われます。スマートコントラクトを実施した際も、実行したアカウントが主体となり、同じブロックチェーン上の仕組み上で動作するので、コントラクトを検証した対価として賃金をマイナーに支払う必要があります。
ここで、「賃金」と表現しているのは、支払金額そのものではなく、マイナーへの作業に対する支払いのことで、イーサリアムでは、マイナーに対する作業のコストを「GAS」という単位で表現します。「GAS」は、マイナーへの賃金のように扱われています。
イーサリアムの仕組みの中で、トランザクションを実施した対価がGASで、その必要額は、トランザクションの複雑さによって変わってきます。もちろん、スマートコントラクトだけでなく、ETH通貨を送信する際の、マイナーへの作業も同様にGASで支払います。
同じ支払うなら、ETH通貨で支払っても問題ないと思われるかもしれませんが、ETH通貨の価値は大きく変動するため、希少性のある通貨という価値と、単純にコンピュータの作業量に対する労働の対価を区別して考えたほうがよいということで、イーサリアムでは、「GAS」という考え方が生み出されました。
もちろん、GASは、トランザクションを実施したアカウントが支払う必要があります。しかし、各アカウントは、GASという価値を保有性ていません。これはどういうことなのでしょうか?
まず、ETH通貨とGASは等価値ではありません。トランザクションを実施する主体となるアカウントは、マイナーに対して、GASを支払いますが、マイナーが実際に受け取るものは、ETH通貨となります。トランザクションを行う際に、トランザクションの実行主体であるアカウントが、支払っても良いというGASによって、コンピュータの計算によりETHに変換されます。実際にどのようにGASからETH通貨へと変換されるのかその中身を見ていきたいと思います。
GAS検証準備
まず、testrpcを立ち上げてください。
$ export PATH=$PATH:/usr/local/Cellar/node/8.9.1/bin/
$ testrpc
次に、ブロックチェーン上のエミュレーターにつなぐために、gethコンソールを起動します。
$ geth attach http://localhost:8545
次のようなコマンドを打ち込んでみてください。eth.accounts では、アカウントのアドレス一覧が表示されたと思います。また、残りの2つのコマンドは、保持しているETH通貨が表示されたかと思います。
> eth.accounts
> web3.fromWei(eth.getBalance(eth.accounts[0]), "ether")
> web3.fromWei(eth.getBalance(eth.coinbase), "ether")
このeth.accounts[0]とeth.coinbaseは、同一のアカウントですが、とても重要なアカウントです。ノードを走らせた際にプライマリーなアカウントとなり、マイニングを走らせた際も、そのプライマリーがマイナーとなって、次のブロックを発掘した際には、報酬を受け取るアカウントになります。
このアカウントのアドレスは、以下のコマンドで確認出来ます。
> eth.accounts[0]
> eth.coinbase
話が横道にそれました。
次に、helloworldtruffleのスマートコントラクトをデプロイします。
スマートコントラクトは、なんでもよいのですが、作成するのであれば、
Ethereum入門(1) - スマートコントラクトの開発環境を構築する
Ethereum入門(2) - スマートコントラクトでHello world!
Ethereum入門(3) - スマートコントラクト Truffleの使い方
を参考に環境を構築してみてください。
プロジェクト直下のフォルダまで移動して、以下のコマンドを打ち込みます。
$ export PATH=$PATH:/usr/local/Cellar/node/8.9.1/bin/
$ truffle migrate
GASの検証
さて、今回のどれくらいのGASが今回のマイグレーションとデプロイで消費されたのでしょうか?
testrpcのログの1番めを見てください。truffle migrateコマンドでは、4つのトランザクションが実施されます。
- スマートコントラクトのマイグレーション
- スマートコンコントラクトのステータス更新
- Helloworldスマートコントラクトのデプロイ
- Helloworldスマートコントラクトのステータス更新
の4つです。そのなかで、一番目に実行された「スマートコントラクトのマイグレーション」トランザクションを見ていきます。
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
Transaction は、そのトランザクションをトラッキングするためのIDです。
Gas usage に注目してください。では、GASの使用量を見ていきます。
# | Gas Usage | 消費量 |
---|---|---|
1 | Gas Usage | 269607 |
2 | Gas Usage | 41981 |
3 | Gas Usage | 284220 |
4 | Gas Usage | 26981 |
合計 | TOTAL | 622789 |
合計で、622789 となりました。これには、どんな意味があるのでしょうか?
coinbaseアカウントの、ETHの残高を確認してみましょう。
> web3.fromWei(eth.getBalance(eth.coinbase), "ether")
99.9377211
もともとのcoinbaseの残高が、100だったので、
100 - 99.9377211 = 0.0622789
になります。
ということで、この4つのトランザクションによって、マイナーに支払われたETH通貨は、0.0622789ETHとなります。
では、一体、どのようにこの数値が計算されたのでしょうか?
gethコンソールに移動して、getTransaction コマンドを打ち込んでみます。引数は、Transaction を特定するためのIDを指定します。
eth.getTransaction('0x73151f02e403fc97d10d4778335035e7901e72270753ca7e5575b925386d204e')
{
blockHash: "0x95a2d300f122e1c23146a119e5d1d248b7e48d2398471cc3a4a0eb5712bf3fc2",
blockNumber: 1,
from: "0xa47964decea42227d060f564802b8f7acd075585",
gas: 4600000,
gasPrice: 100000000000,
hash: "0x73151f02e403fc97d10d4778335035e7901e72270753ca7e5575b925386d204e",
input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820eca484f2c326143b2652282850ae7046e3282efa4ec12b0ce0e2099f89c220ff0029",
nonce: 0,
to: "0x0",
transactionIndex: 0,
value: 0
}
この中で、gasとgasPriceパラメータに注目してください。
gas: 4600000,
gasPrice: 100000000000,
gas: 4600000 は、genesisブロックを生成した際に設定した数値で、支払うべきgasの最大値と定義されていました。
gasPrice: 100000000000 は、デフォルトで設定される数値で、Weiの単位で表記されています。
100000000000(Wei)は、0.0000001(ETH)となります。
この単位は、イーサリアム独特の単位なのですが(たぶん)、便利なETH コンバーターがあるので、試してみてください。
https://etherconverter.online/
では、消費したETHを計算してみます。
計算式としては、
gasPrice(ETH) × Total Gas Usage = 支払われたETH
これを当てはめていくと、
0.0000001(ETH) × 622789(total gas usage) = 0.0622789 (ETH)
となり、実際に支払われたETHと一致します。
注意したいのは、gasPriceは、それぞれのネットワークノード上で異なる値が設定されてるはずです。
100000000000(Wei)は、testrpc上のデフォルトの値であり、その他のマイナーは、別の値を設定しているはずです。
次に、gas: 4600000
を詳しく見ていきます。gas: 4600000
は、genesisブロック
を生成した際に記載した数値で、支払うべきgas
の最大値でした。実際に支払われた値は、Gas usage: 269607 です。
ここで、重要なパラメータは、Gas
とGas Price
です。Gas Price
は、Wei単位のgasPrice: 100000000000 でした。イーサリアム上では、Gasは、イーサリアムノードで、トランザクションを実施させた際の支払うべき対価であり、作業に対するコストです。
では、このGas usage: 269607 はどうやって決定されたのでしょうか?
実は、スマートコントラクトのトランザクションを行った際に、前もって正確なGasの量を見積もるのは非常に難しいです。その時のコンピュータの状態やマイナーの設定にも依存しているからです。ここの詳細な計算量の見積もりは、次回に書きたいたいと思いますが、すごくざっくりと説明すると、トランザクションの複雑さに応じて、必要なCPUパワーが変わってきて、GASとして換算される。ということです。
なので、非常に非効率なスマートコントラクトを記述した際などは、想定以上にGASが必要になります。その他、コントラクト内で無限ループに陥った場合などは、際限なくGASが支払われ、その結果、ETH残高がなくなる恐れがあります。
その為、イーサリアムでは、そのスマートコントラクトに対して、支払うGASの上限を儲けて、想定以上のGASを支払わなくて良い配慮がなされています。それが、Genesisブロックのパラメータで設定したGasということになります。
注意したいのは、スマートコントラクトでトランザクションを行う際は、上限に達したら、対象のコントラクトは実施されませんが、それに関連する作業をしてもらったマイナーには、支払いが発生するところです。これは、経営者が事業を起こし、労働者を雇って作業を依頼したとして、たとえ、その事業に失敗したとしても、賃金は払わなければならない、という関係ににています。
マイナーは、より高いGASを支払うトランザクションに興味があります。もしGAS価格が高く設定されていれば、対象のトランザクションは、より早く実施されます。GASは、支払う報酬の上限値でした。gas:4600000をマイナーに支払うことにしましたが、実際には、マイナーは、Gas usage:269607しか消費しませんでした。この場合は、マイナーからGasが返却されます。
スマートコントラクトの関数とGASの関係について
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;
}
}
helloworldコントラクトを詳細に見ていきます。
truffleコンソールを開いて、デプロイされたスマートコントラクトのインスタンスに対して操作を行います。
$ PATH=$PATH:/usr/local/Cellar/node/8.9.1/bin/
$ truffle console
次に以下のようなコマンドを打ち込んでみます。
> truffle(development)> helloworld.deployed().then(function(instance){app = instance;})
undefined
> truffle(development)> app
:
:
> app.getHelloworld()
> Hello world!
> app.getHelloworld.call({from: web3.eth.accounts[1]})
> Hello world!
> app.getHelloworld.call({from: web3.eth.accounts[2]})
> Hello world!
それぞれ、異なるアカウントから実行しましたが、すべて同じ値が返されてきていますね。
次に、Gethコンソールでチェックすると、これらの残高は減っていません。
> web3.fromWei(eth.getBalance(eth.coinbase), "ether")
99.9377211 ← これは、migrateの時に減った。
> web3.fromWei(eth.getBalance(eth.accounts[1]), "ether")
100 ← 減っていない
> web3.fromWei(eth.getBalance(eth.accounts[2]), "ether")
100 ← 減っていない
Gasのところで、トランザクションには、対価が伴うという話をしましたが、getHelloworld()関数ではその残高が減っていません。これはどういうことでしょうか?
実は、helloworld.sol のsolidityの記述をみると、
function getHelloworld() constant でわかるように、getHelloworld()関数は、constantで定義されています。プログラミングにおいて、constantとは、値が変化しないことが保証される定数のことでした。constantが指定されると、状態が変化しないので、イーサリアム上ではこの操作に関しては、Gasが発生しません。
次に、状態を変化させてみたいと思います。truffleコンソールで以下のコマンドを打ち込んでみてください。
# アカウント6から、トランザクションを実施
$ truffle(development)> app.setHelloworld("Here is Another world, {from: web3.eth.accounts[5]})
{ tx: '0x20e6675f5da3e17d857f13eccd2ffa1ae8620e425fa5a3008354b6abd52dd2ed',
receipt:
{ transactionHash: '0x20e6675f5da3e17d857f13eccd2ffa1ae8620e425fa5a3008354b6abd52dd2ed',
transactionIndex: 0,
blockHash: '0x67ee12cad802911d9625fccd345d362429b704edd5d9dbb31db649776873b3a9',
blockNumber: 5,
gasUsed: 33288,
cumulativeGasUsed: 33288,
contractAddress: null,
logs: [],
status: 1 },
logs: [] }
次にGethコンソールで以下を打ち込みます。Gasがアカウント6から支払われているのが確認できました。
> web3.fromWei(eth.getBalance(eth.accounts[5]), "ether")
99.9966712
Gasは、トランザクションを発生させたアカウントから支払われり、Gasが支払われる状況としては、コントラクトのデプロイ、コントラクトの状態の変更となります。また、constanで定義されたコントラクトに対しては、Gasが支払われません。