LayerzeroV2:message跨链
<!--StartFragment-->
根据时间顺序分为四个阶段:
- Src :Send 阶段(OApp → Endpoint → SendLib → assignJob DVN/Executor)
- Off-chain:DVN / Executor 监听事件,等待确认
- Dst :DVN 验证 & commitVerification → Endpoint.verify → _inbound
- Dst :Executor.lzReceive → _clearPayload → OAppReceiver.lzReceive / _lzReceive
LayerZero 跨链消息传递流程(按时间顺序分为四个阶段)
① Src:Send 阶段(OApp 发message + assignJob + 付款)
1. OApp 发起跨链
- OApp 调用:
-
这里 params 里包含:
dstEid、receiver、message、options、payInLzToken
2. EndpointV2.send(...) 入口
-
EndpointV2.send:
- 检查:如果
payInLzToken == true但lzToken == address(0)→ revert - 调
_send(msg.sender, params)进入真正的发送逻辑
- 检查:如果
3. EndpointV2._send(…):生成 Packet + 选择 SendLib
-
EndpointV2._send(_sender, _params):
_outbound (_sender, _params.dstEid, _params.receiver)→ 这条路径的 nonce- 生成 Packet:
getSendLibrary(_sender, _params.dstEid)→ 查此 OApp 对这个 dstEid 使用哪个 SendLib(如 SendUln302)- 调用该 SendLib:
- 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):
_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):-
首先:
-
得到
packetHeader、payload(包含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
-
_payTreasury(sender, dstEid, totalNativeFee, payInLzToken):
-
调 Treasury 合约,让它根据
totalNativeFee报一个协议费: -
用 native 支付:按 bps 比例取一部分
-
用 LZ token 支付:按配置决定 LZ token 数量
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上执行
updateHash的calldataGas+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
- 把 LZ token 转入
-
_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发起:
-
内部主要步骤:
_assertHeader(packetHeader, localEid)确认:
- header 长度正确
- 版本正确
dstEid == localEid
- 解出:
receiver = packetHeader.receiver()srcEid = packetHeader.srcEid()
UlnConfig config = getUlnConfig(receiver, srcEid)_verifyAndReclaimStorage(config, headerHash, payloadHash):
-
调
_checkVerifiable(config, headerHash, payloadHash): -
确保 所有
requiredDVNs都在hashLookup中有 witness -
optionalDVNs中 witness 数量 ≥optionalDVNThreshold -
否则 revert
LZ_ULN_Verifying() -
在验证通过后,对 requiredDVNs / optionalDVNs 的记录:
Origin origin = Origin(srcEid, packetHeader.sender(), packetHeader.nonce())- 调用 dst Endpoint.verify():
12. EndpointV2.verify(...):把消息插入 message channel
-
入口
EndpointV2.verify(origin, receiver, payloadHash):isValidReceiveLibrary(receiver, origin.srcEid, msg.sender):
-
确保这次调用 verify 的 ReceiveLib 是:
-
当前配置的 receiveLib;或
-
正处于 Timeout grace period 内的旧 receiveLib
- 取出:
_initializable(origin, receiver, lazyNonce):\ - 如果lazyNonce > 0→ 表示这个 path 已经初始化过(之前有成功执行的消息)\ - 否则调用:
-
_verifiable(origin, receiver, lazyNonce):\ - 确保这条(receiver, srcEid, sender, nonce)的消息还没被执行过:- 如果
origin.nonce > lazyNonce→ 可以插入新的消息 - 如果
origin.nonce <= lazyNonce→ 只能当作“重验证”,但前提是inboundPayloadHash里仍有payloadHash(尚未执行)
- 如果
-
_inbound(…):\ - 真正把这条消息的payloadHash写入message channel:
- 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 调入
- 校验
srcEid与sender是否匹配已经注册的 peer - 调用
_lzReceive(_origin, _guid, _message, _executor, _extraData);
-
在
_lzReceive(...),里面写自己的业务,比如:- 触发后续跨链 send 等\ 这条跨链消息在 dst上“完整执行完毕”。

<!--EndFragment-->
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
区块链技术网
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。