2018.10.01

Constantinople でリリースされる機能の検証


こんにちは。次世代システム研究室のT.M. です。

注意:この記事の執筆は2018/09/28 に行われました。

はじめに

近々、Ethereum は、Constantinople というコードネームが振られたハードフォークを伴うアップデートが行われます。今回のハードフォークでは、Casper のリリースが見送られるなど、リリースされる機能に大きな目玉はありませんが、以下の機能がリリースされます。

  • EIP 145 Bitwise shifting instructions in EVM
  • EIP 1014 Skinny CREATE2
  • EIP 1052 EXTCODEHASH Opcode
  • EIP 1234 Delay diff bomb
  • EIP 1283 Net gas metering for SSTORE (replaces 1087)

opcode の追加およびディフィカルティボムの延期がリリースされます。我々の提供するサービスである、Z.com Cloud ブロックチェーン では、コントラクトにおいてサインのチェックをすることがあります。今回追加されるopcode である、EXTCODEHASH はその際に利用できる可能性のあるopcode ですので、リリースされる前に検証をしたいと思います。

準備

Ethereum のクライアントのひとつであるparity では、バージョン2.1.0 でConstantinople の機能はリリースされています(https://github.com/paritytech/parity-ethereum/issues/8427)。そこで、今回は、2.1.0 のparity をインストールします。今回の機能は、ハードフォークを伴うもののため、ハードフォークをしないと利用することができません。ハードフォークをするためには、チェーンの定義ファイルに以下の内容を記述します。

{
  ...
  "params": {
    ...
    "eip1052Transition": "ハードフォークを始めるブロック番号"
  }
  ...
}

コンパイル

parity の準備が整ったところで、EXTCODEHASH を利用するコントラクトを実装します。EXTCODEHASH は引数のアドレスに紐づくコードに対してハッシュする、というものです。詳細な仕様はここを確認してください。

pragma solidity ^0.4.23;

contract Test {
  bytes32 public out;

  function testExtcode(address _addr) public {
    bytes32 _out;
    assembly {
      _out := extcodehash(_addr)
    }
    out = _out;
  }
}

上記コードを実装したTest.sol を用意し、コンパイルをします。私は、truffle を普段から利用していますので、今回もtruffle を用いてコントラクトのデプロイをします。

root@vagrant:~/project# truffle compile
Compiling ./contracts/Test.sol...

/root/project/contracts/Test.sol:9:15: DeclarationError: Function not found.
      _out := extcodehash(_addr)
              ^---------^
Compilation failed. See above.

となって、コンパイルが失敗しました。原因としては、コンパイラが新しいopcode に対して、対応していない、というものです。EVM が新しいopcode に対応していたとしても、そのEVM で動かすコードに新しいopcode が使えなければ検証できません。そこで、コントラクトのバイナリを直接修正することで、EXTCODEHASH を利用することにしました。

pragma solidity ^0.4.23;

contract Test {
  bytes32 public out;

  function testExtcode(address _addr) public {
    bytes32 _out;
    assembly {
      _out := extcodesize(_addr)
    }
    out = _out;
  }
}

まずは、先ほどコンパイルしようとしたコードに似ていますが、extcodehash の代わりにextcodesize にした、コードを用意します。EXTCODESIZE は、EXTCODEHASH と似た関数であり、パラメータのアドレスに紐づくコードのサイズを返す関数です。extcodesize は以前から存在する関数ですので、現在のコンパイラでもコンパイルすることができます。

root@vagrant:~/project# truffle compile
Compiling ./contracts/Test.sol...
Writing artifacts to ./build/contracts

コンパイルが成功すると、バイナリが作られます。上記コードのバイナリは以下のようになります。

0x608060405234801561001057600080fd5b50610105806100206000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063afe50c0214604e578063b2a1449b14608e575b600080fd5b348015605957600080fd5b50608c600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505060be565b005b348015609957600080fd5b5060a060d3565b60405180826000191660001916815260200191505060405180910390f35b6000813b905080600081600019169055505050565b600054815600a165627a7a72305820d97dba7f4d39cd119dd9756c7e9bd2b48092f84b4a2327da71d41da6f2b0d1540029

このバイナリだけを見ても、よくわかりませんので、これをopcode に変換します。Etherscan で変換することができます。




バイナリをopcode に変換したところ、EXTCODESIZE が利用されていることがわかります。EXTCODESIZE のコードは0x3b であるので、バイナリにおける、EXTCODESIZE の場所がわかりました。EXTCODEHASH のコードは0x3f であるので、上記バイナリにおいて、3b の箇所を3f に変換することで、EXTCODEHASH を利用したバイナリを作成することができます。




Etherscan で確認したところ、EXTCODESIZE であったところが変更されていることがわかります。ただ、この変換ツールがまだEXTCODEHASH に対応していないため、Unknown Opcodeとなってしまっています。


動作検証

EXTCODEHASH を用いたバイナリを作成することができたので、このバイナリをデプロイします。これからの作業はtruffle のコンソールに入っているものとします。

truffle(develop)> migrate -f 2
Using network 'develop'.

Running migration: 2_test.js
  Replacing Test...
  ... 0xf2d6f73845295778540e0259d083df76763438a6347efb659f4afbbfcb01c5c4
  Test: 0x7560cef6cb8d951dd8f714096cb8acf18c7d8e83
Saving successful migration to network...
  ... 0x59aac6635f1afb987da3a03af1057d64d3402873db20dd1cba358399a7bea27f
Saving artifacts...

デプロイすることができました。EXTCODEHASH を利用したコードを実行してみます。まずは、ハードフォーク前に実行するとどうなるかを検証します。

truffle(develop)> Test.deployed().then(instance => t = instance);
truffle(develop)> t.testExtcode(t.address);
Error: Transaction: 0x480ab463930313403f3aa8d074201cf0e0d829ce8ee5022dba22aba18f3bb23e exited with an error (status 0) after consuming all gas.
Please check that the transaction:
    - satisfies all conditions set by Solidity `assert` statements.
    - has enough gas to execute the full transaction.
    - does not trigger an invalid opcode by other means (ex: accessing an array out of bounds).
    at StatusError.ExtendableError (/usr/local/lib/node_modules/truffle/build/webpack:/packages/truffle-error/index.js:10:1)
    at new StatusError (/usr/local/lib/node_modules/truffle/build/webpack:/packages/truffle-contract/statuserror.js:29:1)
    at Object.callback (/usr/local/lib/node_modules/truffle/build/webpack:/packages/truffle-contract/contract.js:181:1)
    at /usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/method.js:142:1
    at /usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/requestmanager.js:89:1
    at /usr/local/lib/node_modules/truffle/build/webpack:/packages/truffle-provider/wrapper.js:134:1
    at XMLHttpRequest.request.onreadystatechange (/usr/local/lib/node_modules/truffle/build/webpack:/~/web3/lib/web3/httpprovider.js:128:1)
    at XMLHttpRequestEventTarget.dispatchEvent (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:64:1)
    at XMLHttpRequest._setReadyState (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:354:1)
    at XMLHttpRequest._onHttpResponseEnd (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:509:1)
    at IncomingMessage.<anonymous> (/usr/local/lib/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:469:1)
    at IncomingMessage.emit (events.js:187:15)
    at IncomingMessage.EventEmitter.emit (domain.js:460:23)
    at endReadableNT (_stream_readable.js:1092:12)
    at process._tickCallback (internal/process/next_tick.js:63:19)

ハードフォーク前に実行すると、上記のようなエラーが発生しました。次に、ハードフォーク後に実行してみます。

truffle(develop)> t.testExtcode(t.address);
{ tx:
   '0x842b6de221293d390bd9c7324dc1d12f1592be757df93d55544adf6449e27ae4',
  receipt:
   { blockHash:
      '0x05ff7e1edb79eda488016cc37f63f8a879f392ef4a10614e19318fbee0dc845d',
     blockNumber: 4430,
     contractAddress: null,
     cumulativeGasUsed: 28313,
     from: '0x560510e601d4bcad12fcd4704b566df0d353bd68',
     gasUsed: 28313,
     logs: [],
     logsBloom:
      '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
     root: null,
     status: '0x1',
     to: '0x7560cef6cb8d951dd8f714096cb8acf18c7d8e83',
     transactionHash:
      '0x842b6de221293d390bd9c7324dc1d12f1592be757df93d55544adf6449e27ae4',
     transactionIndex: 0 },
  logs: [] }

成功しました。きちんとハッシュが計算されたかを確認します。

truffle(develop)> t.out();
'0xa4ec7901d6f7817b70db4bea76dd6a80a212bf67e341aaa1eaed518af09f6597'

きちんとハッシュが計算され、その結果が保存されていることが確認されました。最後に、パラメータには、コントラクトのアドレスではなく、EOA のアドレスにして実行してみます。

truffle(develop)> t.testExtcode(web3.eth.accounts[0]);
{ tx:
   '0xd91387b94710a5f241a78ba343a383e7efb2bcfe14d5a282213ac30a6859c5db',
  receipt:
   { blockHash:
      '0x76a80a82c58f4f7d22df166550153bb5a4ac73ddc889bcb2e47d716fa0509327',
     blockNumber: 4939,
     contractAddress: null,
     cumulativeGasUsed: 43313,
     from: '0x560510e601d4bcad12fcd4704b566df0d353bd68',
     gasUsed: 43313,
     logs: [],
     logsBloom:
      '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
     root: null,
     status: '0x1',
     to: '0x7560cef6cb8d951dd8f714096cb8acf18c7d8e83',
     transactionHash:
      '0xd91387b94710a5f241a78ba343a383e7efb2bcfe14d5a282213ac30a6859c5db',
     transactionIndex: 0 },
  logs: [] }
truffle(develop)> t.out();
'0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'

トランザクションが成功し、0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 という値が保存されました。これは、コードがついていないアドレスに対しては、一律、0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 が返る、というEXTCODEHASH の仕様に則って計算された結果であり、正しく動作していることが確認されました。

さいごに

今回は、近々リリース予定のConstantinople の機能の一つを検証しました。EVM の対応はされていますが、コンパイラが対応されておらず、EXTCODEHASH を確認するためには、直接バイナリを編集する必要がありました。parity で検証を行ったところ、ハードフォーク前はエラーが発生し、ハードフォーク後は正常に動作することを確認しました。
今後は、EXTCODEHASH 以外の検証やZ.com Cloud ブロックチェーンにおけるハードフォークの検証をしたいと考えています。


次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。インフラ設計、構築経験者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ募集職種一覧からご応募をお願いします。

皆さんのご応募をお待ちしています。