【完全保存版】zkEVM(zkSync Era Testnet)の具体的な処理を読み解こう!
今回は、zkSync Eraの処理を順を追ってみていきたいと思います。
0 はじめに
まずは、thirdwebを使って、このようなコントラクトを作成しました。
ERC20のトークンのコントラクトです。
作成方法が不明の場合は、こちらのnoteをご参照ください。
1 zkSync Eraでのトランザクションについて
これを仮に、「0xBc..77」というアドレスから「0xec~a4」というアドレスへ、「40…00」送りました。
下の図のようになりました。
これをエクスプローラーで見てみましょう。
① エクスプローラでの確認
まずは、「zkSync Era」側に一つ(上のもの)、ETH側に3つのトランザクションが起こっていることがわかります。
下の3つがETH側なのは、後述します。
ここでは、ETH側に下の3つのトランザクションが起こっていることがわかりました。
② ブロックとバッチの関係について
ここで、もう少し、ブロックとバッチの関係も見てみましょう。
こちらを調べてみると、周辺がこのようになっていました。
ブロックをひとまとめにしたものがバッチであるということがわかります。
③ ブロックサイズについて
さらに、ブロックサイズについても見ていきましょう。
下のように、ブロックサイズとは、中に入っているトランザクションの数です。
周辺も調べてみると、このようになっていました。
④ バッチサイズについて
そして、それに対するバッチサイズはこのようになっていました。
つまり、このような状況になります。
つまり、バッチ:84562は400のブロックとその中の750のトランザクションで構成されていることがわかりました。
⑤ インプットデータについて
インプットデータについても見てみましょう。
最初の「0xa9059cbb」の部分はtransfer(address,uint256)のメソッドIDです。(今回は詳細は省きます。)
そして、それに続く部分が、誰にいくら送っているのかという部分です。
まさに実行した内容となっていました。
2 イーサリアム側でのトランザクションについて
① イーサリアム側であることの確認について
まずは、下のうち、コミットトランザクションについて見てみましょう。
コミットトランザクションを選択すると、下のように、Etherscanのページに行きます。
これにより、処理がイーサリアム上(今回はGoerli)で行われていることが確認できます。
② 実行コントラクトの確認について
次に、To:のこちらのアドレスを見てみましょう
見てみると、「ValidatorTimeLock」というコントラクトであることがわかりました。
③ バリデータについて
ちなみに、validatorのアドレスが確認できます。
つまり、先ほどの「From」はバリデータであったことがわかりました。
つまり、こういうことでした。
3 ValidatorTimelockコントラクトについて
① commitBlocks関数について
さらに見てみると、「commitBlocks」という関数があります。
後で見ますが、まさにこの関数を「インプットデータ」に入れています。
こんな感じです。
これは「calldata」と「_newBlocksData」を引数に取っています。
もう少し詳しく見てみると、まずはこちらで新しいブロックに対し、タイムスタンプを設定しています。
その後、「_propagateToZkSync」関数を実行しています。
② _propagateToZkSync関数について
_propagateToZkSync関数はこちらです。
まずは、calldatacopyで元の関数呼び出しのデータ(関数の署名と引数)をメモリにコピーしています。
次に、zkSyncスマートコントラクトを呼び出し、その結果をresultに保存しています。
次に、zkSyncからの戻りデータを取得し、メモリにコピーします。
最後に、resultの結果によって、分岐を行っています。
エラーの場合には、全ての状態変更が取り消され、エラーでない場合には、呼び出し元にデータが返されます。
4 zkSyncコントラクトについて
① コントラクトアドレスの確認
では、次はzkSyncContractを見てみましょう。
「Read Contract」から、下のように、コントラクトを確認しました。
② zkSyncコントラクトの構成の確認
では、そのzkSyncContractを見てみましょう。
すると、DiamondProxyであることがわかりました。
ということは、「Read as Proxy」で実装コントラクトを見てみましょう。
つまり、このような構成になっていました。
③ 実装コントラクトの確認(読み取り用)
すると、このように実装コントラクトを確認することができました。
ここには読み取り用のpublicの関数として、下の4つがありました。
こんな感じですね。
大まかに、見てみましょう。
③-1 l2TransactionBaseCost関数
まずはl2TransactionBaseCost関数です。
これは、L2でのトランザクションの実行リクエストのEtherでのコストを推定しています。
③-2 proveL1ToL2TransactionStatus関数
次は、「proveL1ToL2TransactionStatus」関数です。
こちらは特定のL1からL2へのトランザクションが、特定のステータスで処理されたことを証明します。
具体的には、L2のトランザクションハッシュやブロックナンバーなどを渡しています。
③-3 proveL2LogInclusion関数
次は、「proveL2LogInclusion」関数です。
その名の通り、特定のL2ブロックに特定のログが含まれていたことを証明します。
③-4 proveL2MessageInclusion関数
最後に、「proveL2MessageInclusion」関数についてです。
こちらも、その名の通り、特定のL2ブロックに特定のメッセージが含まれていたことを証明します
④ 実装コントラクトの確認(書き込み用)
せっかくなので、書き込み用の関数も見てみましょう。
下のように、二つのpublicの関数が存在しています。
このような感じですね。
④-1 finalizeEthWithdrawal関数
まずは、「finalizeEthWithdrawal」関数についてです。
既にL2で処理されたETHの出金を最終化するために使われます。
中身は、「proveL2MessageInclusion」関数で、メッセージが存在しているのかを確認し、
「isEthWithdrawalFinalized」のマッピングでtrueとして、ファイナライズ済みであることを示します。
その上で、「_withdrawalFunds」関数を実行しています。
④-2 requestL2Transaction関数
最後に、「requestL2Transaction」関数です。
L1から L2へのトランザクションの実行を要求するために使用されます。
具体的には、こちらで、「_requestL2Transaction」を実行しています。
4 イーサリアム側でのトランザクションについて②
では、イーサリアム側でのトランザクションの話に戻りたいと思います。
① コミットトランザクションについて
トランザクションの「インプットデータ」を確認すると、下の2種類のデータを渡していることが確認できます。
つまり、こんな感じです。
最初の種類(0)では一つ前のブロックナンバーや「indexRepeatedStorageChanges」などが入っています。
そして、2つ目の引数(1)では、新しいブロックナンバーや「indexRepeatedStorageChanges」などがあります。
その他、「initialStorageChanges」、「repeatedStorageChanges」が存在しています。
② Proveトランザクションについて
次は、Proveトランザクションです。
FromとToが先ほどと同じです。
つまり、バリデータが「ValidatorTimelock」に処理を投げていることがわかります。
インプットデータを確認すると、「proveBlocks」関数が入っていることがわかります。
そして、prove(証明)なので、コミットの時よりも引数が1セット増え、Proofを引数に取っていることがわかります。
つまり、このようになります。
③ 実行トランザクションについて
最後に実行トランザクションです。
FromとToは先ほどと同じです。
インプットデータを確認すると、「executeBlocks」関数が入っていることがわかります。
また、引数としては、「_newBlocksData」の1種類であることがわかります。
つまり、このようになります。
④ 全体の構成について
そのため、全体の構成としては、このようになります。
なお、流れとしましては、①コミットと②証明が同じブロックで行われていました。
その後、一定の時間を置いて、③実行が行われていました。
これで、大まかにどのような流れで実行しているのかが確認できました。
今回は以上です。
最後までありがとうございました!
サポートをしていただけたらすごく嬉しいです😄 いただけたサポートを励みに、これからもコツコツ頑張っていきます😊