L2 -> L1通信


L2 -> L1通信

本节描述了从L2与Ethereum互动的接口。它假定你已经熟悉了与L2->L1通信工作的基本概念。如果你是这个话题的新手,你可以阅读概念性介绍这里

Note

请注意,在新的0.13.0SDK中,API层是用气体操作的。ergs的概念只被VM使用。

结构

与L1->L2通信不同,不可能直接将事务从L2初始化到L1。然而,你可以从zkSync向Ethereum发送一个任意长度的消息,然后在L1智能合约上处理收到的消息。要从L2端发送消息,你应该调用[信使系统合约](.../system-contracts.md#L1Messenger)中的sendToL1方法。它只接受发送到Ethereum上的zkSync智能合约的消息的字节数。

从L1端来看,zkSync智能合约提供了proveL2MessageInclusion方法,以证明消息被发送到L1并包含在zkSync块中。

从L2向L1发送消息

从L2端发送消息需要用户调用Messenger系统合同中的sendToL1方法。这个方法只接受被发送到L1上的zkSync智能合约的消息的字节数。

function sendToL1(bytes memory _message) external returns (bytes32 messageHash);
  • _message是一个参数,包含信息的原始字节数

Tips

消息的发件人将由上下文决定。

这个函数从L2发送一个消息,并返回消息字节的keccak256哈希值。该消息的哈希值可以在以后用来证明该消息是在L1上发送的。它的使用是可选的,只是为了方便。

关于Messenger的更多信息可以在系统合同部分找到。

例子

使用zksync-web3从L2向L1发送消息

import { Wallet, Provider, Contract, utils } from "zksync-web3";
import { ethers } from "ethers";

const TEST_PRIVATE_KEY = "<YOUR_PRIVATE_KEY>";

async function main() {
  const zkSyncProvider = new Provider("https://zksync2-testnet.zksync.dev");

  const wallet = new Wallet(TEST_PRIVATE_KEY, zkSyncProvider);

  const messengerContract = new ethers.Contract(utils.L1_MESSENGER_ADDRESS, utils.L1_MESSENGER, wallet);

  console.log(`Messenger contract address is ${messengerContract.address}`);

  const someString = ethers.utils.toUtf8Bytes("Some L2->L1 message");
  console.log(`Sending message from L2 to L1`);
  const tx = await messengerContract.sendToL1(someString);

  console.log("L2 trx hash is ", tx.hash);
  const receipt = await tx.waitFinalize();

  console.log(`Transaction included in block ${receipt.blockNumber}`);

  // Get proof that the message was sent to L1
  const msgProof = await zkSyncProvider.getMessageProof(receipt.blockNumber, wallet.address, ethers.utils.keccak256(someString));

  console.log("Proof that message was sent to L1 :>> ", msgProof);
}

try {
  main();
} catch (error) {
  console.error(error);
}

L2中的智能合约,向L1发送了一条信息

以下合约通过信使系统合约将其地址发送给L1。

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

// Importing interfaces and addresses of the system contracts
import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol";

contract Example {
    function sendMessageToL1() external returns(bytes32 messageHash) {
        // Construct the message directly on the contract
        bytes memory message = abi.encode(address(this));

        messageHash = L1_MESSENGER_CONTRACT.sendToL1(message);
    }
}

证明消息被纳入L2块中

从L1端来看,zkSync智能合约提供了一个接口来证明消息被发送到L1并包含在zkSync块中。

来自邮箱L1合约open in new windowproveL2MessageInclusion函数,返回一个布尔值,表明带有此类参数的消息,被发送到了L1。

    struct L2Message {
        address sender;
        bytes data;
        uint256 txNumberInblock;
    }

    function proveL2MessageInclusion(
        uint256 _blockNumber,
        uint256 _index,
        L2Message calldata _message,
        bytes32[] calldata _proof
    ) external view returns (bool);

下面是对所需参数的详细描述。

  • _blockNumber是L1批号,其中包括L2块。它可以用getBlock方法来检索。
  • _index是该块中L2日志的索引。它由zksync-web3API的getMessageProof方法作为id返回。
  • _message是一个参数,包含发送消息的全部信息。它应该是一个包含以下内容的对象。
    • sender: 从L2发送消息的地址。
    • data: 发送的消息的字节数。
    • txNumberInBlock:L2块中交易的索引,使用getTransaction返回transactionIndex
  • _proof是一个参数,包含消息包含的Merkle证明。它可以从观察Ethereum或从zksync-web3API的getMessageProof方法接收。

重要

请注意,在证明L1的包含性之前,你的交易的L2块必须被验证(因此交易被最终确定)。

例子

L1信息处理合约

下面的合约接收发送到L2信使合约的交易信息,并证明它被包含在L2块中。

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

// Importing zkSync contract interface
import "@matterlabs/zksync-contracts/l1/contracts/zksync/interfaces/IZkSync.sol";

contract Example {
  // NOTE: The zkSync contract implements only the functionality for proving that a message belongs to a block
  // but does not guarantee that such a proof was used only once. That's why a contract that uses L2 -> L1
  // communication must take care of the double handling of the message.
  /// @dev mapping L2 block number => message number => flag
  /// @dev Used to indicated that zkSync L2 -> L1 message was already processed
  mapping(uint256 => mapping(uint256 => bool)) isL2ToL1MessageProcessed;

  function consumeMessageFromL2(
    // The address of the zkSync smart contract.
    // It is not recommended to hardcode it during the alpha testnet as regenesis may happen.
    address _zkSyncAddress,
    // zkSync block number in which the message was sent
    uint256 _l2BlockNumber,
    // Message index, that can be received via API
    uint256 _index,
    // The tx number in block
    uint16 _l2TxNumberInBlock,
    // The message that was sent from l2
    bytes calldata _message,
    // Merkle proof for the message
    bytes32[] calldata _proof
  ) external {
    // check that the message has not been processed yet
    require(!isL2ToL1MessageProcessed[_l2BlockNumber][_index]);

    IZkSync zksync = IZkSync(_zkSyncAddress);
    address someSender = 0x19A5bFCBE15f98Aa073B9F81b58466521479DF8D;
    L2Message memory message = L2Message({sender: someSender, data: _message, txNumberInBlock:_l2TxNumberInBlock});

    bool success = zksync.proveL2MessageInclusion(
      _l2BlockNumber,
      _index,
      message,
      _proof
    );
    require(success, "Failed to prove message inclusion");

    // Mark message as processed
    isL2ToL1MessageProcessed[_l2BlockNumber][_index] = true;
  }
}

L2到L1

下面的脚本从L2向L1发送一个消息,检索消息证明,并验证在L1收到的消息是否来自L2区块。

import * as ethers from "ethers";
import { Provider, utils, Wallet } from "zksync-web3";
const TEST_PRIVATE_KEY = "<YOUR_PRIVATE_KEY>";

const MESSAGE = "Some L2->L1 message";

const l2Provider = new Provider("https://zksync2-testnet.zksync.dev");
const l1Provider = ethers.getDefaultProvider("goerli");

const wallet = new Wallet(TEST_PRIVATE_KEY, l2Provider, l1Provider);

async function sendMessageToL1(text: string) {
  console.log(`Sending message to L1 with text ${text}`);
  const textBytes = ethers.utils.toUtf8Bytes(MESSAGE);

  const messengerContract = new ethers.Contract(utils.L1_MESSENGER_ADDRESS, utils.L1_MESSENGER, wallet);
  const tx = await messengerContract.sendToL1(textBytes);
  await tx.wait();
  console.log("L2 trx hash is ", tx.hash);
  return tx;
}

async function getL2MessageProof(blockNumber: ethers.BigNumberish) {
  console.log(`Getting L2 message proof for block ${blockNumber}`);
  return await l2Provider.getMessageProof(blockNumber, wallet.address, ethers.utils.keccak256(ethers.utils.toUtf8Bytes(MESSAGE)));
}

async function proveL2MessageInclusion(l1BatchNumber: ethers.BigNumberish, proof: any, trxIndex: number) {
  const zkAddress = await l2Provider.getMainContractAddress();

  const mailboxL1Contract = new ethers.Contract(zkAddress, utils.ZKSYNC_MAIN_ABI, l1Provider);
  // all the information of the message sent from L2
  const messageInfo = {
    txNumberInBlock: trxIndex,
    sender: wallet.address,
    data: ethers.utils.toUtf8Bytes(MESSAGE),
  };

  console.log(`Retrieving proof for batch ${l1BatchNumber}, transaction index ${trxIndex} and proof id ${proof.id}`);

  const res = await mailboxL1Contract.proveL2MessageInclusion(l1BatchNumber, proof.id, messageInfo, proof.proof);

  return res;
}

/**
 * Full end-to-end of an L2-L1 messaging with proof validation.
 * Recommended to run in 3 steps:
 * 1. Send message.
 * 2. Wait for transaction to finalize and block verified
 * 3. Wait for block to be verified and validate proof
 */
async function main() {
  // Step 1: send message
  const l2Trx = await sendMessageToL1(MESSAGE);

  console.log("Waiting for transaction to finalize...");

  // Step 2: waiting to finalize can take a few minutes.
  const l2Receipt = await l2Trx.waitFinalize();

  // Step 3: get and validate proof (block must be verified)
  const proof = await getL2MessageProof(l2Receipt.blockNumber);

  console.log(`Proof is: `, proof);

  const { l1BatchNumber, l1BatchTxIndex } = await l2Provider.getTransactionReceipt(l2Receipt.transactionHash);

  console.log("L1 Index for Tx in block :>> ", l1BatchTxIndex);

  console.log("L1 Batch for block :>> ", l1BatchNumber);

  // IMPORTANT: This method requires that the block is verified
  // and sent to L1!
  const result = await proveL2MessageInclusion(
    l1BatchNumber,
    proof,
    // @ts-ignore
    l1BatchTxIndex
  );

  console.log("Result is :>> ", result);
  process.exit();
}

try {
  main();
} catch (error) {
  console.error(error);
}
Last update:
Contributors: Blessing Krofegha,Antonio,Newbee740,AnastasiiaVashchuk,Antonio,Simon Perriard,Vlad Bochok,barakshani,defigen