LayerzeroV2:message跨链

  • 老道
  • 发布于 13小时前
  • 阅读 40

LayerzeroV2:message跨链

<!--StartFragment-->

根据时间顺序分为四个阶段:

  1. Src :Send 阶段(OApp → Endpoint → SendLib → assignJob DVN/Executor)
  2. Off-chain:DVN / Executor 监听事件,等待确认
  3. Dst :DVN 验证 & commitVerification → Endpoint.verify → _inbound
  4. Dst :Executor.lzReceive → _clearPayload → OAppReceiver.lzReceive / _lzReceive

LayerZero 跨链消息传递流程(按时间顺序分为四个阶段)

① Src:Send 阶段(OApp 发message + assignJob + 付款)

1. OApp 发起跨链

  • OApp 调用:
  • 这里 params 里包含:

    • dstEidreceivermessageoptionspayInLzToken

2. EndpointV2.send(...) 入口

  • EndpointV2.send:

    • 检查:如果 payInLzToken == truelzToken == address(0) → revert
    • _send(msg.sender, params) 进入真正的发送逻辑

3. EndpointV2._send(…):生成 Packet + 选择 SendLib

  • EndpointV2._send(_sender, _params):

    1. _outbound (_sender, _params.dstEid, _params.receiver) → 这条路径的 nonce
    2. 生成 Packet:
  1. getSendLibrary(_sender, _params.dstEid) → 查此 OApp 对这个 dstEid 使用哪个 SendLib(如 SendUln302)
  2. 调用该 SendLib:
  1. emit PacketSent(encodedPacket, options, sendLib) → 给 Executor、DVN等监听事件
  • return:

    • MessagingReceipt(packet.guid, nonce, fee)
    • sendLib 地址

4. SendUln302.send(...):assignJob给 Executor 和 DVNs + 算 Worker fee + Treasury fee

  • SendUln302.send(packet, options, payInLzToken):

    1. _payWorkers(packet, options)
    • 拆成:

    • Executor: executorOptions

    • DVN: validationOptions

    • getExecutorConfig(sender, dstEid) → 拿到 executor 地址 + maxMessageSize

    • _assertMessageSize: → 限制 message length(默认 10000 bytes)

    • _payExecutor(executor, dstEid, sender, msgSize, executorOptions)

    • 在 src 调用 Executor.assignJob(...)

    • 🌟 这里只是:算 ExecutorFee + 记账,不是真正执行消息

    • executorFee 加到 fees[executor]

    • emit ExecutorFeePaid

    • _payVerifier(packet, validationOptions)

    • 内部 _payDVNs(fees, packet, options)

      • 首先:

      • 得到 packetHeaderpayload(包含 guid + message

      • payloadHash = keccak256(payload)

      • getUlnConfig(sender, dstEid) 拿到 UlnConfig(required / optional DVNs + threshold)

      • _assignJobs(...)

      • 对所有 required + optional DVN 调 DVN.assignJob(...)

      • dvnFee 加到 fees[dvn]

      • return totalFee + encodedPacket = abi.encodePacked(packetHeader, payload)

      • emit DVNFeePaid

    1. _payTreasury(sender, dstEid, totalNativeFee, payInLzToken)
    • 调 Treasury 合约,让它根据 totalNativeFee 报一个协议费:

    • 用 native 支付:按 bps 比例取一部分

    • 用 LZ token 支付:按配置决定 LZ token 数量

    1. totalNativeFee += treasuryNativeFee
  • return:

    • MessagingFee(totalNativeFee, lzTokenFee)
    • encodedPacket(给 Endpoint emit 出去)

5. Executor.assignJob(...)(仍在 src,只是“assignJob + 计费”)

  • Executor.assignJob(dstEid, sender, calldataSize, executorOptions)

    • 构造 FeeParams{ priceFeed, dstEid, sender, calldataSize, defaultMultiplierBps }

    • ExecutorFeeLib.getFeeOnSend(params, dstConfig[dstEid], executorOptions)

    • decode executorOptions

    • 用 PriceFeed 把目标链 gas 费用和 dstAmount 折算成本链 native

    • return executorFee,并在 SendLib 里:

  • emit ExecutorFeePaid(executor, executorFee);

6. DVN.assignJob(...)(仍在 src,只是“assignJob + 计费”)

  • DVN.assignJob(AssignJobParam{dstEid, packetHeader, payloadHash, confirmations, sender}, dvnOptions)

    • 构造 DVNFeeLib.FeeParams{ priceFeed, dstEid, confirmations, sender, quorum, defaultMultiplierBps }

    • DVNFeeLib.getFeeOnSend(...)

    • 估算 DVN 在 dst上执行 updateHashcalldataGas + gas

    • 折算成本链 native

    • return dvnFee 并加到 fees[dvn]

7. EndpointV2.send(…):检查付的钱 + 真正转账到 SendLib

  • _send(...) 得到:

    • MessagingReceipt{guid, nonce, fee}
    • _sendLibrary 地址
  • 计算在这次交易中真正提供的:

    • suppliedNative = _suppliedNative()(等于 msg.value
    • suppliedLzToken = _suppliedLzToken(payInLzToken)
  • _assertMessagingFee(receipt.fee, suppliedNative, suppliedLzToken)

    • 确保(供给>需求):

    • suppliedNative >= fee.nativeFee

    • suppliedLzToken >= fee.lzTokenFee

    • 不够 → revert,整个 send 失败,并不会真的assignJob / emit PacketSent

  • _payToken(lzToken, fee.lzTokenFee, suppliedLzToken, _sendLibrary, refundAddress)

    • 把 LZ token 转入 _sendLibrary
    • 多余部分退回给 refundAddress
  • _payNative(fee.nativeFee, suppliedNative, _sendLibrary, refundAddress)

    • fee.nativeFee 的 native 转给 _sendLibrary
    • 多余部分退回给 refundAddress
  • emit PacketSent(encodedPacket, options, _sendLibrary) 已经在 _send 中完成

  • return给 OApp: MessagingReceipt { guid, nonce, fee }

👉 src上这次 send 的 on-chain 行为就结束了。\ 后面的事情都在:

  • Off-chain(DVN / Executor 监听事件、处理src状态)
  • dst(verify + execute)

② Off-chain:DVN / Executor 监听事件

8. DVN 网络:

  • 监听 src上的:

    • PacketSent(...)
    • DVN 自己相关的 DVNFeePaid 等事件
  • 确认:

    • 源链交易已上链且达到 confirmations
    • packetHeader + payload 的数据正确(与源链状态一致)

9. Executor 网络:

  • 同样监听 src上的 PacketSent(...)
  • 记录要在 dst执行的消息(guid / payload / options
  • 同时注意收款账户在 SendLib 里的 fees[executor]

③ dst:DVN 验证 + commitVerification → Endpoint.verify → _inbound

10. DVN 在 dst提交 witness

  • 当确认 src已达到 confirmations 且消息有效后,\ 每个被 assign 的 DVN 在 dst调用:
  • 内部 _verify(...)
  • emit PayloadVerified(msg.sender, packetHeader, confirmations, payloadHash);

👉 这一步相当于 “DVN 在 dst上对这条消息举手表态:我已经验证过这个消息了。”

11. 有人调用 ReceiveUln302.commitVerification(...)

  • 任何人都可以在 dst发起:
  • 内部主要步骤:

    1. _assertHeader(packetHeader, localEid) 确认:
    • header 长度正确
    • 版本正确
    • dstEid == localEid
    1. 解出:
    • receiver = packetHeader.receiver()
    • srcEid = packetHeader.srcEid()
    1. UlnConfig config = getUlnConfig(receiver, srcEid)
    2. _verifyAndReclaimStorage(config, headerHash, payloadHash)
    • _checkVerifiable(config, headerHash, payloadHash)

    • 确保 所有 requiredDVNs 都在 hashLookup 中有 witness

    • optionalDVNs 中 witness 数量 ≥ optionalDVNThreshold

    • 否则 revert LZ_ULN_Verifying()

    • 在验证通过后,对 requiredDVNs / optionalDVNs 的记录:

  1. Origin origin = Origin(srcEid, packetHeader.sender(), packetHeader.nonce())
  2. 调用 dst Endpoint.verify():

12. EndpointV2.verify(...):把消息插入 message channel

  • 入口 EndpointV2.verify(origin, receiver, payloadHash)

    1. isValidReceiveLibrary(receiver, origin.srcEid, msg.sender)
    • 确保这次调用 verify 的 ReceiveLib 是:

    • 当前配置的 receiveLib;或

    • 正处于 Timeout grace period 内的旧 receiveLib

    1. 取出:
  1. _initializable(origin, receiver, lazyNonce):\ - 如果 lazyNonce > 0 → 表示这个 path 已经初始化过(之前有成功执行的消息)\ - 否则调用:
  1. _verifiable(origin, receiver, lazyNonce):\ - 确保这条 (receiver, srcEid, sender, nonce) 的消息还没被执行过:

    • 如果 origin.nonce > lazyNonce → 可以插入新的消息
    • 如果 origin.nonce &lt;= lazyNonce → 只能当作“重验证”,但前提是 inboundPayloadHash里仍有 payloadHash(尚未执行)
  2. _inbound(…):\ - 真正把这条消息的 payloadHash 写入message channel:

  1. emit PacketVerified(origin, receiver, payloadHash)\ 👉 到这里:消息已经被 DVN 网络验证 & 被 Endpoint 插入 message channel,等待 Executor 来执行。

④ dst:Executor.lzReceive → _clearPayload → OAppReceiver.lzReceive / _lzReceive

13. Executor 在 dst调用 Endpoint.lzReceive(...) 执行消息

  • Executor off-chain 看到消息已 PacketVerified
  • 就在 dst发起:EndpointV2.lzReceive(origin, receiver, guid, message, extraData)
  • 这里的 origin / guid / message / extraData 都是 Executor 根据 src上的 encodedPacket 解出的。

14. EndpointV2.lzReceive(...):先清 channel,再调 OApp

  • 调用 _clearPayload:先清理payload
  • 再 x调用 OApp 的 lzReceive(实现 ILayerZeroReceiver

15. _clearPayload(...):更新 lazyInboundNonce + 校验 payloadHash + 删除

  • 确保执行顺序:\ 验证可以无序(任意顺序 commitVerification),但执行必须有序(lazyNonce 单调递增)

  • 确保 Executor 提供的 (guid + message) 的哈希 == 当初 DVN commit 的 payloadHash

    • 否则视为不合法的 payload,直接 revert
  • 删除 channel 中该 nonce 的 payloadHash,表示这条消息已经被消费,不能再执行一次。

16. OAppReceiver.lzReceive(...):OApp 自身的消息处理入口

  • OApp 会继承 OAppReceiver

    • 只允许 Endpoint 调入
    • 校验 srcEidsender 是否匹配已经注册的 peer
    • 调用 _lzReceive(_origin, _guid, _message, _executor, _extraData);
  • _lzReceive(...),里面写自己的业务,比如:

    • 触发后续跨链 send 等\ 👉 这条跨链消息在 dst上“完整执行完毕”。

image.png

<!--EndFragment-->

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
老道
老道
0xbf65...58d8
江湖只有他的大名,没有他的介绍。