How a reorg breaks nonce agreement
Each pathway keeps a nonce on both chains. The source endpoint incrementsoutboundNonce on every send. The destination endpoint tracks lazyInboundNonce, the last nonce it has settled, and stores a payload hash for each verified nonce.
A deep reorg breaks the agreement between the two:
- Your OApp sends a message on the source chain. The endpoint increments
outboundNoncetoN. - The DVNs wait for your configured confirmations, then attest. The destination verifies nonce
N, and the executor delivers it. - The source chain reorgs past the block that held the send. That block is no longer canonical, so the chain discards the storage write and
outboundNoncefalls back toN-1. - The next send on the source chain takes nonce
Nagain.
N. The source is about to reuse it. What happens to that replacement message depends entirely on how far the destination got before the reorg.
Recovery depends on execution, not verification
The destination endpoint decides whether a nonce can be verified again with one rule:init_verify instruction requires the nonce to be greater than the inbound_nonce held on the pathway’s Nonce account. A consumed nonce therefore has no account left to re-verify and cannot be opened again.
Verified but not executed: the message recovers
If the destination verified nonceN but no one executed it, the payload hash is still stored and lazyInboundNonce has not moved. The second clause holds, so the replacement message can verify.
Verifying it overwrites the stored payload hash. The orphaned message becomes unexecutable and the replacement executes in its place. The pathway delivers exactly one message for nonce N and loses nothing. This is the ordinary re-verification path, and it needs no intervention.
Executed, cleared, or skipped: the message is lost
If the destination executed nonceN, lazyInboundNonce is at or above N and execution deleted the payload hash. Both clauses fail, so verify reverts with LZ_PathNotVerifiable.
This state is permanent. There is no timeout after which the message becomes deliverable, and no number of retries changes the outcome. Calling clear or skip on a nonce leaves it in the same dead state, because both advance lazyInboundNonce past it.
Replacement messages reuse the GUID
A GUID is derived from the nonce and the pathway, with no input from the payload or the block:N therefore carries the same GUID as the message the reorg removed, even though its contents differ. If your off-chain accounting treats the GUID as a unique message identifier, it will merge two different messages into one record. Key your replay protection and reconciliation on something other than the GUID, because GUID uniqueness does not survive a reorg.
How many messages you lose
Only the reused nonces fail. OnceoutboundNonce climbs back above the destination’s lazyInboundNonce, messages verify and deliver normally again.
Once the reorg settles, the destination counter is ahead of the source counter. The distance between them is the number of undeliverable sends:
outboundNonce to 2. The gap is 3 - 2 = 1. The next send takes nonce 3 and is lost. The send after that takes nonce 4 and delivers normally.
Detect the divergence
Nothing surfaces this automatically, so monitor for it. ReadoutboundNonce(sender, dstEid, receiver) on the source endpoint and lazyInboundNonce(receiver, srcEid, sender) on the destination endpoint for each pathway you operate. In normal operation the source counter is at or above the destination counter. If the source counter falls below the destination counter, the pathway has diverged and every send until it catches up will fail to verify.
Also alert on LZ_PathNotVerifiable reverts from verify on the destination. A healthy pathway that starts producing them has diverged.
Respond to a reorg
- Stop sending on the affected pathway. Every send while the source counter sits below the destination counter debits the sender and cannot be delivered.
- Record the gap between the two counters. That is how many sends are unrecoverable.
- Reconcile the message that executed before the reorg. The destination applied it and the source no longer records the send, so an OFT has credited tokens on the destination with no matching debit on the source.
- Advance
outboundNoncepast the destination’slazyInboundNoncebefore you resume real traffic.- Sending is the only way to do this. The endpoint increments
outboundNonceinside_outbound, which onlysendcalls, and it exposes no function to set the counter directly. - Every send you use to close the gap pays fees and is never delivered.
- If the gap is large, deploying a new OApp on either side gives you a fresh channel, because the nonce is keyed by the sender and receiver addresses.
- Sending is the only way to do this. The endpoint increments
- Re-issue the lost payloads as new messages once the counters agree.
Prevent it
Setconfirmations above the worst reorg depth the source chain has produced, not its typical depth, and pin the value explicitly on both sides of the pathway. The per-chain floors are in Confirmation depth. Nothing you do after the fact substitutes for this, because the stranded messages cannot be recovered.
Two design choices reduce what a single reorg can cost you. Per-message or per-window transfer caps bound the value that one incident can strand. A pause on the pathway lets you stop debiting users as soon as you detect the divergence, which is what keeps the loss from growing while you investigate.
For high-value pathways, note that the damage becomes permanent only at execution. A pathway that delays or gates destination execution, for example by requiring an operator to confirm before the executor runs, stays in the recoverable case for longer.