Subscribe to Untitled
<100 subscribers
<100 subscribers
根据时间顺序分为四个阶段:
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
OApp 调用:
endpoint.send(params, refundAddress)
这里 params 里包含:
dstEid、receiver、message、options、payInLzToken
EndpointV2.send:
检查:如果 payInLzToken == true 但 lzToken == address(0) → revert
调 _send(msg.sender, params) 进入真正的发送逻辑
EndpointV2._send(_sender, _params):
_outbound (_sender, _params.dstEid, _params.receiver) → 这条路径的 nonce
生成 Packet:
// construct the packet with a GUID
Packet memory packet = Packet({
nonce: latestNonce,
srcEid: eid,
sender: _sender,
dstEid: _params.dstEid,
receiver: _params.receiver,
guid: GUID.generate(latestNonce, eid, _sender, _params.dstEid, _params.receiver),
message: _params.message
});
getSendLibrary(_sender, _params.dstEid) → 查此 OApp 对这个 dstEid 使用哪个 SendLib(如 SendUln302)
调用该 SendLib:
(fee, encodedPacket) = ISendLib(sendLib).send(packet, options, payInLzToken);
emit 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):
内部
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 里:
fees[executor] += executorFee;
emit 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):
把 LZ token 转入 _sendLibrary
多余部分退回给 refundAddress
_payNative(fee.nativeFee, suppliedNative, _sendLibrary, refundAddress):
👉 src上这次 send 的 on-chain 行为就结束了。
后面的事情都在:
Off-chain(DVN / Executor 监听事件、处理src状态)
dst(verify + execute)
监听 src上的:
PacketSent(...)
DVN 自己相关的 DVNFeePaid 等事件
确认:
源链交易已上链且达到 confirmations
packetHeader + payload 的数据正确(与源链状态一致)
同样监听 src上的 PacketSent(...)
记录要在 dst执行的消息(guid / payload / options)
同时注意收款账户在 SendLib 里的 fees[executor]
当确认 src已达到 confirmations 且消息有效后,
每个被 assign 的 DVN 在 dst调用:
ReceiveUln302.verify(packetHeader, payloadHash, confirmations)
内部 _verify(...):
hashLookup[keccak256(packetHeader)][payloadHash][msg.sender]
- = Verification(true, confirmations);
emit PayloadVerified(msg.sender, packetHeader, confirmations, payloadHash);
👉 这一步相当于 “DVN 在 dst上对这条消息举手表态:我已经验证过这个消息了。”
任何人都可以在 dst发起:
ReceiveUln302.commitVerification(packetHeader, payloadHash)
内部主要步骤:
_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 的记录:
Delete hashLookup[headerHash][payloadHash][dvn];
- → 回收 DVN witness 的存储(state cleanup)
Origin origin = Origin(srcEid, packetHeader.sender(), packetHeader.nonce())
调用 dst Endpoint.verify():
ILayerZeroEndpointV2(endpoint).verify(origin, receiver, payloadHash);
入口 EndpointV2.verify(origin, receiver, payloadHash):
isValidReceiveLibrary(receiver, origin.srcEid, msg.sender):
确保这次调用 verify 的 ReceiveLib 是:
当前配置的 receiveLib;或
正处于 Timeout grace period 内的旧 receiveLib
取出:
lazyNonce = lazyInboundNonce[receiver][origin.srcEid][origin.sender];
- `_origin.nonce` 是“这条消息自己的编号”,packet 里的那个 nonce。
- `lazyInboundNonce` 是“dst上这个 path 已经连续处理到哪一条的index”,是 Endpoint 自己维护的状态,表示’这个 `(srcEid, sender, receiver)`路径上,前面的消息已经都验证 + 执行干净了’的连续 nonce。
_initializable(origin, receiver, lazyNonce):
- 如果 lazyNonce > 0 → 表示这个 path 已经初始化过(之前有成功执行的消息)
- 否则调用:
allowInitializePath(origin): 来确认这个 (srcEid, sender) 路径是否被 OApp 允许初始化
_verifiable(origin, receiver, lazyNonce):
- 确保这条 (receiver, srcEid, sender, nonce) 的消息还没被执行过:
如果 origin.nonce > lazyNonce → 可以插入新的消息
如果 origin.nonce <= lazyNonce → 只能当作“重验证”,但前提是 inboundPayloadHash里仍有 payloadHash(尚未执行)
_inbound(…):
- 真正把这条消息的 payloadHash 写入message channel:
inboundPayloadHash[receiver][srcEid][sender][nonce] = payloadHash;
emit PacketVerified(origin, receiver, payloadHash)
👉 到这里:消息已经被 DVN 网络验证 & 被 Endpoint 插入 message channel,等待 Executor 来执行。
Executor off-chain 看到消息已 PacketVerified
就在 dst发起:EndpointV2.lzReceive(origin, receiver, guid, message, extraData)
这里的 origin / guid / message / extraData 都是 Executor 根据 src上的 encodedPacket 解出的。
调用 _clearPayload:先清理payload
再 x调用 OApp 的 lzReceive(实现 ILayerZeroReceiver)
确保执行顺序:
验证可以无序(任意顺序 commitVerification),但执行必须有序(lazyNonce 单调递增)
确保 Executor 提供的 (guid + message) 的哈希 == 当初 DVN commit 的 payloadHash:
否则视为不合法的 payload,直接 revert
删除 channel 中该 nonce 的 payloadHash,表示这条消息已经被消费,不能再执行一次。
OApp 会继承 OAppReceiver:
只允许 Endpoint 调入
校验 srcEid 与 sender 是否匹配已经注册的 peer
调用 _lzReceive(_origin, _guid, _message, _executor, _extraData);
在 _lzReceive(...),里面写自己的业务,比如:
触发后续跨链 send 等
👉 这条跨链消息在 dst上“完整执行完毕”。

根据时间顺序分为四个阶段:
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
OApp 调用:
endpoint.send(params, refundAddress)
这里 params 里包含:
dstEid、receiver、message、options、payInLzToken
EndpointV2.send:
检查:如果 payInLzToken == true 但 lzToken == address(0) → revert
调 _send(msg.sender, params) 进入真正的发送逻辑
EndpointV2._send(_sender, _params):
_outbound (_sender, _params.dstEid, _params.receiver) → 这条路径的 nonce
生成 Packet:
// construct the packet with a GUID
Packet memory packet = Packet({
nonce: latestNonce,
srcEid: eid,
sender: _sender,
dstEid: _params.dstEid,
receiver: _params.receiver,
guid: GUID.generate(latestNonce, eid, _sender, _params.dstEid, _params.receiver),
message: _params.message
});
getSendLibrary(_sender, _params.dstEid) → 查此 OApp 对这个 dstEid 使用哪个 SendLib(如 SendUln302)
调用该 SendLib:
(fee, encodedPacket) = ISendLib(sendLib).send(packet, options, payInLzToken);
emit 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):
内部
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 里:
fees[executor] += executorFee;
emit 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):
把 LZ token 转入 _sendLibrary
多余部分退回给 refundAddress
_payNative(fee.nativeFee, suppliedNative, _sendLibrary, refundAddress):
👉 src上这次 send 的 on-chain 行为就结束了。
后面的事情都在:
Off-chain(DVN / Executor 监听事件、处理src状态)
dst(verify + execute)
监听 src上的:
PacketSent(...)
DVN 自己相关的 DVNFeePaid 等事件
确认:
源链交易已上链且达到 confirmations
packetHeader + payload 的数据正确(与源链状态一致)
同样监听 src上的 PacketSent(...)
记录要在 dst执行的消息(guid / payload / options)
同时注意收款账户在 SendLib 里的 fees[executor]
当确认 src已达到 confirmations 且消息有效后,
每个被 assign 的 DVN 在 dst调用:
ReceiveUln302.verify(packetHeader, payloadHash, confirmations)
内部 _verify(...):
hashLookup[keccak256(packetHeader)][payloadHash][msg.sender]
- = Verification(true, confirmations);
emit PayloadVerified(msg.sender, packetHeader, confirmations, payloadHash);
👉 这一步相当于 “DVN 在 dst上对这条消息举手表态:我已经验证过这个消息了。”
任何人都可以在 dst发起:
ReceiveUln302.commitVerification(packetHeader, payloadHash)
内部主要步骤:
_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 的记录:
Delete hashLookup[headerHash][payloadHash][dvn];
- → 回收 DVN witness 的存储(state cleanup)
Origin origin = Origin(srcEid, packetHeader.sender(), packetHeader.nonce())
调用 dst Endpoint.verify():
ILayerZeroEndpointV2(endpoint).verify(origin, receiver, payloadHash);
入口 EndpointV2.verify(origin, receiver, payloadHash):
isValidReceiveLibrary(receiver, origin.srcEid, msg.sender):
确保这次调用 verify 的 ReceiveLib 是:
当前配置的 receiveLib;或
正处于 Timeout grace period 内的旧 receiveLib
取出:
lazyNonce = lazyInboundNonce[receiver][origin.srcEid][origin.sender];
- `_origin.nonce` 是“这条消息自己的编号”,packet 里的那个 nonce。
- `lazyInboundNonce` 是“dst上这个 path 已经连续处理到哪一条的index”,是 Endpoint 自己维护的状态,表示’这个 `(srcEid, sender, receiver)`路径上,前面的消息已经都验证 + 执行干净了’的连续 nonce。
_initializable(origin, receiver, lazyNonce):
- 如果 lazyNonce > 0 → 表示这个 path 已经初始化过(之前有成功执行的消息)
- 否则调用:
allowInitializePath(origin): 来确认这个 (srcEid, sender) 路径是否被 OApp 允许初始化
_verifiable(origin, receiver, lazyNonce):
- 确保这条 (receiver, srcEid, sender, nonce) 的消息还没被执行过:
如果 origin.nonce > lazyNonce → 可以插入新的消息
如果 origin.nonce <= lazyNonce → 只能当作“重验证”,但前提是 inboundPayloadHash里仍有 payloadHash(尚未执行)
_inbound(…):
- 真正把这条消息的 payloadHash 写入message channel:
inboundPayloadHash[receiver][srcEid][sender][nonce] = payloadHash;
emit PacketVerified(origin, receiver, payloadHash)
👉 到这里:消息已经被 DVN 网络验证 & 被 Endpoint 插入 message channel,等待 Executor 来执行。
Executor off-chain 看到消息已 PacketVerified
就在 dst发起:EndpointV2.lzReceive(origin, receiver, guid, message, extraData)
这里的 origin / guid / message / extraData 都是 Executor 根据 src上的 encodedPacket 解出的。
调用 _clearPayload:先清理payload
再 x调用 OApp 的 lzReceive(实现 ILayerZeroReceiver)
确保执行顺序:
验证可以无序(任意顺序 commitVerification),但执行必须有序(lazyNonce 单调递增)
确保 Executor 提供的 (guid + message) 的哈希 == 当初 DVN commit 的 payloadHash:
否则视为不合法的 payload,直接 revert
删除 channel 中该 nonce 的 payloadHash,表示这条消息已经被消费,不能再执行一次。
OApp 会继承 OAppReceiver:
只允许 Endpoint 调入
校验 srcEid 与 sender 是否匹配已经注册的 peer
调用 _lzReceive(_origin, _guid, _message, _executor, _extraData);
在 _lzReceive(...),里面写自己的业务,比如:
触发后续跨链 send 等
👉 这条跨链消息在 dst上“完整执行完毕”。

首先:
得到 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 出去)
把 fee.nativeFee 的 native 转给 _sendLibrary
多余部分退回给 refundAddress
emit PacketSent(encodedPacket, options, _sendLibrary) 已经在 _send 中完成
return给 OApp: MessagingReceipt { guid, nonce, fee }
首先:
得到 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 出去)
把 fee.nativeFee 的 native 转给 _sendLibrary
多余部分退回给 refundAddress
emit PacketSent(encodedPacket, options, _sendLibrary) 已经在 _send 中完成
return给 OApp: MessagingReceipt { guid, nonce, fee }
Share Dialog
Share Dialog
No activity yet