# Starknet消息机制

By [julei](https://paragraph.com/@julei) · 2022-09-12

---

Starknet消息机制
------------

翻译： julei

原文：

[https://docs.starknet.io/docs/L1-L2%20Communication/messaging-mechanism](https://docs.starknet.io/docs/L1-L2%20Communication/messaging-mechanism)

L2 → L1 消息
----------

L2 上的合约可以通过 L2→L1 消息传递协议与 L1 上的合约进行异步交互。

在执行一个交易时，L2合约可通过send\_message\_to\_L1()系统调用发送 L2→L1 消息。之后，Starknet系统就会将消息参数（包含 L1 上的接收者合约和相关数据）附加到L2的世界状态更新中。

代码示例：

    let (message_payload : felt*) = alloc()
    // 消息Payload。可增加。like msg_payload[1] ...
    assert message_payload[0] = <payload_parameter> 
    
    assert_lt_felt(to_address, ETH_ADDRESS_BOUND)
    assert_not_zero(to_address)
    send_message_to_l1(to_address=to_address, payload_size=1, payload=message_payload)
    

在包含了此交易的L2世界状态更新后，并且在L1验证后，此消息的哈希会被存储在 StarkNet Core Contract ( L1) 中（引用计数+1），并发送一个LogMessageTo的L1事件（包含消息参数）。

稍后，L1 上的收件人可以消费这条消息。这是通过调用StarkNet 核心合约的consumeMessageFromL2 ()方法来完成的。 Starknet核心合约会验证Hash值是否对应于存储的消息，并且调用者确实是 L1 上的接收者。之后，StarkNet Core Contract 中消息哈希的引用计数减1。

上述流程如下图所示：

L2 → L1 消息的结构由下式给出：

    // L2 → L1 消息
    struct L2L1Message {
      felt fromAddress;
      felt toAddress;
      Payload payload;
    }
    

L2 → L1 消息的哈希值在 L1 上计算方法如下：

    keccak256(
      abi.encodePacked(
       FromAddress,
       ToAddress,
       Payload.length,
       Payload
      )
    );
    

L1 → L2 消息
----------

L1 上的合约可以通过 L1→L2 消息传递协议与 L2 上的合约进行异步交互。该协议包括以下阶段：

*   1\. L1 合约向 StarkNet 上的 L2 合约发起消息。它通过sendMessageToL2使用消息参数调用 StarkNet Core Contract 上的函数来实现。 1.1 StarkNet Core Contract 对消息参数进行散列处理，并更新 L1→L2 消息映射，以表明确实发送了具有此散列的消息。事实上，L1 合约记录了发送方支付的费用。有关更多信息，请参阅L1 → L2 消息费用。
    
*   2\. 然后将消息解码为 StarkNet 交易，该交易调用l1\_handler目标合约上的装饰器注解的函数。L2 上的此类事务称为L1 处理程序事务。
    
         2\.1. StarkNet Sequencer在看到发送消息的交易的足够 L1 确认后，启动相应的 L2 交易。
        
         2\.2. L2 事务调用相关的 l1_handler。
        
    
*   3\. 将上一步创建的 L1 Handler 交易添加到证明中。
    
*   4\. Core 合约收到状态更新
    
*   5\. 消息从核心合约的存储中清除。至此，消息被处理。 上述流程如下图所示：
    

一条 L1→L2 消息包括：

L1 发件人地址 StarkNet 上的收件人合约地址 功能选择器 调用数据数组 消息随机数 消息随机数 消息 nonce 在 L1 上的 StarkNet Core 合约上维护，并且每当消息发送到 L2 时都会被碰撞。它用于避免在 L1 上多次发送同一消息引起的不同 L1 处理程序事务之间的哈希冲突（见下文）。

### L1 → L2 消息的取消机制

想象一下用户将资产从 L1 转移到 L2 的场景。该流程从用户将资产发送到 StarkNet 网桥和相应的 L1→L2 消息生成开始。现在，假设 L2 消息消费不起作用（可能由于 dApp 的 Cairo 合约中的bug）。这可能导致用户永远失去对其eth资产。

为了降低这种风险，我们允许发起 L1→L2 消息的合约取消它——在声明意图并等待适当的时间之后。用户首先调用StarkNet Core Contract的startL1ToL2MessageCancellation() 方法来开始取消流程。然后，在延迟五天后，用户调用消cancelL1ToL2Message()来完成取消流程。延迟的原因是为了保护Sequencer免受以重复发送和取消消息形式的 DOS 攻击——在它包含在 L1 之前，使包含相应 L1 处理程序激活的 L2 块无效。请注意，此流程应仅用于边缘情况，例如第 2 层合同上的错误阻止消息消费。

### L1 → L2 消息Gas费用

L1 → L2 消息在 L2 上引发交易，与常规交易不同，该交易不是由账户发送的。这需要一种不同的机制来支付交易费用，否则定序器没有动力将 L1 处理程序交易包含在一个块中。

为了避免在发送消息时必须与 L1 和 L2 交互，L1 → L2 消息是在 L1 上支付的，通过调用sendMessageToL2StarkNet Core 合约上的支付函数发送 ETH。定序器收取这笔费用以换取处理消息。定序器在使用此消息更新 L1 状态时全额收取费用。

**注意**

在消息的整个生命周期中，相关gas费用被锁定在核心合约中。如果由于任何原因未处理该消息，则取消该消息也会将费用退还给发件人。

费用本身的计算方式与“常规”L2 交易相同。您可以使用CLI估算 L1 → L2 消息费用。

### 消息结构定义和Hash的计算

为了完整起见，我们给出 L1 上的消息和 L2 上的相关交易的精确结构定义。

    struct L2L1Message {
       EthereumAddress FromAddress;
       felt ToAddress;
       felt Selector;
       feltList<felt> Payload;
       felt Nonce;
    };
    
    struct L1HandlerTransaction {
       felt Version;
       felt ContractAddress;
       felt Selector;
       List<felt> Calldata;
       felt Nonce;
    };
    

在L1上，消息的哈希值x计算方法如下：

    keccak256(
      abi.encodePacked(
        uint256(FromAddress),
        ToAddress,
        Nonce,
        Selector,
        Payload.length,
        Payload
      )
    );
    

在L2上，使用下述方法计算L1 Handler Transaction的Hash:

    l1_hander_tx_hash = h(
      "l1_handler",version,
      contract_address,
      entry_point_selector,
      h(calldata),
      max_fee,
      chain_id,
    nonce)
    

其中：

“l1\_handler” 是一个常量前缀，以字节 (ASCII) 编码，采用bigendian。

chain\_id是一个常数值，它指定了这个交易被发送到的网络。请参阅链 ID。

h()是Pedersen Hash

**Calldata中的发件人地址**

在l1\_handler交易中，calldata 的第一个元素始终是发送者的以太坊地址。

---

*Originally published on [julei](https://paragraph.com/@julei/starknet)*
