見出し画像

【完全保存版】zkEVM(zkSync Era Testnet)の具体的な処理を読み解こう!

今回は、zkSync Eraの処理を順を追ってみていきたいと思います。

0 はじめに

まずは、thirdwebを使って、このようなコントラクトを作成しました。

ERC20のトークンのコントラクトです。

https://thirdweb.com/zksync-era-testnet/0x65de2192B47b6a9500dE95cf0af82e255261f0ed

作成方法が不明の場合は、こちらのnoteをご参照ください。

1 zkSync Eraでのトランザクションについて

これを仮に、「0xBc..77」というアドレスから「0xec~a4」というアドレスへ、「40…00」送りました。

下の図のようになりました。

これをエクスプローラーで見てみましょう。

https://thirdweb.com/zksync-era-testnet/0x65de2192B47b6a9500dE95cf0af82e255261f0ed/events

① エクスプローラでの確認

まずは、「zkSync Era」側一つ(上のもの)、ETH側に3つのトランザクションが起こっていることがわかります。

下の3つがETH側なのは、後述します。

https://goerli.explorer.zksync.io/tx/0x5561392cea1a06bf716daf9f3dcef4199b029b17cce59150194147ee6c3069d9

ここでは、ETH側に下の3つのトランザクションが起こっていることがわかりました。

② ブロックとバッチの関係について

ここで、もう少し、ブロックバッチの関係も見てみましょう。

https://goerli.explorer.zksync.io/tx/0x5561392cea1a06bf716daf9f3dcef4199b029b17cce59150194147ee6c3069d9

こちらを調べてみると、周辺がこのようになっていました。

ブロックをひとまとめにしたものがバッチであるということがわかります。

③ ブロックサイズについて

さらに、ブロックサイズについても見ていきましょう。

下のように、ブロックサイズとは、中に入っているトランザクションの数です。

周辺も調べてみると、このようになっていました。

④ バッチサイズについて

そして、それに対するバッチサイズはこのようになっていました。

つまり、このような状況になります。

つまり、バッチ:84562400のブロックとその中の750のトランザクションで構成されていることがわかりました。

⑤ インプットデータについて

インプットデータについても見てみましょう。

最初の「0xa9059cbb」の部分はtransfer(address,uint256)メソッドIDです。(今回は詳細は省きます。)

そして、それに続く部分が、いくら送っているのかという部分です。

まさに実行した内容となっていました。

2 イーサリアム側でのトランザクションについて

① イーサリアム側であることの確認について

まずは、下のうち、コミットトランザクションについて見てみましょう。

https://goerli.explorer.zksync.io/tx/0x5561392cea1a06bf716daf9f3dcef4199b029b17cce59150194147ee6c3069d9

コミットトランザクションを選択すると、下のように、Etherscanのページに行きます。

これにより、処理がイーサリアム上(今回はGoerli)で行われていることが確認できます。

https://goerli.etherscan.io/tx/0x278bace3cf8adb48e6bec3dea49de8b8b8dab52379d0084654448794d2e34405

② 実行コントラクトの確認について

次に、To:のこちらのアドレスを見てみましょう

https://goerli.etherscan.io/tx/0x278bace3cf8adb48e6bec3dea49de8b8b8dab52379d0084654448794d2e34405

見てみると、「ValidatorTimeLock」というコントラクトであることがわかりました。

https://goerli.etherscan.io/address/0xb949b4e3945628650862a29abef3291f2ed52471#code

③ バリデータについて

ちなみに、validatorのアドレスが確認できます。

つまり、先ほどの「From」はバリデータであったことがわかりました。

つまり、こういうことでした。

3 ValidatorTimelockコントラクトについて

① commitBlocks関数について

さらに見てみると、「commitBlocks」という関数があります。

後で見ますが、まさにこの関数を「インプットデータ」に入れています。

こんな感じです。

これは「calldata」「_newBlocksData」を引数に取っています。

もう少し詳しく見てみると、まずはこちらで新しいブロックに対し、タイムスタンプを設定しています。

その後、「_propagateToZkSync」関数を実行しています。

② _propagateToZkSync関数について

_propagateToZkSync関数はこちらです。

まずは、calldatacopyで元の関数呼び出しのデータ(関数の署名と引数)をメモリにコピーしています。

次に、zkSyncスマートコントラクトを呼び出し、その結果をresultに保存しています。

次に、zkSyncからの戻りデータを取得し、メモリにコピーします。

最後に、resultの結果によって、分岐を行っています。

エラーの場合には、全ての状態変更が取り消され、エラーでない場合には、呼び出し元にデータが返されます。

4 zkSyncコントラクトについて

① コントラクトアドレスの確認

では、次はzkSyncContractを見てみましょう。

「Read Contract」から、下のように、コントラクトを確認しました。

https://goerli.etherscan.io/address/0xb949b4e3945628650862a29abef3291f2ed52471#readContract

② zkSyncコントラクトの構成の確認

では、そのzkSyncContractを見てみましょう。

すると、DiamondProxyであることがわかりました。

ということは、「Read as Proxy」実装コントラクトを見てみましょう。

つまり、このような構成になっていました。

③ 実装コントラクトの確認(読み取り用)

すると、このように実装コントラクトを確認することができました。

ここには読み取り用のpublicの関数として、下の4つがありました。

https://goerli.etherscan.io/address/0x1908e2BF4a88F91E4eF0DC72f02b8Ea36BEa2319#readProxyContract

こんな感じですね。

大まかに、見てみましょう。

③-1 l2TransactionBaseCost関数

まずはl2TransactionBaseCost関数です。

これは、L2でのトランザクションの実行リクエストのEtherでのコストを推定しています。

https://goerli.etherscan.io/address/0xc03dd333392c312717f71970f14f42ba511ceaa1#code

③-2 proveL1ToL2TransactionStatus関数

次は、「proveL1ToL2TransactionStatus」関数です。

こちらは特定のL1からL2へのトランザクションが、特定のステータスで処理されたことを証明します。

具体的には、L2のトランザクションハッシュブロックナンバーなどを渡しています。

https://goerli.etherscan.io/address/0xc03dd333392c312717f71970f14f42ba511ceaa1#code

③-3 proveL2LogInclusion関数

次は、「proveL2LogInclusion」関数です。

その名の通り、特定のL2ブロックに特定のログが含まれていたことを証明します。

https://goerli.etherscan.io/address/0xc03dd333392c312717f71970f14f42ba511ceaa1#code

③-4 proveL2MessageInclusion関数

最後に、「proveL2MessageInclusion」関数についてです。

こちらも、その名の通り、特定のL2ブロックに特定のメッセージが含まれていたことを証明します

https://goerli.etherscan.io/address/0xc03dd333392c312717f71970f14f42ba511ceaa1#code

④ 実装コントラクトの確認(書き込み用)

せっかくなので、書き込み用の関数も見てみましょう。

下のように、二つのpublicの関数が存在しています。

https://goerli.etherscan.io/address/0x1908e2BF4a88F91E4eF0DC72f02b8Ea36BEa2319#writeProxyContract

このような感じですね。

④-1 finalizeEthWithdrawal関数

まずは、「finalizeEthWithdrawal」関数についてです。

既にL2で処理されたETHの出金を最終化するために使われます。

https://goerli.etherscan.io/address/0xc03dd333392c312717f71970f14f42ba511ceaa1#code

中身は、「proveL2MessageInclusion」関数で、メッセージが存在しているのかを確認し、

https://goerli.etherscan.io/address/0xc03dd333392c312717f71970f14f42ba511ceaa1#code

「isEthWithdrawalFinalized」のマッピングでtrueとして、ファイナライズ済みであることを示します。

その上で、「_withdrawalFunds」関数を実行しています。

https://goerli.etherscan.io/address/0xc03dd333392c312717f71970f14f42ba511ceaa1#code

④-2 requestL2Transaction関数

最後に、「requestL2Transaction」関数です。

L1から L2へトランザクションの実行を要求するために使用されます。

https://goerli.etherscan.io/address/0xc03dd333392c312717f71970f14f42ba511ceaa1#code

具体的には、こちらで、「_requestL2Transaction」を実行しています。

https://goerli.etherscan.io/address/0xc03dd333392c312717f71970f14f42ba511ceaa1#code

4 イーサリアム側でのトランザクションについて②

では、イーサリアム側でのトランザクションの話に戻りたいと思います。

https://goerli.explorer.zksync.io/tx/0x5561392cea1a06bf716daf9f3dcef4199b029b17cce59150194147ee6c3069d9

① コミットトランザクションについて

トランザクションの「インプットデータ」を確認すると、下の2種類のデータを渡していることが確認できます。

https://goerli.etherscan.io/tx/0x278bace3cf8adb48e6bec3dea49de8b8b8dab52379d0084654448794d2e34405

つまり、こんな感じです。

最初の種類(0)では一つ前のブロックナンバーや「indexRepeatedStorageChanges」などが入っています。

<根拠が見つからなかったので、これは推測です。>
「indexRepeatedStorageChanges」とはストレージ内の特定の変数が変更された回数を追跡するためのインデックスだと思いました。
バッチで、複数のトランザクションがまとめられているので、それが全て完了した後のインデックスだと考えました。

https://goerli.etherscan.io/tx/0x278bace3cf8adb48e6bec3dea49de8b8b8dab52379d0084654448794d2e34405

そして、2つ目の引数(1)では、新しいブロックナンバーや「indexRepeatedStorageChanges」などがあります。

https://goerli.etherscan.io/tx/0x278bace3cf8adb48e6bec3dea49de8b8b8dab52379d0084654448794d2e34405

その他、「initialStorageChanges」、「repeatedStorageChanges」が存在しています。

<根拠が見つからなかったので、これは推測です。>
initialStorageChanges」とは、ブロック内で行われた特定のデータストレージの初期の変更、
repeatedStorageChanges」とは、ブロック内で行われた特定のデータストレージのバッチ内のトランザクションの全ての変更だと考えました。


https://goerli.etherscan.io/tx/0x278bace3cf8adb48e6bec3dea49de8b8b8dab52379d0084654448794d2e34405

② Proveトランザクションについて

次は、Proveトランザクションです。

FromとToが先ほどと同じです。

つまり、バリデータ「ValidatorTimelock」に処理を投げていることがわかります。

https://goerli.etherscan.io/tx/0x6135949abde3323472ba62c108d48da6cf1c2fd100b68621630fa720e74530f7

インプットデータを確認すると、「proveBlocks」関数が入っていることがわかります。

https://goerli.etherscan.io/tx/0x6135949abde3323472ba62c108d48da6cf1c2fd100b68621630fa720e74530f7

そして、prove(証明)なので、コミットの時よりも引数が1セット増え、Proofを引数に取っていることがわかります。

https://goerli.etherscan.io/tx/0x6135949abde3323472ba62c108d48da6cf1c2fd100b68621630fa720e74530f7

つまり、このようになります。

③ 実行トランザクションについて

最後に実行トランザクションです。

FromとToは先ほどと同じです。

https://goerli.etherscan.io/tx/0x8da35462da3f9f2364006f0fe29ba0eab66916f401916634ee1e23f479b3b364

インプットデータを確認すると、「executeBlocks」関数が入っていることがわかります。

また、引数としては、「_newBlocksData」1種類であることがわかります。

https://goerli.etherscan.io/tx/0x8da35462da3f9f2364006f0fe29ba0eab66916f401916634ee1e23f479b3b364

つまり、このようになります。

④ 全体の構成について

そのため、全体の構成としては、このようになります。

なお、流れとしましては、①コミット②証明同じブロックで行われていました。

その後、一定の時間を置いて③実行が行われていました。

これで、大まかにどのような流れで実行しているのかが確認できました。

今回は以上です。

最後までありがとうございました!

サポートをしていただけたらすごく嬉しいです😄 いただけたサポートを励みに、これからもコツコツ頑張っていきます😊