LayerzeroV2:message跨链
<!--StartFragment-->
根据时间顺序分为四个阶段:
这里 params 里包含:
dstEid、receiver、message、options、payInLzTokenEndpointV2.send:
payInLzToken == true 但 lzToken == address(0) → revert_send(msg.sender, params) 进入真正的发送逻辑EndpointV2._send(_sender, _params):
_outbound (_sender, _params.dstEid, _params.receiver) → 这条路径的 nonce
getSendLibrary(_sender, _params.dstEid) → 查此 OApp 对这个 dstEid 使用哪个 SendLib(如 SendUln302)
PacketSent(encodedPacket, options, sendLib) → 给 Executor、DVN等监听事件return:
MessagingReceipt(packet.guid, nonce, fee)sendLib 地址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 += treasuryNativeFeereturn:
MessagingFee(totalNativeFee, lzTokenFee)encodedPacket(给 Endpoint emit 出去)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 里:
ExecutorFeePaid(executor, executorFee);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]
从 _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):
_sendLibraryrefundAddress_payNative(fee.nativeFee, suppliedNative, _sendLibrary, refundAddress):
fee.nativeFee 的 native 转给 _sendLibraryrefundAddressemit PacketSent(encodedPacket, options, _sendLibrary) 已经在 _send 中完成
return给 OApp: MessagingReceipt { guid, nonce, fee }
👉 src上这次 send 的 on-chain 行为就结束了。\ 后面的事情都在:
监听 src上的:
PacketSent(...)DVNFeePaid 等事件确认:
confirmationspacketHeader + payload 的数据正确(与源链状态一致)PacketSent(...)guid / payload / options)fees[executor]confirmations 且消息有效后,\
每个被 assign 的 DVN 在 dst调用:
_verify(...):
PayloadVerified(msg.sender, packetHeader, confirmations, payloadHash);👉 这一步相当于 “DVN 在 dst上对这条消息举手表态:我已经验证过这个消息了。”
内部主要步骤:
_assertHeader(packetHeader, localEid) 确认:dstEid == localEidreceiver = 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())
入口 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:
PacketVerified(origin, receiver, payloadHash)\
👉 到这里:消息已经被 DVN 网络验证 & 被 Endpoint 插入 message channel,等待 Executor 来执行。PacketVerifiedEndpointV2.lzReceive(origin, receiver, guid, message, extraData)origin / guid / message / extraData 都是 Executor 根据 src上的 encodedPacket 解出的。_clearPayload:先清理payloadlzReceive(实现 ILayerZeroReceiver)_clearPayload(...):更新 lazyInboundNonce + 校验 payloadHash + 删除确保执行顺序:\
验证可以无序(任意顺序 commitVerification),但执行必须有序(lazyNonce 单调递增)
确保 Executor 提供的 (guid + message) 的哈希 == 当初 DVN commit 的 payloadHash:
删除 channel 中该 nonce 的 payloadHash,表示这条消息已经被消费,不能再执行一次。
OApp 会继承 OAppReceiver:
srcEid 与 sender 是否匹配已经注册的 peer_lzReceive(_origin, _guid, _message, _executor, _extraData);在 _lzReceive(...),里面写自己的业务,比如:

<!--EndFragment-->
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!