アルゴリズムとかオーダーとか

仕事で勉強したことなどをまとめてます

estimateGasのすゝめ

Ethereumでスマートコントラクトを実行するとgasを消費します。そのため、スマートコントラクトを実行するために発行するTransactionにgas limitを指定する必要があります。
さて、このgas limitって一体いくつに設定したらいいのでしょうか?Transactionのgas limitの指定にはいろいろな条件があるため勝手な値を指定したところでうまく動かない場合もあります。
今回はgas limitを設定する時に考慮すべき条件と、適切なgas量を計算してくれるestimateGasの使い方についてまとめてみました。

gas limitに関係してくる様々な条件

gas limitを設定する場合にはいくつかの条件をクリアする必要があります。これらの条件をクリアしていない場合は多くの場合でtransactionの実行に失敗します。まずは正常にtransactionを実行するためにクリアすべきgas limitの条件を詳しく紹介します。

2つのgas limit

実はgas limitはBlock gas limitTransaction gas limitの2つが存在します。それぞれの違いは以下の通りです。

  • block gas limit: 1つのBlockで消費可能な最大gas量のこと。1つのblockに取り込むtransactionの個数はこのblock gas limitによって決定される。transactionのgas limitの総和がblock gas limitを超えない分だけのtransactionが1つのblockに取り込まれる。
  • transaction gas limit: そのtransactionによって起動されたContractを実行する時に消費される最大gas量のこと。Contractの実行に十分ではないgas limitを指定した場合は、Contractの実行に失敗し、設定していたgasを全て消費されてしまう。つまり、etherだけ取られて何も実行されなかったという結果になる。

block gas limitによる制限

block gas limitより大きいgas limitを指定したtransactionは"exceeds block gas limit"エラーとなり発行できません。

じゃあblock gas limitギリギリまでtransaction gas limitを指定したらいいんじゃないか?と思われますが、その場合はblockに取り込まれるまでの時間が伸びます。
というのも、すでに説明した通り、blockはblock gas limitに到達するまで複数のtransactionをblockに取り込もうとします。この時にたった1つのtransactionでblockがいっぱいになると他のtransactionが取り込めなくなり、手数料が減ってしまう可能性がある*1のでblockに取り込まれる可能性が低くなります。
なので transaction gas limitは大きければいいというわけではありません。

最低gas limitの制限

transaction gas limitの指定には最低値も存在します。決められたgas limit値以下のtransactionもまた"intrinsic gas too low"エラーとなり発行できません。
最低値は21000 + α or 53000 + αです。transactionのdata部に格納するデータのbyte数によって+αが決定します。
また、この最低値を満たしたとしても実際にContractを実行する時に消費される十分なgas量に足りていない場合はContractの実効に失敗します。gas不足でContractの実効に失敗した場合はgasは戻ってこず、全て消費され(= etherも消費する)、Contractは実効前の状態にロールバックされます。
そのため、 gas limitは小さければ良いというわけでもありません。

gasの消費量を予想できるestimateGas

ということで、transaction gas limitを設定する場合は、Contract実行で消費されるより十分な量であり、かつできるだけ小さい値を設定する必要があります。

そこで登場するのが、実際に実行した時のgas消費量を計算してくれるestimateGasコマンドです。
transactionを発行する前に、まずはestimateGasで消費量を計算しておくことで、適切なgas limitを設定できます。
次はこのestimateGasの使い方を簡単に紹介します。

web3.jsでの使用方法

web3.eth.estimateGas({to: 'to address', data: 'send datas'})

これはdata部分を自前で構築しないといけなくて大変なので、実際は次で紹介するweb3.eth.Contract、もしくはTruffleContractから呼び出す方法を使います。
web3.eth.estimateGasを使うのはContract creationの消費量を計算したい時ぐらいかな。

web3.eth.ContractもしくはTruffleContractでの使用方法

anyContractInstance.someFunctionName.estimateGas(args)

Contractのinstanceから実行したい関数名にそのままつなげてestimateGasを呼び出せばOKです。関数名.call(args)や関数名.sendTransaction(args)するのと同じ呼び出し方ですね。

以下はruffle console上でERC20Tokenのtransferの消費gas量を取得する例です。

MyToken.deployed().then((ins) => { return ins.transfer.estimateGas("0xf17f52151ebef6c7334fad080c5704d77216b732", 200);})

注意点

estimateGasはその名の通りestimateです。というのは、estimateGasは実行した瞬間のcurrent pending blockの状態を元に計算されます。
そのため、実際に発行したtransactionがブロックに取り込まれた時と、estimateGasで計算した時とでContractの状態が変わっている可能性があり、状態が変わったことで必要となるgas量も変わってきます。
とは言ってもestimateGasしてからすぐに実行する様な場合であれば、+10000ぐらいの幅をもたせておけば問題ないとは思います。

まとめ

非常に便利ではあるんですが、実際サービスを作ってる上では、それぞれの関数で消費されるgas量はテストの段階で測定できるので、あんまり使う機会がなかったりします。
Walletなど、ユーザが任意のContractを呼びだすために使うツールなどを実装する際には非常に便利ですね。

*1:手数料はused gasに掛かるのでgas limitが多いからといって手数料が多くなるわけではないため