イノベーション エンジニアブログ


株式会社イノベーションのエンジニアたちの技術系ブログです。ITトレンド・List Finderの開発をベースに、業務外での技術研究などもブログとして発信していってます!


このエントリーをはてなブックマークに追加

Gasの節約、それが大事

こんばんは。(TZ=JST-9)
矢ヶ崎です。

GASにもいろいろあるよね

最近、このブログではGoogle Apps Script(GAS)の記事が多い雰囲気になってきております。
GASブログ感がだいぶ出てきているので、わたしも便乗してGasの記事を書きたいと思います。

GasはGasでも、EthereumのGasですいません。

なにかをするにはコストがかかる

EthereumでSmart Contractを作成していると、ぶち当たるの壁のひとつとしてGasがあるという噂がちまたでは流れていたりいなかったりします。

Smart Contractでは、「ブロックチェーンに書く」とかとか、全ノード(マイナー)に負荷がかかることをやるときには、その報酬としてEtherがかかります。
その時に処理のコストと実際のEtherへの計算をするために、Gas(燃料のガス)という単位を利用します。

また、トランザクション(処理)の実行者は、1Gasがいくらか(なんEtherか)を設定することができます。
それをGas Priceと呼びます。

マイナーは手数料がいっぱいもらえたほうが嬉しいので、このGas Priceが高いトランザクションから実行していこうとします。なので、Gas Priceを低く設定すれば、手数料も安くすみますが、あまり安くしすぎると誰も処理してくれなくなるので、なかなか実行されないという微妙な塩梅になってしまいますので注意が必要です。

Gas ⇒ 処理コスト
Gas Price ⇒ 1GasあたりなんEtherと設定するか
Ether ⇒ 必要 Gas * Gas Price ⇒ 実際に払う仮想通貨での手数量

詳しい記事はいっぱいありますので、詳細は検索していただくとして、じゃあ、どんな感じでやっていくのか?を、以前記事にした「いのコイン」をもとに考えていってみます。

「いのコイン」ERC20版

こちらの記事で、「いのコイン」を作ってみているのですが、もう半年以上前の記事になっちゃいました。
新しい技術において、半年というのはとてつもなく昔のお話でして、もはやこれはあんまり参考になりません。

こちらの記事は、まだギリギリ半年たっていない記事なので、まだマシです。
ただ、やはり最新ではありませんが、今回のお話では十分かと思いますので、こちらをベースにお話します。

こちらの記事の場合は、ERC20というEthereumのトークン(Ethereumを利用して自分で発行する仮想通貨のようなもの)のデファクトスタンダードの仕様に則って作ってあります。

ERC20に準拠するということは、
https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
こちらにあるようなメソッドが実装されているというものになります。

例えば、

balanceOf

というメソッドを呼ぶと、指定したアドレスの「いのコイン」の残高がわかる、っていうものです。

実際にメソッドを呼んでGasの消費を見てみる

残高照会してみる

では、試しに似たようなメソッドを作って呼んでみましょう。

    function balanceOfV(address _owner) public view returns (uint256 balance) {
        return balances[_owner];
    }

    function balanceOfN(address _owner) public returns (uint256 balance) {
        return balances[_owner];
    }

例えばこのように、balanceOfと同じような挙動をするであろうメソッドを作ってみます。

balanceOfVは、balanceOfのまんまです。
では、これを足したいのコインをテストネットにデプロイして、試してみましょう。

truffleを使って試してみますが、デプロイや試す方法は、前述のQiitaの記事を参照してください。

前提として、

0xfc9ebc55d9b192c455a27883d1fb0ef2e4462fb2 => いのコインのコントラクトのアドレス
0x3FE91f1A3cF9697DFbC41bEEB0b1d4F71F56d0F4 => テスト用アカウントのアドレス

となります。

で、実行してみたところ、

truffle(ropsten)> con = InnoCoin.at("0xfc9ebc55d9b192c455a27883d1fb0ef2e4462fb2");

truffle(ropsten)> con.balanceOf("0x3FE91f1A3cF9697DFbC41bEEB0b1d4F71F56d0F4");
{ [String: '1e+24'] s: 1, e: 24, c: [ 10000000000 ] }
truffle(ropsten)> con.balanceOfV("0x3FE91f1A3cF9697DFbC41bEEB0b1d4F71F56d0F4");
{ [String: '1e+24'] s: 1, e: 24, c: [ 10000000000 ] }

ERC20のbalanceOfも、balanceOfVもほぼ同じものですが、両方ともいい感じに残高の値が返ってきました。

では、balanceOfNはどうかというと、

truffle(ropsten)> bl = con.balanceOfN("0x3FE91f1A3cF9697DFbC41bEEB0b1d4F71F56d0F4");
{ tx: '0x9969a5af34c00909a6c653979ea7930d460dd02adf919d5b796f0bb87c1a83b6',
  receipt:
   { blockHash: '0x3ca97b13e9f2f299538f1efbb814bf5ac90c2aea25be0f5fcd6664e73d6db68b',
     blockNumber: 3300038,
     contractAddress: null,
     cumulativeGasUsed: 23459,
     from: '0x3fe91f1a3cf9697dfbc41beeb0b1d4f71f56d0f4',
     gasUsed: 23459,
     logs: [],
     logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
     status: '0x1',
     to: '0xfc9ebc55d9b192c455a27883d1fb0ef2e4462fb2',
     transactionHash: '0x9969a5af34c00909a6c653979ea7930d460dd02adf919d5b796f0bb87c1a83b6',
     transactionIndex: 0 },
  logs: [] }

あれ?なんだかおかしいですね。
特に、

gasUsed: 23459,

が気になります。
さらに、Etherscanで該当トランザクションを見てみると、

[TxFee] 0.0023459

おわ〜!思いっきりトランザクションフィーがかかってる!
残高を知りたかっただけなのに、手数料がかかってしまった〜。最悪。みたいな状況ですね。

これは、

    function balanceOfV(address _owner) public view returns (uint256 balance) {
        return balances[_owner];
    }

    function balanceOfN(address _owner) public returns (uint256 balance) {
        return balances[_owner];
    }

この違い、view が無いからですね。view がついていると、読み取り専用ということを表していて、トランザクションを発行せずに、対象ノードからブロックチェーンの内容を読み取ってくるだけなので、フィーはかからないのです。
が?!しかし!なんと、view をつけ忘れるだけで、たったのそれだけで!お金がかかるメソッドになってしまうのです。これは要注意!!!

ブロックチェーンに書き込みをしてみる

前述のQiita記事の通り、いのコインは感謝の言葉を送ると、いっしょに通貨INNOも送られる仕組みです。
感謝の言葉は、ブロックチェーンに書かれ、どこからどこにどんな言葉を送ったかが、誰でもわかる状態で保存されます。

では、感謝の言葉を送ってみます。

truffle(ropsten)> con.thanks("0x3FE91f1A3cF9697DFbC41bEEB0b1d4F71F56d0F4", "ありがとうございます!!!!!");
{ tx: '0xc82f436c9cedca5a1b1d152ff9f4ac602385f054621f419dd7abc8d6a6eac6c8',
  receipt:
   { blockHash: '0xcbf167c1fce485ccd83a0d4aa3cad09837a4fed279b58a763638d6948e8f7a6e',
     blockNumber: 3300078,
     contractAddress: null,
     cumulativeGasUsed: 85837,
     from: '0x3fe91f1a3cf9697dfbc41beeb0b1d4f71f56d0f4',
     gasUsed: 85837,
     logs: [ [Object] ],
     logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040400000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000010000000000000000000000000000008000000000000000000000000000000000000000000000800000002000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000',
     status: '0x1',
     to: '0xfc9ebc55d9b192c455a27883d1fb0ef2e4462fb2',
     transactionHash: '0xc82f436c9cedca5a1b1d152ff9f4ac602385f054621f419dd7abc8d6a6eac6c8',
     transactionIndex: 0 },
  logs:
   [ { address: '0xfc9ebc55d9b192c455a27883d1fb0ef2e4462fb2',
       blockNumber: 3300078,
       transactionHash: '0xc82f436c9cedca5a1b1d152ff9f4ac602385f054621f419dd7abc8d6a6eac6c8',
       transactionIndex: 0,
       blockHash: '0xcbf167c1fce485ccd83a0d4aa3cad09837a4fed279b58a763638d6948e8f7a6e',
       logIndex: 0,
       removed: false,
       event: 'Transfer',
       args: [Object] } ] }

なかなかのGasっぷりですね。

gasUsed: 85837,

Gas Priceが、MyEtherWalletのデフォルト値である
41Gwei
だったとすると、
0.003519317Ether
だけ手数料がかかるということになります。
1Etherが昨今の価格から安く見積もっても、
50,000円
だったとすると、このトランザクションは、
約176円
かかるということになります!

「ありがとうございます!!!!!」をブロックチェーンに書いて伝えるだけでも、なかなかのお金がかかりますね。

では、もうちょっと感謝を言葉に込めて送ってみます。

truffle(ropsten)> con.thanks("0x3FE91f1A3cF9697DFbC41bEEB0b1d4F71F56d0F4", "このたびは、ほんとうに、まじで、ガチで、ありがとうございます!!!!!");
{ tx: '0xd97dcf1b25c7a5d08d7c42ef1f732b008dbf4648ec58b227be4ee217ece76f1c',
  receipt:
   { blockHash: '0x7d7f0250fb1413c098ea48f644262417bc8eccaf0e32bee4cafbc7d05c9ebfae',
     blockNumber: 3300082,
     contractAddress: null,
     cumulativeGasUsed: 100085,
     from: '0x3fe91f1a3cf9697dfbc41beeb0b1d4f71f56d0f4',
     gasUsed: 100085,
     logs: [ [Object] ],
     logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040400000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000010000000000000000000000000000008000000000000000000000000000000000000000000000800000002000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000',
     status: '0x1',
     to: '0xfc9ebc55d9b192c455a27883d1fb0ef2e4462fb2',
     transactionHash: '0xd97dcf1b25c7a5d08d7c42ef1f732b008dbf4648ec58b227be4ee217ece76f1c',
     transactionIndex: 0 },
  logs:
   [ { address: '0xfc9ebc55d9b192c455a27883d1fb0ef2e4462fb2',
       blockNumber: 3300082,
       transactionHash: '0xd97dcf1b25c7a5d08d7c42ef1f732b008dbf4648ec58b227be4ee217ece76f1c',
       transactionIndex: 0,
       blockHash: '0x7d7f0250fb1413c098ea48f644262417bc8eccaf0e32bee4cafbc7d05c9ebfae',
       logIndex: 0,
       removed: false,
       event: 'Transfer',
       args: [Object] } ] }

やはり、さらに高くなりました。

     gasUsed: 100085,

200円オーバーですね。。。

これだと、気軽に文章なんて送れません。。。どうしよう。。。

送る前にいくらかかるのか知りたくない?

これって、前もっていくらくらいかかるかわかっていないと、正直ビビって送れないですよね。

では、メソッド単位ですが、いくらくらいかかるのかを見てみましょう。
estimateGas というのを使います。

truffle(ropsten)> con.thanks.estimateGas("0x3FE91f1A3cF9697DFbC41bEEB0b1d4F71F56d0F4", "ガチで、ありがとうございます!!!!!");
86605

雑な説明をすると、このように実際に呼ぶやつのカッコの前の最後に、estimateGasをはさむといい感じに出してくれます。
ほかにもやってみます。

truffle(ropsten)> con.thanks.estimateGas("0x3FE91f1A3cF9697DFbC41bEEB0b1d4F71F56d0F4", "あああ");
48283
truffle(ropsten)> con.thanks.estimateGas("0x3FE91f1A3cF9697DFbC41bEEB0b1d4F71F56d0F4", "ああ");
48091
truffle(ropsten)> con.thanks.estimateGas("0x3FE91f1A3cF9697DFbC41bEEB0b1d4F71F56d0F4", "あ");
47899

ここでは、全角1文字増えるごとに、192Gas増えているように見えます。

では、実際に処理を動かしてみます。

※gasUsedの行以外は省略
truffle(ropsten)> con.thanks("0x3FE91f1A3cF9697DFbC41bEEB0b1d4F71F56d0F4", "あああ");
     gasUsed: 48283,
truffle(ropsten)> con.thanks("0x3FE91f1A3cF9697DFbC41bEEB0b1d4F71F56d0F4", "ああ");
     gasUsed: 48091,
truffle(ropsten)> con.thanks("0x3FE91f1A3cF9697DFbC41bEEB0b1d4F71F56d0F4", "あ");
     gasUsed: 47899,

ぴったりあっちゃいましたね。

時代を感じる

このように、スマートコントラクトのプログラミングは、Gasをいかに節約できるかが課題になる部分があったりなかったりします。
例えば?!配列の中身をソートするとか、頻繁にブロックチェーン上のストレージとしてのデータを書き換える処理なんて書いちゃったりした日には、そりゃ死亡確認(結局生きてるやつ)になっちゃうわけです。すぐにGas Limit到達!みたいな感じになっちゃいます。

メインメモリが8KBみたいなPCでプログラミングをしていて、メモリ容量との戦いを繰り広げていたことを思い出して、懐かしみつつ最新の技術を触るということをやっており、とっても楽しいですのでおすすめです!

Gasも大切にね!

こちらからは以上です。